用FFmpeg实现的RTMP接收类

      现在很多直播都用到RTMP来传输,而接收RTMP用FFmpeg比较常见(当然也有用其他库的)。FFmpeg对RTMP接收有比较完善实现了,API使用方法也很简单,大多数流程跟文件流处理一样,但是一些区别的地方。下面大概说一下用FFmpeg怎么实现RTMP接收功能,后面再附上封装类的代码。

1.  初始化和打开流。

 这是第一步要做的工作,打开了流后才能往下接收数据,打开流需要调用FFmpeg的API avformat_open_input函数,这个函数连接网络的时候会阻塞的,所以要设置超时值,否则有时候会阻塞很久时间。怎么设置超时时间?

   AVDictionary* options = nullptr;   
    av_dict_set(&options, "stimeout", "3000000", 0);  //设置超时断开连接时间  

上面两行代码设置了连接的超时时间为3秒,但是我试过了,到了超时时间函数还没有返回,好像是FFmpeg的问题。但没有关系,另外我们还有一种方法检测超时,就是通过异常回调函数,avformat_open_input函数可以传入一个回调函数地址作为参数,如果发生连接超时、接收超时,可以直接在回调函数里通知,这样可以使avformat_open_input函数马上返回,避免阻塞太久。

下面是打开流的代码:

BOOL RtmpStreamSession::openInputStream()
{
    int res = 0;

    bool bIsNetPath = false;

	if(_strnicmp(m_InputUrl.c_str(), "rtsp://", 7) == 0 || _strnicmp(m_InputUrl.c_str(), "RTSP://", 7) == 0)
	{
		bIsNetPath = true;
	}
	else if(_strnicmp(m_InputUrl.c_str(), "rtmp://", 7) == 0 || _strnicmp(m_InputUrl.c_str(), "RTMP://", 7) == 0)
	{
		bIsNetPath = true;
	}
	else
	{
		bIsNetPath = false;
	}

	if(bIsNetPath) //从网络接收
	{
		//Initialize format context
		m_inputAVFormatCxt = avformat_alloc_context();

		//Initialize intrrupt callback
		AVIOInterruptCB icb = {interruptCallBack,this};
		m_inputAVFormatCxt->interrupt_callback = icb;
	}


	m_dwLastRecvFrameTime = 0;
	m_dwStartConnectTime = GetTickCount();

	//m_inputAVFormatCxt->flags |= AVFMT_FLAG_NONBLOCK;

    AVDictionary* options = nullptr;   
    av_dict_set(&options, "stimeout", "3000000", 0);  //设置超时断开连接时间  

	//m_inputAVFormatCxt->max_analyze_duration = 2000000;
    // m_inputAVFormatCxt->fps_probe_size = 30;

    res = avformat_open_input(&m_inputAVFormatCxt, m_InputUrl.c_str(), 0, &options);

    if(res < 0)
    {
        string strError = "can not open file:" + m_InputUrl + ",errcode:" + to_string(res) + ",err msg:" + av_make_error_string(m_tmpErrString, AV_ERROR_MAX_STRING_SIZE, res);
		TRACE("%s \n", strError.c_str());
		return FALSE;
    }

	TRACE("%d, %d, %d \n", m_inputAVFormatCxt->max_analyze_duration, m_inputAVFormatCxt->probesize, m_inputAVFormatCxt->fps_probe_size);

  //获得各个流的信息

	return TRUE;
}

    OpenInputStream函数首先检测输入的URL是不是RTSP或RTMP地址,如果不是,则当作文件流。对网络流,需要传入一个回调函数地址,看下面代码:

	if(bIsNetPath) //从网络接收
	{
		//Initialize format context
		m_inputAVFormatCxt = avformat_alloc_context();

		//Initialize intrrupt callback
		AVIOInterruptCB icb = {interruptCallBack,this};
		m_inputAVFormatCxt->interrupt_callback = icb;
	}

上面传入的异常回调函数是interruptCallback,下面代码是异常回调函数的实现:

static int interruptCallBack(void *ctx)
{
    RtmpStreamSession * pSession = (RtmpStreamSession*) ctx;

   //once your preferred time is out you can return 1 and exit from the loop
    if(pSession->CheckTimeOut(GetTickCount()))
    {
      return 1;
    }

   //continue 
   return 0;

}

BOOL   RtmpStreamSession::CheckTimeOut(DWORD dwCurrentTime)
{
	if(dwCurrentTime < m_dwLastRecvFrameTime) //CPU时间回滚
	{
		return FALSE;
	}

	if(m_stop_status)
		return TRUE;

	if(m_dwLastRecvFrameTime > 0)
	{
		if((dwCurrentTime - m_dwLastRecvFrameTime)/1000 > m_nMaxRecvTimeOut) //接收过程中超时
		{
		    return TRUE;
		}
	}
	else
	{
		if((dwCurrentTime - m_dwStartConnectTime)/1000 > m_nMaxConnectTimeOut) //连接超时
		{
			return TRUE;
		}
	}
	return FALSE;
}

如果interruptCallback返回1则退出连接或接收循环,返回0则继续接收。如果是前者,则avformat_open_input函数马上返回,这样就可以通过自己指定超时来限制连接的时间。

2. 获得视频流和音频流的信息。

  if (avformat_find_stream_info(m_inputAVFormatCxt, 0) < 0)
    {
        TRACE("can not find stream info \n");
		return FALSE;
    }
    av_dump_format(m_inputAVFormatCxt, 0, m_InputUrl.c_str(), 0);
    for (int i = 0; i < m_inputAVFormatCxt->nb_streams; i++)
    {
        AVStream *in_stream = m_inputAVFormatCxt->streams[i];

		if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_videoStreamIndex = i;

			coded_width = in_stream->codec->width;
			coded_height = in_stream->codec->height;

			if(in_stream->avg_frame_rate.den != 0 && in_stream->avg_frame_rate.num != 0)
			{
			  m_frame_rate = in_stream->avg_frame_rate.num/in_stream->avg_frame_rate.den;//每秒多少帧 
			}

			TRACE("video stream index: %d, width: %d, height: %d, FrameRate: %d\n", m_videoStreamIndex, in_stream->codec->width, in_stream->codec->height, m_frame_rate);
		}
		else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_audioStreamIndex = i;
		}
    }

	 //m_bsfcAAC = av_bitstream_filter_init("aac_adtstoasc");
  //  if(!m_bsfcAAC)
  //  {
  //      TRACE("can not create aac_adtstoasc filter \n");
  //  }

	m_bsfcH264 = av_bitstream_filter_init("h264_mp4toannexb");
    if(!m_bsfcH264)
    {
        TRACE("can not create h264_mp4toannexb filter \n");
    }

主要是获取视频流和音频流的索引,视频宽、高等信息。代码还调用av_bitstream_filter_init函数创建了一个转换Filter,这句代码有什么用呢?后面会讲到。

3.  接收和分离流。

调用av_read_frame函数解析网络接收到的数据并分离出视频包和音频包。

void RtmpStreamSession::readAndDemux()
{
	DWORD start_time = GetTickCount(); 

    AVPacket pkt;
	av_init_packet(&pkt);

    while(1)
    {
        if(m_stop_status == true)
        {
            break;
        }

        int res;
       
        res = av_read_frame(m_inputAVFormatCxt, &pkt);
        if (res < 0)  //读取错误或读到了文件尾
        {
			if(AVERROR_EOF == res)
			{
				TRACE("End of file \n");

				break;
			}
			else
			{
				TRACE("av_read_frame() got error: %d, pkt.size = %d \n", res, pkt.size);

			  if(res == -11)
			  {
				 Sleep(10);
			     continue;
			  }

			  break;
			}

			//break;  
        }

		AVStream *in_stream = m_inputAVFormatCxt->streams[pkt.stream_index];

        Demuxer(in_stream, pkt);
		av_free_packet(&pkt);

		m_dwLastRecvFrameTime = GetTickCount();

    }//while

	ReleaseCodecs();
}

Demuxer函数负责处理分离后的视频包和音频包,然后对数据进行解码或写到文件。

int RtmpStreamSession::Demuxer(AVStream *pStream, AVPacket & pkt)
{
	if(pStream->codec->codec_type != AVMEDIA_TYPE_VIDEO 
		&& pStream->codec->codec_type != AVMEDIA_TYPE_AUDIO)
	{
		return 0;
	}

	if(pkt.pts < 0)
		pkt.pts = 0;

	int64_t pts_time = (pkt.pts)*90000*pStream->time_base.num/pStream->time_base.den; //转成90KHz为单位

	if(pStream->codec->codec_type == AVMEDIA_TYPE_VIDEO)  //视频
	{
		if(pStream->codec->codec_id == AV_CODEC_ID_H264)
		{
			m_nVideoFramesNum++;

			if(!(pkt.data[0] == 0x0 && pkt.data[1] == 0x0 && pkt.data[2] == 0x0 && pkt.data[3] == 0x01))
			{
				//TRACE("Not H264 StartCode!\n");

				AVPacket tempPack; 
				av_init_packet(&tempPack);
				//av_copy_packet(&tempPack, &pkt); 

				int nRet = av_bitstream_filter_filter(m_bsfcH264, pStream->codec, NULL, &tempPack.data, &tempPack.size, pkt.data, pkt.size, 0);

				if(nRet >= 0)
				{
					if(m_pfd != NULL) //保存视频数据
					{
						fwrite( tempPack.data, tempPack.size, 1, m_pfd);		
					}


					DecodeVideo(pStream, tempPack);

					if(tempPack.data != NULL)
					{
						av_free(tempPack.data); //一定要加上这句,否则会有内存泄漏
						tempPack.data = NULL;
					}

				}
				else
				{
					TRACE("av_bitstream_filter_filter got error: %d \n", nRet);
				}

				//TRACE("FrameNo: %d,  size: %d \n", m_nVideoFramesNum,  pkt.size);
			}
			else
			{

				if(m_pfd != NULL) //保存视频数据
				{
					fwrite( pkt.data, pkt.size, 1, m_pfd);		
				}

				DecodeVideo(pStream, pkt);
			}


		}

		//int nSecs = pkt.pts*in_stream->time_base.num/in_stream->time_base.den;
		//TRACE("Frame time: %02d:%02d \n", nSecs/60, nSecs%60);


	}
	else if(pStream->codec->codec_type == AVMEDIA_TYPE_AUDIO) //音频
	{

		AVPacket tempPack; 
		av_init_packet(&tempPack);

		if(pStream->codec->codec_id == AV_CODEC_ID_AAC)
		{
			int nOutAACLen = 0;

			AAC_TO_ADTS(pkt.data, pkt.size, pStream->codec->sample_rate, m_aacADTSBuf, ONE_AUDIO_FRAME_SIZE, &nOutAACLen);

			tempPack.data = m_aacADTSBuf;
			tempPack.size = nOutAACLen;
			tempPack.pts = pkt.pts;
		}
		else
		{
			av_copy_packet(&tempPack, &pkt);
		}
		
		DecodeAudio(tempPack.data, tempPack.size, pStream->codec->codec_id, TRUE);
	}

	return 0;
}

注意:分离出来后的H264视频数据并不能马上解码,因为从RTP包中提取出的视频H264数据并不是一个标准的NALU单元,它没有包含H264前缀码,需要使用FFMPEG中的名为"h264_mp4toannexb"的bitstream filter 进行处理。在上面代码中调用了h264_mp4toannexb_filter函数, 它对分离出的H264帧进行转换,处理后就得到标准的ES流。

另外,对分离出来的AAC音频帧需要插入ADTS头,否则也不能正确解码的。

4.  解码音频、视频,或保存数据到文件。

解码实现详细请看这两个函数:

int RtmpStreamSession::DecodeVideo(AVStream * st, AVPacket & dec_pkt) 

bool RtmpStreamSession:: DecodeAudio(PBYTE pData, int nDataLen, AVCodecID audioID,  BOOL bPlayAudio)

5. 接收结束,释放相关资源。


void RtmpStreamSession::ReleaseCodecs()
{
	if (m_pfd != nullptr)
	{
		fclose(m_pfd);
		m_pfd = nullptr;
	}

	if(m_pframe)
	{
		av_frame_free(&m_pframe);
		m_pframe = NULL;
	}

	m_bVideoDecoderInited = FALSE;

	if(m_pAudioCodecCtx != NULL)
	{
		avcodec_close(m_pAudioCodecCtx);
		av_free(m_pAudioCodecCtx);

        m_pAudioCodecCtx = NULL;
		m_pAudioCodec = NULL;
	}

	if(m_pAudioFrame != NULL)
	{
		av_free(m_pAudioFrame);
		m_pAudioFrame = NULL;
	}

	if(m_pSamples != NULL)
	{
		av_free(m_pSamples);
		m_pSamples = NULL;
	}


	if(m_aacADTSBuf != NULL)
	{
		delete m_aacADTSBuf;
		m_aacADTSBuf = NULL;
	}

	 m_stop_status = true;
}

封装类的代码下载链接:http://download.csdn.net/download/zhoubotong2012/10906954

你可能感兴趣的:(ffmpeg)