web-dev-qa-db-ja.com

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

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で、libx264によって3秒以内にscenecutキーフレームが挿入された場合、次のキーフレームは5秒後になるのでしょうか、それとも2秒後になるのでしょうか。

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

どちらが正しいですか?

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

  • h.264のGOP仕様に準拠しています(ある場合は)。
  • libx264のscenecutのキーフレームに関係なく、固定のリズムでキーフレームがあることを保証します。

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

最後に、keyintメソッドはハックのようです。私はこれが正しい答えではないことを願って反対しています。

参考文献:

-force_key_framesメソッドを使った例

keyintメソッドを使った例

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

33

そのため、答えは次のようになります。

  • 方法1は動作することが確認されていますが、libx264特有であり、libx264の非常に便利なscenecutオプションを削除するという犠牲を払っています。
  • 方法3は2015年4月のFFMPEGバージョンの時点で機能しますが、FFMPEGのドキュメントではオプションの効果については不明確であるため、この記事の最後に含まれているスクリプトを使用して結果を確認する必要があります。それがうまくいけば、それは2つのオプションの優れています。
  • 方法2を使用しないでください、-gは推奨されていないようです動作しないようで、ドキュメントで明示的に定義されていることも、ドキュメントで定義されていることもありません。また、コード内で使用されているようには見えません。コード検査により、-gオプションはおそらくMPEG-2ストリーム用であることがわかります(PALおよびNTSCを参照するコードスタンザさえあります)。

また:

  • 方法3で生成されたファイルは、インタースティシャルIフレーム(キーフレーム)が許可されているため、方法1よりわずかに大きくなる場合があります。
  • 方法3がIフレームを次のフレームスロットの先頭またはに指定された時刻の後に配置したとしても、どちらの場合も明示的に "-r"フラグを設定する必要があります。 "-r"フラグを設定しなかった場合、おそらく可変のフレームレートで、ソースファイルの責任になります。互換性のないDASH遷移が発生する可能性があります。
  • FFMPEGドキュメンテーションの警告にもかかわらず、方法3はNOTの効率が他よりも低くなります。実際、テストの結果、方法1よりもわずかに効率が良い場合があります。

-force_key_framesオプション用のスクリプト

これはslhckのffprobe提案の出力に基づいてIフレームケイデンスを検証するために使った短いPerlプログラムです。 -force_key_framesメソッドも機能することを確認しているようで、scenecutフレームを許容するという追加の利点があります。 FFMPEGがどのように機能するのか、あるいは私のストリームがうまく調整されているために何らかの形でうまく動かなかったのかどうか、私にはまったくわかりません。

私の場合は、6秒の予想GOPサイズ、つまり180フレームで30fpsでエンコードしました。このプログラムのgopsize引数として180を使用して、180の倍数ごとにIフレームを検証しましたが、181(または180の倍数ではない他の任意の数)に設定すると文句を言われました。

#!/usr/bin/Perl
use strict;
my $gopsize = shift(@ARGV);
my $file = shift(@ARGV);
print "GOPSIZE = $gopsize\n";
my $linenum = 0;
my $expected = 0;
open my $pipe, "ffprobe -i $file -select_streams v -show_frames -of csv -show_entries frame=pict_type |"
        or die "Blah";
while (<$pipe>) {
  if ($linenum > $expected) {
    # Won't catch all the misses. But even one is good enough to fail.
    print "Missed IFrame at $expected\n";
    $expected = (int($linenum/$gopsize) + 1)*$gopsize;
  }
  if (m/,I\s*$/) {
    if ($linenum < $expected) {
      # Don't care term, just an extra I frame. Snore.
      #print "Free IFrame at $linenum\n";
    } else {
      #print "IFrame HIT at $expected\n";
      $expected += $gopsize;
    }
  }
  $linenum += 1;
}
6

TL、DR

私は以下をお勧めします。

  • 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、…秒など)にあるようにします。
  • 符号化効率/品質を改善するために、シーンカット検出を可能にする。これは、IフレームをIDRフレーム間に配置できるようにすることを意味します。シーンカットの検出を無効にして作業することはできますが(これは多くのガイドの一部ですが)、必須ではありません。

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

エンコーダを設定するために、キーフレームパラメータが何をするのか理解しなければなりません。 FFmpegの3つのエンコーダlibx264libx265およびlibvpx-vp9について、いくつかテストを行い、次のことを発見しました。

  • libx264

    • -gはキーフレーム間隔を設定します。
    • -keyint_minは最小キーフレーム間隔を設定します。
    • -x264-params "keyint=x:min-keyint=y"-g x -keyint_min yと同じです。
    • 注:両方を同じ値に設定すると、最小値は内部的に half 最大間隔プラスx264コードに見られるように、1つ:

      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;
      

      libvpxkf_min_distに異なる値を設定することを確実にサポートしているので、これはバグ(あるいは機能の欠如?)かもしれません。

-force_key_framesを使うべきですか?

-force_key_framesオプションは与えられた間隔(式)でキーフレームを強制的に挿入します。これはすべてのエンコーダで機能しますが、レート制御メカニズムがおかしくなる可能性があります。特にVP9に関しては、私は深刻な品質変動に気づいたので、この場合それを使うことはお勧めできません。

23
slhck

これは私の場合の50セントです。

方法1:

libx264の引数をいじる

-c:v libx264 -x264opts keyint = GOPSIZE:最小keyint = GOPSIZE:シーンカット= -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秒ごとにキーフレームを挿入する(MAYBE):

-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が配置され、シーンカット(浮動部分のある秒数)に配置されます。これは、ビデオストリームの複雑さにとって重要です。

生成されたファイルサイズはほぼ同じです。非常に奇妙なことに、方法3でより多くのキーフレームがあっても、標準のx264ライブラリアルゴリズムよりも少ないファイルしか生成されないことがあります。

HLSストリーム用に複数のビットレートファイルを生成するには、方法3を選択します。それはチャンクの間の2秒と完全に一致しています、彼らはあらゆるチャンクの始めにiframeを持っています、そして彼らは時代遅れの装置を持っていてx264ハイプロファイルを再生できないユーザーにとってより良い経験を提供します.

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

11
Ara Saahov

私のグーグルが私のDASHエンコーディングを自分の望む方法でセグメント化する方法を見つけるための情報を探すために私のグーグルがこの議論をかなり引き上げたので、ここに情報を追加したいと思いました。

取り除くための最初のいくつかの誤解:

  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について(たくさん読むことから)考え出すまで。

目的のGOPサイズを2倍にするためにkeyintを設定する必要があることがわかりました。これはmin-keyintを私の望むGOPサイズに設定することができることを意味します(内部コードは半分にそれをカットしません)。常にmin-keyinitより小さい。

そして最後にforce_key_frameオプションを設定すると、倍サイズのkeyintがオーバーライドされます。それで、ここでうまくいくのです:

私は2秒単位のセグメントを好むので、私のGOPSIZE = Framerate * 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-Frameが表示されるはずですが、他のすべてのIフレームはシーンカット検出で必要に応じて挿入された非IDR I-frameです。

4
Reuben

私たちのVODコンテンツやライブコンテンツ(ファイルダンプ)についてはかなりテストされていますが、シーンカットがうまく動作せず、中間のiframeが発生することもあります。

I50-> p50アップコンバージョン、2秒gop/segment、開始時のIDR、必要ならばその間のiframeの構文

ffmpeg.exe -loglevel verbose -i avc_50i.ts -pix_fmt yuv420p -filter_complex yadif=1,scale=1920:1080 -vcodec libx264 -preset fast -x264-params "rc-lookahead=100:keyint=200:min-keyint=100:hrd=1:vbv_maxrate=12000:vbv_bufsize=12000:no-open-gop=1" -r 50 -crf 22 -force_key_frames "expr:eq(mod(n,100),0)" -codec:a aac -b:a 128k -y target.ts
0
TEB

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

あなたがリアルタイムストリーミングをしていないなら、あなたはもっと贅沢を持っています。 「正しい」方法は、-gで指定されたGOPサイズだけで1つの解像度でエンコードしてから、他の解像度を同じ場所に強制的にエンコードすることです。

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

しかし、ほとんどのコンテンツでは、5秒ごとに1つのキーフレームを使用することと5秒ごとに2つのキーフレームを使用することの違いはほとんどありません(1つは強制的、もう1つはシーンカットから)。これは、Iフレームの平均サイズとPフレームおよびBフレームのサイズの差です。一般的な設定でx264を使用する場合(これらに影響を与えるために何かをすべきだと思う唯一の理由は、x264が簡単なコンテンツでビットレートを使用しないようにする悪い方法として-qminを設定する場合です。 、私は思う)と46キロバイトのIフレームの平均サイズ、Pフレームの24キロバイト、Bフレームの17キロバイト(Pフレームの半分の頻度)、そして30 fpsで毎秒余分なIフレームのような結果を得るファイルサイズのわずか3%の増加です。 h264とh263の違いは3%減少したものであるかもしれませんが、1つでもそれほど重要ではありません。

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

よくわからないことの1つは、強制キーフレームが-maxrateおよび-bufsizeで設定されたレートリミッタとどのように相互作用するかです。 YouTubeでも、一貫した品質を得るためにバッファ設定を正しく構成することに最近問題があったと思います。いくつかのサイトで見られるような平均ビットレート設定を使っているだけなら(hex /エディタでheader/mov atom?の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フィルターを使用し、何らかの形でselectフィルターを使用します。あるいは、テストビデオを入力として使用して、そのffmpegオプションが機能する場合はキーフレームのみをデコードするか、選択フィルタを使用してキーフレームを選択することもできます。それからそれを正しいサイズにスケーリングし(これにはscale2refフィルタさえあります)、その上にオリジナルのビデオを重ねます。次に、インターリーブフィルタを使用して、これらの強制キーフレームを元のビデオと結合します。これがインターリーブフィルタが防ぐことができない0.001秒間隔である2つのフレームという結果に終る場合、そして別の選択フィルタでこの問題に自分自身で対処してください。ここでの主な問題は、インターリーブフィルタのフレームバッファ制限を処理することです。これらはすべてうまくいく可能性があります。高密度のストリームをバッファリングするためにある種のフィルタを使用します(fifo filter?)。入力ファイルを複数回参照するので、複数回デコードされ、フレームを保存する必要はありません。私がこれまで一度も行ったことのない「streamselect」フィルタを、キーフレームのタイミングで使用します。デフォルトの振る舞いを変更するか、またはフレームを落とす代わりに最も古いフレームをバッファに出力するオプションを追加することによって、インターリーブフィルタを改善します。

0
Misaki