YUV420planar zu BGR Konvertierung ist kritisch langsam
Verfasst: 20.06.2019, 10:24
Hallo Freunde der IT!
Heute brauche ich Hilfe/Rat von denen von Euch, die sich mit Code-Optimierung auskennen.
Ich habe in den letzten Wochen einen rudimentären Video-Player geschrieben (libvpx, libwebm, libopus). Die Videocodecs spucken am Ende YUV420planar Bilder aus. Soweit, so gut.
Um am Ende in meinem RGB/BGR basierten „Universum“ klarzukommen, mache ich eine Transformation von YU420planar zu BGR (8 bit/channel) auf der CPU.
Mein Testvideo ist ein Clip mit 720p.
Je nach CPU ist es unmöglich, eine flüssige Video-Wiedergabe zu bekommen – nicht, weil der lahme VP8/VP9 so limitieren, sondern weil ich die YUV-BGR-Konvertierung nicht schnell genug hinbekomme.
Auf meinen schnelleren Test-Rechnern (i7 Laptop – Strom angestöpselt; Ryzen PC) braucht die Konvertierung bei besagten Testvideo ca. 8-11ms. Das reicht fürs Playback dicke.
Auf „schwächeren“ Rechnern, wenn zum Beispiel der Laptop runtertaktet (mobilmodus, Strom ausgestöpselt) oder auf Celeron-basierten Mini-PCs geht die Zeit für die Konvertierung hoch auf >45ms. Das reicht dann nicht mehr für flüssiges Playback bei 25 fps.
Ich frage mich, ob ich einen groben Fehler in der Konvertierung mache, ob ich die Sache falsch angehe.
Klar könnte ich die Konvertierung auf der GPU machen. Aber ich frage mich, ob dieses Thema WIRKLICH son CPU-Killer ist oder meine Implementierung einfach Mist ist. Hier ist besagte Methode - inhaltlich funktioniert Sie super:
Die Ausführzeit skaliert auch schön mit der Auflösung des Videos (Pixelanzahl).
Der Profiler sagt, dass sich die CPU während des Playbacks die größte Zeit in dieser Methode aufhält.
Besonders kritisch sind die drei Blöcke, wo die Farbskalierung, das Clipping und das Schreiben in den Zielpuffer gemacht wird. Ich habe schon mit verschiedenen Datentypen/Casts für verschiedene Stellen dort rumgespielt – ohne Erfolg.
Habt Ihr nen Vorschlag, was ich an der Implementierung ändern könnte?
Wie machen andere das – gerade schwache Plattformen wie ARM-Systeme/Handys? Müssen die die GPU benutzen? Ist sone „popelige“ Farbraumkonvertierung wirklich sone Geschwindigkeitsherausforderung?
Besten Dank für Hinweie schonmal!
Heute brauche ich Hilfe/Rat von denen von Euch, die sich mit Code-Optimierung auskennen.
Ich habe in den letzten Wochen einen rudimentären Video-Player geschrieben (libvpx, libwebm, libopus). Die Videocodecs spucken am Ende YUV420planar Bilder aus. Soweit, so gut.
Um am Ende in meinem RGB/BGR basierten „Universum“ klarzukommen, mache ich eine Transformation von YU420planar zu BGR (8 bit/channel) auf der CPU.
Mein Testvideo ist ein Clip mit 720p.
Je nach CPU ist es unmöglich, eine flüssige Video-Wiedergabe zu bekommen – nicht, weil der lahme VP8/VP9 so limitieren, sondern weil ich die YUV-BGR-Konvertierung nicht schnell genug hinbekomme.
Auf meinen schnelleren Test-Rechnern (i7 Laptop – Strom angestöpselt; Ryzen PC) braucht die Konvertierung bei besagten Testvideo ca. 8-11ms. Das reicht fürs Playback dicke.
Auf „schwächeren“ Rechnern, wenn zum Beispiel der Laptop runtertaktet (mobilmodus, Strom ausgestöpselt) oder auf Celeron-basierten Mini-PCs geht die Zeit für die Konvertierung hoch auf >45ms. Das reicht dann nicht mehr für flüssiges Playback bei 25 fps.
Ich frage mich, ob ich einen groben Fehler in der Konvertierung mache, ob ich die Sache falsch angehe.
Klar könnte ich die Konvertierung auf der GPU machen. Aber ich frage mich, ob dieses Thema WIRKLICH son CPU-Killer ist oder meine Implementierung einfach Mist ist. Hier ist besagte Methode - inhaltlich funktioniert Sie super:
Code: Alles auswählen
void convertPlanesYUV420pToBGR(const unsigned char* PlaneY, const unsigned char* PlaneU, const unsigned char* PlaneV, unsigned char* TargetBuffer, int ResX, int ResY)
{
const int _HalfResX(ResX/2);
for (int CntY=0; CntY<ResY; CntY++) {
const int _HalfLineYMulHalfResX( (CntY/2) * _HalfResX);
const int _ResXMulLineY(CntY * ResX);
for (int CntX=0; CntX<ResX; CntX++) {
const long int _ColorPlaneIndex (_HalfLineYMulHalfResX + (CntX/2) );
const short int _ValY (PlaneY[_ResXMulLineY + CntX] - 16);
const short int _ValU (PlaneU[_ColorPlaneIndex] - 128);
const short int _ValV (PlaneV[_ColorPlaneIndex] - 128);
/// convert the blue component
const float _Blue(_ValY + (1.140f*_ValU) );
if (_Blue > 255.0f) {
(*TargetBuffer) = 255;
}
else {
if (_Blue < 0.0f) {
(*TargetBuffer) = 0;
}
else {
(*TargetBuffer) = static_cast<unsigned char>(_Blue);
}
}
TargetBuffer++;
/// convert the green component
const float _Green(_ValY - (0.395f*_ValU) - (0.581*_ValV) );
if (_Green > 255.0f) {
(*TargetBuffer) = 255;
}
else {
if (_Green < 0.0f) {
(*TargetBuffer) = 0;
}
else {
(*TargetBuffer) = static_cast<unsigned char>(_Green);
}
}
TargetBuffer++;
/// convert the red component
const float _Red( _ValY + (2.032*_ValV) );
if (_Red > 255.0f) {
(*TargetBuffer) = 255;
}
else {
if (_Red < 0.0f) {
(*TargetBuffer) = 0;
}
else {
(*TargetBuffer) = static_cast<unsigned char>(_Red);
}
}
TargetBuffer++;
}
}
}
Der Profiler sagt, dass sich die CPU während des Playbacks die größte Zeit in dieser Methode aufhält.
Besonders kritisch sind die drei Blöcke, wo die Farbskalierung, das Clipping und das Schreiben in den Zielpuffer gemacht wird. Ich habe schon mit verschiedenen Datentypen/Casts für verschiedene Stellen dort rumgespielt – ohne Erfolg.
Habt Ihr nen Vorschlag, was ich an der Implementierung ändern könnte?
Wie machen andere das – gerade schwache Plattformen wie ARM-Systeme/Handys? Müssen die die GPU benutzen? Ist sone „popelige“ Farbraumkonvertierung wirklich sone Geschwindigkeitsherausforderung?
Besten Dank für Hinweie schonmal!