![]() |
YUV2 unter Firemonkey
Hi,
ich bekomme hier mittels Stream Rohdaten im YUY2 Format umwandelt. Da der Durchsatz relativ hoch ist, wollte ich die Darstellung mittels Pixelshader realisieren. Mein Ziel ist Android und iOS, unter Windows bzw. MacOS nutze ich für die Darstellung ein reines OpenGL Fenster. Dort mache ich das via:
Delphi-Quellcode:
Ich habe schon versucht einen Filter zu schreiben, aber ich scheitere daran dass Firemonkey außer RGBA Bitmaps ja überhaupt nichts erlaubt. Jetzt den Code wild zu patchen widerstrebt mir, denn das bei jedem Update machen zu müssen. Da wird man doch blöde.
vertexShaderString = 'attribute vec4 position;' + sLineBreak +
'attribute vec2 texcoord;' + sLineBreak + 'uniform mat4 modelViewProjectionMatrix;' + sLineBreak + 'varying vec2 v_texcoord;' + sLineBreak + 'void main()' + sLineBreak + '{' + sLineBreak + ' gl_Position = modelViewProjectionMatrix * position;' + sLineBreak + ' v_texcoord = texcoord.xy;' + sLineBreak + ' gl_FrontColor = gl_Color;' + sLineBreak + '}'; fragmentShaderString = 'varying vec2 v_texcoord;' + sLineBreak + 'uniform sampler2D s_texture_y;' + sLineBreak + 'uniform sampler2D s_texture_u;' + sLineBreak + 'uniform sampler2D s_texture_v;' + sLineBreak + 'void main() ' + sLineBreak + '{' + sLineBreak + ' float y = texture2D(s_texture_y, v_texcoord).r;' + sLineBreak + ' float u = texture2D(s_texture_u, v_texcoord).r - 0.5;' + sLineBreak + ' float v = texture2D(s_texture_v, v_texcoord).r - 0.5;' + sLineBreak + ' float r = y + 1.402 * v;' + sLineBreak + ' float g = y - 0.344 * u - 0.714 * v;' + sLineBreak + ' float b = y + 1.772 * u;' + sLineBreak + ' gl_FragColor = vec4(r,g,b,1.0) * gl_Color;' + sLineBreak + '}'; ... glUseProgram(FProgram); glUniformMatrix4fv(FuniformMatrix, 1, GLboolean(0), @FModelviewProj); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture.Handle); glUniform1i(FUniformSamplers[0], 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, Texture.UHandle); glUniform1i(FUniformSamplers[1], 1); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, Texture.VHandle); glUniform1i(FUniformSamplers[2], 2); Hat jemand vielleicht eine Idee? |
AW: YUV2 unter Firemonkey
Am Ende muss doch so oder so nach RGBA/ARGB umgewandelt werden, damit das auf dem jeweiligen Display dargestellt werden kann?
Kann du deine Verarbeitung nicht solange auf den YUV2-Daten machen, bis du kurz vorm Anzeigen bist? |
AW: YUV2 unter Firemonkey
Das ist nen Videostream :) Hätte ich wohl sagen sollen. Die Umwandlung von dem einen in den anderen Farbraum kann man auch mit der CPU machen, aber das ist lahm, bei HD.
|
AW: YUV2 unter Firemonkey
Jaja, das habe ich schon verstanden.
Aber zum darstellen/blitten/whatever auf dem Monitor/Smartphone-Display musst du doch eh in den RGB(A)-Farbraum konvertieren. Die Displays können nur RGB! Das kannst du natürlich auch per Pixelshader machen! Derartige Farbraumkonvertierungen wurden früher unter Windows automatisch von DirectDraw übernommen, was mal mehr oder weniger gut war, je nach Grafikkarten-Treiber. Bei neueren Grafik-APIs (Direct10, 11, 12 und OpenGL) muss man Farbraumkonvertierungen selber machen. Wenn ich deinen "fragmentShaderString" richtig interpretiere, versuchst du sowas ja auch schon. Kannst du dein Problem vielleicht nochmal genauer und näher erläutern? Du musst dein YUV2-Buffer so verarbeiten, dass ein RGBA-Buffer bei rum kommt und dann per FMX.Graphics.TBitmap.Map ins Bitmap stecken und das anzeigen. ![]() ![]() ![]() |
AW: YUV2 unter Firemonkey
Das macht ja eben dieser OpenGL Code.
Angenommen ich mache das in der CPU, also r = y + 1.402 * v; g = y - 0.344 * u - 0.714 * v; b = y + 1.772 * u; Dann ist das extrem langsam. Selbst mit Festkomma zu hantieren und/oder Tabellen zu nutzen ist lahm. Die Idee ist das ganze auf der GPU zu machen und das geht auch, wenn ich ein OpenGL Fenster nutze und dort die Textur mit Shadern versehe. Sobald Firemonkey ins Spiel kommt, geht das aber nicht mehr. Ich müsste in dem Firemonkey Klassen manuell die Möglichkeit für Nicht RGBA Texturen einbinden und an einigen Stellen den Code patchen. Dann könnte man einen TMaterialSource mit einem TForm3D nehmen. Ich glaube da gab es aber mit dem neuen Delphi ein Problem auf den Mobilen Plattformen. Stand hier irgendwo im Link. Am liebsten wäre mir das auf einer TForm zu machen. Also eine Komponente die Y U und V Daten gebe und diese dann mit einem GPU Shader das dann rendert. |
AW: YUV2 unter Firemonkey
Hm, irgendwie reden wir aneinander vorbei oder du liest die anderen Beiträge nicht zuende?
Wo genau hakt es denn? Zeig uns doch mal bitte den Code in Verbindung mit dem OpenGL-Fenster. Du hast ja im Prinzip schon alles. Du musst nur nach dem Umwandeln per GPU von YUV in irgendein RGB-Format den Buffer per Map-Methode in das Bitmap kopieren und fertig. Und dann halt das Bitmap auf irgendwas anzeigen, aber das ist eine Fingerübung. |
AW: YUV2 unter Firemonkey
Stimmt, ich denke wir reden etwas aneinander vorbei:
Delphi-Quellcode:
Im Prinzip machst du nichts anderes als das du 3 Texturen für Y, U und die V Werte hast und diese in der GPU mittels eines Shaders direkt im Grafikspeicher in RGB umwandelst. Du kannst das ganze auch so wie du vorschlagen willst machen:
vertices[0] := vr.left;
vertices[1] := vr.bottom; vertices[2] := vr.right; vertices[3] := vr.bottom; vertices[4] := vr.left; vertices[5] := vr.top; vertices[6] := vr.right; vertices[7] := vr.top; texcoords[0] := tr.left; texcoords[1] := tr.bottom; texcoords[2] := tr.right; texcoords[3] := tr.bottom; texcoords[4] := tr.left; texcoords[5] := tr.top; texcoords[6] := tr.right; texcoords[7] := tr.top; if FProgram = 0 then LoadShader; glUseProgram(FProgram); glUniformMatrix4fv(FuniformMatrix, 1, GLboolean(0), @FModelviewProj); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture.Handle); glUniform1i(FUniformSamplers[0], 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, Texture.UHandle); glUniform1i(FUniformSamplers[1], 1); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, Texture.VHandle); glUniform1i(FUniformSamplers[2], 2); glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, {$IFDEF MSWINDOWS}false{$ELSE}0{$ENDIF}, 0, @vertices); glEnableVertexAttribArray(ATTRIBUTE_VERTEX); glVertexAttribPointer(ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, {$IFDEF MSWINDOWS}false{$ELSE}0{$ENDIF}, 0, @texcoords); glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glUseProgram(0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, 0); ... function LoadShader: Boolean; var vertShader, fragShader: GLUInt; status: GLint; begin result := false; if not FInitialized then exit; if FProgram <> 0 then // Already Loaded begin result := true; exit; end; vertShader := 0; fragShader := 0; FProgram := glCreateProgram(); try vertShader := compileShader(GL_VERTEX_SHADER, vertexShaderString); if vertShader = 0 then exit; fragShader := compileShader(GL_FRAGMENT_SHADER, fragmentShaderString); if fragShader = 0 then exit; glAttachShader(FProgram, vertShader); glAttachShader(FProgram, fragShader); glBindAttribLocation(FProgram, ATTRIBUTE_VERTEX, 'position'); glBindAttribLocation(FProgram, ATTRIBUTE_TEXCOORD, 'texcoord'); glLinkProgram(FProgram); glGetProgramiv(FProgram, GL_LINK_STATUS, @status); if status = 0 then exit; result := validateProgram(FProgram); FuniformMatrix := glGetUniformLocation(FProgram, 'modelViewProjectionMatrix'); FUniformSamplers[0] := glGetUniformLocation(FProgram, 's_texture_y'); FUniformSamplers[1] := glGetUniformLocation(FProgram, 's_texture_u'); FUniformSamplers[2] := glGetUniformLocation(FProgram, 's_texture_v'); finally glDeleteShader(vertShader); glDeleteShader(fragShader); if not result then begin glDeleteProgram(FProgram); FProgram := 0; end; end; end;
Delphi-Quellcode:
Wie ich aber schon im Eingangspost erwähnt habe ist das auf mobilen Geräten nicht gangbar. Die Umwandlung bei einem 1080i Bild ist mit 25fps nicht realisierbar.
const
YUV_FIX2 = 6; YUV_MASK2 = (256 shl YUV_FIX2) - 1; function MultHi(const v, coeff: integer): integer; inline; begin result := (v * coeff) shr 8; end; function VP8Clip8(const v: integer): Byte; inline; begin if (v and not YUV_MASK2) = 0 then result := v shr YUV_FIX2 else if v < 0 then result := 0 else result := 255; end; function VP8YUVToR(const y, v: integer): Byte; inline; begin result := VP8Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); end; function VP8YUVToG(const y, u, v: integer): Byte; inline; begin result := VP8Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); end; function VP8YUVToB(const y, u: integer): Byte; inline; begin result := VP8Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); end; procedure VP8YuvToRgb(y, u, v: integer; argb: PCardinal); inline; begin argb^ := $FF000000 or VP8YUVToR(y, v) shl 16 or VP8YUVToG(y, u, v) shl 8 or VP8YUVToB(y, u); end; type TPacket = record srcY, srcU, srcV: PByte; LineSize: Array [0 .. 2] of integer; end; procedure copyYuv(Packet: TPacket; Bitmap: TBitmap); overload; var x, y: integer; ptr: PByte; dst: PCardinal; Data: TBitmapData; begin Bitmap.Map(TMapAccess.Write, Data); try ptr := Data.Data; with Packet do for y := 0 to Bitmap.height - 1 do begin dst := PCardinal(ptr); for x := 0 to Bitmap.width - 1 do begin VP8YuvToRgb(srcY[x], srcU[x shr 1], srcV[x shr 1], dst); inc(dst); end; inc(srcY, LineSize[0]); inc(ptr, Data.Pitch); if ((y + 1) mod 2) = 0 then begin inc(srcU, LineSize[1]); inc(srcV, LineSize[2]); end; end; finally Bitmap.Unmap(Data); end; end; Die einzige Alternative für mich wäre das mit einem, so wie ich das bei Firemonkey verstanden habe soll das ja gehen, GPU unterstützten TFilter zu machen. Das ganze würde dann zwar auch bedeuten, dass du was in der GPU warst wieder in eine Bitmap schiebst, aber es ist denke ich schneller, als die Umwandlung mittels CPU. |
AW: YUV2 unter Firemonkey
Zitat:
Mein Vorschlag ist NICHT es per CPU zu machen. Du musst nur deine beiden Codeschnipsel kombinieren:
Delphi-Quellcode:
Ich bin ja nicht so firm mit OpenGL, aber anhand deiner Codezeilen kann man erkennen, dass du pro Videoframe ja eine Variable Texture (mit .Handle, .UHandle und .YHandle) hast, um die YUV-Daten aufzunehmen.
function ConvertYuvToRgbWithOpenGL(const Packet: TPacket): TBytes;
begin // make OpenGL magic here end; procedure CopyYuv(const Packet: TPacket; Bitmap: TBitmap); overload; var BitmapData: TBitmapData; RGBABuffer: TBytes; begin RGBABuffer := ConvertYUVToRGBWithOpenGL(Packet); Bitmap.Map(TMapAccess.Write, BitmapData); try System.Move(@RGBABuffer[0], BitmapData.Data, Length(RGBABuffer)); finally Bitmap.Unmap(BitmapData); end; end; Du brauchst jetzt nur noch eine weitere Variable für eine 2D-Texture, die deine umgewandelten RGBA-Daten aufnimmt. Den Inhalt deiner - ich nenne sie mal RGBATexture - musst du nur noch in den Speicher des FMX-Bitmaps umkopieren. Die Farbraumkonvertierung erfolgt weiterhin mit OpenGL auf der GPU!!! Beim obigen Pseudocodeschnipsel kann man sich ggf. den Zwischenschritt mit den TBytes-Array sparen, wenn du direkt den TBitmapData.Data Zeiger übergibst. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:32 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz