wake-up-neo.com

Was ist der richtige Weg, um Keyframes in FFmpeg für DASH zu reparieren?

Wenn Sie einen Stream für die DASH-Wiedergabe aufbereiten, müssen sich die Direktzugriffspunkte in allen Streams zur exakt gleichen Quellstreamzeit befinden. Der übliche Weg, dies zu tun, besteht darin, eine feste Bildrate und eine feste GOP-Länge (d. H. Ein Schlüsselbild alle N Bilder) zu erzwingen.

Bei FFmpeg ist die feste Bildrate einfach (-r NUMBER).

Aber für feste Keyframe-Positionen (GOP-Länge) gibt es drei Methoden ... welche ist "korrekt"? 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 Scenecut deaktiviert werden soll oder nicht, da unklar ist, ob der Keyframe- "Zähler" neu gestartet wird, wenn ein Szenenschnitt stattfindet.

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

-g GOP_LEN_IN_FRAMES

Dies ist leider nur beiläufig in der FFMPEG-Dokumentation dokumentiert, und daher ist die Wirkung dieses Arguments sehr unklar.

Methode 3: Fügen Sie alle N Sekunden einen Keyframe ein (Vielleicht?):

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

Dieses is ist explizit dokumentiert. Es ist aber immer noch nicht sofort klar, ob der "Zeitzähler" nach jedem Schlüsselbild neu startet. Wenn beispielsweise in einer erwarteten 5-Sekunden-GOP ein Keyframe scenecut3 Sekunden von libx264 eingefügt wird, ist der nächste Keyframe 5 Sekunden später oder 2 Sekunden später?

In der FFmpeg-Dokumentation wird zwischen dieser und der -g-Option unterschieden, aber es wird nicht wirklich angegeben, wie diese beiden Optionen sich am wenigsten unterscheiden (offensichtlich erfordert -g eine feste Bildrate).

Welches ist richtig?

Der -force_key_frames scheint überlegen zu sein , da keine feste Framerate erforderlich ist. Dies setzt jedoch voraus, dass

  • es entspricht den GOP-Spezifikationen in H.264 (falls vorhanden)
  • es GARANTIERT, dass es einen Keyframe in fester Trittfrequenz gibt, unabhängig von libx264 scenecutname__-Keyframes.

Es scheint auch, dass -g nicht funktionieren könnte, ohne eine feste Bildrate (-r) zu erzwingen , da nicht garantiert werden kann, dass mehrere Durchläufe von 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!).

Schließlich scheint die Methode keyintnur ein Hack zu sein . Ich hoffe gegen die Hoffnung, dass dies nicht die richtige Antwort ist.

Verweise:

Ein Beispiel mit der -force_key_frames Methode

Ein Beispiel mit der Methode keyintname__

FFmpeg erweiterte Videooptionen Abschnitt

32

Die Antwort scheint daher zu sein:

  • Methode 1 ist funktionsfähig, aber libx264spezifisch und geht zu Lasten der Eliminierung der sehr nützlichen Option scenecut in libx264.
  • Methode 3 funktioniert ab der FFMPEG-Version von April 2015, Sie sollten Ihre Ergebnisse jedoch anhand des Skripts überprüfen, das am Ende dieses Beitrags enthalten ist, da in der FFMPEG-Dokumentation die Auswirkung der Option unklar ist. Wenn es funktioniert, ist es die überlegene der beiden Optionen.
  • NICHT VERWENDEN Methode 2, -g scheint veraltet zu sein. Es scheint weder zu funktionieren, noch ist es explizit in der Dokumentation definiert, noch ist es in der Hilfe zu finden, noch scheint es im Code verwendet zu werden. Die Überprüfung des Codes zeigt, dass die Option -g wahrscheinlich für MPEG-2-Streams gedacht ist (es gibt sogar Codezeilen, die sich auf PAL und NTSC beziehen!).

Ebenfalls:

  • Dateien, die mit Methode 3 erstellt wurden, sind möglicherweise etwas größer als mit Methode 1, da Interstitial-I-Frames (Keyframes) zulässig sind.
  • Sie sollten in beiden Fällen explizit das Flag "-r" setzen, obwohl Methode 3 einen I-Frame am nächsten Frameslot platziert oder nach der angegebenen Zeit. Wenn Sie das Flag "-r" nicht setzen, sind Sie der Quelldatei ausgeliefert, möglicherweise mit einer variablen Bildrate. Inkompatible DASH-Übergänge können die Folge sein.
  • Trotz der Warnungen in der FFMPEG-Dokumentation ist Methode 3NICHTweniger effizient als andere. Tatsächlich zeigen Tests, dass es möglicherweise etwas effizienter als Methode 1 ist.

Skript für die Option -force_key_frames

Hier ist ein kurzes Perl-Programm, mit dem ich die I-Frame-Trittfrequenz basierend auf der Ausgabe von slhcks ffprobe-Vorschlag überprüft habe. Es scheint zu überprüfen, ob die -force_key_frames-Methode auch funktioniert, und hat den zusätzlichen Vorteil, dass scenecut-Frames zulässig sind. Ich habe absolut keine Ahnung, wie FFMPEG diese Arbeit macht, oder ob ich irgendwie Glück gehabt habe, weil meine Streams zufällig gut konditioniert sind.

In meinem Fall habe ich mit 30 fps mit einer erwarteten GOP-Größe von 6 Sekunden oder 180 Bildern codiert. Ich habe 180 als gopsize-Argument für dieses Programm verwendet und bei jedem Vielfachen von 180 ein I-Frame verifiziert. Wenn ich es jedoch auf 181 (oder eine andere Zahl, die kein Vielfaches von 180 ist) einstelle, beschwert sich das Programm.

#!/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

Ich würde folgendes empfehlen:

  • libx264: -g X -keyint_min X (und optional -force_key_frames "expr:gte(t,n_forced*N)" hinzufügen)
  • libx265: -x265-params "keyint=X:min-keyint=X"
  • libvpx-vp9: -g X

dabei ist X das Intervall in Frames und N das Intervall in Sekunden. Beispiel: Für ein 2-Sekunden-Intervall mit einem 30-fps-Video ist X = 60 und N = 2.

Ein Hinweis zu verschiedenen Rahmentypen

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

  • IDR-Frames (Instantaneous Decoder Refresh): Ermöglichen das unabhängige Decodieren der folgenden Frames, ohne auf Frames vor dem IDR-Frame zuzugreifen.
  • Non-IDR-Frames: Diese benötigen einen vorherigen IDR-Frame, damit die Dekodierung funktioniert. Nicht-IDR-Frames können für Szenenschnitte in der Mitte einer GOP (Gruppe von Bildern) verwendet werden.

Was wird für das Streaming empfohlen?

Für den 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 Codierungseffizienzqualitä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), dies ist jedoch nicht erforderlich.

Was machen die Parameter?

Um den Encoder konfigurieren zu können, müssen wir verstehen, was die Keyframe-Parameter bewirken. Ich habe einige Tests durchgeführt und Folgendes für die drei Encoder libx264, libx265 und libvpx-vp9 in FFmpeg entdeckt:

  • libx264:

    • -g legt das Keyframe-Intervall fest.
    • -keyint_min legt das minimale Keyframe-Intervall fest.
    • -x264-params "keyint=x:min-keyint=y" ist dasselbe wie -g x -keyint_min y.
    • / - Hinweis: Wenn beide auf den gleichen Wert gesetzt werden, wird das Minimum intern auf halb das maximale Intervall plus eins gesetzt, wie im Code x264 zu sehen ist:

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

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

    • -g legt das Keyframe-Intervall fest.
    • -keyint_min legt das minimale Keyframe-Intervall fest
    • Hinweis: Aufgrund der Funktionsweise von FFmpeg wird -keyint_min nur an den Encoder weitergeleitet, wenn er mit -g identisch ist. Im Code von libvpxenc.c in 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 kann ein Fehler sein (oder ein Mangel an Funktionen?), Da libvpx definitiv das Festlegen eines anderen Werts für kf_min_dist unterstützt.

Solltest du -force_key_frames verwenden?

Mit der Option -force_key_frames werden im angegebenen Intervall (Ausdruck) zwangsweise Keyframes eingefügt. Dies funktioniert bei allen Encodern, kann jedoch zu Problemen mit der Geschwindigkeitskontrolle führen. Insbesondere bei VP9 sind mir starke Qualitätsschwankungen aufgefallen, daher kann ich die Verwendung in diesem Fall nicht empfehlen.

23
slhck

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

Methode 1:

mit den Argumenten von libx264 herumspielen

-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

Generiere iframes wie erwartet so:

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 abgeschrieben. Weggelassen

Methode 3:

füge alle N Sekunden einen Keyframe ein (MÖGLICHERWEISE):

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

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 ein iframes auf eine etwas andere Art:

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 IFrames UND auf Scenecut (Sekunden mit schwebendem Teil) platziert, was meiner Meinung nach für die Komplexität des Videostreams wichtig ist.

Genearated Dateigrößen sind ziemlich gleich. Sehr seltsam, dass selbst mit mehr Keyframes in Methode 3 manchmal weniger Dateien erzeugt werden als mit dem normalen x264-Bibliotheksalgorithmus.

Um mehrere Bitrate-Dateien für den HLS-Stream zu generieren, wählen wir Methode drei. Es ist perfekt ausgerichtet mit 2 Sekunden zwischen den Chunks, sie haben Iframe am Anfang jedes Chunks und sie haben zusätzliche Iframes in komplexen Szenen, was eine bessere Erfahrung für Benutzer bietet, die veraltete Geräte haben und keine x264-High-Profile wiedergeben können.

Hoffe es hilft jemandem.

11
Ara Saahov

Ich wollte ein paar Informationen hier hinzufügen, da meine googeln diese Diskussion ganz infos ein bisschen in meiner Suche zu finden nach oben gezogen zu versuchen, einen Weg zu segmentieren meine DASH kodiert, wie ich wollte, und keine der Informationen, die ich war völlig richtig gefunden zu finden.

Zuerst einige Missverständnisse, die beseitigt werden müssen:

  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 als "Keyframes" bezeichnet) erzeugen eine neue GOP. Die Nicht-IDR-Frames werden nicht. Sie sind praktisch, um sich in einer GOP zu befinden, in der sich die Szene ändert.

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE ← Dies entspricht nicht Ihren Vorstellungen. Ich brauchte eine Weile, um das herauszufinden. Es stellt sich heraus, dass der min-keyint im Code begrenzt ist. Es darf nicht größer als (keyint / 2) + 1 sein. Wenn Sie diesen beiden Variablen denselben Wert zuweisen, wird der Wert für min-keyint beim Codieren um die Hälfte reduziert.

Hier ist die Sache: Szenenschnitt ist wirklich großartig, besonders in Videos mit schnellen harten Schnitten. Es bleibt schön und knackig, sodass ich es nicht deaktivieren möchte, aber gleichzeitig konnte ich keine feste GOP-Größe erhalten, solange es aktiviert war. Ich wollte den Szenenschnitt aktivieren, aber nur Nicht-IDR-I-Frames verwenden. Aber es hat nicht funktioniert. Bis ich (aus vielen Lektüren) herausgefunden habe, dass ich falsch gedacht habe # 2.

Es stellte sich heraus, dass ich keyint einstellen musste, um meine gewünschte GOP-Größe zu verdoppeln. Dies bedeutet, dass min-keyint kann meine gewünschten GOP-Größe eingestellt werden (ohne den internen Code in Halbschnitt), der Szene-Cut- Erkennungs verwenden IDR I-Frames innerhalb der GOP-Größe verhindert, weil der Rahmen seit dem letzten IDR zählt I-Frame ist immer kleiner als min-keyinit.

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

Ich bevorzuge Segmente in 2 Sekundenblöcken, daher ist 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 mit ffprobe Folgendes ü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 in jeder Zeile Folgendes angegeben: 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 festgelegten GOPSIZE-Intervallen sehen sollten, während alle anderen I-Frames Nicht-IDR-I-Frames sind, die bei Bedarf von der Szenenschnitterkennung eingefügt werden.

4
Reuben

Es scheint, dass diese Syntax nicht immer funktioniert. Ich habe ziemlich viel auf unseren VOD-Inhalten sowie auf Live-Inhalten (Datei-Dumps) getestet und manchmal funktioniert scenecut nicht und löst einen Zwischen-Iframe aus:

Syntax für eine i50-> p50-Aufwärtskonvertierung, 2 Sek. Gop/Segment, IDR am Anfang, iFrames dazwischen, falls erforderlich

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