FFMPEG開發(fā)快速入坑——音視頻混流處理
共 3097字,需瀏覽 7分鐘
·
2022-02-09 17:35
本章節(jié)重點講解對于編碼后的音視頻包寫入mp4文件的處理,混流所有的API函數(shù)都屬于libavformat 庫。音視頻混流操作的流程比較簡單:
1、創(chuàng)建一個新的媒體格式上下文 avformat_alloc_output_context2()
2、根據(jù)音視頻編碼器信息,分別創(chuàng)建音頻流 和 視頻流 avformat_new_stream() 和 avcodec_parameters_from_context()
3、打開文件IO操作 avio_open()
4、寫入文件頭信息 avformat_write_header()
5、循環(huán)交錯調(diào)用 av_interleaved_write_frame() 寫入音視頻幀數(shù)據(jù)。音視頻數(shù)據(jù)包寫入都是通過這個函數(shù),需要注意的是AVPacket中 stream_index 流索引值要設(shè)置對。在具體項目中通常都是交錯調(diào)用這個函數(shù)分別寫入的(并不需要1對1的交錯,通常是寫入一個視頻包,寫入幾個音頻包的交錯)
6、寫入文件尾信息 av_write_trailer()
7、關(guān)閉文件IO操作 avio_closep()、釋放媒體格式上下文 avformat_free_context()
另外需要注意一點的是:
打開媒體格式上下文后,如果輸出媒體格式有 AVFMT_GLOBALHEADER 這個標記,那么音視頻編碼器創(chuàng)建的時候也需要設(shè)置 AV_CODEC_FLAG_GLOBAL_HEADER 標記。即:音視頻編碼器創(chuàng)建時需要:
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;
// 創(chuàng)建輸出流格式上下文
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;
}
// 創(chuàng)建寫入的視頻流
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;
// 創(chuàng)建寫入的音頻流
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 關(guān)閉音視頻混流器
* @param 無
* @return 無
*
*/
void MuxerClose()
{
// 寫入尾信息
if (m_pFormatCtx != nullptr)
{
av_write_trailer(m_pFormatCtx);
}
// 先關(guān)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()內(nèi)部已經(jīng)銷毀了
m_pVideoStream = nullptr;
m_pAudioStream = nullptr;
}
/**
* @brief 寫入編碼后的音頻或者視頻數(shù)據(jù)包
* @param 無
* @return 無
*
*/
int32_t MuxerWrite(bool bVideoPkt, AVPacket* pInPacket)
{
// 設(shè)置寫入數(shù)據(jù)包的流索引
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;
}
文章系列目錄
華叔-視覺魔術(shù)師:FFMPEG開發(fā)快速入坑——緒論