用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函数马上返回,避免阻塞太久。

下面是打开流的代码:


 
 
  1. BOOL RtmpStreamSession::openInputStream()
  2. {
  3. int res = 0;
  4. bool bIsNetPath = false;
  5. if(_strnicmp(m_InputUrl.c_str(), "rtsp://", 7) == 0 || _strnicmp(m_InputUrl.c_str(), "RTSP://", 7) == 0)
  6. {
  7. bIsNetPath = true;
  8. }
  9. else if(_strnicmp(m_InputUrl.c_str(), "rtmp://", 7) == 0 || _strnicmp(m_InputUrl.c_str(), "RTMP://", 7) == 0)
  10. {
  11. bIsNetPath = true;
  12. }
  13. else
  14. {
  15. bIsNetPath = false;
  16. }
  17. if(bIsNetPath) //从网络接收
  18. {
  19. //Initialize format context
  20. m_inputAVFormatCxt = avformat_alloc_context();
  21. //Initialize intrrupt callback
  22. AVIOInterruptCB icb = {interruptCallBack, this};
  23. m_inputAVFormatCxt->interrupt_callback = icb;
  24. }
  25. m_dwLastRecvFrameTime = 0;
  26. m_dwStartConnectTime = GetTickCount();
  27. //m_inputAVFormatCxt->flags |= AVFMT_FLAG_NONBLOCK;
  28. AVDictionary* options = nullptr;
  29. av_dict_set(&options, "stimeout", "3000000", 0); //设置超时断开连接时间
  30. //m_inputAVFormatCxt->max_analyze_duration = 2000000;
  31. // m_inputAVFormatCxt->fps_probe_size = 30;
  32. res = avformat_open_input(&m_inputAVFormatCxt, m_InputUrl.c_str(), 0, &options);
  33. if(res < 0)
  34. {
  35. 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);
  36. TRACE( "%s \n", strError.c_str());
  37. return FALSE;
  38. }
  39. TRACE( "%d, %d, %d \n", m_inputAVFormatCxt->max_analyze_duration, m_inputAVFormatCxt->probesize, m_inputAVFormatCxt->fps_probe_size);
  40. //获得各个流的信息
  41. return TRUE;
  42. }

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


 
 
  1. if(bIsNetPath) //从网络接收
  2. {
  3. //Initialize format context
  4. m_inputAVFormatCxt = avformat_alloc_context();
  5. //Initialize intrrupt callback
  6. AVIOInterruptCB icb = {interruptCallBack, this};
  7. m_inputAVFormatCxt->interrupt_callback = icb;
  8. }

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


 
 
  1. static int interruptCallBack( void *ctx)
  2. {
  3. RtmpStreamSession * pSession = (RtmpStreamSession*) ctx;
  4. //once your preferred time is out you can return 1 and exit from the loop
  5. if(pSession->CheckTimeOut(GetTickCount()))
  6. {
  7. return 1;
  8. }
  9. //continue
  10. return 0;
  11. }
  12. BOOL RtmpStreamSession::CheckTimeOut(DWORD dwCurrentTime)
  13. {
  14. if(dwCurrentTime < m_dwLastRecvFrameTime) //CPU时间回滚
  15. {
  16. return FALSE;
  17. }
  18. if(m_stop_status)
  19. return TRUE;
  20. if(m_dwLastRecvFrameTime > 0)
  21. {
  22. if((dwCurrentTime - m_dwLastRecvFrameTime)/ 1000 > m_nMaxRecvTimeOut) //接收过程中超时
  23. {
  24. return TRUE;
  25. }
  26. }
  27. else
  28. {
  29. if((dwCurrentTime - m_dwStartConnectTime)/ 1000 > m_nMaxConnectTimeOut) //连接超时
  30. {
  31. return TRUE;
  32. }
  33. }
  34. return FALSE;
  35. }

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

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


 
 
  1. if (avformat_find_stream_info(m_inputAVFormatCxt, 0) < 0)
  2. {
  3. TRACE( "can not find stream info \n");
  4. return FALSE;
  5. }
  6. av_dump_format(m_inputAVFormatCxt, 0, m_InputUrl.c_str(), 0);
  7. for (int i = 0; i < m_inputAVFormatCxt->nb_streams; i++)
  8. {
  9. AVStream *in_stream = m_inputAVFormatCxt->streams[i];
  10. if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
  11. {
  12. m_videoStreamIndex = i;
  13. coded_width = in_stream->codec->width;
  14. coded_height = in_stream->codec->height;
  15. if(in_stream->avg_frame_rate.den != 0 && in_stream->avg_frame_rate.num != 0)
  16. {
  17. m_frame_rate = in_stream->avg_frame_rate.num/in_stream->avg_frame_rate.den; //每秒多少帧
  18. }
  19. 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);
  20. }
  21. else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
  22. {
  23. m_audioStreamIndex = i;
  24. }
  25. }
  26. //m_bsfcAAC = av_bitstream_filter_init("aac_adtstoasc");
  27. // if(!m_bsfcAAC)
  28. // {
  29. // TRACE("can not create aac_adtstoasc filter \n");
  30. // }
  31. m_bsfcH264 = av_bitstream_filter_init( "h264_mp4toannexb");
  32. if(!m_bsfcH264)
  33. {
  34. TRACE( "can not create h264_mp4toannexb filter \n");
  35. }

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

3.  接收和分离流。

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


 
 
  1. void RtmpStreamSession::readAndDemux()
  2. {
  3. DWORD start_time = GetTickCount();
  4. AVPacket pkt;
  5. av_init_packet(&pkt);
  6. while( 1)
  7. {
  8. if(m_stop_status == true)
  9. {
  10. break;
  11. }
  12. int res;
  13. res = av_read_frame(m_inputAVFormatCxt, &pkt);
  14. if (res < 0) //读取错误或读到了文件尾
  15. {
  16. if( AVERROR_EOF == res)
  17. {
  18. TRACE( "End of file \n");
  19. break;
  20. }
  21. else
  22. {
  23. TRACE( "av_read_frame() got error: %d, pkt.size = %d \n", res, pkt.size);
  24. if(res == -11)
  25. {
  26. Sleep( 10);
  27. continue;
  28. }
  29. break;
  30. }
  31. //break;
  32. }
  33. AVStream *in_stream = m_inputAVFormatCxt->streams[pkt.stream_index];
  34. Demuxer(in_stream, pkt);
  35. av_free_packet(&pkt);
  36. m_dwLastRecvFrameTime = GetTickCount();
  37. } //while
  38. ReleaseCodecs();
  39. }

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


 
 
  1. int RtmpStreamSession::Demuxer(AVStream *pStream, AVPacket & pkt)
  2. {
  3. if(pStream->codec->codec_type != AVMEDIA_TYPE_VIDEO
  4. && pStream->codec->codec_type != AVMEDIA_TYPE_AUDIO)
  5. {
  6. return 0;
  7. }
  8. if(pkt.pts < 0)
  9. pkt.pts = 0;
  10. int64_t pts_time = (pkt.pts)* 90000*pStream->time_base.num/pStream->time_base.den; //转成90KHz为单位
  11. if(pStream->codec->codec_type == AVMEDIA_TYPE_VIDEO) //视频
  12. {
  13. if(pStream->codec->codec_id == AV_CODEC_ID_H264)
  14. {
  15. m_nVideoFramesNum++;
  16. if(!(pkt.data[ 0] == 0x0 && pkt.data[ 1] == 0x0 && pkt.data[ 2] == 0x0 && pkt.data[ 3] == 0x01))
  17. {
  18. //TRACE("Not H264 StartCode!\n");
  19. AVPacket tempPack;
  20. av_init_packet(&tempPack);
  21. //av_copy_packet(&tempPack, &pkt);
  22. int nRet = av_bitstream_filter_filter(m_bsfcH264, pStream->codec, NULL, &tempPack.data, &tempPack.size, pkt.data, pkt.size, 0);
  23. if(nRet >= 0)
  24. {
  25. if(m_pfd != NULL) //保存视频数据
  26. {
  27. fwrite( tempPack.data, tempPack.size, 1, m_pfd);
  28. }
  29. DecodeVideo(pStream, tempPack);
  30. if(tempPack.data != NULL)
  31. {
  32. av_free(tempPack.data); //一定要加上这句,否则会有内存泄漏
  33. tempPack.data = NULL;
  34. }
  35. }
  36. else
  37. {
  38. TRACE( "av_bitstream_filter_filter got error: %d \n", nRet);
  39. }
  40. //TRACE("FrameNo: %d, size: %d \n", m_nVideoFramesNum, pkt.size);
  41. }
  42. else
  43. {
  44. if(m_pfd != NULL) //保存视频数据
  45. {
  46. fwrite( pkt.data, pkt.size, 1, m_pfd);
  47. }
  48. DecodeVideo(pStream, pkt);
  49. }
  50. }
  51. //int nSecs = pkt.pts*in_stream->time_base.num/in_stream->time_base.den;
  52. //TRACE("Frame time: %02d:%02d \n", nSecs/60, nSecs%60);
  53. }
  54. else if(pStream->codec->codec_type == AVMEDIA_TYPE_AUDIO) //音频
  55. {
  56. AVPacket tempPack;
  57. av_init_packet(&tempPack);
  58. if(pStream->codec->codec_id == AV_CODEC_ID_AAC)
  59. {
  60. int nOutAACLen = 0;
  61. AAC_TO_ADTS(pkt.data, pkt.size, pStream->codec->sample_rate, m_aacADTSBuf, ONE_AUDIO_FRAME_SIZE, &nOutAACLen);
  62. tempPack.data = m_aacADTSBuf;
  63. tempPack.size = nOutAACLen;
  64. tempPack.pts = pkt.pts;
  65. }
  66. else
  67. {
  68. av_copy_packet(&tempPack, &pkt);
  69. }
  70. DecodeAudio(tempPack.data, tempPack.size, pStream->codec->codec_id, TRUE);
  71. }
  72. return 0;
  73. }

注意:分离出来后的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. 接收结束,释放相关资源。


 
 
  1. void RtmpStreamSession::ReleaseCodecs()
  2. {
  3. if (m_pfd != nullptr)
  4. {
  5. fclose(m_pfd);
  6. m_pfd = nullptr;
  7. }
  8. if(m_pframe)
  9. {
  10. av_frame_free(&m_pframe);
  11. m_pframe = NULL;
  12. }
  13. m_bVideoDecoderInited = FALSE;
  14. if(m_pAudioCodecCtx != NULL)
  15. {
  16. avcodec_close(m_pAudioCodecCtx);
  17. av_free(m_pAudioCodecCtx);
  18. m_pAudioCodecCtx = NULL;
  19. m_pAudioCodec = NULL;
  20. }
  21. if(m_pAudioFrame != NULL)
  22. {
  23. av_free(m_pAudioFrame);
  24. m_pAudioFrame = NULL;
  25. }
  26. if(m_pSamples != NULL)
  27. {
  28. av_free(m_pSamples);
  29. m_pSamples = NULL;
  30. }
  31. if(m_aacADTSBuf != NULL)
  32. {
  33. delete m_aacADTSBuf;
  34. m_aacADTSBuf = NULL;
  35. }
  36. m_stop_status = true;
  37. }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值