FFplay音訊濾鏡分析

音訊流的 濾鏡是透過

configure_audio_filters()

函式來建立的,因為

ffplay

為了程式碼的通用性,即便命令列引數不使用濾鏡,

AVFrame

也會過一遍 空濾鏡做下樣子。

configure_audio_filters()

函式的流程圖如下:

FFplay音訊濾鏡分析

configure_audio_filters()

函式的定義如下:

static int configure_audio_filters(VideoState *is, const char *afilters, int force_output_format){。。。。}

下面講解一下這個函式的引數。

VideoState *is

,是 ffplay 播放器的全域性管理器。

char *afilters

,是濾鏡字串,例如 下面的命令:

ffplay -af “atempo=2。0” -i juren-5s。mp4

“atempo=2。0”

這個字串就會賦值給

afilters

int force_output_format

,代表是否強制把

buffersink

出口濾鏡的音訊幀取樣等資訊 設定為 跟

is->audio_tgt

一樣。

之前說過

is->audio_tgt

是音響硬體裝置開啟的資訊。

is->audio_tgt

最終

要傳遞給 SDL 的音訊格式。所有的取樣率,聲道數等等最後都要轉成

is->audio_tgt

下面來分析一下

configure_audio_filters()

函數里面的重點程式碼,如下:

FFplay音訊濾鏡分析

這個函式一開始就定義了 一些只有 2 個元素的陣列,這其實是 ffmpeg 專案傳遞引數的方式,傳遞一個數組進去函式,主要有兩種方式。

1,

傳遞陣列的大小。就是有多少個元素。

2,

傳遞陣列的結尾,只要讀到結尾元素 (-1),就算結束了。

ffmpeg 大部分函式採用的是第二種方式。

然後他會調

avfilter_graph_free()

釋放

濾鏡容器

(FilterGraph),有些同學可能會疑惑,

is->agraph

一開始不是

NULL

嗎? 為什麼需要釋放?

is->agraph

一開始確實是 NULL,但是

configure_audio_filters()

這個函式可能會呼叫

第二次

,第二次的時候

is->agraph

就不是 NULL了。

configure_audio_filters()

第一次呼叫

是在

stream_component_open()

裡面,如下:

FFplay音訊濾鏡分析

第二次呼叫

是在

audio_thread()

裡面,如下:

FFplay音訊濾鏡分析

【騰訊文件】FFmpegWebRTCRTMPRTSPHLSRTP播放器-音影片流媒體高階開發-資料領取FFmpegWebRTCRTMPRTSPHLSRTP鎾斁鍣�-闊寵棰戞祦濯掍綋楂樼駭寮€鍙�-璧勬枡棰嗗彇

FFplay音訊濾鏡分析

FFplay音訊濾鏡分析

第二次呼叫

configure_audio_filters()

是因為實際解碼出來的 AVFrame 的取樣率,聲道等,跟容器裡面記錄的不一致,之前

is->audio_filter_src

是直接從容器,封裝層取的資料。封裝層記錄的音訊取樣率等,

可能是錯的

,需要以實際解碼出來的

AVFrame

為準。

而且,注意,第二次的時候,

force_output_format

引數會置為 1,這樣會強制

buffersink

出口濾鏡的取樣資訊等 設定為

is->audio_tgt

一樣。

其實

configure_audio_filters()

必然會調第二次的

,因為

is->auddec。pkt_serial != last_serial

這個條件肯定是真。

接著就是設定 濾鏡使用的執行緒數量,0 為自動選擇執行緒數量,如下:

is->agraph->nb_threads = filter_nbthreads;

第三個重點是,設定重取樣選項(aresample_swr_opts),如下:

FFplay音訊濾鏡分析

什麼樣的命令列引數才是重取樣選項的,在

libswresample/options。c

裡面可以找到,如下:

FFplay音訊濾鏡分析

舉個例子,如下:

ffpaly -ich 1 -i juren-5s。mp4

ich 1

就會被解析複製進去

ffplay。c

裡面的

swr_opts

變數裡面。

這裡還用到了一個新的函式

av_opt_set()

,這個函式其實不只可以設定

濾鏡的屬性欄位

,還可以設定大多數

資料結構

的屬性欄位,例如解碼器,封裝器 等等,只要內部有

AVClass

的資料結構,都能用

av_opt_set()

來設定屬性,詳情請閱讀《opt。h檔案函式分析》

接下來的重點是設定入口跟出口濾鏡,如下:

FFplay音訊濾鏡分析

出口濾鏡

還設定了

sample_fmts

AV_SAMPLE_FMT_S16

,這是

ffpaly

播放器自己的特性,就是說無論MP4檔案裡面的音訊格式是怎樣的,他都會轉成

AV_SAMPLE_FMT_S16

格式丟給 SDL 播放,而且它在用 SDL_OpenAudioDevice 開啟音訊裝置的時候,就是用的 S16 格式,

這是寫死的

force_output_format

的邏輯主要是 強制

buffersink

出口濾鏡的取樣資訊等 設定為跟

is->audio_tgt

一樣。

audio_tgt

是 SDL 接受音訊幀的最終格式。

第一次呼叫

configure_audio_filters()

函式,

force_output_format

為 0,不會跑進去這塊邏輯。

最後就是調

configure_filtergraph()

函式來

連結入口跟出口濾鏡

,同時建立濾鏡容器(FilterGraph),如下:

FFplay音訊濾鏡分析

上圖最重要的是,入口濾鏡 跟 出口濾鏡 被賦值到全域性管理器

is

了。後面只要把解碼器輸出的 AVFrame 往入口濾鏡丟,然後往出口濾鏡讀就行了。