FFMPEG开发快速入坑——音视频混流处理

华叔-视觉魔术师

共 3097字,需浏览 7分钟

 ·

2022-02-09 17:35

本章节重点讲解对于编码后的音视频包写入mp4文件的处理,混流所有的API函数都属于libavformat 库。音视频混流操作的流程比较简单:

1、创建一个新的媒体格式上下文 avformat_alloc_output_context2()

2、根据音视频编码器信息,分别创建音频流 和 视频流 avformat_new_stream() 和 avcodec_parameters_from_context()

3、打开文件IO操作 avio_open()

4、写入文件头信息 avformat_write_header()

5、循环交错调用 av_interleaved_write_frame() 写入音视频帧数据。音视频数据包写入都是通过这个函数,需要注意的是AVPacket中 stream_index 流索引值要设置对。在具体项目中通常都是交错调用这个函数分别写入的(并不需要1对1的交错,通常是写入一个视频包,写入几个音频包的交错)

6、写入文件尾信息 av_write_trailer()

7、关闭文件IO操作 avio_closep()、释放媒体格式上下文 avformat_free_context()


另外需要注意一点的是:

打开媒体格式上下文后,如果输出媒体格式有 AVFMT_GLOBALHEADER 这个标记,那么音视频编码器创建的时候也需要设置 AV_CODEC_FLAG_GLOBAL_HEADER 标记。即:音视频编码器创建时需要:

if (m_pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
{
    m_ptrVideoEncCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

整个混流处理的示例代码:

AVFormatContext*  m_pFormatCtx    = nullptr;      // 媒体格式上下文
bool              m_bGlobalHeader = true;         // 音视频编解码器是否需要标记 AV_CODEC_FLAG_GLOBAL_HEADER
AVStream*         m_pVideoStream  = nullptr;      // 视频流信息
AVStream*         m_pAudioStream  = nullptr;      // 音频流信息


/**
  * @brief 打开音视频混流器
  * @param 
  * @return 返回错误码, 0 表示正常; < 0 表示错误码
  *
  */
int32_t MuxerOpen(
  const char*           pszFilePath,        // 要保存的媒体文件,通常是.mp4文件
  const AVCodecContext* pVideoEncCtx,       // 视频编码器上下文
  const AVCodecContext* pAudioEncCtx  )     // 音频编码器上下文
{
  int res = 0;


  // 创建输出流格式上下文
  res = avformat_alloc_output_context2(&m_pFormatCtx, nullptr, nullptr, pszFilePath);
  if (nullptr == m_pFormatCtx || res < 0)
  {
    LOGE(" [ERROR] fail to avformat_alloc_output_context2()\n");
    return -1;
  }
  if (m_pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
  {
    m_bGlobalHeader = true;
  }


  // 创建写入的视频流
  m_pVideoStream = avformat_new_stream(m_pFormatCtx, nullptr);
  if (nullptr == m_pVideoStream)
  {
    LOGE(" [ERROR] fail to create video stream\n");
    avformat_free_context(m_pFormatCtx);
    return -2;
  }
  res = avcodec_parameters_from_context(m_pVideoStream->codecpar, pVideoEncCtx);
  if (res < 0)
  {
    LOGE(" [ERROR] fail to video avcodec_parameters_from_context(), res=%d\n", res);
    avformat_free_context(m_pFormatCtx);
    return -2;
  }
  m_pVideoStream->time_base = pVideoEncCtx->time_base;


  // 创建写入的音频流
  m_pAudioStream = avformat_new_stream(m_pFormatCtx, nullptr);
  if (nullptr == m_pAudioStream)
  {
    LOGE(" [ERROR] fail to create video stream\n");
    avformat_free_context(m_pFormatCtx);
    return -2;
  }
  res = avcodec_parameters_from_context(m_pAudioStream->codecpar, pAudioEncCtx);
  if (res < 0)
  {
    LOGE(" [ERROR] fail to audio avcodec_parameters_from_context(), res=%d\n", res);
    avformat_free_context(m_pFormatCtx);
    return -2;
  }
  m_pAudioStream->time_base = pVideoEncCtx->time_base;


  // 打开文件IO上下文
  res = avio_open(&m_pFormatCtx->pb, pszFilePath, AVIO_FLAG_WRITE);
  if (res < 0)
  {
    LOGE(" [ERROR] fail to avio_open(), res=%d\n", res);
    avformat_free_context(m_pFormatCtx);
    return -2;
  }


  //
  // 写入文件头信息
  //
  res = avformat_write_header(m_pFormatCtx, nullptr);
  if (res < 0)
  {
    LOGE(" [ERROR] fail to FF_avformat_write_header(), res=%d\n", res);
    avformat_free_context(m_pFormatCtx);
    return -3;
  }

  return 0;
}


/**
  * @brief  关闭音视频混流器
  * @param  无
  * @return 无
  * 
  */
void MuxerClose()
{
  // 写入尾信息
  if (m_pFormatCtx != nullptr)
  {
    av_write_trailer(m_pFormatCtx);
  }

  // 先关IO上下文
  if (m_pFormatCtx->pb != nullptr)
  {
    avio_closep(&m_pFormatCtx->pb);
    m_pFormatCtx->pb = nullptr;
  }

  // 再释放媒体格式上下文
  if (m_pFormatCtx != nullptr)
  {
    avformat_free_context(m_pFormatCtx);
    m_pFormatCtx = nullptr;
  }

  // 流文件直接在 avformat_free_context()内部已经销毁了
  m_pVideoStream = nullptr;  
  m_pAudioStream = nullptr;
 
}


/**
  * @brief  写入编码后的音频或者视频数据包
  * @param  无
  * @return 无
  * 
  */
int32_t MuxerWrite(bool bVideoPkt, AVPacket* pInPacket)
{
  // 设置写入数据包的流索引
  if (bVideoPkt)
  {
    pInPacket->stream_index = m_pVideoStream->index;
  }
  else
  {
    pInPacket->stream_index = m_pAudioStream->index;
  }

  // 写入媒体文件
  int res = av_interleaved_write_frame(m_pFormatCtx, pInPacket);
  return res;
}


文章系列目录

华叔-视觉魔术师:FFMPEG开发快速入坑——绪论

浏览 6
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报