使用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_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++;

	}//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
			{

			}
		}
	}

 

你可能感兴趣的:(流媒体协议)