Umwandlung von YUV in RGB und Anzeige mit opengl es 2.0 von Android ndk unter Verwendung von Shadern

Lesezeit: 9 Minuten

Benutzer-Avatar
Lionel B.

Ich arbeite derzeit an einem Rtsp-Player auf Android, der ffmpeg verwendet, um den Videostream zu verbinden und zu decodieren. Ich möchte OpenGL es 2.0 verwenden, um den YUV-Frame in einen RGB-Frame zu konvertieren und anzuzeigen, aber ich bin blockiert (es ist das erste Mal, dass ich OpenGL verwende).

Ich werde versuchen, klar zu erklären, was mein Problem ist.

Vom NDK-Android aus initialisiere ich einen OpenGL-Kontext (aus dem Thread, den ich zum Anzeigen von Bildern verwenden möchte) mit dieser Methode:

    //
EGLint attribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_NONE
};
EGLint contextAttrs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
};

LOGI("Initializing context");

if((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY)
{
    closeContext();
    return;
}

if(!eglInitialize(display, 0, 0))
{
    closeContext();
    return;
}

if(!eglChooseConfig(display, attribs, &config, 1, &numConfigs))
{
    closeContext();
    return;
}

if(!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format))
{
    closeContext();
    return;
}

ANativeWindow_setBuffersGeometry(window, 0, 0, format);

if(!(surface = eglCreateWindowSurface(display, config, window, 0)))
{
    closeContext();
    return;
}

if(!(context = eglCreateContext(display, config, 0, contextAttrs)))
{
    closeContext();
    return;
}

if(!eglMakeCurrent(display, surface, surface, context))
{
    closeContext();
    return;
}

if(!eglQuerySurface(display, surface, EGL_WIDTH, &width) || !eglQuerySurface(display, surface, EGL_HEIGHT, &height))
{
    closeContext();
    return;
}

LOGI("EGLWIDTH : %d EGLHEIGHT : %d ", (int)width, (int)height);

isInitEGLContext = 1;

Dann richte ich die Grafiken mit dieser Methode ein:

    //
//Load Vertex and Fragment Shader, attach shader and link program
 programId = createProgram(kVertexShader, kFragmentShader);
 LOGI("Program id : %d error :  %d",(int) programId, glGetError());


if(!programId)
{
    LOGI("Could not create program");
    return;
}
// get index of the generic vertex attribute bound to vPosition
positionObject = (int) glGetAttribLocation(programId, "vPosition");

 // get index of the generic vertex attribute bound to vTexCoord
texturePosition = (int) glGetAttribLocation(programId, "vTexCoord");

// get the location of yTexture within the program (corresponding to   program id)
yuv_texture_object[0] = glGetUniformLocation(programId, "yTexture");

// get the location of uTexture within the program
yuv_texture_object[1] = glGetUniformLocation(programId, "uTexture");

// get the location of vTexture within the program
yuv_texture_object[2] = glGetUniformLocation(programId, "vTexture");

// Setup width of each planes (display size)
stream_yuv_width[0] = 800;
stream_yuv_width[1] = 400;
stream_yuv_width[2] = 400;

// Setup height of each planes (display size)
stream_yuv_height[0] = 600;
stream_yuv_height[1] = 300;
stream_yuv_height[2] = 300;

//set the view port
glViewport(0,0,stream_yuv_width[0],stream_yuv_height[0]);
LOGI("glViewPort() %d ", glGetError());

Ich habe die Anzeigegröße (vorerst) fest codiert, bis ich etwas bekomme, das funktioniert.

Die createProgram-Methode lädt die Shader, erstellt das Programm, kompiliert und verknüpft die Shader erfolgreich.

Hier sind meine Shader:

const char kVertexShader[] =
"attribute vec4 vPosition;\n"
"attribute vec2 vTexCoord;\n"
"varying vec2 v_vTexCoord;\n"
"void main() {\n"
"gl_Position = vPosition;\n"
"v_vTexCoord = vTexCoord;\n"
"}\n";

const char kFragmentShader[] =
"precision mediump float; \n"
"varying vec2 v_vTexCoord;\n"
"uniform sampler2D yTexture;\n"
"uniform sampler2D uTexture;\n"
"uniform sampler2D vTexture;\n"
"void main() {\n"
"float nx, ny; \n"
"nx = v_vTexCoord.x; \n"
"ny = v_vTexCoord.y; \n"
"float y=texture2D(yTexture, v_vTexCoord).r;\n"
"float u=texture2D(uTexture, vec2(nx / 2.0, ny / 2.0)).r;\n"
"float v=texture2D(vTexture, vec2(nx / 2.0, ny / 2.0)).r;\n"
"y = 1.1643 * (y - 0.0625);\n"
"u = u - 0.5; \n"
"v = v - 0.5; \n"
"float r=y + 1.5958 * v;\n"
"float g=y - 0.39173 * u - 0.81290 * v;\n"
"float b=y + 2.017 * u;\n"
"gl_FragColor = vec4(r, g, b, 1.0);\n"
"}\n";

const GLfloat kVertexInformation[] = {
    -1.0f, 1.0f,           // TexCoord 0 top left
    -1.0f,-1.0f,           // TexCoord 1 bottom left
    1.0f,-1.0f,           // TexCoord 2 bottom right
    1.0f, 1.0f            // TexCoord 3 top right
};
const GLshort kTextureCoordinateInformation[] = {
    0, 0,         // TexCoord 0 top left
    0, 1,         // TexCoord 1 bottom left
    1, 1,         // TexCoord 2 bottom right
    1, 0          // TexCoord 3 top right  
};
const GLuint kStride = 0;//COORDS_PER_VERTEX * 4;
const GLshort kIndicesInformation[] = {
    0, 1, 2,
    0, 2, 3
};

Dann setze ich die yuv-Texturen und das Rendern zu Texturen, in diesem Moment yuv_width[i] und yuv_height[i] auf den richtigen Wert eingestellt sind:

void setupYUVTexture()
{
//Setup the pixel alignement
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
LOGI("glPixelStorei() : %d ", glGetError());
int i = 0;

for(i = 0 ; i < 3 ; ++i)
{
    //Check if the texture already setup
    if(yuv_texture_id[i] != 0)
    {
        glDeleteTextures(1, &yuv_texture_id[i]);
        yuv_texture_id[i] = 0;
    }
    // Active the i texture
    glActiveTexture(GL_TEXTURE0 + i);

    //Generate the texture name
    glGenTextures(1, &yuv_texture_id[i]);

    // Bind the texture
    glBindTexture(GL_TEXTURE_2D, yuv_texture_id[i]);

   // Setup the texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);  
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);   
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


    //Define the texture image
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, yuv_width[i], yuv_height[i], 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
    LOGI("glTexImage2D() %d ", glGetError());
}
}

void renderToTexture()
{
 // Generate framebuffer object name
    glGenFramebuffers(1, &frameBufferObject);
    //Bind the framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferObject);


    //Generate render buffer object name
    glGenRenderbuffers(1, &renderBufferObject);

    //Bind render buffer
    glBindRenderbuffer(GL_RENDERBUFFER, renderBufferObject);

   //Create and initialize render buffer for display RGBA with the same size of the viewport
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 800, 600);

    //Attach render buffer to frame buffer object
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferObject);

    //Attach y plane to frame buffer object
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yuv_texture_id[0], 0);

   //Attach u plane to frame buffer object
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yuv_texture_id[1], 0);

     //Attach v plane to frame buffer object
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yuv_texture_id[2], 0);

    // Bind the framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    //Check if the framebuffer is correctly setup
    GLint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if(status != GL_FRAMEBUFFER_COMPLETE)
    {
        LOGI(" FBO setting fault : %d ", status);
        return;
    }
}

Zum Abschluss meine Ziehrahmenmethode:

void drawFrame()
{
LOGI("DrawFrame");
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferObject);
printGLError("glBindFramebuffer");
glUseProgram(programId);
printGLError("glUseProgram");
int i = 0;
for(i = 0 ; i < 3 ; ++i)
{
    glActiveTexture(GL_TEXTURE0 + i);
    printGLError("glActiveTexture");

    glBindTexture(GL_TEXTURE_2D, yuv_texture_object[i]);
    printGLError("glBindTexture");

    glUniform1i(yuv_texture_object[i], i);
    printGLError("glUniform1i");            
    LOGI("Plan : %d Largeur : %d Hauteur : %d ", i, yuv_width[i], yuv_height[i]);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,yuv_width[i], yuv_height[i], GL_LUMINANCE, GL_UNSIGNED_BYTE, yuv_planes[i]);
            printGLError("glTexSubImage2D");

    }

    glVertexAttribPointer(positionObject, 2, GL_FLOAT, GL_FALSE, kStride, kVertexInformation);
    printGLError("glVertexAttribPointer");

    glVertexAttribPointer(texturePosition, 2, GL_SHORT, GL_FALSE, kStride, kTextureCoordinateInformation);
    printGLError("glVertexAttribPointer");

    glEnableVertexAttribArray(positionObject);      
    printGLError("glVertexAttribArray");

    glEnableVertexAttribArray(texturePosition);
    printGLError("glVertexAttribArray");

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    printGLError("glBindFramebuffer");

    glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_SHORT, kIndicesInformation);
    printGLError("glDrawElements");

    eglSwapBuffers(display, surface);
    printGLError("eglSwapBuffers");

}

Ich initialisiere einmal die OpenGL-Texturen und andere Attribute, die erforderlich sind, und wenn ein Frame dekodiert wird, kopiere ich den Puffer erneut hinein yuv_planes[0]du pufferst ein yuv_planes[ 1] und v Puffer ein yuv_planes[2].

Sobald ein Frame mit ffmpeg korrekt decodiert ist, rufe ich in dieser Reihenfolge auf:
initContext()

setupGraphics()

setupYUVTexture()

renderToTexture()

dann rufe ich an drawFrame. Wenn alles initialisiert ist, rufe ich natürlich drawFrame nach jedem decodierten Frame direkt auf.

Da ist die Ausgabe, die ich jetzt habe.

Videoanzeige durch opengl

Die Größe des Bildes ist korrekt, aber jetzt bin ich hier blockiert. Ich verstehe nicht, warum die Bildanzeige grün ist! Irgendwelche Ideen

  • Meldet glTexSubImage2D bei jedem der 3 Aufrufe einen Fehler? Beachten Sie, dass Sie (++i) verwenden, das dann reicht [1, 3] stattdessen hinein [0, 2]. yuv_breite[3] scheint nicht definiert zu sein, also sollte der letzte Aufruf definitiv falsch sein (für i=3). Sie sollten auch wissen, dass glGetError den obersten Wert aus dem Fehlerstapel zurückgibt und ihn ausgibt. Das bedeutet, dass Sie im Allgemeinen eine While-Schleife benötigen, um alle Fehler aus dem Stack zu erhalten. Da Sie dies nicht tun, wird der Fehler möglicherweise beim nächsten glGetError-Aufruf gemeldet, obwohl er von einer vorherigen Zeile generiert wurde.

    – Matic Oblak

    15. Januar 2016 um 9:39 Uhr

  • Wenn ich mir den Shader ansehe, scheinen Sie Farbkomponenten nicht als YUV, sondern als RGB zu verwenden und sie dann in RGB umzuwandeln. Das scheint mir in Ordnung zu sein, aber Sie sollten dann GL_RGB anstelle von GL_LUMINANCE verwenden und die RGB-Werte im Shader als YUV behandeln, was Sie bereits zu tun scheinen (oder nicht, der Fragment-Shader scheint etwas seltsam zu sein. Vielleicht würden einige Kommentare im Code Hilfe).

    – Matic Oblak

    15. Januar 2016 um 9:42 Uhr

  • Ich habe den Beitrag bearbeitet, um Kommentare hinzuzufügen und die Ausgabe meines Android-Monitors hinzuzufügen. Zu Beginn meldet glTexSubImage2D einen Fehler beim ersten Aufruf und beim letzten Aufruf. Das Inkrement vor der Anweisung, die ich in meiner for-Schleife verwende, ist korrekt. In diesem Fall beginnt es nicht von 1 bis 3, sondern von 0 bis 2. Einige Kompilierungen verwenden ein temporäres Attribut, wenn Sie Increment after Statement verwenden. Ich habe mich daran gewöhnt, for loop auf diese Weise zu schreiben, da es kein temporäres Attribut verwendet (es ist etwas schneller). Ich hatte ein Protokoll, bevor ich meine Android Monitor-Ausgabe erhalte, um Ihnen zu zeigen, dass es korrekt ist.

    – Lionel B.

    15. Januar 2016 um 15:12 Uhr

  • Während meiner Recherche verstehe ich das “Problem” mit glGetError(), also hatte ich nach jedem gl-Aufruf ein Protokoll, um sicher zu sein, welche Zeile schief geht. Ich fand es ausreichend, ich hatte eine While-Schleife, um zu sehen, ob der Fehler von einer anderen Zeile kam. Ich habe versucht, GL_RGB anstelle von GL_LUMINANCE zu verwenden, aber ich erhalte das gleiche Ergebnis. Ich dachte, GL_LUMINANCE wäre der gute “Typ”, weil ich jedes Flugzeug in 3 verschiedenen Texturen hochlade, liege ich falsch? Kann es sein, dass es von der Eingangsbildgröße kommt? (keine Zweierpotenz), ich habe irgendwo gelesen, dass es in Opengles 2.0 keine Verpflichtung mehr gibt, Zweierpotenz-Texturen zu verwenden.

    – Lionel B.

    15. Januar 2016 um 15:20 Uhr

  • Nicht sicher, wo der Fehler liegt, aber wenn U und V 0 sind, ist die Farbe grün. Also haben Sie entweder einen Rechenfehler mit U und V oder die tatsächlichen Eingabewerte für U und V sind 0.

    – Benutzer3386109

    27. Januar 2016 um 23:30 Uhr

Das ist eine Menge Code, den man durchgehen muss, und eine Menge Dinge, die schief gehen können ;). Um diese Art von Problemen zu debuggen, würde ich Schritt für Schritt vorgehen.

  1. einfach rot ausgeben (gl_FragColor = vec4(1.0, 0.5, 0.5, 1.0)), um sicherzustellen, dass Ihre Konfiguration ordnungsgemäß funktioniert.
  2. versuchen Sie, jede Textur in Graustufen auszugeben. (gl_FragColor = vec4(y, y, y, 1.0))
  3. Wenn das alles funktioniert, bedeutet dies höchstwahrscheinlich, dass Ihre yuv => rgb-Konvertierung irgendwo falsch ist.
  4. Wenn das nicht funktioniert, dann würde ich etwas im Textur-Mapping vermuten. Überprüfen Sie Ihre glTexSubImage2D Anruf. Möglicherweise müssen Sie eine andere Schrittlänge durchlaufen oder ein anderes Koordinatensystem verwenden.

1355210cookie-checkUmwandlung von YUV in RGB und Anzeige mit opengl es 2.0 von Android ndk unter Verwendung von Shadern

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy