FFmpeg簡析
FFmpeg從無到有,發展至今,功能日益強大,代碼也越來越多,很多初學者都被其衆多的源文件、龐大的結構體和複雜的算法打消了繼續學習的念頭。本章節將從總體對FFmpeg進行簡單的解析,教您如何閱讀FFmpeg源碼。
2.1 總體說明
FFmpeg包含如下類庫:
libavformat - 用於各種音視頻封裝格式的生成和解析,包括獲取解碼所需信息、讀取音視頻數據等功能。各種流媒體協議代碼(如rtmpproto.c等)以及音視頻格式的(解)複用代碼(如flvdec.c、flvenc.c等)都位於該目錄下。
libavcodec - 音視頻各種格式的編解碼。各種格式的編解碼代碼(如aacenc.c、aacdec.c等)都位於該目錄下。
libavutil - 包含一些公共的工具函數的使用庫,包括算數運算,字符操作等。
libswscale - 提供原始視頻的比例縮放、色彩映射轉換、圖像顏色空間或格式轉換的功能。
libswresample - 提供音頻重採樣,採樣格式轉換和混合等功能。
libavfilter - 各種音視頻濾波器。
libpostproc - 用於後期效果處理,如圖像的去塊效應等。
libavdevice - 用於硬件的音視頻採集、加速和顯示。
如果您之前沒有閱讀FFmpeg代碼的經驗,建議優先閱讀libavformat、libavcodec以及libavutil下面的代碼,它們提供了音視頻開發的最基本功能,應用範圍也是最廣的。
2.2 常用結構
FFmpeg裏面最常用的數據結構,按功能可大致分爲以下幾類(以下代碼行數,以branch: origin/release/3.4爲準):
- 封裝格式
◦AVFormatContext - 描述了媒體文件的構成及基本信息,是統領全局的基本結構體,貫穿程序始終,很多函數都要用它作爲參數;
◦AVInputFormat - 解複用器對象,每種作爲輸入的封裝格式(例如FLV、MP4、TS等)對應一個該結構體,如libavformat/flvdec.c的ff_flv_demuxer;
◦AVOutputFormat - 複用器對象,每種作爲輸出的封裝格式(例如FLV, MP4、TS等)對應一個該結構體,如libavformat/flvenc.c的ff_flv_muxer;
◦AVStream - 用於描述一個視頻/音頻流的相關數據信息。
2.編解碼
◦AVCodecContext - 描述編解碼器上下文的數據結構,包含了衆多編解碼器需要的參數信息;
◦AVCodec - 編解碼器對象,每種編解碼格式(例如H.264、AAC等)對應一個該結構體,如libavcodec/aacdec.c的ff_aac_decoder。每個AVCodecContext中含有一個AVCodec;
◦AVCodecParameters - 編解碼參數,每個AVStream中都含有一個AVCodecParameters,用來存放當前流的編解碼參數。
- 網絡協議
◦AVIOContext - 管理輸入輸出數據的結構體;
◦URLProtocol - 描述了音視頻數據傳輸所使用的協議,每種傳輸協議(例如HTTP、RTMP)等,都會對應一個URLProtocol結構,如libavformat/http.c中的ff_http_protocol;
◦URLContext - 封裝了協議對象及協議操作對象。
- 數據存放
◦AVPacket - 存放編碼後、解碼前的壓縮數據,即ES數據;
◦AVFrame - 存放編碼前、解碼後的原始數據,如YUV格式的視頻數據或PCM格式的音頻數據等;
上述結構體的關係圖如下所示(箭頭表示派生出):
圖2. FFmpeg結構體關係圖
2.3 代碼結構
下面這段代碼完成了讀取媒體文件中音視頻數據的基本功能,本節以此爲例,分析FFmpeg內部代碼的調用邏輯。
char *url = "http://192.168.1.105/test.flv";
AVPacket pkt;
int ret = 0;
//註冊複用器、編碼器等
av_register_all();
avformat_network_init();
//打開文件
AVFormatContext *fmtCtx = avformat_alloc_context();
ret = avformat_open_input(&fmtCtx, url, NULL, NULL);
ret = avformat_find_stream_info(fmtCtx, NULL);
//讀取音視頻數據
while(ret >= 0)
{
ret = av_read_frame(s, &pkt);
}
2.3.1 註冊
av_register_all函數的作用是註冊一系列的(解)複用器、編/解碼器等。它在所有基於FFmpeg的應用程序中幾乎都是第一個被調用的,只有調用了該函數,才能使用複用器、編碼器等。
static void register_all(void)
{
avcodec_register_all();
/* (de)muxers */
……
REGISTER_MUXDEMUX(FLV, flv);
……
}
REGISTER_MUXDEMUX實際上調用的是av_register_input_format和av_register_output_format,通過這兩個方法,將(解)複用器分別添加到了全局變量first_iformat與first_oformat鏈表的最後位置。
編/解碼其註冊過程相同,此處不再贅述。
2.3.2 文件打開
FFmpeg讀取媒體數據的過程始於avformat_open_input,該方法中完成了媒體文件的打開和格式探測的功能。但FFmpeg是如何找到正確的流媒體協議和解複用器呢?可以看到avformat_open_input方法中調用了init_input函數,在這裏面完成了查找流媒體協議和解複用器的工作。
static intinit_input(AVFormatContext s, const char filename,
AVDictionary **options)
{
int ret;
……
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat)
return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
[if !supportLists]1. [endif]s->io_open實際上調用的就是io_open_default,它最終調用到url_find_protocol方法。
static conststructURLProtocol url_find_protocol(const char filename)
{
constURLProtocol **protocols;
……
protocols = ffurl_get_protocols(NULL, NULL);
if (!protocols)
return NULL;
for (i = 0; protocols[i]; i++) {
constURLProtocol *up = protocols[i];
if (!strcmp(proto_str, up->name)) {
av_freep(&protocols);
return up;
}
if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
!strcmp(proto_nested, up->name)) {
av_freep(&protocols);
return up;
}
}
av_freep(&protocols);
return NULL;
}
ffurl_get_protocols可以得到當前編譯的FFmpeg支持的所有流媒體協議,通過url的scheme和protocol->name相比較,得到正確的protocol。例如本例中URLProtocol最終指向了libavformat/http.c中的ff_http_protocol。
[if !supportLists]1. [endif]av_probe_input_buffer2最終調用到av_probe_input_format3,該方法遍歷所有的解複用器,即first_iformat鏈表中的所有節點,調用它們的read_probe()函數計算匹配得分,函數最終返回計算找到的最匹配的解複用器。本例中AVInputFormat最終指向了libavformat/flvdec.c中的ff_flv_demuxer。
2.3.3 數據讀取
av_read_frame作用是讀取媒體數據中的每個音視頻幀,該方法中最關鍵的地方就是調用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一個函數指針,指向當前的AVInputFormat的讀取數據的函數。在本例中,AVInputFormat爲ff_flv_demuxer,也就是說read_packet最終指向了flv_read_packet。
本文有金山視頻雲團隊提供。