在嵌入式设备中实现webrtc的第三种方式③
本系列的最后一篇,讲解收发音视频数据。
贴出最终效果:
其实很简单,直接调用writeFrame即可,如下图:
当然,这是部分代码,完整代码在下面,展开可见:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "com/amazonaws/kinesis/video/webrtcclient/Include.h" 2 #include "json/json.h" 3 4 #include "ws_client.h" 5 6 #include <iostream> 7 #include <thread> 8 9 static PRtcPeerConnection peer_; 10 static PRtcDataChannel dc_; 11 static PRtcRtpTransceiver videoTransceiver_; 12 static PRtcRtpTransceiver audioTransceiver_; 13 static WSClient *ws = nullptr; 14 15 STATUS readFrameFromDisk(PBYTE pFrame, PUINT32 pSize, PCHAR frameFilePath) 16 { 17 STATUS retStatus = STATUS_SUCCESS; 18 UINT64 size = 0; 19 20 if (pSize == NULL) { 21 printf("[KVS Master] readFrameFromDisk(): operation returned status code: 0x%08x \n", STATUS_NULL_ARG); 22 goto CleanUp; 23 } 24 25 size = *pSize; 26 27 // Get the size and read into frame 28 retStatus = readFile(frameFilePath, TRUE, pFrame, &size); 29 if (retStatus != STATUS_SUCCESS) { 30 printf("[KVS Master] readFile(): operation returned status code: 0x%08x \n", retStatus); 31 goto CleanUp; 32 } 33 34 CleanUp: 35 36 if (pSize != NULL) { 37 *pSize = (UINT32) size; 38 } 39 40 return retStatus; 41 } 42 43 static void onWsData(const std::string& data, void* usrParam) { 44 Json::Reader reader; 45 Json::Value root; 46 if (reader.parse(data, root)) { 47 if(root["type"] == "answer") { 48 RtcSessionDescriptionInit answer; 49 Json::FastWriter writer; 50 writer.omitEndingLineFeed(); 51 std::string _sdp_json_answer = writer.write(root); 52 int ret = deserializeSessionDescriptionInit((PCHAR)_sdp_json_answer.data(), _sdp_json_answer.size(), &answer); 53 std::cout << "deserializeSessionDescriptionInit:" << std::hex << ret << std::endl; 54 std::cout << "answer:\n" << answer.sdp << std::endl; 55 if(ret == 0) { 56 std::cout << "setRemoteDescription:" << std::hex << setRemoteDescription(peer_, &answer) << std::endl; 57 } 58 } else if (root["type"] == "candidate") { 59 if(!root["candidate"].asString().empty()) { 60 //std::cout << "addIceCandidate:" << addIceCandidate(peer_, "") << std::endl; 61 //} else{ 62 RtcIceCandidateInit ice; 63 Json::FastWriter writer; 64 writer.omitEndingLineFeed(); 65 std::string _ice_json = writer.write(root); 66 int ret = deserializeRtcIceCandidateInit((PCHAR)_ice_json.data(), _ice_json.size(), &ice); 67 std::cout << "deserializeRtcIceCandidateInit:" << std::hex << ret << std::endl; 68 if(ret == 0) { 69 std::cout << "remote-ice:" << ice.candidate << std::endl; 70 std::cout << "addIceCandidate:" << std::hex << addIceCandidate(peer_, ice.candidate) << std::endl; 71 } 72 } 73 } else if(root["type"] == "hello") { 74 75 RtcSessionDescriptionInit offer; 76 std::cout << "createOffer:" << std::hex << createOffer(peer_, &offer) << std::endl; 77 std::cout << "offer:\n" << offer.sdp << std::endl; 78 79 std::cout << "setLocalDescription:" << std::hex << setLocalDescription(peer_, &offer) << std::endl; 80 81 char sdp_json[10240] = {0}; 82 unsigned int sdp_json_buf_len = sizeof sdp_json; 83 std::cout << "serializeSessionDescriptionInit:" << std::hex << serializeSessionDescriptionInit(&offer, sdp_json, &sdp_json_buf_len) << std::endl; 84 std::cout << "sdp_json_offer:\n" << sdp_json << std::endl; 85 86 ws->Send(sdp_json); 87 } 88 } else { 89 std::cout << "Json parse failed" << std::endl; 90 } 91 } 92 93 void func_RtcOnIceCandidate(UINT64 custmerData, PCHAR iceStr) { 94 if(iceStr != nullptr && strlen(iceStr) > 0) { 95 std::cout << "local-ice:" << iceStr << std::endl; 96 Json::Value ice; 97 Json::Reader reader; 98 if(reader.parse(iceStr, ice)) { 99 ice["type"] = "candidate"; 100 Json::FastWriter writer; 101 writer.omitEndingLineFeed(); 102 std::string _ice_json = writer.write(ice); 103 ws->Send(_ice_json); 104 } 105 } 106 } 107 108 void func_RtcOnMessage(UINT64 customData, PRtcDataChannel dc, BOOL isBinary, PBYTE msgData, UINT32 msgLen) { 109 printf("func_RtcOnMessage dc_label=%s isBinary=%d msgLen=%d\n", dc->name, isBinary, msgLen); 110 if(!isBinary) { 111 printf("\t%.*s\n", msgLen, msgData); 112 } 113 } 114 115 void func_RtcOnConnectionStateChange(UINT64 customData, RTC_PEER_CONNECTION_STATE state) { 116 if(state == RTC_PEER_CONNECTION_STATE::RTC_PEER_CONNECTION_STATE_CONNECTED) { 117 RtcDataChannelInit dcInit; 118 memset(&dcInit, 0, sizeof dcInit); 119 dcInit.ordered = TRUE; 120 std::cout << "createDataChannel:" << std::hex << createDataChannel(peer_, (PCHAR)"op", &dcInit, &dc_) << std::endl; 121 std::cout << "dataChannelOnMessage:" << std::hex << dataChannelOnMessage(dc_, 0, func_RtcOnMessage) << std::endl; 122 std::thread([]{ 123 int vidx = 6; 124 char vfn[256] = {0}; 125 Frame frame = {0}; 126 char vbuffer[100 * 1024]; 127 frame.frameData = (PBYTE)vbuffer; 128 STATUS retStatus = STATUS_SUCCESS; 129 UINT64 startTime, lastFrameTime, elapsed; 130 startTime = GETTIME(); 131 lastFrameTime = startTime; 132 while(true) { 133 sprintf(vfn, "/data_fs/h264s/%d.h264", vidx++); 134 frame.size = sizeof vbuffer; 135 retStatus = readFrameFromDisk(frame.frameData, &frame.size, vfn); 136 if(retStatus != STATUS_SUCCESS) { 137 std::cout << "readFrameFromDisk:" << std::hex << retStatus << std::endl; 138 break; 139 } 140 frame.presentationTs += (((10LL * 1000LL) * 1000LL) / 25); 141 retStatus = writeFrame(videoTransceiver_, &frame); 142 if(retStatus != STATUS_SUCCESS) { 143 if(retStatus == STATUS_SRTP_NOT_READY_YET) { 144 //std::cout << "writeFrame(video):" << std::hex << retStatus << std::endl; 145 vidx--; 146 } else{ 147 std::cout << "writeFrame(video):" << std::hex << retStatus << std::endl; 148 break; 149 } 150 } 151 elapsed = lastFrameTime - startTime; 152 std::this_thread::sleep_for(std::chrono::nanoseconds((((10LL * 1000LL) * 1000LL) / 25) - elapsed % (((10LL * 1000LL) * 1000LL) / 25))); 153 lastFrameTime = GETTIME(); 154 } 155 }).detach(); 156 std::thread([]{ 157 int aidx = 1; 158 char afn[256] = {0}; 159 Frame frame = {0}; 160 char abuffer[100 * 1024]; 161 frame.frameData = (PBYTE)abuffer; 162 STATUS retStatus = STATUS_SUCCESS; 163 while(true) { 164 sprintf(afn, "/data_fs/pcms/%d.pcma", aidx++); 165 frame.size = sizeof abuffer; 166 retStatus = readFrameFromDisk(frame.frameData, &frame.size, afn); 167 if(retStatus != STATUS_SUCCESS) { 168 std::cout << "readFrameFromDisk:" << std::hex << retStatus << std::endl; 169 break; 170 } 171 frame.presentationTs += (20 * (10LL * 1000LL)); 172 retStatus = writeFrame(audioTransceiver_, &frame); 173 if(retStatus != STATUS_SUCCESS) { 174 if(retStatus == STATUS_SRTP_NOT_READY_YET) { 175 //std::cout << "writeFrame(audio):" << std::hex << retStatus << std::endl; 176 aidx--; 177 } else{ 178 std::cout << "writeFrame(audio):" << std::hex << retStatus << std::endl; 179 break; 180 } 181 } 182 std::this_thread::sleep_for(std::chrono::nanoseconds((20 * (10LL * 1000LL)))); 183 } 184 }).detach(); 185 } 186 } 187 188 void func_RtcOnFrame(UINT64 customData, PFrame frame) { 189 printf("func_RtcOnFrame(audio) size=%d\n", frame->size); 190 } 191 192 int main() { 193 //printf("STATUS_ICE_CANDIDATE_INIT_MALFORMED %lu\n", STATUS_ICE_CANDIDATE_INIT_MALFORMED); 194 195 char wsUrl[64]; 196 sprintf(wsUrl, "ws://192.168.0.44:8080/webrtc/channel/01239F-C8ADE4-630DEE"); 197 ws = new WSClient(wsUrl); 198 199 ws->SetDataCb(onWsData, nullptr); 200 if(!ws->Start()) return -1; 201 202 initKvsWebRtc(); 203 204 RtcConfiguration conf_; 205 memset(&conf_, 0, sizeof conf_); 206 conf_.iceTransportPolicy = ICE_TRANSPORT_POLICY::ICE_TRANSPORT_POLICY_ALL; 207 //conf_.kvsRtcConfiguration.generatedCertificateBits = 2048; 208 conf_.kvsRtcConfiguration.generateRSACertificate = 1; 209 strcpy(conf_.iceServers[0].urls, "stun:77.72.169.213:3478"); 210 std::cout << "createPeerConnection:" << std::hex << createPeerConnection(&conf_, &peer_) << std::endl; 211 212 std::cout << "peerConnectionOnIceCandidate:" << std::hex << peerConnectionOnIceCandidate(peer_, 0, func_RtcOnIceCandidate) << std::endl; 213 std::cout << "peerConnectionOnConnectionStateChange:" << std::hex << peerConnectionOnConnectionStateChange(peer_, 0, func_RtcOnConnectionStateChange) << std::endl; 214 215 PRtcMediaStreamTrack video_track_ = new RtcMediaStreamTrack; 216 memset(video_track_, 0, sizeof *video_track_); 217 video_track_->codec = RTC_CODEC::RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; 218 video_track_->kind = MEDIA_STREAM_TRACK_KIND::MEDIA_STREAM_TRACK_KIND_VIDEO; 219 strcpy(video_track_->streamId, "0"); 220 strcpy(video_track_->trackId, "0"); 221 RtcRtpTransceiverInit rtcRtpTransceiverInit_v_; 222 rtcRtpTransceiverInit_v_.direction = RTC_RTP_TRANSCEIVER_DIRECTION::RTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; 223 std::cout << "addTransceiver:" << std::hex << addTransceiver(peer_, video_track_, &rtcRtpTransceiverInit_v_, &videoTransceiver_) << std::endl; 224 225 PRtcMediaStreamTrack audio_track_ = new RtcMediaStreamTrack; 226 memset(audio_track_, 0, sizeof *audio_track_); 227 audio_track_->codec = RTC_CODEC::RTC_CODEC_ALAW; 228 audio_track_->kind = MEDIA_STREAM_TRACK_KIND::MEDIA_STREAM_TRACK_KIND_AUDIO; 229 strcpy(audio_track_->streamId, "1"); 230 strcpy(audio_track_->trackId, "1"); 231 RtcRtpTransceiverInit rtcRtpTransceiverInit_a_; 232 rtcRtpTransceiverInit_a_.direction = RTC_RTP_TRANSCEIVER_DIRECTION::RTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; 233 std::cout << "addTransceiver:" << std::hex << addTransceiver(peer_, audio_track_, &rtcRtpTransceiverInit_a_, &audioTransceiver_) << std::endl; 234 std::cout << "transceiverOnFrame:" << std::hex << transceiverOnFrame(audioTransceiver_, 0, func_RtcOnFrame) << std::endl; 235 236 while(true) { 237 std::this_thread::sleep_for(std::chrono::seconds(1)); 238 } 239 deinitKvsWebRtc(); 240 241 return -1; 242 }
由于上一篇文章已经把项目发出来了,因此这里就没有再发,这个文件替换即可。
(发h264时,就是每个nal发一次,当然了,一般I帧前头带着SPS/PPS,好像行业内的编码器都是这样,是可以的。pcma的话就是160一段;我是8K的,50Hz)
收数据就是transceiverOnFrame注册的回调,代码中也有,但我暂时没有从h5获取,因为我电脑也没有mic。
资源文件如下:
(pcm我没录到,因为录音设备没有mic,这边算是一个互动,大家自己动手试试)
That's all.
有高手希望指点的话可以通过微信与我联系,我的id是wxid_8r2mjkbcu2an22
最后修改时间 2020-11-09 11:30:08
最后我把所有代码都上传上来供大家参考(代码是很早以前写的,那时候我甚至还不会c++,所以写的比较乱,大家担待)
代码包含了一个嵌入式的CMake项目(需配合一些h264/alaw/opus等资源文件,可以自己提供,也可用kvs附带的)和tomcat信令服务器。
(注意,此次提供的代码中.a文件并非海思芯片库,而是另一款soc,基于linaro linux的aarch64版本)
最后修改时间 2022-09-02 18:12:22
认真你就输了,一直认真你就赢了!