新手學習FFmpeg - 調用API完成視頻的讀取和輸出


在寫了幾個avfilter之后,原本以為對ffmpeg應該算是入門了。 結果今天想對一個視頻文件進行轉碼操作,才發現基本的視頻讀取,輸出都搞不定。 痛定思痛,仔細研究了一下ffmpeg提供的example,總結歸納讀取處理視頻文件的簡要思路。

在讀取,處理視頻文件時,以下四個結構體是非常重要的,所以放在片首提一下。

  • AVFormatContext

媒體源的抽象描述,可以理解成視頻/音頻文件信息描述

  • AVInputFormat / AVOutputFormat

容器的抽象描述

  • AVCodecContext / AVCodecParameters

編解碼的抽象描述,ffmpeg使用率最高的結構體(AVCodecContext被AVCodecParameters所取代)

  • AVStream

每個音視頻的抽象描述

  • AVCodec

編解碼器的抽象描述

四個結構體的包含關系大致如下:

          |AVFormatContext
                |
                |---------> AVInputFormat / AVOutputFormat
                |
                |---------> AVStream
                                |-------> time_base (解碼時不需要設置, 編碼時需要用戶設置)
                                |
                                |------->   AVCodecParameters|--------> codec id
                                                                            |
                                                                            |
          |AVCodec ------------通過 codec id 關聯----------------------------+
              |
              |
              |-------------->AVCodecContext

讀取/輸出視頻時,基本就是圍繞這五個結構體來進行操作的。 而不同點在於,讀取文件時,ffmpeg會通過讀取容器metadata來完成AVFormateContext的初始化。輸出文件時,我們需要根據實際情況自行封裝AVFormateContext里面的數據。封裝時的數據來源,一部分來自於實際情況(例如time_base,framerate等等),另外一部分則來自於數據源。

下面分別來描述讀取和輸出的差異。

先看讀取的大致流程:

                                                                |------------------------------------------------------loop---------------------------------------------------|
                                                                |                                      |--------------------------------------------------------------------  |
                                                                |                                      |                                                                   |  |
avformat_open_input ---------->  AVFormatContext  ----------> stream   ----avcodec_find_decoder---->  codec  -------avcodec_alloc_context3-----------> codecContent ---avcodec_open2-->
                                                               |                                                                                           |
                                                               |------------------------------------avcodec_parameters_to_context--------------------------|

avformat_open_input會嘗試根據指定文件的metadata完成AVFormatContext的部分初始化,如果視頻源是包含header的,那么此時的AVFormatContext數據基本都齊了。如果是不包含header的容器格式(例如MPEG),AVFormatContext此時就沒有AVStream的數據,需要單獨使用avformat_find_stream_info來完成AVStream的初始化。

無論怎樣,待AVFormatContext完成了初始化,就可以通過輪詢AVStream來單獨處理每一個stream數據,也就是上面的loop。下面單拎一條stream來聊。

解碼視頻只需要AVCodecContext就可以了,從包含圖可以得知根據AVCodec可以生成AVCodecContext,而avcodec_find_decoder又可以生成對應的codec。所以大致的思路就清晰了,首先通過inStream->codecpar(AVCodecParameters)->codec_idavcodec_find_decoder生成指定的解碼器AVCodec, 然后通過avcodec_alloc_context3就可以生成可以解碼視頻源的AVCodecContext

此時產生了第一個誤區:生成的AVCodecContext就可以直接解碼視頻! 這是錯誤的

現在的AVCodecContext只是一個通用Codec描述,沒有視頻源的特定信息(avcodec_parameters_to_context的代碼有些長,我也沒搞明白具體是哪些信息)。 所以需要調用avcodec_parameters_to_contextinStream->codecparAVCodecContext糅合到一起(俗稱merge)。這時的AVCodecContext才能打開特定的視頻文件。

對於沒有header的容器。 framerate 和 time_base 仍然需要特別設定。
fraterate 可以通過av_guess_frame_rate獲取。 time_base可以直接使用AVStream的time_base;

最后就是使用avcodec_open2打開AVCodecContext並處於待機狀態。

輸出的流程和讀取的流程相似,但又有不同。 讀取讀取參數較多,而輸出更多的是封裝參數。 下面是輸出的大致流程:

                                                                            |----------------------------------avcodec_parameters_from_context-----------------|
                                                                            |                                                                                  |
                                                                     stream(enc)---avcodec_find_encoder ---> codec(enc)---avcodec_alloc_context3---> codecContent(enc)----avcodec_open2---->
                                                                                                              -----------
                                                                                                                   |
                                                                                                                   |
                                                                                                                   |
avformat_alloc_output_context2  -------> AVFormatContext --------avformat_new_stream--------> stream -------copy dec to enc---
                                                                           |                                       |
                                                                           |---------------------loop--------------|

無論是讀取還是輸出,首要任務都是構建AVFormateContext。有所不同的是此時(輸出),我們先構建一個模板,然后往里面填值,因此使用的是avformat_alloc_output_context2函數。

avformat_alloc_output_context2和avformat_open_input 都是用來生成AVFormatContext的。不同的是,一個生成模板往里面填值,另一個生成的是已經完成初始化的。

編碼一個視頻文件,需要的也只是一個AVCodecContext. 但此時離生成AVCodecContext還差很多東西。所以需要我們逐一進行准備,按照最上面的包含圖,需要先生成一個AVStream。因此調用avformat_new_stream生成一個空AVStream

有了AVStream之后,就需要將這個Stream與具體的Codec關聯起來。 其次,根據需要我們指定avcodec_find_encoder生成一個標准的AVCodec,而后使用avcodec_alloc_context3生成對應的AVCodecContext

第二個誤區:生成的AVCodecContext就可以直接解碼視頻! 這是錯誤的

現在生成的AVCodecContext不能直接使用,因為還有參數是標准參數沒有適配。以下參數是經常容易出錯的:

    width
    height
    framerate
    time_base
    sample_aspect_ratio
    pix_fmt

    time_base(AVSteam)

在對codecparAVCodecContext進行反向merge. 反向指的是從AVCodecContext讀取參數填充到codecpar中所以才需要提前設置AVCodecContext中的參數。

最后調用avcodec_open2處於待輸出狀態。

上面是讀取/輸出的流程,下面來補充說一下如何從視頻源讀數據,再寫到目標視頻中。

真正讀取視頻數據涉及到的結構體是:

  • AVPacket

可能包含一個或多個 frame。 如果包含多個,則讀取第一個

  • AVFrame

保存當前幀的數據格式

一個典型的讀取處理代碼,看起來應該是下面的樣子:

    while (1){
        av_read_frame (讀取數據)
         ...
        avcodec_decode_video2 (對讀到的數據進行解碼)
         ...
        avcodec_encode_video2 (對數據進行編碼)
         ...
        av_interleaved_write_frame (寫到文件中)
    }

    av_write_trailer (寫metadata)

avcodec_decode_video2 和 avcodec_encode_video2 是即將廢棄的函數,替代函數的使用可參看前幾篇文章

在這里也有幾個誤區:

第三個誤區,AVPacket聲明后就可用。 這是錯誤的

AVPacket 聲明后需要手動設置{.data = NULL, .size = 0}.

第四個誤區,AVPacket time_base直接設置 經過驗證,這也是錯誤的

直接設置不好使。 還是老老實實通過av_packet_rescale_ts來調整 AVPacket的time base吧。同理,在寫文件之前也需要調用av_packet_rescale_ts來修改time base。

以上就是今天學習的結果,希望對以后解析/輸出視頻能有所幫助。示例代碼可以參考 https://andy-zhangtao.github.io/ffmpeg-examples


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM