Das Ergebnis sah zunächst nach einem Wechsel auf einen neueren Codec aus: Meine Videodateien wurden um ein Mehrfaches kleiner, während die normale Wiedergabe weiterhin gut aussah und ich bei üblichen Darstellungsgrößen keinen offensichtlichen Qualitätsverlust erkennen konnte.
Dabei war ich weder auf AV1 noch auf HEVC oder VP9 gewechselt. Ich verwendete weiterhin H.264 in einem MP4-Container.
Geändert hatte sich alles rund um den Codec. Ich baute die Encoding-Policy für einen konkreten Workload neu auf: kurze illustrierte und animierte Clips, ein stark mobiler Traffic-Mix, Bandbreite als wiederkehrender Kostenfaktor und praktisch keine Anforderung an die Dauer eines einmaligen Offline-Encodes.
Mein Baseline-Profil wurde deshalb bewusst konservativ für die Wiedergabe und bewusst teuer beim Encoding: H.264 Main Profile @ Level 3.1, avc1, 8-bit yuv420p, CRF 28, x264 veryslow, eine Auflösungsgrenze ungefähr auf 720p-Niveau, nur sinnvolle Bildraten in der Regel bis 30 fps, begrenzte Referenz- und B-Frames sowie faststart für progressive MP4-Auslieferung.
Die wichtigste Optimierung war kein FFmpeg-Schalter
Der entscheidende Wechsel betraf meine Kostenrechnung.
Encoding passiert einmal. Die Übertragung der Datei passiert bei jedem Abruf.
Bei Echtzeitvideo kann es ein schlechter Tausch sein, viel mehr CPU für etwas weniger Bitrate zu verwenden. Meine Dateien werden dagegen offline codiert und anschließend immer wieder ausgeliefert. In diesem Modell ist es wirtschaftlich fast egal, beim Encoding zehn Minuten zu sparen, wenn der schnellere Encode jeden späteren Abruf vergrößert.
Deshalb ergibt -preset veryslow für mich Sinn. Ich bin bereit, einmal CPU zu bezahlen, wenn x264 dadurch eine effizientere Darstellung findet. Der Browser wiederholt diese aufwendige Suche nicht; er decodiert nur den fertigen Bitstream.
Meine Regel wurde: Rechenzeit beim einmaligen Schritt großzügig einsetzen und bei den wiederkehrenden Bytes geizig sein.
Warum ich bei H.264 blieb, statt dem neuesten Codec hinterherzulaufen
Ich behaupte nicht, dass H.264 heute die beste Kompression bietet. Neuere Codecs können sehr attraktiv sein, wenn die Delivery-Architektur mehrere Renditions vorhalten und pro Client auswählen kann.
Meine Anforderung war eine andere: eine URL, eine Datei, ein Codec und möglichst wenig Wiedergabeprobleme bei einem stark mobilen Publikum.
Für diesen Zweck bleibt H.264 in MP4 ein sehr sicherer Baseline. Apple empfiehlt Webentwicklern aktuell H.264-codierte MP4-Dateien für statische Videos in Safari. Android listet H.264 in MP4 und verlangt ab Android 6.0 einen Main-Profile-Decoder; als HD-H.264-Konfiguration nennt die Dokumentation außerdem 1280×720 bei 30 fps. Siehe Androids unterstützte Medienformate.
Das bedeutet nicht, dass moderne Geräte auf Main Profile oder Level 3.1 beschränkt wären. Apple bevorzugt für HLS beispielsweise grundsätzlich High Profile gegenüber Main oder Baseline. Ich verwende Main@3.1, weil ich für eine einzelne statische MP4-Datei bewusst bescheidene Decoder-Anforderungen wollte – nicht weil Apple dieses Profil verlangt.
Ich hörte auf, Pixel zu codieren, die ich nicht brauchte
Die Auflösung war einer der größten Hebel. Meine Obergrenze liegt ungefähr bei 1280×720 im Querformat, 720×1280 im Hochformat und etwa 960×960 für quadratisches oder gemischtes Material.
Wichtiger ist die Regel: nie nur deshalb hochskalieren, um diese Obergrenze zu erreichen.
Eine 900×600-Quelle auf 1280×720 zu vergrößern, erzeugt keine neuen Details. Sie erzeugt lediglich mehr Samples, die der Encoder beschreiben muss. 1920×1080 kann ich auf 720p-Klasse reduzieren; 900×600 darf ungefähr 900×600 bleiben. Die Grenze ist ein Maximum, kein Ziel.
Das klingt banal, kann aber mehr sparen als viele exotische Codec-Tweaks.
Ich bezahle nicht mehr für Frames, die in der Quelle kaum existieren
Auch die Bildrate multipliziert den Aufwand. Wenn eine Animation ungefähr 16 tatsächlich unterschiedliche visuelle Zustände pro Sekunde besitzt, macht eine Speicherung mit 30 oder 60 fps die Bewegung nicht automatisch besser. Häufig entstehen vor allem wiederholte oder synthetisierte Zeit-Samples, die trotzdem codiert werden müssen.
Meine Policy ist, die sinnvolle natürliche Kadenz beizubehalten und normalerweise nicht über 30 fps zu gehen. Bei diesem Material können 12, 15, 16, 18, 20, 24, 25 oder 30 fps sinnvoll sein, wenn sie die Quelle tatsächlich beschreiben.
Für die erzeugte Ausgabe bevorzuge ich zudem eine saubere konstante Bildrate. VFR ist nicht grundsätzlich fehlerhaft; CFR vereinfacht in meinem Pipeline lediglich Zeitstempel, Framezahlen, Dauerprüfungen, Seeking und spätere Validierung.
Das allgemeine Prinzip ist wichtiger als eine konkrete Zahl: keine Bandbreite für zeitliche Information bezahlen, die in der Quelle nicht vorhanden ist.
CRF 28 ist eine Workload-Entscheidung, keine magische Zahl
Ich wollte nicht jeden Clip auf dieselbe Zielbitrate zwingen. Eine nahezu statische Illustration und eine Szene mit komplexer Bewegung benötigen unterschiedlich viele Bits, um akzeptabel auszusehen.
Deshalb nutze ich den CRF-Modus von x264 und landete für diesen bandbreitenorientierten illustrierten Workload ungefähr bei -crf 28. FFmpeg dokumentiert CRF bei libx264 als Constant-Quality-Rate-Control; siehe die FFmpeg-Codec-Dokumentation.
CRF 28 ist bewusst aggressiv. Ich würde diesen Wert nicht blind auf Filmkorn, verrauschtes Kameramaterial, winzigen Text in Screen Recordings oder einen Workload übertragen, bei dem Bildtreue wichtiger als Bandbreite ist.
Ich habe auch keinen universellen Wahrnehmungswert, der beweist, dass CRF 28 transparent ist. Für meinen Workload kann ich enger formulieren: Die Dateien wurden deutlich kleiner und sahen bei normaler Wiedergabe für mich weiterhin normal aus. Das ist eine praktische Beobachtung, keine Behauptung visueller Verlustfreiheit.
Veryslow ist teuer für den Encoder, nicht automatisch für den Decoder
Mein Preset ist -preset veryslow. Ein langsameres Preset gibt x264 mehr Gelegenheit, effizientere Vorhersage- und Coding-Entscheidungen zu suchen. Der Preis ist Encoding-Zeit und CPU.
Wichtig ist: Encoder-Aufwand und Decoder-Komplexität sind nicht dasselbe.
Ich kann x264 sehr lange suchen lassen und den fertigen Stream trotzdem separat begrenzen. Mein konservativer Output-Vertrag lautet:
H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled
FFmpeg stellt CRF, Presets, Tuning, Profilbeschränkungen, Referenzframes und B-Frames getrennt bereit. Genau so behandle ich sie: Der Encoder darf aufwendig suchen, die Wiedergabeseite soll gewöhnlich bleiben.
GOP und VBV sind Leitplanken, nicht die eigentliche Qualitätssteuerung
Für diese kurzen progressiven Clips verwende ich einen maximalen GOP von ungefähr fünf Sekunden: etwa -g 150 bei 30 fps, -g 120 bei 24 fps oder -g 80 bei 16 fps.
Das ist eine Entscheidung für meinen Workload, keine allgemeine Regel. Adaptive Streaming hat andere Anforderungen; Apple empfiehlt für HLS beispielsweise IDRs alle zwei Sekunden. Diese HLS-Regel übernehme ich nicht blind für kurze statische progressive MP4-Dateien.
Zusätzlich nutze ich ungefähr:
-maxrate:v 4M
-bufsize:v 8M
Das sind Obergrenzen gegen ungewöhnliche Bitrate-Spitzen. Sie bedeuten nicht „alles mit 4 Mbps codieren“. CRF steuert weiterhin die normale Bitverteilung, daher dürfen einfache Clips sehr klein werden.
Auch den MP4-Container habe ich langweilig gemacht
Ich verwende ausdrücklich avc1. Apples aktuelle HLS-Dokumentation empfiehlt Sample-Formate wie avc1 statt avc3. Das verkleinert die Datei nicht wesentlich, passt aber zum Ziel eines konventionellen H.264-in-MP4.
Außerdem nutze ich -movflags +faststart. Laut FFmpeg-Formatdokumentation verschiebt faststart den MP4-Index moov an den Dateianfang. Android verlangt für HTTP-Streaming bei MPEG-4 ebenfalls, dass moov nach ftyp, aber vor mdat liegt.
ftyp
moov
mdat
Faststart verbessert nicht die Kompression. Es macht progressive HTTP-Wiedergabe unkomplizierter.
Für normales SDR nutze ich 8-bit yuv420p und signalisiere BT.709 mit Limited/Video Range. Wenn ein Clip keinen Ton braucht, erzeuge ich keinen Audiotrack. Für illustriertes Material nutze ich zusätzlich -tune animation; das ist für mich eine Content-spezifische Wahl und kein Teil des universellen Kompatibilitätsvertrags.
Das zentrale FFmpeg-Profil
Für eine illustrierte 30-fps-Quelle sieht der Kern des Befehls ungefähr so aus:
ffmpeg -i input \
-c:v libx264 \
-preset veryslow \
-tune animation \
-crf 28 \
-profile:v main \
-level:v 3.1 \
-pix_fmt yuv420p \
-tag:v avc1 \
-refs 4 \
-bf 5 \
-g 150 \
-maxrate:v 4M \
-bufsize:v 8M \
-x264-params "open-gop=0:b-pyramid=normal:nal-hrd=none" \
-color_range tv \
-color_primaries bt709 \
-color_trc bt709 \
-colorspace bt709 \
-an \
-movflags +faststart \
output.mp4
Scaling und Bildrate sind hier absichtlich nicht fest verdrahtet. Eine 900×600-Quelle sollte nicht nur deshalb vergrößert werden, weil die Obergrenze 1280×720 beträgt, und eine Animation mit natürlich niedriger Bildrate sollte nicht auf 30 fps gezwungen werden, nur weil das Beispiel -g 150 verwendet.
Der Befehl implementiert die Policy; er ist nicht selbst die Policy.
Warum die Dateien um ein Mehrfaches kleiner wurden
Es gab keinen Wunder-Schalter.
Die Einsparung entstand durch mehrere Entscheidungen, die jeweils eine andere Art von Verschwendung entfernten: unnötige Pixel, unnötige Frames, starres Bitrate-Denken, billige Encoder-Einstellungen, zu häufige Keyframes und Streams, die ich nicht brauchte.
Darum sagt „die Datei ist H.264“ erstaunlich wenig über ihre Größe. Zwei H.264-Encodes derselben Quelle können stark voneinander abweichen, weil der Codecname weder Auflösung noch Bildrate, Rate Control, Preset, GOP-Struktur, Profil oder Quellenaufbereitung beschreibt.
In meinem Fall waren diese Entscheidungen rund um den Codec wichtiger als ein Codecwechsel.
Was dieses Ergebnis nicht beweist
Ich habe nicht jede Einstellung in einem isolierten kontrollierten Experiment getestet. Deshalb kann ich keinen ehrlichen exakten Anteil der Einsparung veryslow, CRF 28, der geringeren Auflösung oder der reduzierten Bildrate einzeln zuschreiben.
Ich kann auch nicht behaupten, dass jeder CRF-28-Output wahrnehmbar transparent ist. „Kein offensichtlicher Qualitätsverlust“ ist meine Beobachtung für diesen illustrierten Workload bei normalen Darstellungsgrößen, keine wissenschaftliche Garantie für beliebiges Video.
Und ich behaupte nicht, dass eine einzelne H.264-Datei für jede Website die richtige Architektur ist. Mehrere Renditions, Adaptive Streaming, HDR, 4K und Codec-Negotiation verändern die Abwägungen.
Das Ergebnis ist enger und nützlicher: Für eine bandbreitenorientierte, mobile-heavy Bibliothek kurzer illustrierter und animierter Clips, bei der Encoding-Zeit billig und vorhersehbare Wiedergabe wichtig ist, machte dieses Profil meine Dateien um ein Mehrfaches kleiner, ohne dass die normale Wiedergabe sichtbar litt.
Die Regel, die ich heute verwende
Früher betrachtete ich Videooptimierung hauptsächlich als Problem der Encoder-Einstellungen. Heute betrachte ich sie als Lebenszyklus-Kostenproblem.
Der Encoder läuft vielleicht einmal. Die Bytes können tausend- oder millionenfach durchs Netz gehen.
Damit bekommt „teuer“ eine andere Bedeutung.
Ich gebe gerne einmal CPU aus. Ich bin viel weniger bereit, bei jedem zukünftigen Abruf hochskalierte Pixel, Frames ohne zusätzliche nützliche Bewegung oder unnötige Bitrate zu übertragen.
Der Codec blieb langweilig: H.264 in MP4. Die Optimierung fand darum herum statt.
Für meinen Workload ist die wichtigste Regel klarer als jeder einzelne FFmpeg-Schalter: Optimiere die Kosten, die du immer wieder bezahlst, nicht die, die du einmal bezahlst.