新手學習FFmpeg – 調用API調整影片局部速率

  • 2019 年 10 月 3 日
  • 筆記

通過修改setpts程式碼實現調整影片部分的播放速率。 完整程式碼可參考: https://andy-zhangtao.github.io/ffmpeg-examples/

在前面提到了PTS/DTS/Timestamp的關係,播放器在渲染影片時就是根據PTS來確定渲染和展示時間點的。 根據這個原理,我們就可以通過調整幀的PTS時間來實現影片加速/降速播放。

加速/降速的原理

我們都知道,當幀速率(frame rate)大於24時,也就是1秒播放24幀時,我們的視覺就會看到流程的影片。 在幀總量不變的情況下,如果將1/24變為1/48,那麼在相同時間內多播放了一倍的幀,對於我們的視覺來說,就感覺播放速度加快了(因為本該20秒才能播放完的幀,在10秒內就播放完了,就相當加速了一倍)。同理,如果將1/24調整為1/12,就會看到慢動作。

FFmpeg提供了setpts濾鏡可以實現調整pts的效果。 典型的用法如下:

ffmpeg -i ~/tmp/trailer.mp4 -filter:v "setpts=0.5*PTS" output.mp4

0.5*PTS表示將幀的PTS值乘以0.5後作為新的PTS值。 比如說: 幀A當前的PTS是4000(根據以前的知識,根據PTS和Time_base可以計算出渲染的時間點)。 假設對應的時間點是: 00:00:05, 現在將PTS調整為0.5*PTS就變成了2000,那麼對應的渲染時間點就變成了: 00:00:02.5。這樣就實現了加速播放。

同理,如果是2*PTS就是降速播放。

局部調整

setpts只能實現全部加速或者全部減速。 因為在其內部實現中,對每個幀都應用相同的計算規則,所以要麼都調整要麼都不調整。如果要實現局部調整,按照通用的解決方案,只能先切割影片,然後對單獨影片進行加速/降速處理,然後再將影片連接起來。

但如果我們適當調整PTS值,也可以實現部分調整的效果。

  • 問題分析

假設存在一段30s的影片,幀分布如下:

        +------------------------------------------------------------------+          |       F1     F2     F3      F4      F5     F6      F7            |          |       |--------------|--------------|--------------|--->         |          |Time   0              10             20             30            |          |PTS    0      100     200     250    300     350    400           |          +------------------------------------------------------------------+

F1 - F7表示7個I幀(30秒包含的幀比這個多多了,這裡是為了方便描述問題)。 假設我們需要加速前15秒(後15秒播放速率不變)的影片,那麼需要調整F1到F4(F4是第15秒時渲染的幀)如下:

        +------------------------------------------------------------------+          |       F1  F2   F3   F4              F5     F6      F7            |          |       |--------------|--------------|--------------|--->         |          |Time   0              10             20             30            |          |PTS    0      100     200     250    300     350    400           |          +------------------------------------------------------------------+

這樣調整看似沒問題,但仔細分析會發現在10s-20s之間會出現天窗,這是因為這段時間內的PTS沒有任何幀需要渲染,直到第20秒的時候,才會開始繼續渲染F5幀。顯然這樣不滿足實際應用需求。

而發生問題的關鍵在於將F2-F4調整PTS之後,也需要實時調整F5-F7的PTS。 也就是正確的幀分布應該是下面的樣子:

        +------------------------------------------------------------------+          |       F1  F2   F3   F4       F5     F6      F7                   |          |       |--------------|--------------|--------------|--->         |          |Time   0              10             20             30            |          |PTS    0      100     200     250    300     350    400           |          +------------------------------------------------------------------+

F1-F4以一個速率播放,而F5-F7以另外一個速率播放。這樣就實現了部分加速的效果。

  • 程式碼實現

為了簡化編碼難度,我們以setpts的程式碼為基礎進行修改。 在setpts程式碼中修改pts的程式碼是下面部分:

d = av_expr_eval(setpts->expr, setpts->var_values, NULL);  frame->pts = D2TS(d);

d是根據規則(0.5*PTS)計算出來的pts值. 然後將新的PTS賦值給當前幀,而後繼續後面的編碼處理。

所以在這裡,我們做一些判斷,為了簡化其它無關步驟,先假設只修改前5秒的影片,所以需要先判斷當前幀是否需要修改:

(frame->pts * av_q2d(inlink->time_base)) < 5.0

通過pts*time_base可以計算出當前時間點,通過這個判斷,可以得出是否需要修改此幀的PTS值。 如果需要修改,那麼仍然通過frame->pts = D2TS(d)來調整。 而處理不需要修改的幀才是重點,

按照上圖所示意的PTS,F5應該繼承F4調整前的PTS值,所以需要在調整F4之前需要保存舊的PTS。所以是下面的偽程式碼:

    保存Old PTS      if (當前時間 < 0.5) {          計算新的PTS並賦值給當前幀      }else{          當前幀使用上個幀的PTS      }

將偽程式碼實現後如下:

    oldPts = frame->pts;      if ((frame->pts * av_q2d(inlink->time_base)) < 5.0) {          frame->pts = D2TS(d);          setpts->_pts = frame->pts;      } else {          frame->pts = setpts->_pts;      }        setpts->_pts++;

完整程式碼可參考 isetpts中的程式碼。