新手學習FFmpeg - 呼叫API調整視訊區域性速率
通過修改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
中的程式碼