某些時候,我們會遇到一個需求,就是輸入幀率是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