新手學習FFmpeg – 調用API編寫實現多次淡入淡出效果的濾鏡

  • 2019 年 10 月 3 日
  • 筆記

前面幾篇文章聊了聊FFmpeg的基礎知識,我也是接觸FFmpeg不久,除了時間處理之外,很多高深(濾鏡)操作都沒接觸到。在學習時間處理的時候,都是通過在ffmpeg目前提供的avfilter基礎上面修修補補(補充各種debug log)來驗證想法。 而這次我將嘗試新創建一個avfilter,來實現一個新濾鏡。 完整的程式碼可參考 https://andy-zhangtao.github.io/ffmpeg-examples/

因為我是新手,所以本著先易後難的原則(其實是不會其它高深API的操作),從fade濾鏡入手來仿製一個new fade(就起名叫做ifade)。

目標

fade是一個淡入淡出的濾鏡,可以通過參數設置fade type(in表示淡入, out表示淡出),在影片的頭部和尾部添加淡入淡出效果。 在使用過程中,fade有一些使用限制。

  • 淡入只能從片頭開始設置(00:00:00.0位置起)
  • 淡出只能從片尾開始設置
  • 一次只能設置一個類型

如果想在一個影片中間設置多次淡入淡出效果,那麼只能先分割影片,分別應該fade之後在合併(可能還有其它方式,可我沒找到)。如果想一次實現多個fade效果,那麼就要通過-filter-complex來組合多個fade,併合理安排調用順序,稍顯麻煩。

這次,ifade就嘗試支援在同一個影片中實現多次fade效果。ifade計劃完成的目標是:

  • 一次支援設置一個類型(淡入/淡出)
  • 一次支援設置多個fade時間點
  • 支援fade時長

分析

先看看原版fade是如何實現的。

     1  static int filter_frame(AVFilterLink *inlink, AVFrame *frame)       2  {       3      AVFilterContext *ctx = inlink->dst;       4      FadeContext *s       = ctx->priv;       5      double frame_timestamp = frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base);       6       7      // Calculate Fade assuming this is a Fade In       8      if (s->fade_state == VF_FADE_WAITING) {       9          s->factor=0;      10          if (frame_timestamp >= s->start_time/(double)AV_TIME_BASE      11              && inlink->frame_count_out >= s->start_frame) {      12              // Time to start fading      13              s->fade_state = VF_FADE_FADING;      14      15              // Save start time in case we are starting based on frames and fading based on time      16              if (s->start_time == 0 && s->start_frame != 0) {      17                  s->start_time = frame_timestamp*(double)AV_TIME_BASE;      18              }      19      20              // Save start frame in case we are starting based on time and fading based on frames      21              if (s->start_time != 0 && s->start_frame == 0) {      22                  s->start_frame = inlink->frame_count_out;      23              }      24          }      25      }      26      if (s->fade_state == VF_FADE_FADING) {      27          if (s->duration == 0) {      28              // Fading based on frame count      29              s->factor = (inlink->frame_count_out - s->start_frame) * s->fade_per_frame;      30              if (inlink->frame_count_out > s->start_frame + s->nb_frames) {      31                  s->fade_state = VF_FADE_DONE;      32              }      33      34          } else {      35              // Fading based on duration      36              s->factor = (frame_timestamp - s->start_time/(double)AV_TIME_BASE)      37                              * (float) UINT16_MAX / (s->duration/(double)AV_TIME_BASE);      38              if (frame_timestamp > s->start_time/(double)AV_TIME_BASE      39                                    + s->duration/(double)AV_TIME_BASE) {      40                  s->fade_state = VF_FADE_DONE;      41              }      42          }      43      }      44      if (s->fade_state == VF_FADE_DONE) {      45          s->factor=UINT16_MAX;      46      }      47      48      s->factor = av_clip_uint16(s->factor);      49      50      // Invert fade_factor if Fading Out      51      if (s->type == FADE_OUT) {      52          s->factor=UINT16_MAX-s->factor;      53      }      54      55      if (s->factor < UINT16_MAX) {      56          if (s->alpha) {      57              ctx->internal->execute(ctx, filter_slice_alpha, frame, NULL,      58                                  FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));      59          } else if (s->is_packed_rgb && !s->black_fade) {      60              ctx->internal->execute(ctx, filter_slice_rgb, frame, NULL,      61                                     FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));      62          } else {      63              /* luma, or rgb plane in case of black */      64              ctx->internal->execute(ctx, filter_slice_luma, frame, NULL,      65                                  FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));      66      67              if (frame->data[1] && frame->data[2]) {      68                  /* chroma planes */      69                  ctx->internal->execute(ctx, filter_slice_chroma, frame, NULL,      70                                      FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));      71              }      72          }      73      }      74      75      return ff_filter_frame(inlink->dst->outputs[0], frame);      76  }

不想貼程式碼,但發現不貼程式碼好像很難表述清楚。-_-!

fade在處理fame時最關鍵的是三種狀態和一個變數因子。

三種狀態:

  • VF_FADE_WAITING 待渲染, 初始狀態
  • VF_FADE_FADING 渲染中
  • VF_FADE_DO 渲染結束

變數因子:

  • factor 控制效果強度

假設現在設置的是淡入效果(如果是淡出效果,52行會實現一個反轉)): s->fade_state初始化狀態是VF_FADE_WAITING,濾鏡工作時就會進入第八行的判斷,此時將s->factor設置為0。如果我們假設淡入的背景顏色是黑色(默認色),當s->factor==0時,渲染強度最大,此時渲染出的就是一個純黑的畫面。

第八行的if判斷是一個全局初始化,一旦進入之後,s->fade_status就會被修改為VF_FADE_FADING狀態。

而26到43行的判斷,是為了找到渲染結束時間點。通過不停的判斷每幀的frame_timestamp和start_time+duration之間的關係(通過start_frame同理),來決定是否結束渲染。start_time是由fade st=xxx來設定的,當到達結束時間點後,將s->fade_status變更為VF_FADE_DO,即可結束渲染(其實是將s->factor置為UINT16-MAX,這樣就不會進入到第55行的渲染邏輯)。

fade大致的處理流程如下:

     +------------------------------------------------------------------------------------------------------------- +       |                                                                                                              |       |       |----------------------------------------------------------|------------------|-------------------->   |       |time   0                                                          st             st+duration                  |       |                                                                                                              |       |status VF_FADE_WAITING                                                                                        |       |                               VF_FADE_FADING                                                                 |       |                                                              VF_FADE_DO                                      |       |factor 0       0        0         0              0        0       100  500 4000 ...  65535  65535  65535 65535|       |                                                                                                              |       +--------------------------------------------------------------------------------------------------------------+

0->st這段時間內,status一直是VF_FADE_FADING狀態,factor是0。 這段時間內渲染出來的全是黑色。到達st點後,開始逐步調整factor的值(不能一次性的調整到UINT16-MAX,要不就沒有逐漸明亮的效果了),直到st+duration這個時間後,在將factor調整為UINT16-MAX。以後流經fade的幀就原樣流轉到ff_filter_frame了。

改造

分析完fade的處理邏輯之後,如果要實現ifade的效果,那麼應該是下面的流程圖:

     +------------------------------------------------------------------------------------------------------------------+       |                                     A                  B                C                  D                     |       |       |-----------------------------|------------------|----------------|------------------|-------------------->|       |time   0                            st1               st2-duration      st2            st2+duration               |       |                                                                                                                  |       |status    VF_FADE_FADING                                                                                          |       |                                VF_FADE_DO                                                                        |       |                                                                                                                  |       |                                                  VF_FADE_FADING                                                  |       |                                                                                          VF_FADE_DO              |       |factor 0       0        0           65535     65535    0  0 0  0 0 0 0 0 100  500 4000 ... 65535                  |       |                                                                                                                  |       +------------------------------------------------------------------------------------------------------------------+

0-A點仍然是fade原始邏輯。到達A點之後,將s->fade_status改完VF_FADE_DO表示關閉渲染。 當到達B點時(距離st2還有duration的時間點),開始將s->factor調整為0. 這是為了模擬出畫面從暗到亮的效果。同時s->fade_status再次置為VF_FADE_FADING狀態,到達C點是開始重新計算s->factor的值,將畫面逐漸變亮。

可以看出ifade就是利用s->fade_status重複利用現有的處理邏輯來實現多次淡入的效果。

實現

上面分析完之後,就可以動手寫程式碼了。 具體程式碼就不貼出來了,可以直接看源碼。 下面就說幾個在ffmpeg 4.x中需要注意的地方:

  • 添加新avfilter

    • libavfilter/Makefile中添加新filter名稱。 OBJS-$(CONFIG_IFADE_FILTER) += vf_ifade.o
    • libavfilter/allfilter.c中添加新filter. extern AVFilter ff_vf_ifade
  • 重新生成makefile

    • 重新根據實際情況執行configure,生成最新的makefile腳本

然後就是漫長的等待。

在編寫filter時,ffmpeg提供了AVFILTER_DEFINE_CLASS這個宏來生成默認的avclassoptions,所以一定要注意class名稱和options名稱要和宏定義中的名字保持一致,否則會導致編譯失敗。