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开发快速入坑——绪论

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报