1. 程式人生 > 其它 >FFmpeg - 通過修改setpts程式碼實現調整視訊部分的播放速率

FFmpeg - 通過修改setpts程式碼實現調整視訊部分的播放速率

  播放器在渲染視訊時就是根據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++;