
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 scenecut
Keyframe 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 -g
Option unterschieden, es wird jedoch nicht wirklich gesagt, inwiefern sich die beiden oben genannten Optionen im Geringsten unterscheiden (offensichtlich -g
ist 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 -g
nicht ohne die Erzwingung einer festen Bildrate funktionieren könnte ( -r
), da es keine Garantie dafür gibt, dass mehrere Durchläufe ffmpeg
mit 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 keyint
Methode 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_frames
Methode
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 X
das Intervall in Frames und N
das 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 libx264
und libx265
in libvpx-vp9
FFmpeg herausgefunden:
libx264
:-g
legt das Keyframe-Intervall fest.-keyint_min
legt 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
x264
Code zu sehen: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 festNotiz:Aufgrund der Funktionsweise von FFmpeg
-keyint_min
wird nur dann an den Encoder weitergeleitet, wenn es mit identisch ist-g
. Im Code vonlibvpxenc.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 könnte ein Fehler sein (oder ein Mangel an Funktion?), da
libvpx
definitiv das Festlegen eines anderen Werts für unterstützt wirdkf_min_dist
.
Sollten Sie verwenden -force_key_frames
?
Die -force_key_frames
Option 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:
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.
-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, dassmin-keyint
im 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ürmin-keyint
beim 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 keyint
die gewünschte GOP-Größe verdoppeln musste. Das bedeutet, dass min-keyint
ich 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 GOPSIZE
Abstä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.