video – DASH用FFmpegでキーフレームを修正する正しい方法とは?

ffmpeg streaming video video-conversion

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 のキーフレームに関わらず、固定ケイデンスのキーフレームがあることを保証します

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

最後に、keyintの方法は、ただのハックにしか見えません。これが正解ではないことを祈るばかりです

References:

-force_key_framesメソッドを用いた例

keyintメソッドを用いた例

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

  47  Mark Gerolimatos  2015-04-30


ベストアンサー

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

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

エンコーダを設定するためには、キーフレームのパラメータが何をするのかを理解する必要があります。私はいくつかのテストを行い、FFmpegの3つのエンコーダlibx264libx265libvpx-vp9について、以下のことを発見しました

  • libx264:

    • -gはキーフレームの間隔を設定します
    • -keyint_minは、キーフレームの最小間隔を設定します
    • -x264-params "keyint=x:min-keyint=y"-g x -keyint_min yと同じです
    • 注意: 両方を同じ値に設定する場合、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;
      

      libvpxは間違いなくkf_min_distに別の値を設定することをサポートしているので、これはバグかもしれません(機能がない?)

-force_key_framesを使うべきか?

-force_key_frames オプションは、与えられた間隔 (式) でキーフレームを強制的に挿入します。これはすべてのエンコーダーで動作しますが、レート制御機構を混乱させる可能性があります。特に VP9 の場合、品質の変動が激しいことに気がついたので、この場合の使用はお勧めできません

35  slhck  2015-04-30


これが私の50セントだ

Method 1:

libx264 の引数をいじくりまわしている

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

希望する間隔でのみiframeを生成します

Example 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は減価償却しています。オミットされている

Method 3:

N秒ごとにキーフレームを挿入する(MAYBE)

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

Example 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を配置し、さらにscenecut(フローティング部分のある秒数)にもiframeを配置しています

生成されるファイルサイズはほぼ同じ。方法3ではキーフレーム数を増やしても、標準のx264ライブラリアルゴリズムよりも生成されるファイル数が少ないことがあるのが非常に不思議だ

HLSストリームのための複数のビットレートファイルを生成するために、私たちは方法3を選択します。これは、チャンク間に2秒の間隔で完全に整列し、すべてのチャンクの最初にiframeを持っており、複雑なシーンに追加のiframeを持っているので、古いデバイスを持っていてx264の高プロファイルを再生できないユーザーのためのより良い経験を提供します

誰かのお役に立てれば幸いです

12  Ara Saahov  2016-07-08


したがって、答えはこうなるようです

  • 方法1は動作することが確認されていますが、libx264固有のものであり、libx264の非常に有用なscenecutオプションを排除することを犠牲にしています
  • 方法3は、2015年4月のFFMPEGバージョンの時点で動作しますが、FFMPEGのドキュメントは、オプションの効果として不明確であるため、この記事の下部に含まれているスクリプトを使用して結果を確認する必要があります。それが動作する場合、それは2つのオプションの優れたものです
  • DO NOT USE Method 2, -g は非推奨のようです。これは動作しないように見えますし、ドキュメントにも明示的に定義されていませんし、ヘルプにも記載されていませんし、コード中で使用されているようにも見えません。コードを調べてみると、-g オプションは MPEG-2 ストリームのためのものである可能性が高いことがわかります (PAL と NTSC に言及しているコードもあります!)

Also:

  • 方法3で生成されたファイルは、インタースティシャルIフレーム(キーフレーム)が許可されているため、方法1よりも若干大きくなる可能性がある
  • 方法3では、指定された時間以降の次のフレームスロットにIフレームを配置しますが、どちらの場合も”-r “フラグを明示的に設定する必要があります。r” フラグを設定しないと、ソースファイルに依存することになり、フレームレートが変化する可能性があります。互換性のない DASH トランジションが発生する可能性があります
  • FFMPEGのドキュメントで警告が出ているにもかかわらず、メソッド3は他のものよりも効率が悪いわけではありません。実際、テストでは、メソッド1よりもわずかに効率が良いかもしれないことが示されています

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

slhckのffprobe提案の出力に基づいて、Iフレームのケイデンスを検証するために使用した短いPERLプログラムを以下に示します。これは、-force_key_framesメソッドも動作することを検証しているようで、scenecutフレームを許可するという付加的な利点があります。私は、FFMPEGがどのようにこれを動作させるか、または私のストリームがよく条件付けされていることが起こるので、私はただ運が良かった場合は、何らかの方法で出ているかどうかについては全く考えていません

私の場合は、30fpsで6秒のGOPサイズ、つまり180フレームを想定してエンコードしました。180の倍数ごとにIフレームを検証するプログラムで、GOPサイズの引数に180を使っていたが、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;
}

7  Mark Gerolimatos  2015-05-01


私は、私が望む方法で DASH エンコーディングをセグメント化する方法を見つけようとしている情報を見つけるために、ググったときにこの議論をかなり引っ張ってきたので、ここにいくつかの情報を追加したいと思いました

最初のいくつかの誤解を払拭するために

  1. Iフレームはすべて同じではありません。大きな「I」フレームと小さな「I」フレームがあります。または、正しい用語を使用するには、IDR I-Frameと非IDR I-Frameです。IDRのIフレーム(「キーフレーム」と呼ばれることもある)は新しいGOPを作ります。非IDRフレームはそうではありません。これらのフレームは、シーンチェンジがあるGOPの中にあると便利です

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE ←これは思ったようなことができない。これを理解するのに少し時間がかかりました。コードの中でmin-keyintが制限されていることがわかりました。(keyint / 2) + 1より大きいことは許されていません。つまり、これら二つの変数に同じ値を代入すると、エンコード時にmin-keyintの値が半分になってしまうということです

ここからがポイントです:シーンカットは本当に素晴らしいです、特に高速ハードカットのあるビデオでは。綺麗で鮮明な映像を維持できるので、これを無効にしたくないのですが、同時に、この機能を有効にしている限り、固定のGOPサイズを取得することができませんでした。シーンカットを有効にして、非IDRのIフレームのみを使用するようにしたかったのですが、うまくいきませんでした。しかし、それはうまくいきませんでした。沢山の本を読んでいて、その2の誤解を知るまでは

これにより、keyintを希望のGOPサイズの2倍に設定する必要があることがわかりました。これは、min-keyintを私の希望するGOPサイズに設定することができることを意味します(内部コードがそれを半分にカットすることなく)。これは、最後のIDR I-Frame以降のフレームカウントが常にmin-keyinitよりも小さいため、シーンカット検出がGOPサイズ内でIDR I-Frameを使用することを防ぐことになります

そして最後に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フレームを見るべきではなく、他の全てのIフレームはシーンカット検出によって必要に応じて挿入された非IDR Iフレームである

6  Reuben  2017-06-27


この構文はいつもうまくいかないようです。ライブコンテンツ(ファイルダンプ)だけでなく、VODコンテンツでもテストしてみましたが、時々scenecutが動作せず、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  2018-10-31


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

リアルタイムストリーミングをしていないのであれば、もっと余裕があります。正しい」方法は、ある解像度で-gで指定したGOPサイズだけでエンコードし、他の解像度では同じ場所でキーフレームを強制的にエンコードすることでしょう

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

しかし、ほとんどのコンテンツでは、5秒に1つのキーフレームがあるのと、5秒に2つのキーフレームがあるのとでは、ほとんど違いがありません(1つは強制的に、もう1つはscenecutからのもの)。これは、平均的なIフレームのサイズとPフレームとBフレームのサイズを比較したものです。一般的な設定でx264を使用している場合(これらに影響を与えるために何かする必要があると思う唯一の理由は、x264が簡単なコンテンツでビットレートを使用しないようにするために、-qminを設定した場合です;これはすべてのフレームタイプを同じ値に制限していると思います)、Iフレームの平均サイズが46キロバイト、Pフレームが24キロバイト、Bフレームが17キロバイト(Pフレームの半分の頻度)のような結果が得られた場合、30fpsで1秒ごとに余分なIフレームを追加しても、ファイルサイズは3%増加するだけです。h264とh263の違いは、3%の減少の束で構成されているかもしれませんが、1つの違いはあまり重要ではありません

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

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

Ffmpeg の -g オプション、または使用するその他のエンコーダオプションは、エンコーダ固有のオプションにマッピングされます。つまり、’-x264-params keyint=GOPSIZE’ は ‘-g GOPSIZE’ と同じです

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

可変フレームレートの動画の場合、Twitchのようにライブストリーミングをしていないときは、恒久的に一定フレームレートに変換しなくてもシーンチェンジを使うことができるはずです。ffmpeg の ‘select’ フィルタを使用して、式の中で ‘scene’ 定数を使用すると、デバッグ出力 (-v debug またはエンコード中に ‘+’ を数回押す) にシーンチェンジの番号が表示されます。これはおそらく x264 で使われている番号とは異なるし、それほど有用ではないだろうが、それでも有用であろう

手順としては、キーフレームの変更のみのテストビデオを作成することになりますが、2パスを使用している場合は、レートコントロールデータに使用することができるかもしれません(生成されたデータが異なる解像度や設定で有用かどうかはわかりません。(生成されたデータが異なる解像度や設定に対して全く有用かどうかはわかりません。)これをx264で実行して、希望のキーフレームとGOP設定で実行してみてください

そして、これらのキーフレーム時間を元の可変フレームレートのビデオで使用するだけです

フレーム間のギャップが 20 秒のクレイジーなユーザー生成コンテンツを許可している場合、可変フレームレートエンコードのために、出力を分割したり、fps フィルタを使用したり、何らかの方法で select フィルタを使用したり (キーフレームごとの時間を持つ非常に長い式を作成したり) することができます… あるいは、テストビデオを入力として使用して、fffmpeg オプションが機能する場合はキーフレームのみをデコードするか、または select フィルタを使用してキーフレームを選択することができます。そして、それを正しいサイズに拡大縮小して(このための scale2ref フィルタもあります)、元のビデオをその上にオーバーレイします。そして、インターリーブフィルタを使って、強制的にキーフレームを元のビデオに合成します。その結果、0.001 秒間隔の 2 つのフレームがインターリーブフィルタで防止できない場合は、別の選択フィルタを使ってこの問題に対処してください。インターリーブフィルタのフレームバッファの制限がこの問題の主な原因かもしれません。これらはすべてうまくいくかもしれません:より密度の高いストリームをバッファリングするために何らかのフィルタを使用する(fifoフィルタ?

0  Misaki  2019-04-22


タイトルとURLをコピーしました