幀率轉換:丟幀補幀策略

某些時候,我們會遇到一個需求,就是輸入幀率是25,輸出幀率是30.或者30到25等等幀率由小到大,或由大到小,

且輸出幀率是固定幀率。這種情況下,就涉及到要做丟幀或補幀處理。

比如輸入25,輸出30,那麼就需要補5幀,如果輸入30,輸出25,那麼就需要丟5幀。問題關鍵在於應該丟哪些幀,補哪些幀。一般我們覺得比如30->25,丟5幀,那麼是不是可以每6幀丟一幀,剛好30幀能丟5幀。這個想法是好的,但不是ffmpeg裏面的搞法。具體丟哪5幀,是要經過嚴格計算出來的。

 

爲了驗證ffmpeg裏面究竟是怎麼在丟和補的。我做了一個實驗。

手動用畫布畫三十張圖片。內容寫1-30的數字。

然後用ffmpeg命令生成視頻。

./ffmpeg -loop 0 -f image2 -r 25 -i /Users/bixinwei/Desktop/xxp/%d.png -vcodec libx264 -r 25 -t 1 -bf 0 -g 25 -pix_fmt yuv420p yo25.flv

類似上面的命令用於把輸入png(25張圖片)生成一個幀率25的視頻(且視頻每一幀的內容都是連續的數字,1-25)

分別生成幀率是15,20,25,30的視頻

通過./ffmpeg  -i yo25.flv   25_%d.jpeg  可以逐幀查看視頻的內容,用以觀察到底丟了哪些幀。補了哪些幀。

通過./ffmpeg -i yo20.flv -vcodec libx264 -vf fps=25 bf 0 -g 25 -pix_fmt yuv420p yo20to25.flv

用於20->25的轉碼。(注意:-r 25,並不會丟幀和補幀,只會改變源的時間戳,輸入是20,輸出還是20幀

-vf fps=25 纔是真正的保證了輸出是每秒有25幀)

通過./ffprobe -of xml -show_packets yo25.flv 查看視頻總幀數和每一幀的pts_time,dts_time

 

經過多案例實驗,發現,

補幀策略:

-0.6<delta0<=0.6  sync_ipts=sync_opts 

delta>1.6    補當前幀

丟幀策略:

 delta0<=-0.6  丟幀

輸出的duration都是輸出流的固定值

 

下面具體說明上面的參數是什麼意思。

本文假定輸出和輸出都是固定幀率,

假設輸入是20,輸出是25

固定幀率的第n幀的顯示時間pts_time=n*duration=n*(1/fps); (單位s)

 pts=pts_time/av_q2d(avcodeccontext->time_base)=(n/fps) /(1/fps)= n

可以看出,pts的值就是在當前時間基下,是什麼時候播放。如果固定幀率是25,1秒內幀的pts的值就是從1到25,也可以看出:pts的值可以反應當前是播放第幾幀

 

把輸出流的每幀的pts記作sync_opts

把輸入流的每幀的pts 在輸出流 的AVCodecContext 的time_base下的pts值記作sync_ipts

20-->25下的 sync_ipts=(n/20)/(1/25)=1.25n 

1.25 2.5 3.75 5 6.25 7.5 8.75  10  11.25 12.5   13.75 15  16.25  17.5  18.75 20 21.25 22.5 23.75 25

意思就是輸入流的第一幀,轉換到在輸出流的時間基下應該是什麼時候播放。

以1.25爲例,表示輸入流的第一幀,此時在輸出流的時間基下,對應的是第1.25幀 (上面的結論:pts的值可以反應當前是第幾幀)

但是輸出流的時間基,第一幀應該是pts爲1,這說明輸入流的第一幀相比輸出流的第一幀的播放時間,超前了0.25幀的時間

同樣,第二幀超前了0.5幀,第三幀超前了0.75幀,第4幀超前了1幀...

幀超前了,說明此時不該它播放。如果超前太多,就應該由它的上一幀來補幀。

我們把輸出的第n幀的pts記作sync_opts ,輸入的第n幀的pts記作sync_ipts (必須都是輸出流的時間基下的值,不然兩者比較沒有意義,這也是上面爲什麼要轉換的原因)

把sync_ipts-sync_opts的差值,記作delta0, 即輸入幀超前了多少幀,這個值也可能是負值,負值就表示滯後了多少幀

把輸入幀的下一幀的pts記作next_ipts, next_ipts= sync_ipts+ input_duration (再次重申!!所有的值都必須是輸出流的時間基下的值)

把next_ipts-sync_opts的差值,記作delta, 下面案例說明這個值是幹什麼用的。

比如sync_ipts=2.5,next_ipts=3.75 ,此時應該輸出輸出流的第2幀,即sync_opts此時=2,

則delta0=0.5 ,delta=1.75 , 可以看出delta過大,導致輸出流播放完第2幀以後,下次該播放sync_opts=3了,但是此時輸入流的下一幀是要在3.75的時候才播放,導致相差了0.75幀的時間差。誤差過大,所以這個時候我們應該把第2幀重複播放一次,即補幀。

現在再回頭看之前的結論:

補幀策略:

-0.6<delta0<=0.6  sync_ipts=sync_opts ,sync_ipts=sync_opts 就是把輸出幀的pts值賦給輸入幀

delta>1.6    補當前幀

丟幀策略:

 delta0<=-0.6  丟幀

輸出的duration都是輸出流的固定值 (ffmpeg裏面是這麼幹的,有的博客描述了計算duration的差值策略,這裏不討論)

注意,一定要記錄當前輸出流的該輸出第幾幀了,即記錄sync_ipts的值,(這個值應該是每輸出一幀,就+1),然後拿當前的sync_ipts去比較,計算delta和 delta0,判斷該丟當前幀還是補當前幀。(有的博客文章中描述是補前一幀的策略這裏不討論,我們簡單化處理)

 

 

下面附上我的實驗數據(我就不廢話了,看不懂就算了)

20-->25  25*n/20 =1.25n

1.25 2.5 3.75 5 6.25 7.5 8.75  10  11.25 12.5   13.75 15  16.25  17.5  18.75 20 21.25 22.5 23.75 25

 

1.25(1):delta0=0.25(ipts-opts)  delta=1.5(nextipts-opts)     ipts=opts=1   (1.25後面的(1)指此時的opts, sync_opts的簡寫)

2.5(2):delta0=0.5    delta=1.75   ipts=opts=2      +2(+2表示輸入幀的第2幀補幀)

(3)ipts=opts=3 

3.75(4):delta0=-0.25 delta=1  ipts=opts=4 

5(5):delta0=0  delta=1.25    ipts=opts=5

6.25(6):delta0=0.25  delta=1.5    ipts=opts=6

7.5(7):delta0=0.5  delta=1.75    ipts=opts=7      +6

(8)ipts=opts=8

補幀:2,6,10,14,18

結論:

-0.6<delta0<=0.6  sync_ipts=sync_opts 

delta>1.6    補當前幀

 

 

25-->20  20*n/25 =0.8n

0.8 1.6  2.4 3.2 4.0 4.8 5.6 6.4 7.2 8.0 8.8 9.6 10.4 11.2 12.0 12.8 13.6 14.4 15.2 16

16.8 17.6 18.4 19.2 20

 0.8:delta0=-0.2   delta=0.6  ipts=opts=1

 1.6: delta0=-0.4   delta=0.4  ipts=opts=2

2.4:delta0=-0.6  delta=0.2    -3

3.2: delta0=0.2  delta=1    ipts=opts=3

4.0: delta0=0    delta=0.8   ipts=opts=4

4.8: delta0=-0.2  delta=0.6  ipts=opts=5

5.6: delta0=-0.4   delta=0.4  ipts=opts=6

6.4: delta0=-0.6 delta=0.2    -8

丟幀:3,8,13,18,23

結論:

 delta0<=-0.6  丟幀

 

 

 

25—>15  15*n/25 =0.6n

0.6 1.2 1.8 2.4 3.0 3.6 4.2 4.8 5.4 6.0 6.6 

0.6: delta0=-0.4 delta=0.2  ipts=opts=1

1.2     delta0=-0.8  delta=-0.2   -2

1.8     delta0=0.2  delta=0.4    ipts=opts=2

2.4    delta0=-0.6  delta=0   -4

3.0.   delta0=0.  delta=3.6. ipts=opts=3

3.6.    delta0=-0.4. delta=0.2.  ipts=opts=4

4.2.    delta0=-0.8. delta=-0.2.  -7      

丟幀:2,4,7,9,12,14,17,19,22,24

 

25->30  30*n/25=1.2n

1.2 2.4 3.6  4.8  6.0  7.2 8.4 9.6 10.8

1.2: delta0=0.2 delta=1.4.  ipts=opts=1

2.4:  delta0=0.4 delta=1.6. ipts=opts=2

3.6. delta0=0.6   delta=1.8. ipts=opts=3   +3

ipts=opts=4

4.8.  delta0=-0.2. delta=1.  ipts=opts=5

6.0.  delta0=0.  delta=1.2   ipts=opts=6

7.2.   delta0=0.2. delta=1.4.  ipts=opts=7

8.4.   delta0=0.4   delta=1.6  ipts=opts=8

9.6.   delta0=0.6   delta=1.8  ipts=opts=9  +8

補幀:3,8,13,18,23

 

 

30->25  25*n/30=0.83n

0.83  1.67  2.5  3.33 4.17 5 5.83  6.67  7.5 8.33

0.83: delta0=-0.17. delta=0.67 ipts=opts=1

1.67: delta0=-0.33. delta=0.5.  ipts=opts=2

2.5:   delta0=-0.5.   delta=0.33.  ipts=opts=3

3.33.   delta0=-0.67  delta=0.17.    -4

4.17:  delta0=0.17   delta=1  ipts=opts=4

5         delta0=0    delta=0.83    ipts=opts=5

5.83    delta0=-0.17  delta=0.67  ipts=opts=6

6.67     delta0=-0.33  delta=0.5  ipts=opts=7

7.5        delta0==-0.5   delta=0.33  ipts=opts=8

8.33  delta0=-0.67  delta=0.17   -10

 丟 4,10,16,22,28

 

參考博客:

https://www.jianshu.com/p/f431b7aa9ea2

http://www.rosoo.net/a/201107/14663.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章