mumpel schrieb:
Ich verstehe nicht ganz, warum nicht im ersten Schritt parallel alle B-Frames berechnet werden, danach parallel alle B- und I-Frames zwischen den B-Frames seriell?
Wie soll das gehen? Du kannst die B-Frames nicht berechnen, wenn du VORHER nicht die Makroblöcke genau DER Referenz- (P- und I-) Frames berechnet hast, auf die sich diese B-Frames beziehen. Nehmen wir mal folgendes Beispiel:
Das Frame B2 bezieht sich dabei auf P1 und P2 als Referenz. P1 und P2 wiederum werden sich auf das allererste I-Frame beziehen. Das hieße: ZUERST muss das I-Frame encodet werden, dann erst P1 und P2 und ERST DANACH existieren die Informationen um B2 zu erzeugen.
In der Realität ist das alles noch einen Tick komplizierter, da P2 auch noch Bildinhalte aus B3 übernehmen kann, weshalb vorher eventuell noch B3 berechnet werden muss.
Die Komplexität steigt aber, wenn auch B-Frames als Referenz genutzt werden können. In diesem Beispiel:
... benutzt das Frame B2 (neben P1 und P2) auch B1 als Referenz. Sprich: es müssen nicht nur P1 und P2 berechnet werden, BEVOR eine Berechnung von B2 möglich ist, sondern es müssen zuerst P1, P2 UND B1 berechnet werden, bevor die Informationen zur Berechnung von B2 vorliegen.
Und das sind alles extrem simple Beispiele mit 2 bzw. 3 Referenzen. Stell dir das ganze mal mit 8 oder 16 Referenzen vor. Das wird dann seeeeehr verschachtelt
.
Und falls du I- und B-Frames in deiner Frage verwechselt haben solltest: auch ALLE I-Frames im voraus zu berechnen geht nicht. Zumindest nicht, ohne gigantische Mengen an RAM zu verbrauchen.
mumpel schrieb:
Andererseits: Würde das Encoding aus so vielen B-Frames für möglich bestehen, würde die Dateigröße zwar größer werden, die Qualität aber steigen. Warum bieten die GPU-Encoder dann aber nur so schlechte Ergebnisse an?
Hast du mal gesehen, um wie viel größer eine Datei wird, die NUR aus I-Frames besteht??? Mal ein kleines Rechenbeispiel mit MJPEG (das ja nur I-Frames erzeugt):
Ein unkomprimiertes YV12-Video in 1920x1080 Pixeln und 30 Minuten Länge bei 25fps ergäbe:
1920 (Pixel) x 1080 (Pixel) x 12 bit (YV12) = 24.883.200 Bit =
3.110.400 Byte (je Einzelbild)
30 Minuten = 1.800 Sekunden
1.800 Sekunden x 25 (Bilder je Sekunde) =
45.000 (Einzelbilder gesamt)
45.000 (Einzelbilder gesamt) x 3.110.400 Byte (je Einzelbild) = 139.968.000.000 Byte =
130,36 Gigabyte
MJPEG komprimiert in guter (nicht bester Qualität!) etwa im Verhältnis 1:7. Das ergäbe ca. 18,62 Gigabyte für
NUR 30 Minuten FullHD-Video! Und die Qualität wäre zudem deutlich SCHLECHTER als bei H.264, das ca. 80% effektiver komprimiert!
Die relativ geringen Dateigrößen bei der Verwendung von H.264 werden nämlich
GERADE durch die Verwendung von P- und B-Frames und multiple Referenzen erreicht. Nimmst du das weg, bleibst du auf vergleichsweise riesigen Dateien sitzen - und gerade die will man ja bei der Videokompression für Endformate vermeiden.
mumpel schrieb:
Wieso wird die Qualität mit steigender Parallelisierung schlechter?
Siehe hier:
http://forum.doom9.org/showthread.php?p=881706
akupenguin schrieb:
So video compression is naturally serial, and you have to make some approximations if you want to split it into many threads.
The "approximation" I refer to is that the encoder (either with support from the standard, or just as an implementation decision) must choose to ignore some possible redundancies, in order to independently process some pieces of the encoding task that could have depended on eachother. If you don't exploit that redundancy, then your compression algorithm doesn't compress quite as well as the original serial one. And it is no coincidence that the same features help both error resilience and threading: Error resilience is also about adding a controlled amount of redundancy back into the compressed stream.
Different threading models use different pieces of redundancy:
- x264's slice-based threading fails to exploit some spatial redundancy between blocks within one frame.
- xvid's mb-row threading is lossless, but only works because mpeg4asp's entropy coder fails to adapt its probability model to the observed data.
- my proposed sliceless threading method depends on the fact that a given macroblock is usually predicted only from a nearby block in the previous frame, or a neighbor in the current frame. If typical motion vectors were large, then sliceless threading would also waste bits. It also depends on the fact that h264's entropy coder doesn't adapt to anything beyond the current frame.
- B-frame threading depends on the fact that (most) B-frames are not used in inter-prediction. It only works in sections of the video that use B-frames, and doesn't scale beyond about 2 threads.
- GOP-level threading is lossless, but depends on the fact that you already have to spend bits on I-frames in order to get fast seeking and some minimal error resilience. It also takes a rediculously large amount of RAM if you want to fit it into a standard stream-like API.
- ELDER's threading has a small impact on the precision of ratecontrol, and has to choose between extra I-frames (wasted bits), or a little duplicate work (wasted time). It also doesn't fit at all into a stream-like API.
- Sub-macroblock threading might be possible if thread synchronization were fast enough. But it would duplicate quite a bit of work, since it would bypass some of x264's early termination conditions. And it wouldn't scale beyond 2-4 threads.
Frei übersetzt (ich versuch's mal):
Videokompression ist also von Natur aus seriell, und man muss daher einige Angleichungen vornehmen, wenn man sie in mehrere Threads aufteilen will.
Mit "Angleichungen" meine ich, dass der Encoder (entweder als Standard-Unterstützung oder als alternative Implementierung) einige mögliche Redundanzen ignorieren muss, um unabhängig Einzelteile der Encoding-Aufgabe durchzuführen, die voneinander abhängig sein könnten. Wenn du diese Redundanzen nicht beseitigen kannst, dann komprimiert dein Kompressionsalgorithmus nicht so gut, wie der serielle. Und es ist auch kein Zufall, dass die selben Features beidem, Fehlertoleranz und Thread-Aufteilung, helfen: denn auch bei der Fehlertoleranz geht es darum, eine kontrollierte Menge an redundanten Daten in den komprimierten Stream zurück zu führen.
Verschiedene Threading-Modelle haben verschiedene Redundanzen:
- X264s slice-based Threading liefert räumliche Redundanz zwischen Blöcken innerhalb eines Frames.
- Xvids MB-Row Threading ist verlustlos, aber funktioniert nur, weil der MPEG-4 ASP Entropie-Encoder sein Wahrscheinlichkeitsmodel nicht an die beobachteten Daten anpassen kann.
- Meine vorgeschlagene Sliceless-Threading-Methode basiert darauf, dass ein Makroblock üblicherweise nur basierend auf einem nächstgelegenen Makroblock im vorherigen Frame, oder einem Nachbar im gegenwärtigen Frame vorherbestimmt wird. Wenn die typischen Bewegungsvektoren zu groß wären, dann würde Sliceless-Threading ebenfalls Bits verschwenden. Es beruht ebenfalls auf der Tatsache, dass der H.264-Entropie-Encoder sich an nichts außerhalb des gegenwärtigen Frames anpasst.
- B-Frame-Threading beruht auf der Tatsache, dass (die meisten) B-Frames nicht für Interframe-Vorhersagen genutzt werden. Es funktioniert nur bei den Teilen des Videos, welche B-Frames benutzen, und skaliert nicht über 2 Threads hinaus.
- GOP-Level Threading ist verlustlos, aber beruht auf der Tatsache, dass du schon einige Bits für I-Frames verschwendest, damit ein schneller Suchlauf und eine gute Fehlertoleranz möglich ist. Es benötigt ebenfalls eine riesige Menge RAM, wenn du es in einem Standard-Stream-Anwendungsinterface umsetzen willst.
- ELDERs Threading hat einen geringen Einfluss auf die Präzision der Datenratenkontrolle und muss zwischen extra I-Frames (verschwendete Bits) oder teilweise doppelter Arbeit (verschwendete Zeit) wählen. Es passt außerdem in kein Stream-Anwendungsinterface.
- Sub-Makroblock-Threading wäre eventuell möglich, wenn die Thread-Synchronisierung schnell genug wäre. Aber es würde diverse Arbeitsschritte verdoppeln, da es einige von x264s frühen Abbruchbedingungen umgeht. Und es würde nicht über 2-4 Threads hinaus skalieren können.