FFmpeg從入門到出家(FFmpeg簡析)

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爲準):

  1. 封裝格式

◦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,用來存放當前流的編解碼參數。

  1. 網絡協議

◦AVIOContext - 管理輸入輸出數據的結構體;

◦URLProtocol - 描述了音視頻數據傳輸所使用的協議,每種傳輸協議(例如HTTP、RTMP)等,都會對應一個URLProtocol結構,如libavformat/http.c中的ff_http_protocol;

◦URLContext - 封裝了協議對象及協議操作對象。

  1. 數據存放

◦AVPacket - 存放編碼後、解碼前的壓縮數據,即ES數據;

◦AVFrame - 存放編碼前、解碼後的原始數據,如YUV格式的視頻數據或PCM格式的音頻數據等;

上述結構體的關係圖如下所示(箭頭表示派生出):

FFmpeg從入門到出家(FFmpeg簡析)
圖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。

本文有金山視頻雲團隊提供。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章