Seek 策略以及在有 B 帧情况下的处理
在知識星球分享的文章,順便也在公眾號發表一下,不足之處,歡迎指正。
一個關于音視頻領域專業問答的小圈子!!
最近在做 Seek 相關功能時遇到的問題排查,順便也學到了一些新的東西,和大家分享下。
在視頻播放時執行 Seek 到任意點的操作,一般都是 Seek 到任意點往前最近的 I 幀,然后再逐幀解碼到指定時間點。
這里可以優化,假設當前時間和指定時間在一個 GOP 內,就可以不用 seek ,直接順序向下解碼就好。
而正是這個優化出現了一點問題,現象如下:
已經判斷播放點 A 和 Seek 點 B 不在一個 GOP 內,然后執行 av_seek_frame 方法還是把時間點 A 所在 GOP 全部解碼了,導致播放上出現了卡頓。
這里就很奇怪了,明明判斷不在一個 GOP ,那 Seek 時就應該從時間點 B 所在 GOP 的 I 幀開始解碼, 但執行時還是解碼了上一個 GOP 的內容。
到底是判斷是否同一個 GOP 的函數出問題了還是 Seek 方法有問題呢?
帶著疑問開始深入源碼探索。
FFmpeg 沒有直接提供判斷兩幀是否同一個 GOP 的方法,所以通過 av_index_search_timestamp 方法得到傳入時間點最近的 I 幀的 index 索引,如果兩個時間點的索引相同則表示為同一個 GOP 內,因為最近的 I 幀相同。
然而 av_index_search_timestamp 方法是通過 AVIndexEntry 中的 timestamp 來判斷的,它是一個 DTS 值,通過二分查找得到最近的索引。
在沒有 B 幀的情況下,I 幀的 PTS 等于 DTS ,所以判斷不會出問題。然而正是有了 B 幀,如果 I 幀的 PTS 和 DTS 不相等的話,那么上面的判斷相當于是拿一個 PTS 值和 I 幀的 DTS 比較是否同一個 GOP 了。
如果將錯就錯,判斷 GOP 時得到結論是非同一個 GOP ,那么 Seek 也應該是非同一個 GOP ,但現實恰恰相反,Seek 當做了同一個 GOP ,這里面肯定有計算出問題了,繼續深入源碼。
通過在 mov.c 源碼中看到了如下的操作:
static?int?mov_seek_stream(AVFormatContext?*s,?AVStream?*st,?int64_t?timestamp,?int?flags) {MOVStreamContext?*sc?=?st->priv_data;FFStream?*const?sti?=?ffstream(st);int?sample,?time_sample,?ret;unsigned?int?i;//?Here?we?consider?timestamp?to?be?PTS,?hence?try?to?offset?it?so?that?we//?can?search?over?the?DTS?timeline.timestamp?-=?(sc->min_corrected_pts?+?sc->dts_shift);ret?=?mov_seek_fragment(s,?st,?timestamp);if?(ret?<?0)return?ret;sample?=?av_index_search_timestamp(st,?timestamp,?flags);av_log(s,?AV_LOG_TRACE,?"stream?%d,?timestamp?%"PRId64",?sample?%d\n",?st->index,?timestamp,?sample);//?省略部分代碼注意到如下一行代碼:
timestamp?-=?(sc->min_corrected_pts?+?sc->dts_shift);也就是說我們傳入的時間都會被減上一個值,然后再執行 av_index_search_timestamp 方法,而這個值導致判斷 GOP 和 Seek 之間的關鍵幀索引出問題了。
正如代碼中的注釋所示,假設傳入的時間是 PTS 值,然后給它減去偏移以得到 DTS 值,因為 av_index_search_timestamp 方法就通過 DTS 進行比較的嘛。
出現問題的原因就是 seek 的時間點正好在 I 幀的 PTS 和 DTS 范圍之間了,執行 seek 時減去偏差值就小于 DTS 了,所以變成了同一個 GOP 。
現在要解決問題就是如何得到 sc->min_corrected_pts + sc->dts_shif 之和,然后判斷 GOP 時減去它以修正得到 DTS 值。
還好通過遍歷源碼發現它的值是不會運行時改變的,一旦決定了就定下來了。另外我們可以用第一個 I 幀的 DTS 值作為偏移值。
auto?indexEntry?=?avStream->index_entries;auto?nbIndexEntry?=?avStream->nb_index_entries;for?(int?i?=?0;?i?<?nbIndexEntry;?++i)?{if?(indexEntry[i].flags?==?AVINDEX_KEYFRAME)?{DTSOffset?=?indexEntry[i].timestamp;return;}}如果沒有 B 幀,DTS 值為 0 ,有 B 幀,那么首幀的 DTS 值就可以用來做偏差值進行計算了。
一個音視頻領域專業問答的小圈子!
推薦閱讀:
音視頻開發工作經驗分享 || 視頻版
OpenGL ES 學習資源分享
開通專輯 | 細數那些年寫過的技術文章專輯
Android NDK 免費視頻在線學習!!!
你想要的音視頻開發資料庫來了
推薦幾個堪稱教科書級別的 Android 音視頻入門項目
覺得不錯,點個在看唄~
總結
以上是生活随笔為你收集整理的Seek 策略以及在有 B 帧情况下的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 年入200万的华人程序员,过了30岁就成
- 下一篇: matlab repmat函数的用法