在上一篇文章 FFmpeg學習(四):FFmpeg API 介紹與通用 API 分析 中,我們簡單的講解了一下FFmpeg 的API基本概念,並分析了一下通用API,本文我們將分析 FFmpeg 在編解碼時使用的API。
一、FFmpeg 解碼 API 分析
1. avformat_open_input 分析
函數 avformat_open_input 會根據所提供的文件路徑判斷文件的格式,其實就是通過這一步來決定到底是使用哪個Demuxer。
舉個例子:如果是flv,那么Demuxer就會使用對應的ff_flv_demuxer,所以對應的關鍵生命周期的方法read_header、read_packet、read_seek、read_close都會使用該flv的Demuxer中函數指針指定的函數。read_header會將AVStream結構體構造好,以方便后續的步驟繼續使用AVStream作為輸入參數。
2. avformat_find_stream_info 分析
該方法的作用就是把所有的Stream的MetaData信息填充好。方法內部會先查找對於的解碼器,然后打開對應的解碼器,緊接着會利用Demuxer中的read_packet函數讀取一段數據進行解碼,當然,解碼的數據越多,分析出來的流信息就越准確,如果是本地資源,那么很快就可以得到准確的信息了。但是對於網絡資源來說,則會比較慢,因此該函數有幾個參數可以控制讀取數據的長度,一個是probe size,一個是max_analyze_duration, 還有一個就是fps_probe_size,這三個參數共同控制解碼數據的長度,如果配置的這幾個參數的數值越小,那么這個函數執行的時間就會越快,但會導致AVStream結構體里面的信息(視頻的寬、高、fps、編碼類型)不准確。
3. av_read_frame 分析
該方法讀取出來的數據是AVPacket,在FFmpeg的早期版本中開發給開發者的函數其實就是av_read_packet,但是需要開發者自己來處理AVPacket中的數據不能被解碼器處理完的情況,即需要把未處理完的壓縮數據緩存起來的問題。所以在新版本的FFmpeg中,提供了該函數,用於處理此狀況。 該函數的實現首先會委托到Demuxer的read_packet方法中,當然read_packet通過解服用層和協議層的處理后,會將數據返回到這里,在該函數中進行數據緩沖處理。
對於音頻流,一個AVPacket可能會包含多個AVFrame,但是對於一個視頻流,一個AVPacket只包含一個AVFrame,該函數最終只會返回一個AVPacket結構體。
4. avcodec_decode分析
該方法包含了兩部分內容:一部分是解碼視頻,一部分是解碼音頻。在上面的函數分析中,我們知道,解碼是會委托給對應的解碼器來實施的,在打開解碼器的時候就找到了對應的解碼器的實現,比如對於解碼H264來講,會找到ff_h264_decoder,其中會有對應的生命周期函數的實現,最重要的就是init,decode,close三個方法,分別對應於打開解碼器、解碼及關閉解碼器的操作,而解碼過程就是調用decode方法。
5. avformat_close_input 分析
該函數負責釋放對應的資源,首先會調用對應的Demuxer中的生命周期read_close方法,然后釋放掉,AVFormatContext,最后關閉文件或者遠程網絡鏈接。
二、FFmpeg 編碼 API 分析
1. avformat_alloc_output_context2 分析
該函數內部需要調用方法avformat_alloc_context來分配一個AVFormatContext結構體,當然最關鍵的還是根據上一步注冊的Muxer和Demuxer部分(也就是封裝格式部分)去找對應的格式。有可能是flv格式、MP4格式、mov格式,甚至是MP3格式等,如果找不到對應的格式(應該是因為在configure選項中沒有打開這個格式的開關),那么這里會返回找不到對於的格式的錯誤提示。在調用API的時候,可以使用av_err2str把返回的整數類型的錯誤代碼轉換為肉眼可讀的字符串,這是個在調試中非常有用的工具函數。該函數最終會將找出來的格式賦值給AVFormatContext類型的oformat。
2. avio_open2 分析
首先會調用函數ffurl_open,構造出URLContext結構體,這個結構體中包含了URLProtocol(需要去第一步register_protocol中已經注冊的協議鏈表)中去尋找;接着會調用avio_alloc_contex方法,分配出AVIOContext結構體,並將上一步構造出來的URLProtocol傳遞進來;然后把上一步分配出來的AVIOContext結構體賦值給AVFormatContext屬性。
下面就是針對上面的描述總結的結構之間的構架圖,各位可以參考此圖進行進一步的理解:
avio_open2的過程也恰好是在上面我們分析avformat_open_input過程的一個逆過程。編碼過程和解碼過程從邏輯上來講,也是一個逆過程,所以在FFmpeg實現的過程中,他們也互為逆過程。
3. 編碼其他API(步驟)分析
編碼的其他步驟也是解碼的一個逆過程,解碼過程中的avformat_find_stream_info對應到編碼就是avformat_new_stream和avformat_write_header。
- avformat_new_stream函數會將音頻流或者視頻流的信息填充好,分配出AVStream結構體,在音頻流中分配聲道、采樣率、表示格式、編碼器等信息,在視頻中分配寬、高、幀率、表示格式、編碼器等信息。
- avformat_write_header函數與解碼過程中的read_header恰好是一個逆過程,這里就不多贅述了。
接下來就是編碼階段了:
1. 將手動封裝好的AVFrame結構體,作為avcodec_encodec_video方法的輸入,然后將其編碼成為AVPacket,然后調用av_write_frame方法輸出到媒體文件中。
2. av_write_frame 方法會將編碼后的AVPacket結構體作為Muxer中的write_packet生命周期方法的輸入,write_packet會加上自己封裝格式的頭信息,然后調用協議層,寫到本地文件或者網絡服務器上。
3. 最后一步就是av_write_trailer(該函數有一個非常大的坑,如果沒執行write_header操作,就直接執行write_trailer操作,程序會直接Carsh掉,所以這兩個函數必須成對出現),av_write_trailer會把沒有輸出的AVPacket全部丟給協議層去做輸出,然后會調用Muxer的write_trailer生命周期方法(不同的格式,寫出的尾部也不一樣)。