使用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 30s m_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_tfirst_pts_time = 0; long countbufsize = 0; int nRead = 0; int total_bytes = 0, n = 0; int64_t pts_time; FLVHeadflvHead; FLVTagflvTag; 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 = https://www.it610.com/article/(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++; }//while free(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接收数据时要注意的问题】

    推荐阅读