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