使用librtmp接收数据时要注意的问题
(這篇博文的完整代碼在我的另一篇博文《使用Librtmp接收H264 + AAC》)
librtmp是一個RTMP的開源庫,很多地方用它來做推流、拉流。它是RTMPDump開源軟件里的一部分,librtmp的下載地址:http://rtmpdump.mplayerhq.hu/,目前最新版是V2.3。librtmp如何使用在很多博客已經有介紹,雷神的博客也有幾個相關的例子介紹其使用,這里就不多說,這里只講一下我用librtmp遇到過的幾個問題和給出其解決辦法。
1. 我發現librtmp V2.3代碼里有個Bug,不知道之前的版本有沒有,反正提出來,對大家做個提醒。
原來的librtmp\amf.c文件里有個函數(大概是1124行):
AMFObjectProperty * AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex) {if (nIndex >= 0){if (nIndex <= obj->o_num) //這里有問題return &obj->o_props[nIndex];}else{int n;for (n = 0; n < obj->o_num; n++){if (AVMATCH(&obj->o_props[n].p_name, name))return &obj->o_props[n];}}return (AMFObjectProperty *)&AMFProp_Invalid; }大家注意到沒有,nIndex變量不能等于obj->o_num。如果等于就發生越界訪問了。我測試時發現這個Bug,發生時機是在播放完一個flv節目之后,接收函數里就出錯了。正確的代碼應該這樣:
AMFObjectProperty * AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex) {if (nIndex >= 0){if (nIndex < obj->o_num) // 解決訪問數組越界的問題return &obj->o_props[nIndex];}else{int n;for (n = 0; n < obj->o_num; n++){if (AVMATCH(&obj->o_props[n].p_name, name))return &obj->o_props[n];}}return (AMFObjectProperty *)&AMFProp_Invalid; }2. ?怎么創建和初始化對象?寫上這個有點羅嗦,但為了能使后面解說代碼上下文更連貫,我就把這個初始化的代碼也貼出來。這里要注意的一個地方是:設置超時,默認的超時時間太大了(30秒)。
m_rtmp = RTMP_Alloc();RTMP_Init(m_rtmp);//set connection timeout,default 30sm_rtmp->Link.timeout = 10;if (!RTMP_SetupURL(m_rtmp, (char*)m_InputUrl.c_str())){TRACE("RTMP_SetupURL Err\n");RTMP_Free(m_rtmp);return FALSE;}if (bLiveStream) {m_rtmp->Link.lFlags |= RTMP_LF_LIVE;}RTMP_SetBufferMS(m_rtmp, 5 * 1000);3. 接收數據的循環應該怎么寫?我看到網上的一些例子對接收處理不是太完善,下面給出我的一個實現:
int readRTMPDataLoop() {int nVideoFramesNum = 0;int64_t first_pts_time = 0;long countbufsize = 0;int nRead = 0;int total_bytes = 0, n = 0;int64_t pts_time;FLVHead flvHead;FLVTag flvTag;memset(&flvHead, 0, sizeof(flvHead));if (!RTMP_Connect(m_rtmp, NULL)){TRACE("RTMP_Connect Err\n");RTMP_Free(m_rtmp);m_rtmp = NULL;return -1;}if (!RTMP_ConnectStream(m_rtmp, 0)){TRACE("ConnectStream Err\n");RTMP_Free(m_rtmp);m_rtmp = NULL;return -1;}nRead = RTMP_Read(m_rtmp, (char*)&flvHead, sizeof(FLVHead));if (nRead <= 0){TRACE("RTMP_Read failed \n");RTMP_Close(m_rtmp);RTMP_Free(m_rtmp);m_rtmp = NULL;return -2;}flvHead.offset = HTON32(flvHead.offset);TRACE("文件類型: %c %c %c\n", flvHead.type[0], flvHead.type[1], flvHead.type[2]);TRACE("版本: %d\n", flvHead.version);TRACE("流信息: %d\n", flvHead.stream_info);TRACE("head長度: %d\n", flvHead.offset);AVCodecID audio_codec_id = AV_CODEC_ID_AAC;memset(&flvTag, 0, sizeof(FLVTag));int bufsize = 1024 * 1024 * 2;char *data = (char*)malloc(bufsize);memset(data, 0, bufsize);while(1){if(m_stop_status == true){break;}//音視頻if ((nRead = RTMP_Read(m_rtmp, (char*)&flvTag, sizeof(FLVTag))) == sizeof(FLVTag)){flvTag.tag_size = HTON32(flvTag.tag_size);int length = 0;memcpy(&length, flvTag.length, 3);length = HTON24(length);unsigned int time = 0;memcpy(&time, flvTag.timecamp, 3);time = HTONTIME(time);//TRACE("\nTag大小: %d\n",flvTag.tag_size);//TRACE("Tag類型: %d, 長度:%d, 時間戳: %d\n",flvTag.type, length, time);pts_time = time; //libRTMP的時間戳以毫秒為單位//TRACE("RTMP Packet Type: %d, TimeStamp: %u \n", flvTag.type, time);nRead = RTMP_Read(m_rtmp, data, length);if (nRead > 0){if (flvTag.type == 8) //音頻{}else if (flvTag.type == 9) //視頻{m_nVideoFramesNum++;}}}if (nRead < 0){TRACE("RTMP_Read return: %d \n", nRead);break;}else if (nRead == 0){TRACE("RTMP_Read return: %d \n", nRead);Sleep(10);continue;}n++;}//whilefree(data);if (m_rtmp != NULL){RTMP_Close(m_rtmp);RTMP_Free(m_rtmp);m_rtmp = NULL;}return 0; }4. 如果RTMP流的組成是:H264 + AAC,則接收到的H264和AAC流不能直接保存或播放,還需要經過處理。收到的H264是沒有前面四個字節開始碼的,需要自己插入開始碼(0x01000000);AAC也需要經過處理,轉換成ADTS-AAC。
5. 有一個問題:某些RTMP服務器(ngnix-rtmp)在轉發H264視頻的時候會去掉除第一個I幀前的SPS+PPS段,也就是說當客戶端接收剛開始會收到SPS+PPS(一般是第一幀),但后面的I幀前面都去掉了SPS和PPS的。RTMP服務器這樣做是為了減少數據發送量和節省帶寬,這個問題對你的應用可能有影響也可能沒影響。如果你只是播放視頻或錄制視頻,只要第一個I幀包含SPS+PPS,你的視頻是可以播放的。但是,如果你需要將收到的流再轉發(國內很多人喜歡搞拉流再推流的服務),那就要考慮處理措施。假如你是做轉發的(相當于一個服務器),當收到RTMP服務器發來的第一個的I幀(攜帶SPS+PPS)之后就開始轉發,這時候記為第1秒,當第3秒的時候才有一個客戶端連上來,那么這個客戶端就收不到SPS和SPS了,也就不能正常解碼和播放。解決辦法是自己強行插入SPS+SPS,代碼如下:
nRead = RTMP_Read(m_rtmp, data, length);if (nRead > 0){if (flvTag.type == 8) //音頻{if (audio_codec_id == AV_CODEC_ID_AAC){int nOutAACLen = 0;AAC_TO_ADTS((unsigned char*)data, nRead, 44100, m_aacADTSBuf, AAC_MAX_FRAME_SIZE, &nOutAACLen);}}else if (flvTag.type == 9) //視頻{m_nVideoFramesNum++;if (!(data[0] == 0x0 && data[1] == 0x0 && data[2] == 0x0 && data[3] == 0x01)){//TRACE("Not H264 StartCode!\n");int outLen = 0;UnPackH264(data, nRead, m_h264data, outLen);if (outLen < 4){TRACE("Video frame was too short! \n");continue;}int nalu_type = (m_h264data[4] & 0x1F);TRACE("FrameNo: %d, nalu_type: %d, size: %d \n", m_nVideoFramesNum, nalu_type, outLen);if (!m_bSpsPpsGot && nalu_type == 7){ASSERT(outLen < sizeof(m_SpsPpsBuffer));memcpy(m_SpsPpsBuffer, m_h264data, outLen);m_nSpsPpsSize = outLen;m_bSpsPpsGot = true;}if (nalu_type == 5) //I frame{if (m_bSpsPpsGot) //在I幀前插入SPS和PPS{memmove(m_h264data + m_nSpsPpsSize, m_h264data, outLen);memcpy(m_h264data, m_SpsPpsBuffer, m_nSpsPpsSize);outLen += m_nSpsPpsSize;}}if (outLen > 0){}}else{}}}?
總結
以上是生活随笔為你收集整理的使用librtmp接收数据时要注意的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: RDKit | 处理RDKit分子Mol
- 下一篇: 微信小程序发送验证码短信SDK及文档
