DASH の FFmpeg でキーフレームを修正する正しい方法は何ですか?

DASH の FFmpeg でキーフレームを修正する正しい方法は何ですか?

DASH 再生用にストリームを調整する場合、ランダム アクセス ポイントはすべてのストリームでソース ストリーム時間と同じである必要があります。これを行う通常の方法は、固定フレーム レートと固定 GOP 長 (つまり、N フレームごとにキーフレーム) を強制することです。

FFmpeg では、フレーム レートを固定するのは簡単です (-r NUMBER)。

しかし、固定キーフレーム位置 (GOP 長さ) の場合、3 つの方法があります...どれが「正しい」のでしょうか? FFmpeg のドキュメントは、この点に関してイライラするほど曖昧です。

方法1: libx264の引数を変更する

-c:v libx264 -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE:scenecut=-1

シーンカットが発生したときにキーフレーム「カウンター」が再開されるかどうかが不明であるため、シーンカットをオフにする必要があるかどうかについては議論があるようです。

方法2: 固定GOPサイズの設定:

-g GOP_LEN_IN_FRAMES

残念ながら、これは FFMPEG ドキュメントに簡単に記載されているだけなので、この議論の効果は非常に不明確です。

方法3: N秒ごとにキーフレームを挿入する(多分?):

-force_key_frames expr:gte(t,n_forced*GOP_LEN_IN_SECONDS)

これ明示的に文書化されています。しかし、すべてのキー フレームの後に「時間カウンター」が再開されるかどうかは、まだすぐにはわかりません。たとえば、予想される 5 秒の GOP で、scenecutlibx264 によって 3 秒後にキーフレームが挿入された場合、次のキーフレームは 5 秒後になるのでしょうか、それとも 2 秒後になるのでしょうか。

実際、FFmpeg のドキュメントでは、このオプションと-gオプションを区別していますが、上記の 2 つのオプションがどのように少しでも異なるのかは実際には説明されていません (明らかに、-g固定フレーム レートが必要になります)。

どちらが正しいでしょうか?

-force_key_framesが優れているように思われる固定フレームレートを必要としないためです。しかし、そのためには

  • H.264のGOP仕様に準拠しています(もしあれば
  • libx264 キーフレームに関係なく、キーフレームが固定リズムで存在することを保証しますscenecut

-gまた、固定フレームレートを強制しないと動作しないようです( -r異なるコーデック引数でを複数回実行しても、各解像度で同じ瞬間フレーム レートが得られるという保証はありませんffmpeg。フレーム レートを固定すると、圧縮パフォーマンスが低下する可能性があります (DASH シナリオでは重要)。

ついに、このkeyint方法は単なるハックのようだ望みは薄いですが、これが正解ではないことを願っています。

参考文献:

-force_key_framesこの方法を使用した例

keyintこの方法を使用した例

FFmpeg の高度なビデオ オプション セクション

答え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 になります。

さまざまなフレームタイプに関する注意

このトピックを適切に説明するには、まず 2 種類の I フレーム / キーフレームを定義する必要があります。

  • 瞬時デコーダーリフレッシュ (IDR) フレーム: IDR フレームの前のフレームにアクセスすることなく、後続のフレームを独立してデコードできます。
  • 非 IDR フレーム: デコードが機能するには、前の IDR フレームが必要です。非 IDR フレームは、GOP (画像グループ) の途中のシーン カットに使用できます。

ストリーミングには何がおすすめですか?

ストリーミングの場合は、次のようになります。

  • ビデオを同じ長さのセグメントに分割できるように、すべての IDR フレームが通常の位置 (例: 2 秒、4 秒、6 秒、… 秒) にあることを確認します。
  • シーン カット検出を有効にして、コーディングの効率と品質を向上させます。つまり、IDR フレームの間に I フレームを配置できるようになります。シーン カット検出を無効にした状態でも作業できます (これは多くのガイドで依然として取り上げられています) が、必須ではありません。

パラメータは何をするのですか?

エンコーダーを構成するには、キーフレーム パラメーターが何をするかを理解する必要があります。いくつかテストを行った結果、3 つのエンコーダーと FFmpeg で次のことがわかりlibx264ましたlibx265libvpx-vp9

  • libx264:

    • -gキーフレーム間隔を設定します。
    • -keyint_min最小キーフレーム間隔を設定します。
    • -x264-params "keyint=x:min-keyint=y"と同じです-g x -keyint_min y
    • 注記:両方を同じ値に設定すると、最小値は内部的に次のように設定されます。半分コードに示されているように、最大​​間隔に 1 を加えた値です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

これがこの件に対する私の50セントです。

方法1:

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は減価償却されます。省略。

方法3:

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

ご覧のとおり、iframe は 2 秒ごとに、またシーンカット (浮動部分のある秒数) に配置されますが、これはビデオ ストリームの複雑さにとって重要であると私は考えています。

生成されたファイルサイズはほぼ同じです。キーフレームを増やしても、方法3標準の x264 ライブラリ アルゴリズムよりも少ないファイルを生成する場合があります。

HLS ストリーム用の複数のビットレート ファイルを生成するために、方法 3 を選択します。チャンク間の間隔が 2 秒で完全に調整され、各チャンクの先頭に iframe があり、複雑なシーンには追加の iframe があるため、古いデバイスを使用していて x264 ハイ プロファイルを再生できないユーザーには、より優れたエクスペリエンスが提供されます。

誰かの役に立つことを願います。

答え3

DASH エンコーディングを希望どおりにセグメント化する方法を見つけるための情報を探す中で、Google 検索でこのディスカッションがかなり多く表示されたのですが、見つかった情報はどれも完全に正しいものではなかったため、ここに情報を追加したいと思いました。

まず最初に取り除くべきいくつかの誤解:

  1. すべての I フレームが同じというわけではありません。大きな「I」フレームと小さな「i」フレームがあります。または、正確な用語を使用すると、IDR I フレームと非 IDR I フレームがあります。IDR I フレーム (「キーフレーム」と呼ばれることもあります) は新しい GOP を作成します。非 IDR フレームは作成しません。シーンが変わる GOP 内に置くと便利です。

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE← これは、あなたが考えているような動作をしません。これを理解するのに少し時間がかかりました。 はmin-keyintコード内で制限されていることがわかりました。 より大きい値は許可されていません(keyint / 2) + 1。そのため、これら 2 つの変数に同じ値を割り当てると、エンコード時に の値がmin-keyint半分に減らされます。

問題は、シーン カットが本当に素晴らしいことです。特に、高速なハード カットがあるビデオでは、その効果は絶大です。シーン カットは、映像を鮮明に保ってくれるので、無効にしたくありませんが、有効にしたままでは、GOP サイズを固定することができませんでした。シーン カットを有効にして、非 IDR I フレームのみを使用するようにしたかったのです。しかし、うまくいきませんでした。誤解 2 について (たくさん読んで) 理解するまでは。

結局、keyint希望する GOP サイズの 2 倍に設定する必要がありました。つまり、min-keyintを希望する GOP サイズに設定できるということです (内部コードで半分にカットされることなく)。これにより、最後の IDR I フレーム以降のフレーム数は常に 未満であるため、シーン カット検出が GOP サイズ内で IDR I フレームを使用することを防止できますmin-keyinit

そして最後に、force_key_frameオプションを設定すると、 double size が上書きされます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

その結果、IDR I フレームは固定GOPSIZE間隔でのみ表示され、その他のすべての I フレームはシーン カット検出によって必要に応じて挿入される非 IDR I フレームになります。

答え4

Twitch にはこれに関する投稿があります。彼らは、いくつかの理由から独自のプログラムを使用することにしたと説明しています。その 1 つは、ffmpeg では異なるスレッドで異なる x264 インスタンスを実行できず、代わりに指定されたすべてのスレッドを 1 つの出力の 1 つのフレームに割り当ててから次の出力に進むためです。

リアルタイム ストリーミングを行わない場合は、より贅沢な方法があります。おそらく「正しい」方法は、-g で指定された GOP サイズのみを使用して 1 つの解像度でエンコードし、次に同じ場所にキーフレームを強制して他の解像度をエンコードすることです。

これを実行したい場合は、ffprobe を使用してキーフレーム時間を取得し、シェル スクリプトまたは実際のプログラミング言語を使用してそれを ffmpeg コマンドに変換します。

しかし、ほとんどのコンテンツでは、5 秒ごとに 1 つのキーフレームと 5 秒ごとに 2 つのキーフレーム (1 つは強制、もう 1 つはシーンカットから) の間にほとんど違いはありません。これは、平均 I フレーム サイズと P フレームおよび B フレームのサイズに関するものです。x264 を一般的な設定で使用し (これらに影響を与える必要がある唯一の理由は、-qmin を設定する場合です。これは、x264 が簡単なコンテンツでビットレートを使用しないようにするための不適切な方法です。これにより、すべてのフレーム タイプが同じ値に制限されると思います)、I フレームの平均サイズが 46 kB、P フレームが 24 kB、B フレームが 17 kB (P フレームの半分の頻度) のような結果になった場合、30 fps で毎秒 1 つの余分な I フレームはファイル サイズが 3% 増加するだけです。h264 と h263 の違いは、3% の減少の集まりである可能性がありますが、1 つだけはあまり重要ではありません。

他の種類のコンテンツでは、フレーム サイズは異なります。公平を期すために言うと、これは時間的な複雑さに関するもので、空間的な複雑さに関するものではないため、簡単なコンテンツと難しいコンテンツの違いだけではありません。ただし、一般的に、ストリーミング ビデオ サイトにはビットレート制限があり、比較的大きな I フレームを持つコンテンツは、キーフレームがいくつ追加されても高品質でエンコードされる簡単なコンテンツです。これは無駄ですが、この無駄は通常は気付かれません。最も無駄なケースは、各キーフレームがまったく同じで、曲に付随する静止画像だけのビデオでしょう。

よくわからないのは、強制キーフレームが -maxrate および -bufsize で設定されたレート リミッターとどのように相互作用するかです。YouTube でも最近、一貫した品質を提供するためにバッファー設定を正しく構成できない問題が発生していると思います。一部のサイトで見られるように、平均ビットレート設定のみを使用している場合 (16 進エディターを使用して、ヘッダー/mov アトムの x264 のオプションを調べることができるため)、バッファー モデルは問題になりませんが、ユーザー生成コンテンツを提供している場合、平均ビットレートは、ユーザーがビデオの最後に黒い画面を追加することを促します。

Ffmpeg の -g オプション、または使用するその他のエンコーダー オプションは、エンコーダー固有のオプションにマップされます。したがって、「-x264-params keyint=GOPSIZE」は「-g GOPSIZE」と同等です。

シーン検出を使用する際の 1 つの問題は、何らかの理由で特定の数字に近いキーフレームを希望する場合です。5 秒ごとにキーフレームを指定してシーン検出を使用すると、4.5 でシーン変更があった場合、それは検出されるはずですが、次のキーフレームは 9.5 になります。時間がこのようにステップアップし続けると、40、45、50、55 ではなく、42.5、47.5、52.5 などのキーフレームになる可能性があります。逆に、5.5 でシーン変更があった場合、5 にキーフレームがあり、5.5 は次のキーフレームには早すぎます。Ffmpeg では、「次の 30 フレーム以内にシーン変更がない場合は、ここでキーフレームを作成する」という指定はできません。ただし、C を理解できる人なら、そのオプションを追加できます。

可変フレームレートのビデオの場合、Twitch のようにライブストリーミングしていないときは、固定フレームレートに永続的に変換せずにシーン変更を使用できるはずです。ffmpeg で 'select' フィルターを使用し、式で 'scene' 定数を使用すると、デバッグ出力 (-v debug またはエンコード中に '+' を数回押す) にシーン変更番号が表示されます。これはおそらく x264 で使用される番号とは異なり、それほど便利ではありませんが、それでも役に立つ可能性があります。

手順としては、おそらくキーフレームの変更のみのテストビデオを作成することになりますが、2パスを使用する場合はレート制御データに使用できる可能性があります。(生成されたデータがさまざまな解像度や設定に役立つかどうかはわかりません。マクロブロックツリーデータは役に立ちません。)これを固定フレームレートビデオに変換しますが、このバグfps フィルターを他の目的で使用することにした場合、フレームレートを半分にすると出力が途切れることがあります。希望するキーフレームと GOP 設定で x264 で実行してください。

次に、これらのキーフレーム時間を元の可変フレームレート ビデオで使用します。

フレーム間に 20 秒のギャップがある、完全にクレイジーなユーザー生成コンテンツを許可する場合、可変フレーム レート エンコードでは、出力を分割し、fps フィルターを使用し、何らかの方法で選択フィルターを使用します (すべてのキーフレーム時間を含む非常に長い式を構築するなど)。または、テスト ビデオを入力として使用し、ffmpeg オプションが機能する場合はキーフレームのみをデコードするか、選択フィルターを使用してキーフレームを選択することもできます。次に、正しいサイズに拡大縮小し (このための scale2ref フィルターもあります)、元のビデオをその上にオーバーレイします。次に、インターリーブ フィルターを使用して、強制される予定のこれらのキーフレームを元のビデオと結合します。これにより、インターリーブ フィルターでは防止できない 0.001 秒離れた 2 つのフレームが生成された場合は、別の選択フィルターを使用してこの問題に対処してください。インターリーブ フィルターのフレーム バッファー制限に対処することが、ここでの主な問題である可能性があります。これらはすべて機能する可能性があります。何らかのフィルターを使用して、より密度の高いストリームをバッファーします (fifo フィルターなど)。入力ファイルを複数回参照して複数回デコードし、フレームを保存する必要がないようにします。キーフレームの正確な時間に「streamselect」フィルターを使用します (私はこれまで一度も使用していません)。インターリーブ フィルターの既定の動作を変更するか、フレームをドロップする代わりにバッファー内の最も古いフレームを出力するオプションを追加して、インターリーブ フィルターを改善します。

関連情報