新手學習FFmpeg - 經過API完成filter-complex功能

本篇嘗試經過API實現Filter Graph功能。 源碼請參看 https://andy-zhangtao.github.io/ffmpeg-examples/git

FFmpeg提供了不少實用且強大的濾鏡,好比:overlay, scale, trim, setpts等等。github

經過-filter-complex的表達式功能,能夠將多個濾鏡組裝成一個調用圖,實現更爲複雜的視頻剪輯。如何經過代碼實現這個功能呢?shell

首先按照前面幾篇的套路,在開發FFmpeg應用時,大體有三板斧:api

  1. 初始化輸入設備(初始化解碼器及其應用上下文)
  2. 初始化輸出設備(初始化編碼器及其應用上下文)
  3. 編寫幀處理邏輯(對符合要求的幀數據作各類運算處理)

本次須要實現的Filter Graph功能稍有不一樣,在處理幀以前須要先完成Filter Graph的處理。 處理流程以下:ide

+------------------------------------------------+
|   +---------+                                  |
|   | Input   | ----------read --------+         |
|   +---------+                        |         |
|                                      |         |
|                                     \|/        |
|                          +-----------+         |
|  +-----------------------|   Input   |         |
|  |                       +-----------|         |
|  |                                   |         |
|  |                                  \|/        |
|  |   +-----------+       +-----------+         |
|  +<--|  Filter N |<-.N.--| Filter 1  |         |
|  |   +-----------+       +-----------+         |
|  |                                             |
|  |       +-------------+                       |
|  +------>|     Output  |                       |
|          +-------------+                       |
+------------------------------------------------+

Input讀取到視頻數據以後,會依次通過Filter 1Filter N,每一個Filter會依次根據設定好的參數處理流經的幀數據,當全部Filter都處理完畢以後,再通過編碼器編碼吸入Output.函數

從流程能夠看出,視頻中的每一幀都被處理了N次,這也是視頻在應用濾鏡時感受編解碼時間有些長的緣由。編碼

本次增長了一部分API:code

  1. avfilter_get_by_name
  2. avfilter_inout_alloc
  3. avfilter_graph_alloc
  4. avfilter_graph_create_filter
  5. avfilter_graph_parse_ptr
  6. av_buffersink_get_frame
  • 初始化出入設備

和之前的操做同樣,這裏就不作過多敘述。如有須要能夠翻看前幾篇文章。這裏只增長一個dump函數:orm

av_dump_format(inFormatContext, 0, "1", 0);

av_dump_format能夠輸出指定FormatContext的數據,方便定位問題。視頻

  • 初始化輸出設備

一樣不作過多描述,如有須要可翻看前幾篇文章或者直接看源碼。 僅僅提醒一下關於time_base的幾個坑。
time_base是用來作基準時間轉換的,也就是告訴編碼器以何種速度來播放幀(也就是pts)。前幾篇代碼中所使用的time_base是:

outCodecContext->time_base = (AVRational) {1, 25};

1是分子,25是分母。 在進行編碼時,編碼器須要知道每個關鍵幀要在哪一個時間點進行展現和渲染(對應的就是pts和dts)。 在沒有B幀的狀況下,PTS=DTS。 而計算pts時,須要創建編碼time_base和解碼time_base的對應關係.

假設,time=5. 那麼在1/25(編碼time_base)的時間刻度下應該等於1/10000(編碼time_base)時間刻度下的(5*1/25)/(1/90000) = 3600*5=18000

time_base的詳細應用,能夠參考setpts中的實現。

  • 初始化Filter Graph

Filter Graph API中有兩個特殊的Filter:bufferbuffersink

----------> |buffer| ---------|Filter ..... Filter N|----------->|buffersink|-------->

buffer表示Filter Graph的開始,buffersink表示Filter Graph的結束。這兩中Filter是必需要存在不可缺乏。

Filter Graph使用的步驟以下:

  1. 初始化bufferbuffersink
  2. 初始化其它filter
  3. 設定Filter Graph的Input和Output。
  • 初始化bufferbuffersink

經過avfilter_get_by_name來查找相符的Filter,例如:

const AVFilter *buffersrc = avfilter_get_by_name("buffer");

表示獲取buffer Filter。而後經過avfilter_graph_create_filter來初始化filter,例如初始化buffer:

snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             inCodecContext->width, inCodecContext->height, inCodecContext->pix_fmt,
             time_base.num, time_base.den,
             inCodecContext->sample_aspect_ratio.num, inCodecContext->sample_aspect_ratio.den);

    av_log(NULL, AV_LOG_ERROR, "%s\n", args);

    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, NULL, filter_graph);

"in"表示buffer在整個Graph中叫作'in'。 名稱能夠隨便叫,只要保證惟一不重複就好。

  • 初始化其它filter

經過``使用指定的Filter Graph 語法來初始化剩餘的Filter,例如:

const char *filter_descr = "movie=t.png[wm];[in][wm]overlay=10:20[out]";

    avfilter_graph_parse_ptr(filter_graph, filter_descr,
                                        &inputs, &outputs, NULL)

上面表示使用了兩個filter:movieoverlayinputsoutputs表示Graph的輸入輸出。

  • 設定Filter Graph的Input和Output

這段代碼有些很差理解:

outputs->name = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = NULL;

    inputs->name = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = NULL;

outputs對應的是in(也就是buffer),in是Graph第一個Filter,因此它只有輸出端(因此對應到了outputs)。 同理out(buffersink)是Graph最後一個Filter,只有輸入端,所以對應到了inputs。

+-------+             +---------------------+         +---------------+
             |buffer |             |Filter ..... Filter N|         |   buffersink  |
 ----------> |    |output|------>|input|            |output|---> |input|           |-------->
             +-------+             +---------------------+         +---------------+

在下一篇中,咱們會經過其它api設定每一個Filter的input和output,那個時候應該會更容易理解一點。

在完成Filter Graph初始化以後,必定要經過avfilter_graph_config來驗證參數配置是否正確。

avfilter_graph_config(filter_graph, NULL)
  • 邏輯處理

在處理幀數據時,就和之前的思路基本保持一致了。 從解碼器接受幀,而後發送到Filter Graph中進行濾鏡處理,最後再發送給編碼器寫入到輸出文件。

惟一有些不一樣的就是增長了兩個函數av_buffersrc_add_frame_flagsav_buffersink_get_frame. av_buffersrc_add_frame_flags表示向Filter Graph加入一幀數據,av_buffersink_get_frame表示從Filter Graph取出一幀數據。

所以上一篇中的編碼流程增長了一個while循環:

while av_read_frame
        |
        +---> avcodec_send_packet
                    |
                    +----> while avcodec_receive_frame
                                     | 對每一數據幀進行解碼
                                     | 經過`sws_scale`進行源幀和目標幀的數據轉換
                                     |
                                     +---->av_buffersrc_add_frame_flags
                                                    |
                                                    |
                                                    +while av_buffersink_get_frame
                                                            |
                                                            |
                                                            +-->avcodec_send_frame
                                                                    |
                                                                    +---> while avcodec_receive_packet
                                                                                    |
                                                                                    |
                                                                                    |+--->av_interleaved_write_frame (寫入到輸出設備)

至此就完成了經過代碼實現-filter-complex功能。

相關文章
相關標籤/搜索