Wie lassen sich Keyframes in FFmpeg für DASH richtig korrigieren?

Wie lassen sich Keyframes in FFmpeg für DASH richtig korrigieren?

Wenn ein Stream für die DASH-Wiedergabe aufbereitet wird, müssen die Zufallszugriffspunkte in allen Streams genau zur gleichen Zeit des Quellstreams liegen. Normalerweise wird hierfür eine feste Bildrate und eine feste GOP-Länge erzwungen (d. h. alle N Frames ein Keyframe).

In FFmpeg ist eine feste Bildrate einfach (-r NUMBER).

Aber für feste Keyframe-Positionen (GOP-Länge) gibt es drei Methoden ... welche ist „richtig“? Die FFmpeg-Dokumentation ist diesbezüglich frustrierend vage.

Methode 1: Mit den Argumenten von libx264 herumspielen

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

Es scheint eine Debatte darüber zu geben, ob der Szenenschnitt ausgeschaltet werden sollte oder nicht, da unklar ist, ob der Keyframe-„Zähler“ bei einem Szenenschnitt neu gestartet wird.

Methode 2: Festlegen einer festen GOP-Größe:

-g GOP_LEN_IN_FRAMES

Dies wird in der FFMPEG-Dokumentation leider nur am Rande erwähnt und daher ist die Wirkung dieses Arguments sehr unklar.

Methode 3: Einfügen eines Keyframes alle N Sekunden (Vielleicht?):

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

DasIstexplizit dokumentiert. Es ist jedoch immer noch nicht sofort klar, ob der „Zeitzähler“ nach jedem Keyframe neu gestartet wird. Wenn beispielsweise in einem erwarteten 5-Sekunden-GOP ein scenecutKeyframe 3 Sekunden später von libx264 eingefügt wird, wäre der nächste Keyframe dann 5 Sekunden oder 2 Sekunden später?

Tatsächlich wird in der FFmpeg-Dokumentation zwischen dieser und der -gOption unterschieden, es wird jedoch nicht wirklich gesagt, inwiefern sich die beiden oben genannten Optionen im Geringsten unterscheiden (offensichtlich -gist eine feste Bildrate erforderlich).

Was ist richtig?

Es scheint, dass die -force_key_framesüberlegen wäre, da keine feste Bildrate erforderlich wäre.Dies erfordert jedoch, dass

  • Es entspricht den GOP-Spezifikationen in H.264 (wenn überhaupt)
  • Es GARANTIERT, dass es einen Keyframe mit fester Kadenz gibt, unabhängig von den libx264 scenecut-Keyframes.

Es scheint auch, dass dies -gnicht ohne die Erzwingung einer festen Bildrate funktionieren könnte ( -r), da es keine Garantie dafür gibt, dass mehrere Durchläufe ffmpegmit unterschiedlichen Codec-Argumenten in jeder Auflösung die gleiche momentane Bildrate liefern. Feste Bildraten können die Komprimierungsleistung verringern (WICHTIG in einem DASH-Szenario!).

Endlich,die keyintMethode scheint einfach ein Hack zu sein. Ich hoffe gegen jede Hoffnung, dass dies nicht die richtige Antwort ist.

Verweise:

Ein Beispiel mit der -force_key_framesMethode

Ein Beispiel mit der keyintMethode

Abschnitt „Erweiterte Videooptionen“ von FFmpeg

Antwort1

Kurz zusammengefasst

Ich würde Folgendes empfehlen:

  • libx264: (und optional hinzufügen )-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

wobei Xdas Intervall in Frames und Ndas Intervall in Sekunden ist. Beispielsweise gilt für ein 2-Sekunden-Intervall bei einem 30-fps-Video: X= 60 und N= 2.

Ein Hinweis zu verschiedenen Rahmentypen

Um dieses Thema richtig zu erklären, müssen wir zunächst die beiden Arten von I-Frames/Keyframes definieren:

  • Instantaneous Decoder Refresh (IDR)-Frames: Diese ermöglichen die unabhängige Dekodierung der folgenden Frames ohne Zugriff auf die Frames vor dem IDR-Frame.
  • Nicht-IDR-Frames: Diese erfordern ein vorheriges IDR-Frame, damit die Dekodierung funktioniert. Nicht-IDR-Frames können für Szenenschnitte in der Mitte einer GOP (Group of Pictures) verwendet werden.

Was wird zum Streamen empfohlen?

Im Streaming-Fall möchten Sie:

  • Stellen Sie sicher, dass sich alle IDR-Frames an regulären Positionen befinden (z. B. bei 2, 4, 6, … Sekunden), damit das Video in gleich lange Segmente aufgeteilt werden kann.
  • Aktivieren Sie die Szenenschnitterkennung, um die Codierungseffizienz/-qualität zu verbessern. Dies bedeutet, dass I-Frames zwischen IDR-Frames platziert werden können. Sie können weiterhin mit deaktivierter Szenenschnitterkennung arbeiten (und dies ist immer noch Teil vieler Anleitungen), aber es ist nicht notwendig.

Was bewirken die Parameter?

Um den Encoder zu konfigurieren, müssen wir verstehen, was die Keyframe-Parameter bewirken. Ich habe einige Tests durchgeführt und Folgendes für die drei Encoder libx264und libx265in libvpx-vp9FFmpeg herausgefunden:

  • libx264:

    • -glegt das Keyframe-Intervall fest.
    • -keyint_minlegt das minimale Keyframe-Intervall fest.
    • -x264-params "keyint=x:min-keyint=y"ist das gleiche wie -g x -keyint_min y.
    • Notiz:Wenn beide Werte gleich sind, wird das Minimum intern aufHälftedas maximale Intervall plus eins, wie im x264Code zu sehen:

      h->param.i_keyint_min = x264_clip3( h->param.i_keyint_min, 1, h->param.i_keyint_max/2+1 );
      
  • libx265:

    • -gist nicht implementiert.
    • -x265-params "keyint=x:min-keyint=y"funktioniert.
  • libvpx-vp9:

    • -glegt das Keyframe-Intervall fest.
    • -keyint_minlegt das minimale Keyframe-Intervall fest
    • Notiz:Aufgrund der Funktionsweise von FFmpeg -keyint_minwird nur dann an den Encoder weitergeleitet, wenn es mit identisch ist -g. Im Code von libvpxenc.cin FFmpeg finden wir:

      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;
      

      Dies könnte ein Fehler sein (oder ein Mangel an Funktion?), da libvpxdefinitiv das Festlegen eines anderen Werts für unterstützt wird kf_min_dist.

Sollten Sie verwenden -force_key_frames?

Die -force_key_framesOption fügt Keyframes zwangsweise im angegebenen Intervall (Ausdruck) ein. Dies funktioniert bei allen Encodern, kann aber den Ratenkontrollmechanismus durcheinanderbringen. Insbesondere bei VP9 sind mir starke Qualitätsschwankungen aufgefallen, daher kann ich die Verwendung in diesem Fall nicht empfehlen.

Antwort2

Hier sind meine fünfzig Cent für den Fall.

Methode 1:

Herumspielen mit den Argumenten von libx264

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

Generieren Sie Iframes nur in den gewünschten Intervallen.

Beispiel 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

Generieren Sie Iframes wie erwartet wie folgt:

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

Methode 2 wird nicht mehr verwendet. Wird weggelassen.

Methode 3:

alle N Sekunden einen Keyframe einfügen (VIELLEICHT):

-force_key_frames expr:gte(t,n_erzwungen*GOP_LEN_IN_SEKUNDEN)

Beispiel 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

Generieren Sie einen Iframe auf eine etwas andere Art und Weise:

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

Wie Sie sehen, werden alle 2 Sekunden und beim Szenenschnitt (Sekunden mit schwebendem Teil) Iframes platziert, was meiner Meinung nach für die Komplexität des Videostreams wichtig ist.

Die generierten Dateigrößen sind ziemlich gleich. Sehr seltsam, dass selbst mit mehr Keyframes inMethode 3Es werden manchmal weniger Dateien generiert als der Standardalgorithmus der x264-Bibliothek.

Um Dateien mit mehreren Bitraten für HLS-Streams zu generieren, wählen wir Methode drei. Sie ist perfekt ausgerichtet mit 2 Sekunden zwischen den Blöcken, sie haben ein Iframe am Anfang jedes Blocks und sie haben zusätzliche Iframes bei komplexen Szenen, was ein besseres Erlebnis für Benutzer bietet, die über veraltete Geräte verfügen und keine x264 High Profiles wiedergeben können.

Hoffe, es hilft jemandem.

Antwort3

Ich wollte hier einige Informationen hinzufügen, da ich bei meiner Google-Suche nach Informationen zur Segmentierung meiner DASH-Kodierung nach Wunsch ziemlich oft auf diese Diskussion gestoßen bin und keine der gefundenen Informationen völlig korrekt war.

Zunächst gilt es, einige Missverständnisse auszuräumen:

  1. Nicht alle I-Frames sind gleich. Es gibt große „I“-Frames und kleine „i“-Frames. Oder, um die korrekte Terminologie zu verwenden: IDR-I-Frames und Nicht-IDR-I-Frames. IDR-I-Frames (manchmal auch „Keyframes“ genannt) erstellen eine neue GOP. Bei den Nicht-IDR-Frames ist dies nicht der Fall. Sie sind praktisch, um sie innerhalb einer GOP zu haben, in der es einen Szenenwechsel gibt.

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE← Dies tut nicht das, was Sie denken. Ich habe eine Weile gebraucht, um das herauszufinden. Es stellt sich heraus, dass min-keyintim Code dies begrenzt ist. Es darf nicht größer als sein (keyint / 2) + 1. Wenn Sie also diesen beiden Variablen denselben Wert zuweisen, wird der Wert für min-keyintbeim Kodieren um die Hälfte reduziert.

Das ist der Punkt: Der Szenenschnitt ist wirklich großartig, insbesondere bei Videos mit schnellen, harten Schnitten. Er hält alles schön scharf, also möchte ich ihn nicht deaktivieren, aber gleichzeitig konnte ich keine feste GOP-Größe erreichen, solange er aktiviert war. Ich wollte den Szenenschnitt aktivieren, aber nur I-Frames ohne IDR verwenden lassen. Aber es funktionierte nicht. Bis ich (durch viel Lesen) Missverständnis Nr. 2 herausfand.

Es stellte sich heraus, dass ich keyintdie gewünschte GOP-Größe verdoppeln musste. Das bedeutet, dass min-keyintich die gewünschte GOP-Größe einstellen kann (ohne dass der interne Code sie halbiert), was verhindert, dass die Szenenschnitterkennung IDR-I-Frames innerhalb der GOP-Größe verwendet, da die Frame-Anzahl seit dem letzten IDR-I-Frame immer kleiner als ist min-keyinit.

Und schließlich force_key_frameüberschreibt das Setzen der Option die doppelte Größe keyint. Folgendes funktioniert also:

Ich bevorzuge Segmente in 2-Sekunden-Blöcken, also meine 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>

Sie können dies mit ffprobe überprüfen:

ffprobe <SRC_FLE> -select_streams v -show_frames -of csv -show_entries frame=coded_picture_number,key_frame,pict_type > frames.csv

In der generierten CSV-Datei wird Ihnen in jeder Zeile Folgendes mitgeteilt 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

Das Ergebnis ist, dass Sie IDR-I-Frames nur in festen GOPSIZEAbständen sehen sollten, während alle anderen I-Frames Nicht-IDR-I-Frames sind, die nach Bedarf durch die Szenenschnitterkennung eingefügt werden.

Antwort4

Twitch hat einen Beitrag dazu. Sie erklären, dass sie sich aus mehreren Gründen für ihr eigenes Programm entschieden haben. Einer davon war, dass ffmpeg es nicht zulässt, verschiedene x264-Instanzen in unterschiedlichen Threads auszuführen, sondern stattdessen alle angegebenen Threads einem Frame in einer Ausgabe widmet, bevor mit der nächsten Ausgabe fortgefahren wird.

Wenn Sie kein Echtzeit-Streaming durchführen, haben Sie mehr Luxus. Die „richtige“ Methode besteht wahrscheinlich darin, in einer Auflösung nur mit der mit -g angegebenen GOP-Größe zu kodieren und dann die anderen Auflösungen zu kodieren, wobei Keyframes an denselben Stellen erzwungen werden.

Wenn Sie dies tun möchten, können Sie ffprobe verwenden, um die Keyframe-Zeiten abzurufen und diese dann mit einem Shell-Skript oder einer echten Programmiersprache in einen ffmpeg-Befehl umzuwandeln.

Aber für die meisten Inhalte gibt es kaum einen Unterschied zwischen einem Keyframe alle 5 Sekunden und zwei Keyframes alle 5 Sekunden (einen erzwungenen und einen vom Szenenschnitt). Dies ist ungefähr die durchschnittliche I-Frame-Größe im Vergleich zur Größe von P-Frames und B-Frames. Wenn Sie x264 mit typischen Einstellungen verwenden (der einzige Grund, warum Sie meiner Meinung nach etwas tun sollten, um diese zu beeinflussen, ist, wenn Sie -qmin festlegen, da dies eine schlechte Möglichkeit ist, x264 daran zu hindern, die Bitrate für einfache Inhalte zu verwenden; dies begrenzt alle Frame-Typen auf denselben Wert, glaube ich) und ein Ergebnis wie eine durchschnittliche I-Frame-Größe von 46 kB, P-Frame 24 kB, B-Frame 17 kB (halb so häufig wie P-Frames) erhalten, dann ist ein zusätzlicher I-Frame jede Sekunde bei 30 fps nur eine 3%ige Erhöhung der Dateigröße. Der Unterschied zwischen h264 und h263 besteht möglicherweise aus einer Reihe von 3%igen Verringerungen, aber eine einzelne ist nicht sehr wichtig.

Bei anderen Inhaltstypen sind die Framegrößen unterschiedlich. Fairerweise muss man sagen, dass es hier um zeitliche Komplexität und nicht um räumliche Komplexität geht, es geht also nicht nur um einfache Inhalte im Vergleich zu schwierigen Inhalten. Aber im Allgemeinen haben Streaming-Video-Sites eine Bitratenbeschränkung, und Inhalte mit relativ großen I-Frames sind einfache Inhalte, die in hoher Qualität codiert werden, egal wie viele zusätzliche Keyframes hinzugefügt werden. Das ist Verschwendung, aber diese Verschwendung wird normalerweise nicht bemerkt. Der verschwenderischste Fall ist wahrscheinlich ein Video, das nur ein statisches Bild zu einem Song ist, bei dem jeder Keyframe genau gleich ist.

Ich bin mir nicht sicher, wie erzwungene Keyframes mit dem mit -maxrate und -bufsize eingestellten Ratenbegrenzer interagieren. Ich glaube, selbst YouTube hatte in letzter Zeit Probleme, die Puffereinstellungen richtig zu konfigurieren, um eine gleichbleibende Qualität zu gewährleisten. Wenn Sie nur durchschnittliche Bitrate-Einstellungen verwenden, wie auf einigen Websites zu sehen ist (da Sie die Optionen von x264 im Header/MOV-Atom? mit einem Hex-Editor überprüfen können), ist das Puffermodell kein Problem, aber wenn Sie benutzergenerierte Inhalte bereitstellen, ermutigt die durchschnittliche Bitrate die Benutzer, am Ende ihres Videos einen schwarzen Bildschirm hinzuzufügen.

Die Option -g von Ffmpeg oder jede andere von Ihnen verwendete Encoderoption wird der encoderspezifischen Option zugeordnet. Daher ist „-x264-params keyint=GOPSIZE“ gleichbedeutend mit „-g GOPSIZE“.

Ein Problem bei der Verwendung der Szenenerkennung ist, wenn Sie aus irgendeinem Grund Keyframes in der Nähe bestimmter Zahlen bevorzugen. Wenn Sie alle 5 Sekunden Keyframes angeben und die Szenenerkennung verwenden und es bei 4,5 einen Szenenwechsel gibt, sollte dieser erkannt werden, aber dann wird der nächste Keyframe bei 9,5 sein. Wenn die Zeit so weiter erhöht wird, könnten Sie Keyframes bei 42,5, 47,5, 52,5 usw. erhalten, anstatt bei 40, 45, 50, 55. Umgekehrt, wenn es bei 5,5 einen Szenenwechsel gibt, wird es bei 5 einen Keyframe geben und 5,5 wird für einen weiteren zu früh sein. Ffmpeg lässt Sie nicht angeben „hier einen Keyframe erstellen, wenn es innerhalb der nächsten 30 Frames keinen Szenenwechsel gibt“. Jemand, der C versteht, könnte diese Option jedoch hinzufügen.

Bei Videos mit variabler Bildrate sollten Sie, wenn Sie nicht wie Twitch live streamen, Szenenwechsel verwenden können, ohne dauerhaft auf eine konstante Bildrate umzusteigen. Wenn Sie den Filter „select“ in ffmpeg verwenden und die Konstante „scene“ im Ausdruck verwenden, zeigt die Debug-Ausgabe (-v debug oder während der Codierung mehrmals „+“ drücken) die Nummer des Szenenwechsels an. Diese unterscheidet sich wahrscheinlich von der von x264 verwendeten Nummer und ist nicht so nützlich wie diese, könnte aber dennoch nützlich sein.

Das Verfahren wäre dann wahrscheinlich, ein Testvideo zu erstellen, das nur für Keyframe-Änderungen gedacht ist, aber vielleicht für Ratenkontrolldaten verwendet werden könnte, wenn 2-Pass verwendet wird. (Ich bin nicht sicher, ob die generierten Daten für verschiedene Auflösungen und Einstellungen überhaupt nützlich sind; die Makroblockbaumdaten werden es nicht sein.) Konvertieren Sie es in ein Video mit konstanter Framerate, aber sehen Siedieser Fehlerüber stotternde Ausgabe bei Halbierung der Framerate, falls Sie sich jemals entscheiden, den FPS-Filter für andere Zwecke zu verwenden. Führen Sie ihn mit den gewünschten Keyframe- und GOP-Einstellungen über x264 aus.

Verwenden Sie dann einfach diese Keyframe-Zeiten mit dem Originalvideo mit variabler Bildrate.

Wenn Sie völlig verrückte, benutzergenerierte Inhalte mit einer Lücke von 20 Sekunden zwischen den Frames zulassen, dann könnten Sie für die Kodierung mit variabler Framerate die Ausgabe aufteilen, einen FPS-Filter verwenden, irgendwie einen Auswahlfilter verwenden (vielleicht einen wirklich langen Ausdruck erstellen, der die Zeit für alle Keyframes enthält) ... oder Sie könnten das Testvideo als Eingabe verwenden und entweder nur Keyframes dekodieren, wenn diese ffmpeg-Option funktioniert, oder den Auswahlfilter verwenden, um Keyframes auszuwählen. Dann skalieren Sie es auf die richtige Größe (es gibt sogar einen Scale2Ref-Filter dafür) und überlagern Sie das Originalvideo. Dann verwenden Sie den Interleave-Filter, um diese erzwungenen Keyframes mit dem Originalvideo zu kombinieren. Wenn dies zu zwei Frames führt, die 0,001 Sekunden auseinander liegen und die der Interleave-Filter nicht verhindert, dann lösen Sie dieses Problem selbst mit einem anderen Auswahlfilter. Der Umgang mit den Frame-Puffergrenzen für den Interleave-Filter könnte hier das Hauptproblem sein. All dies könnte funktionieren: Verwenden Sie eine Art Filter, um den dichteren Stream zu puffern (FIFO-Filter?); Verweisen Sie mehrmals auf die Eingabedatei, damit sie mehr als einmal dekodiert wird und Frames nicht gespeichert werden müssen. Verwenden Sie den Filter „Streamselect“, was ich noch nie getan habe, genau zu den Zeitpunkten der Keyframes. Verbessern Sie den Interleave-Filter, indem Sie sein Standardverhalten ändern oder eine Option hinzufügen, um den ältesten Frame in einem Puffer auszugeben, anstatt einen Frame zu löschen.

verwandte Informationen