新手學習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_id
和avcodec_find_decoder
生成指定的解碼器AVCodec
, 然後通過avcodec_alloc_context3
就可以生成可以解碼視訊源的AVCodecContext
。
此時產生了第一個誤區:生成的AVCodecContext
就可以直接解碼視訊! 這是錯誤的
現在的AVCodecContext
只是一個通用Codec
描述,沒有視訊源的特定資訊(avcodec_parameters_to_context的程式碼有些長,我也沒搞明白具體是哪些資訊)。 所以需要呼叫avcodec_parameters_to_context
將inStream->codecpar
和AVCodecContext
糅合到一起(俗稱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)
在對codecpar
和AVCodecContext
進行反向
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-examp