
當調節串流以進行 DASH 播放時,所有串流中的隨機存取點必須處於完全相同的來源串流時間。通常的方法是強制使用固定的幀速率和固定的 GOP 長度(即每 N 幀一個關鍵幀)。
在 FFmpeg 中,固定幀速率很容易(-r NUMBER)。
但對於固定關鍵影格位置(GOP 長度),有三種方法......哪一種是「正確的」?令人沮喪的是,FFmpeg 文件對此含糊其辭。
方法 1:弄亂 libx264 的參數
-c:v libx264 -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE:scenecut=-1
是否應該關閉場景剪切似乎存在一些爭論,因為尚不清楚場景剪切發生時是否重新啟動關鍵幀“計數器”。
方法二:設定固定GOP大小:
-g GOP_LEN_IN_FRAMES
不幸的是,這僅在 FFMPEG 文件中進行了記錄,因此該論點的效果非常不清楚。
方法3:每N秒插入一個關鍵影格(或許?):
-force_key_frames expr:gte(t,n_forced*GOP_LEN_IN_SECONDS)
這是明確記錄。但目前還不清楚「時間計數器」是否在每個關鍵影格後重新啟動。例如,在預期的 5 秒 GOP 中,如果scenecut
libx264 注入了 3 秒的關鍵幀,那麼下一個關鍵幀會晚 5 秒還是 2 秒?
事實上,FFmpeg 文件對此和該-g
選項進行了區分,但它並沒有真正說明上面這兩個選項有何不同(顯然,-g
需要固定的幀速率)。
哪個是對的?
看起來會-force_key_frames
更優越,因為它不需要固定的幀速率。然而,這需要
- 它符合 H.264 中的 GOP 規範(如果有的話)
- 它保證會有固定節奏的關鍵幀,無論 libx264
scenecut
關鍵幀如何。
-g
如果不強制固定幀速率,似乎也無法工作 ( -r
),因為不能保證ffmpeg
使用不同編解碼器參數的多次運行將在每個解析度中提供相同的瞬時幀速率。固定幀速率可能會降低壓縮效能(在 DASH 場景中很重要!)。
最後,這個keyint
方法看起來像是駭客。我希望這不是正確的答案。
參考:
答案1
長話短說
我會推薦以下內容:
libx264
: (並可選擇新增)-g X -keyint_min X
-force_key_frames "expr:gte(t,n_forced*N)"
libx265
:-x265-params "keyint=X:min-keyint=X"
libvpx-vp9
:-g X
其中X
是以幀為單位的間隔,N
是以秒為單位的間隔。例如,對於 30fps 視訊的 2 秒間隔,X
= 60 且N
= 2。
關於不同幀類型的註釋
為了正確解釋這個主題,我們首先必須定義兩種類型的 I 幀/關鍵影格:
- 瞬時解碼器刷新 (IDR) 幀:允許對後續幀進行獨立解碼,而無需存取 IDR 幀之前的幀。
- 非 IDR 幀:需要先前的 IDR 幀才能進行解碼。非 IDR 幀可用於 GOP(圖片組)中間的場景剪切。
推薦什麼串流媒體?
對於串流媒體情況,您需要:
- 確保所有 IDR 訊框都處於固定位置(例如 2、4、6…秒),以便將影片分割成相等長度的片段。
- 啟用場景切換偵測,從而提高編碼效率/品質。這意味著允許將 I 幀放置在 IDR 幀之間。您仍然可以在停用場景切換偵測的情況下工作(這仍然是許多指南的一部分),但這不是必需的。
參數有什麼作用?
為了配置編碼器,我們必須了解關鍵幀參數的作用。我做了一些測試libx264
,libx265
並libvpx-vp9
在 FFmpeg 中針對三個編碼器發現了以下內容:
libx264
:-g
設定關鍵影格間隔。-keyint_min
設定最小關鍵影格間隔。-x264-params "keyint=x:min-keyint=y"
是相同的-g x -keyint_min y
。筆記:當兩者設定為相同值時,最小值在內部設定為一半最大間隔加一,如程式碼所示
x264
:h->param.i_keyint_min = x264_clip3( h->param.i_keyint_min, 1, h->param.i_keyint_max/2+1 );
libx265
:-g
沒有實施。-x265-params "keyint=x:min-keyint=y"
作品。
libvpx-vp9
:-g
設定關鍵影格間隔。-keyint_min
設定最小關鍵影格間隔筆記:由於 FFmpeg 的工作原理,
-keyint_min
只有當它與-g
.在FFmpeg的程式碼中libvpxenc.c
我們可以發現:if (avctx->keyint_min >= 0 && avctx->keyint_min == avctx->gop_size) enccfg.kf_min_dist = avctx->keyint_min; if (avctx->gop_size >= 0) enccfg.kf_max_dist = avctx->gop_size;
這可能是個錯誤(或缺乏功能?),因為
libvpx
絕對支援為kf_min_dist
.
你應該使用嗎-force_key_frames
?
此-force_key_frames
選項以給定間隔(表達式)強制插入關鍵影格。這適用於所有編碼器,但可能會擾亂速率控制機制。特別是對於 VP9,我注意到嚴重的品質波動,因此我不建議在這種情況下使用它。
答案2
這是我買這個箱子的五十美分。
方法一:
弄亂 libx264 的參數
-c:v libx264 -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE:scenecut=-1
僅以所需的時間間隔產生 iframe。
範例1:
ffmpeg -i test.mp4 -codec:v libx264 \
-r 23.976 \
-x264opts "keyint=48:min-keyint=48:no-scenecut" \
-c:a copy \
-y test_keyint_48.mp4
按預期產生 iframe,如下所示:
Iframes Seconds
1 0
49 2
97 4
145 6
193 8
241 10
289 12
337 14
385 16
433 18
481 20
529 22
577 24
625 26
673 28
721 30
769 32
817 34
865 36
913 38
961 40
1009 42
1057 44
1105 46
1153 48
1201 50
1249 52
1297 54
1345 56
1393 58
方法2已折舊。省略。
方法三:
每 N 秒插入一個關鍵影格(可能):
-force_key_frames expr:gte(t,n_forced*GOP_LEN_IN_SECONDS)
實施例2
ffmpeg -i test.mp4 -codec:v libx264 \
-r 23.976 \
-force_key_frames "expr:gte(t,n_forced*2)"
-c:a copy \
-y test_fkf_2.mp4
以稍微不同的方式產生 iframe:
Iframes Seconds
1 0
49 2
97 4
145 6
193 8
241 10
289 12
337 14
385 16
433 18
481 20
519 21.58333333
529 22
577 24
625 26
673 28
721 30
769 32
817 34
865 36
913 38
931 38.75
941 39.16666667
961 40
1008 42
1056 44
1104 46
1152 48
1200 50
1248 52
1296 54
1305 54.375
1344 56
1367 56.95833333
1392 58
1430 59.58333333
1440 60
1475 61.45833333
1488 62
1536 64
1544 64.33333333
1584 66
1591 66.29166667
1632 68
1680 70
1728 72
1765 73.54166667
1776 74
1811 75.45833333
1824 75.95833333
1853 77.16666667
1872 77.95833333
1896 78.95833333
1920 79.95833333
1939 80.75
1968 81.95833333
正如您所看到的,它每 2 秒放置一次 iframe,並且在場景剪輯上(帶有浮動部分的秒數)放置一次 iframe,我認為這對於視訊串流的複雜性很重要。
產生的檔案大小幾乎相同。很奇怪的是,即使有更多的關鍵幀方法三有時它產生的檔案比標準 x264 函式庫演算法少。
為了為 HLS 流產生多個位元率文件,我們選擇方法三。它與區塊之間的2 秒完美對齊,每個區塊的開頭都有iframe,並且在複雜場景上有附加的iframe,這為擁有過時設備且無法播放x264 高配置文件的用戶提供了更好的體驗。
希望它能幫助某人。
答案3
我想在這裡添加一些信息,因為我的谷歌搜索在我尋找有關嘗試按照我想要的方式分段 DASH 編碼的信息的過程中拉起了這個討論,但我找到的信息都不是完全正確的。
首先要消除幾個誤解:
並非所有 I 幀都相同。有大“I”框架和小“i”框架。或使用正確的術語:IDR I 幀和非 IDR I 幀。 IDR I 訊框(有時稱為「關鍵影格」)將建立一個新的 GOP。非 IDR 幀不會。他們很容易進入共和黨內部,那裡有場景的變化。
-x264opts keyint=GOPSIZE:min-keyint=GOPSIZE
← 這並不像你想像的那樣。這花了我一些時間才弄清楚。事實證明,它min-keyint
在程式碼中是有限的。不允許大於(keyint / 2) + 1
。因此,為這兩個變數分配相同的值會導致min-keyint
編碼時值減少一半。
事情是這樣的:場景剪輯真的很棒,尤其是在具有快速硬剪輯的影片中。它保持了它的美觀和清晰,所以我不想停用它,但同時只要它啟用,我就無法獲得固定的 GOP 大小。我想啟用場景剪切,但只讓它使用非 IDR I 幀。但它不起作用。直到我(透過大量閱讀)弄清楚了誤解#2。
事實證明,我需要將keyint
GOP 大小設定為我想要的兩倍。這意味著min-keyint
可以將其設置為我想要的GOP 大小(無需內部代碼將其減半),這可以防止場景切換檢測在GOP 大小內使用IDR I 幀,因為自最後一個IDR I 幀以來的幀計數為總是小於min-keyinit
.
最後設定force_key_frame
選項覆蓋雙倍大小keyint
。所以這是有效的:
我更喜歡 2 秒區塊中的片段,因此我的 GOPSIZE = 幀速率 * 2
ffmpeg <other_options> -force_key_frames "expr:eq(mod(n,<GOPSIZE>),0)" -x264opts rc-lookahead=<GOPSIZE>:keyint=<GOPSIZE * 2>:min-keyint=<GOPSIZE> <other_options>
您可以使用 ffprobe 進行驗證:
ffprobe <SRC_FLE> -select_streams v -show_frames -of csv -show_entries frame=coded_picture_number,key_frame,pict_type > frames.csv
在產生的 CSV 檔案中,每一行都會告訴您frame, [is_an_IDR_?], [frame_type], [frame_number]
:
frame,1,I,60 <-- frame 60, is I frame, 1 means is an IDR I-frame (aka KeyFrame)
frame,0,I,71 <-- frame 71, is I frame, 0 means not an IDR I_frame
結果是,您應該只以固定GOPSIZE
間隔看到 IDR I 幀,而所有其他 I 幀都是根據場景剪切檢測需要插入的非 IDR I 幀。
答案4
Twitch 有一篇關於這個的文章。他們解釋說,他們決定使用自己的程式有幾個原因:其中之一是 ffmpeg 不允許您在不同的執行緒中執行不同的 x264 實例,而是將所有指定的執行緒專用於一個輸出中的一幀,然後再轉到下一個輸出。
如果您不進行即時串流媒體播放,那麼您可以享受更多的樂趣。 「正確」的方法可能是僅使用 -g 指定的 GOP 大小以一種解析度進行編碼,然後對其他解析度進行編碼,強制關鍵影格位於相同位置。
如果您想這樣做,您可以使用 ffprobe 取得關鍵影格時間,然後使用 shell 腳本或實際的程式語言將其轉換為 ffmpeg 命令。
但對於大多數內容來說,每 5 秒使用一個關鍵影格和每 5 秒使用兩個關鍵影格(一個是強制的,一個來自場景剪輯)之間幾乎沒有什麼區別。這是關於平均 I 幀大小與 P 幀和 B 幀大小的比較。如果您使用具有典型設定的x264(我認為您應該採取任何措施來影響這些的唯一原因是設定-qmin,這是防止x264 在簡單內容上使用位元率的糟糕方法;這將所有幀類型限制為相同的值,我認為)並得到以下結果:I 幀平均大小為46 kB,P 幀為24 kB,B 幀為17 kB(頻率是P 幀的一半),然後每秒以30 fps 生成一個額外的I幀檔案大小僅增加 3%。 h264 和 h263 之間的差異可能由一堆 3% 的下降組成,但單一下降並不是很重要。
對於其他類型的內容,幀大小會有所不同。公平地說,這是關於時間複雜性而不是空間複雜性,因此這不僅僅是簡單內容與困難內容的問題。但一般來說,串流影片網站都有碼率限制,I 幀相對較大的內容是容易被高品質編碼的內容,無論添加多少額外的關鍵影格。這是一種浪費,但這種浪費通常不會被注意到。最浪費的情況可能是影片只是伴隨歌曲的靜態影像,其中每個關鍵影格都完全相同。
我不確定的一件事是強制關鍵影格如何與使用 -maxrate 和 -bufsize 設定的速率限制器互動。我認為即使 YouTube 最近也遇到了正確配置緩衝區設定以提供一致品質的問題。如果您只是使用某些網站可以看到的平均位元率設定(因為您可以使用十六進位編輯器檢查標題/mov原子中的x264選項?)那麼緩衝區模型就不是問題,但如果您服務使用者生成的內容時,平均位元率會鼓勵使用者在影片最後加上黑畫面。
Ffmpeg 的 -g 選項或您使用的任何其他編碼器選項都會對應到特定於編碼器的選項。因此“-x264-params keyint=GOPSIZE”相當於“-g GOPSIZE”。
使用場景檢測的一個問題是,無論出於何種原因,您是否更喜歡特定數字附近的關鍵影格。如果您每 5 秒指定一次關鍵影格並使用場景偵測,並且在 4.5 處有場景更改,則應該偵測到它,但下一個關鍵影格將在 9.5 處。如果時間繼續像這樣加快,您最終可能會得到 42.5、47.5、52.5 等關鍵幀,而不是 40、45、50、55。關鍵影格對於另一個關鍵影格來說太早了。 FFmpeg 不允許您指定「如果接下來的 30 幀內沒有場景變化,則在此建立關鍵影格」。不過,了解 C 的人可以新增該選項。
對於可變幀速率視頻,當您不像 Twitch 那樣進行直播時,您應該能夠使用場景更改,而無需永久轉換為恆定幀速率。如果您在 ffmpeg 中使用「select」過濾器並在表達式中使用「scene」常數,則偵錯輸出(-v debug 或在編碼時按「+」多次)會顯示場景變更編號。這可能與 x264 使用的數字不同,並且不如 x264 使用的數字那麼有用,但它仍然可能有用。
那麼,該過程可能是製作一個僅用於關鍵幀更改的測試視頻,但如果使用 2 遍,則可能可用於速率控制資料。 (不確定生成的資料對於不同的解析度和設定是否有用;宏塊樹資料不會。)將其轉換為恆定幀速率視頻,但請參閱這個錯誤如果您決定將 fps 過濾器用於其他目的,請了解將幀速率減半時的口吃輸出。使用您所需的關鍵影格和 GOP 設定透過 x264 來運行它。
然後只需將這些關鍵幀時間與原始可變幀速率視訊一起使用即可。
如果您允許完全瘋狂的用戶生成內容,幀之間有20 秒的間隙,那麼對於可變幀速率編碼,您可以分割輸出,使用fps 過濾器,以某種方式使用選擇過濾器(也許構建一個非常長的表達式,其中包含每個關鍵影格時間)...或者您可以使用測試影片作為輸入,並且僅解碼關鍵影格(如果 ffmpeg 選項有效),或使用選擇過濾器來選擇關鍵影格。然後將其縮放到正確的尺寸(甚至有一個scale2ref過濾器)並在其上覆蓋原始影片。然後使用交錯過濾器將這些注定要強制的關鍵影格與原始影片組合起來。如果這會導致相隔 0.001 秒的兩個幀,而交錯濾波器無法阻止,則可以使用另一個選擇濾波器自行解決此問題。處理交錯濾波器的幀緩衝區限制可能是這裡的主要問題。這些都可以工作:使用某種過濾器來緩衝更密集的流(fifo 過濾器?);多次引用輸入文件,因此它會被多次解碼,並且不必儲存幀;在關鍵幀的時間使用“streamselect”過濾器,這是我從未做過的;透過更改交錯過濾器的預設行為或新增選項以輸出緩衝區中最舊的幀而不是丟棄幀來改進交錯過濾器。