作者: 葉餘 來源: https://www.cnblogs.com/leisure_chn/p/10584901.html
FFmpeg編解碼處理系列筆記:
[0].
FFmpeg時間戳詳解 [1]. FFmpeg編解碼處理1-轉碼全流程簡介 [2]. FFmpeg編解碼處理2-編解碼API詳解 [3]. FFmpeg編解碼處理3-視訊編碼 [4]. FFmpeg編解碼處理4-音頻編碼 基于 FFmpeg 4.1 版本。1. 轉碼全流程簡介
看一下 FFmpeg 正常處理流程:

大流程可以劃分為輸入、輸出、轉碼、播放四大塊。其中轉碼涉及比較多的處理環節,從圖中可以看出,轉碼功能在整個功能圖中占比很大。轉碼的核心功能在解碼和編碼兩個部分,但在一個可用的示例程式中,編碼解碼與輸入輸出是難以分割的。解複用器為解碼器提供輸入,解碼器會輸出原始幀,對原始幀可進行各種複雜的濾鏡處理,濾鏡處理後的幀經編碼器生成編碼幀,多路流的編碼幀經複用器輸出到輸出檔案。
1.1 解複用
從輸入檔案中讀取編碼幀,判斷流類型,根據流類型将編碼幀送入視訊解碼器或音頻解碼器。
av_read_frame(ictx.fmt_ctx, &ipacket);
if (codec_type == AVMEDIA_TYPE_VIDEO) {
transcode_video(&stream, &ipacket);
} else if (codec_type == AVMEDIA_TYPE_AUDIO) {
transcode_audio(&stream, &ipacket);
}
else {
av_interleaved_write_frame(octx.fmt_ctx, &ipacket);
}
1.2 解碼
将視音頻編碼幀解碼生成原始幀。後文詳述。
1.3 濾鏡
FFmpeg 提供多種多樣的濾鏡,用來處理原始幀資料。
本例中,為每個音頻流/視訊流使用空濾鏡,即濾鏡圖中将 buffer 濾鏡和 buffersink 濾鏡直接相連。目的是:通過視訊 buffersink 濾鏡将視訊流輸出像素格式轉換為編碼器采用的像素格式;通過音頻 abuffersink 濾鏡将音頻流輸出聲道布局轉換為編碼器采用的聲道布局。為下一步的編碼操作作好準備。如果不使用這種方法,則需要處理圖像格式轉換和音頻重采樣,進而確定進入編碼器的幀是編碼器支援的格式。
當然,例程可擴充,可以很容易的在 buffer 濾鏡和 buffersink 濾鏡中間插入其他功能濾鏡,實作豐富的視音頻處理功能。
濾鏡的使用方法不是本實驗關注的重點。詳細用法可參考 "
FFmpeg原始幀處理-濾鏡API用法”
1.4 編碼
将原始視音頻幀編碼生成編碼幀。後文詳述。
1.5 複用
将編碼幀按不同流類型交織寫入輸出檔案。
av_interleaved_write_frame(octx.fmt_ctx, &ipacket);
2. 轉碼例程簡介
轉碼功能複雜,示例程式很難寫得簡短,這幾篇筆記共用同一份示例代碼。在 shell 中運作如下指令下載下傳例程源碼:
svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_transcode
例程支援在指令行中指定視音頻編碼格式以及輸出檔案封裝格式。如果編碼格式指定為 "copy",則輸出流使用與輸入流相同的編碼格式。與 FFmpeg 指令不同的是,FFmpeg 指令指定編碼器參數為 "copy" 時,将不會啟動編解碼過程,而僅啟用轉封裝過程,整個過程很快執行完畢;本例程指定編碼格式為 "copy" 時,則會使用相同的編碼格式進行解碼與編碼,整個過程比較耗時。
例程驗證方法:
./transcode -i input.flv -c:v mpeg2video -c:a mp2 output.ts
和如下指令效果大緻一樣:
ffmpeg -i input.flv -c:v mpeg2video -c:a mp2 output.ts
源代碼檔案說明:
1
2
3
4
5
Makefile
main.c 轉複用轉碼功能
av_codec.c 編碼解碼功能
av_filter.c 濾鏡處理
open_file.c 打開輸入輸出檔案
轉碼的主流程主要在 main. c中 transcode_video()、transcode_audio() 和 transcode_audio_with_afifo() 三個函數中。當輸入音頻幀尺寸能被音頻編碼器接受時,使用 transcode_audio() 函數;否則,引入音頻 FIFO,使每次從 FIFO 中取出的音頻幀尺寸能被音頻編碼器接受,使用 transcode_audio_with_afifo() 函數實作此功能。這幾個函數僅提供示意功能,示範音視訊轉碼功能的實作方法,源碼糾結、可讀性差,暫無時間優化。
2.1 視訊轉碼流程
視訊轉碼函數 transcode_video(),其主要處理流程如下(已删除大量細節代碼):
static int transcode_video(const stream_ctx_t *sctx, AVPacket *ipacket)
{
AVFrame *frame_dec = av_frame_alloc();
AVFrame *frame_flt = av_frame_alloc();
AVPacket opacket;
// 一個視訊packet隻包含一個視訊frame,但沖洗解碼器時一個flush packet會取出
// 多個frame出來,每次循環取處理一個frame
while (1)
{
// 1. 時間基轉換,解碼
av_packet_rescale_ts(ipacket, sctx->i_stream->time_base, sctx->o_codec_ctx->time_base);
ret = av_decode_frame(sctx->i_codec_ctx, ipacket, &new_packet, frame_dec);
// 2. 濾鏡處理
ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt);
// 3. 編碼
// 3.1 設定幀類型
frame_flt->pict_type = AV_PICTURE_TYPE_NONE;
// 3.2 編碼
ret = av_encode_frame(sctx->o_codec_ctx, frame_flt, &opacket);
// 3.3 更新編碼幀中流序号
opacket.stream_index = sctx->stream_idx;
// 3.4 時間基轉換,AVPacket.pts和AVPacket.dts的機關是AVStream.time_base,不同的封裝格式其
// AVStream.time_base不同是以輸出檔案中,每個packet需要根據輸出封裝格式重新計算pts和dts
av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base);
// 4. 将編碼後的packet寫入輸出媒體檔案
ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket);
av_packet_unref(&opacket);
}
return ret;
}
2.2 音頻轉碼流程
音頻轉碼函數 transcode_audio(),其主要處理流程如下(已删除大量細節代碼):
static int transcode_audio_with_afifo(const stream_ctx_t *sctx, AVPacket *ipacket)
{
AVFrame *frame_dec = av_frame_alloc();
AVFrame *frame_flt = av_frame_alloc();
AVFrame *frame_enc = NULL;
AVPacket opacket;
int enc_frame_size = sctx->o_codec_ctx->frame_size;
AVAudioFifo* p_fifo = sctx->aud_fifo;
static int s_pts = 0;
while (1) // 處理一個packet,一個音頻packet可能包含多個音頻frame,循環每次處理一個frame
{
// 1. 時間基轉換,解碼
av_packet_rescale_ts(ipacket, sctx->i_stream->time_base, sctx->o_codec_ctx->time_base);
ret = av_decode_frame(sctx->i_codec_ctx, ipacket, &new_packet, frame_dec);
// 2. 濾鏡處理
ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt);
// 3. 使用音頻fifo,進而保證每次送入編碼器的音頻幀尺寸滿足編碼器要求
// 3.1 将音頻幀寫入fifo,音頻幀尺寸是解碼格式中音頻幀尺寸
if (!dec_finished)
{
uint8_t** new_data = frame_flt->extended_data; // 本幀中多個聲道音頻資料
int new_size = frame_flt->nb_samples; // 本幀中單個聲道的采樣點數
// FIFO中可讀資料小于編碼器幀尺寸,則繼續往FIFO中寫資料
ret = write_frame_to_audio_fifo(p_fifo, new_data, new_size);
}
// 3.2 從fifo中取出音頻幀,音頻幀尺寸是編碼格式中音頻幀尺寸
// FIFO中可讀資料大于編碼器幀尺寸,則從FIFO中讀走資料進行處理
while ((av_audio_fifo_size(p_fifo) >= enc_frame_size) || dec_finished)
{
// 從FIFO中讀取資料,編碼,寫入輸出檔案
ret = read_frame_from_audio_fifo(p_fifo, sctx->o_codec_ctx, &frame_enc);
// 4. fifo中讀取的音頻幀沒有時間戳資訊,重新生成pts
frame_enc->pts = s_pts;
s_pts += ret;
flush_encoder:
// 5. 編碼
ret = av_encode_frame(sctx->o_codec_ctx, frame_enc, &opacket);
// 5.1 更新編碼幀中流序号,并進行時間基轉換
// AVPacket.pts和AVPacket.dts的機關是AVStream.time_base,不同的封裝格式其AVStream.time_base不同
// 是以輸出檔案中,每個packet需要根據輸出封裝格式重新計算pts和dts
opacket.stream_index = sctx->stream_idx;
av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base);
// 6. 将編碼後的packet寫入輸出媒體檔案
ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket);
}
if (finished)
{
break;
}
}
return ret;
}
2.3 轉碼過程中的時間戳處理
在封裝格式處理例程中,不深入了解時間戳也沒有關系。但在編解碼處理例程中,時間戳處理是很重要的一個細節,必須要搞清楚。
容器(檔案層)中的時間基(AVStream.time_base)與編解碼器上下文(視訊層)裡的時間基(AVCodecContex.time_base)不一樣,解碼編碼過程中需要進行時間基轉換。
視訊按幀進行播放,是以原始視訊幀時間基為 1/framerate。視訊解碼前需要處理輸入 AVPacket 中各時間參數,将輸入容器中的時間基轉換為 1/framerate 時間基;視訊編碼後再處理輸出 AVPacket 中各時間參數,将 1/framerate 時間基轉換為輸出容器中的時間基。
音頻按采樣點進行播放,是以原始音頻幀時間為 1/sample_rate。音頻解碼前需要處理輸入 AVPacket 中各時間參數,将輸入容器中的時間基轉換為 1/sample_rate 時間基;音頻編碼後再處理輸出 AVPacket 中各時間參數,将 1/sample_rate 時間基轉換為輸出容器中的時間基。如果引入音頻 FIFO,從 FIFO 從讀出的音頻幀時間戳資訊會丢失,需要使用 1/sample_rate 時間基重新為每一個音頻幀生成 pts,然後再送入編碼器。
解碼前的時間基轉換:
av_packet_rescale_ts(ipacket, sctx->i_stream->time_base, sctx->o_codec_ctx->time_base);
編碼後的時間基轉換:
av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base);
關于時間基與時間戳的詳細内容可參考 "
"。編解碼過程主要關注音視訊幀的 pts,使用者可不關注 dts,詳細說明可參考 "
FFmpeg編解碼處理3-編解碼API詳解"。
3. 編譯與驗證
在 shell 中運作如下指令下載下傳例程源碼:
svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_transcode
在源碼目錄執行 make 指令,生成 transcode 可執行檔案
下載下傳測試檔案(右鍵另存為):
tnmil2.flv使用 ffprobe 看一下檔案格式:
think@opensuse> ffprobe tnmil2.flv
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnmil2.flv':
Metadata:
encoder : Lavf58.20.100
Duration: 00:00:13.68, start: 0.057000, bitrate: 474 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 1k tbn, 50 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
使用輸入檔案中的編碼格式和封裝格式生成輸出檔案
./transcode -i tnmil2.flv -c:v copy -c:a copy tnmil2o.flv
指定編碼格式和封裝格式生成輸出檔案
./transcode -i tnmil2.flv -c:v mpeg2video -c:a mp2 tnmil2.ts
7. 參考資料
FFmpeg關于nb_smples,frame_size以及profile的解釋,
https://blog.csdn.net/zhuweigangzwg/article/details/53335941 What does the output of ffmpeg mean? tbr tbn tbc etc? 視訊編解碼基礎概念,
https://www.cnblogs.com/leisure_chn/p/10285829.html 對ffmpeg的時間戳的了解筆記 https://blog.csdn.net/topsluo/article/details/76239136[6].
ffmpeg中的時間戳與時間基 http://www.imooc.com/article/91381 ffmpeg編解碼中涉及到的pts詳解 http://www.52ffmpeg.com/article/353.html[7].
音視訊錄入的pts和dts問題 https://blog.csdn.net/zhouyongku/article/details/385107478. 修改記錄
2019-03-23 V1.0 初稿
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。