在寫了幾個avfilter以後,本來覺得對ffmpeg應該算是入門了。 結果今天想對一個視頻文件進行轉碼操做,才發現基本的視頻讀取,輸出都搞不定。 痛定思痛,仔細研究了一下ffmpeg提供的example,總結概括讀取處理視頻文件的簡要思路。git
在讀取,處理視頻文件時,如下四個結構體是很是重要的,因此放在片首提一下。github
AVFormatContextshell
媒體源的抽象描述,能夠理解成視頻/音頻文件信息描述ide
AVInputFormat / AVOutputFormat函數
容器的抽象描述oop
AVCodecContext / AVCodecParameters學習
編解碼的抽象描述,ffmpeg使用率最高的結構體(AVCodecContext被AVCodecParameters所取代)編碼
AVStreamcode
每一個音視頻的抽象描述orm
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-examples