OpenGL.Shader: Zhige te enseña a escribir un cliente de filtro en vivo (3) Renderizado OpenGL NV21 (YUV)

OpenGL.Shader: Zhige te enseña a escribir un cliente de filtro en vivo (3)

 

El último capítulo habló sobre dos puntos, el primero es el grupo de búfer de datos de cuadros, que almacena en caché los datos de cuadros de NV21 (la pila de llamadas es JClass Activity-> JClass CFEScheduler-> JCallback Camera.onPreviewFrame-> JNIMethod feedVideoData) ; el segundo es setRotationCamera, Ajuste los volteos horizontales y verticales de acuerdo con el ángulo de rotación (la pila de llamadas es JClass Activity-> JClass CFEScheduler-> JMethod setUpCamera-> JNIMethod setRotationCamera) ; ambos eventualmente activarán AdjustFrameScaling, ajustarán la escala de la imagen del marco y generarán vértices para la representación OpenGL Coordinar datos y datos de coordenadas de textura.

El siguiente paso es representar los datos de la imagen en el soporte de la pantalla. Esta vez es algo diferente al registro de marca de agua introducido en el artículo anterior El método introducido en el artículo anterior es usar el objeto de textura preparado por el sistema, y ​​podemos usar el ID de textura para operarlo directamente. Pero esta vez solo hay los datos de fotogramas de imagen en formato NV21 más primitivos, cómo representar de manera eficiente los datos de fotogramas de imagen en la superficie del portador de salida se ha convertido en el problema clave.

Hay que prestar atención a una pregunta más: ¿cómo cambiar sin problemas después de agregar varios efectos de filtro? (Por el momento, ponga un punto aquí) Sin más preámbulos, muestre el código.

Primero, observe el contenido de las tres devoluciones de llamada del ciclo de vida de GLThread + GLRender: (Si tiene alguna pregunta, consulte el artículo anterior https://blog.csdn.net/a360940265a/article/details/88600962 )

void GpuFilterRender::surfaceCreated(ANativeWindow *window)
{
    if (mEglCore == NULL) {
        mEglCore = new EglCore(NULL, FLAG_TRY_GLES2);
    }
    mWindowSurface = new WindowSurface(mEglCore, window, true);
    assert(mWindowSurface != NULL && mEglCore != NULL);
    LOGD("render surface create ... ");
    mWindowSurface->makeCurrent();
    if( mFilter==NULL) {
        mFilter = new GpuBaseFilter();
    } else {
        mFilter->destroy();
    }
    mFilter->init();
    mWindowSurface->swapBuffers();
    // ...
}
void GpuFilterRender::surfaceChanged(int width, int height) {
    this->mViewWidth = width;
    this->mViewHeight = height;
    mWindowSurface->makeCurrent();
    mFilter->onOutputSizeChanged(width, height);
    mWindowSurface->swapBuffers();
}
void GpuFilterRender::surfaceDestroyed() {
    if (mWindowSurface) {
        mWindowSurface->release();
        delete mWindowSurface;
        mWindowSurface = NULL;
    }
    if (mEglCore) {
        mEglCore->release();
        delete mEglCore;
        mEglCore = NULL;
    }
}

Obviamente, este GpuBaseFilter será una clase clave, desempeñando un papel vital en todo el proceso de renderizado de filtros.
Así que rápidamente eche un vistazo a lo que hace este GpuBaseFilter.

#ifndef GPU_NORMAL_FILTER_HPP
#define GPU_NORMAL_FILTER_HPP
#define FILTER_TYPE_NORMAL          0x1010
/**
 * Filter基础类,支持YUV / RGB渲染模式。
 */
class GpuBaseFilter  {
public:
    // 用于上层获取滤镜列表对应的Filter类型
    virtual int getTypeId() { return FILTER_TYPE_NORMAL; }

    GpuBaseFilter()
    {
        NO_FILTER_VERTEX_SHADER   = "attribute vec4 position;\n\
                                     attribute vec4 inputTextureCoordinate;\n\
                                     varying vec2 textureCoordinate;\n\
                                     void main()\n\
                                     {\n\
                                        gl_Position = position;\n\
                                        textureCoordinate = inputTextureCoordinate.xy;\n\
                                     }";

        NO_FILTER_FRAGMENT_SHADER = "precision mediump float;\n\
                                     varying highp vec2 textureCoordinate;\n\
                                     uniform sampler2D SamplerRGB;\n\
                                     uniform sampler2D SamplerY;\n\
                                     uniform sampler2D SamplerU;\n\
                                     uniform sampler2D SamplerV;\n\
                                     mat3 colorConversionMatrix = mat3(\n\
                                                        1.0, 1.0, 1.0,\n\
                                                        0.0, -0.39465, 2.03211,\n\
                                                        1.13983, -0.58060, 0.0);\n\
                                     vec3 yuv2rgb(vec2 pos)\n\
                                     {\n\
                                        vec3 yuv;\n\
                                        yuv.x = texture2D(SamplerY, pos).r;\n\
                                        yuv.y = texture2D(SamplerU, pos).r - 0.5;\n\
                                        yuv.z = texture2D(SamplerV, pos).r - 0.5;\n\
                                        return colorConversionMatrix * yuv;\n\
                                     }\n\
                                     void main()\n\
                                     {\n\
                                        gl_FragColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
                                        //gl_FragColor = texture2D(SamplerY/U/V, textureCoordinate);\n\
                                     }";
    }
    virtual ~GpuBaseFilter()
    {
        if(!NO_FILTER_VERTEX_SHADER.empty()) NO_FILTER_VERTEX_SHADER.clear();
        if(!NO_FILTER_FRAGMENT_SHADER.empty()) NO_FILTER_FRAGMENT_SHADER.clear();
        mIsInitialized = false;
    }

    virtual void init() {
        init(NO_FILTER_VERTEX_SHADER.c_str(), NO_FILTER_FRAGMENT_SHADER.c_str());
    }

    void init(const char *vertexShaderSource, const char *fragmentShaderSource) {
        mGLProgId = ShaderHelper::buildProgram(vertexShaderSource, fragmentShaderSource);
        mGLAttribPosition = static_cast<GLuint>(glGetAttribLocation(mGLProgId, "position"));
        mGLUniformSampleRGB = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerRGB"));
        mGLUniformSampleY = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerY"));
        mGLUniformSampleU = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerU"));
        mGLUniformSampleV = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerV"));
        mGLAttribTextureCoordinate = static_cast<GLuint>(glGetAttribLocation(mGLProgId, "inputTextureCoordinate"));
        mIsInitialized = true;
    }

    virtual void destroy() {
        mIsInitialized = false;
        glDeleteProgram(mGLProgId);
    }

    virtual void onOutputSizeChanged(int width, int height) {
        mOutputWidth = width;
        mOutputHeight = height;
    }

    virtual void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(mGLProgId);
        // runPendingOnDrawTasks();
        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
        glUniform1i(mGLUniformSampleY, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
        glUniform1i(mGLUniformSampleU, 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
        glUniform1i(mGLUniformSampleV, 2);
        // onDrawArraysPre();
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }

    // GLUniformSampleRGB对应的draw,我这里没用rgb模式.
    //virtual void onDraw2(GLuint textureId, void* positionCords, void* textureCords)
    //{
    //    if (!mIsInitialized)
    //        return;
    //    glUseProgram(mGLProgId);
    //    // runPendingOnDrawTasks();
    //    glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
    //    glEnableVertexAttribArray(mGLAttribPosition);
    //    glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
    //    glEnableVertexAttribArray(mGLAttribTextureCoordinate);
    //    if (textureId != -1) {
    //        glActiveTexture(GL_TEXTURE0);
    //        glBindTexture(GL_TEXTURE_2D, textureId);
    //        glUniform1i(mGLUniformSampleRGB, 0);
    //    }
    //    // onDrawArraysPre();
    //    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    //    glDisableVertexAttribArray(mGLAttribPosition);
    //    glDisableVertexAttribArray(mGLAttribTextureCoordinate);
    //    glBindTexture(GL_TEXTURE_2D, 0);
    //}

    // 相关滤镜对应的可调整参数,通过此借口进行操作
    virtual void setAdjustEffect(float percent) {
        // subclass override
    }
    bool isInitialized(){ return mIsInitialized;}
    GLuint getProgram(){ return mGLProgId;}
protected:
    std::string NO_FILTER_VERTEX_SHADER;
    std::string NO_FILTER_FRAGMENT_SHADER;

    GLuint  mGLProgId;
    GLuint  mGLAttribPosition;
    GLuint  mGLUniformSampleRGB;
    GLuint  mGLAttribTextureCoordinate;

    GLuint  mGLUniformSampleY;
    GLuint  mGLUniformSampleU;
    GLuint  mGLUniformSampleV;

    int     mOutputWidth;
    int     mOutputHeight;
    bool    mIsInitialized;
};
#endif // GPU_NORMAL_FILTER_HPP

La longitud es un poco larga. Esta es una clase base creada por el estándar C ++. Las funciones que comienzan con la palabra clave virtual serán reescritas por la clase heredada (consulte Baidu para conocer los puntos de conocimiento relevantes). Céntrese en el constructor Shader y el método onDraw para renderizar.

Primero mira el sombreador de vértices NO_FILTER_VERTEX_SHADER:

atributo vec4 posición;
atributo vec4 inputTextureCoordinate;
variando vec2 textureCoordinate;
void main ()
{     gl_Position = posición;     textureCoordinate = inputTextureCoordinate.xy; }


El contenido no es complicado, las coordenadas de los vértices se generan de acuerdo con las reglas y las coordenadas de textura se pasan al sombreador de fragmentos.

A continuación, mire NO_FILTER_FRAGMENT_SHADER, que es uno de los enfoques de este artículo.

flotador mediump de precisión;
variando highp vec2 textureCoordinate;
Sampler2D uniforme SamplerRGB;
Sampler2D uniforme SamplerY;
sampler2D SamplerU uniforme;
Sampler2D SamplerV uniforme;
mat3 colorConversionMatrix = mat3 (
                   1.0, 1.0, 1.0,
                   0.0, -0.39465, 2.03211,
                   1.13983, -0.58060, 0.0);
vec3 yuv2rgb (vec2 pos)
{     vec3 yuv;     yuv.x = textura2D (SamplerY, pos) .r;     yuv.y = texture2D (SamplerU, pos) .r - 0.5;     yuv.z = texture2D (SamplerV, pos) .r - 0.5;     return colorConversionMatrix * yuv; } void main () {








    gl_FragColor = vec4 (yuv2rgb (texturaCoordinada), 1.0);
    // gl_FragColor = texture2D (SamplerRGB, textureCoordinate);
}

Obviamente, aquí se hacen preparaciones a dos manos. SamplerRGB es una textura de color. El código utilizado para comentar gl_FragColor se deriva directamente de las coordenadas de la textura; SamplerY / U / V se puede ver por el nombre para saber que los tres componentes se almacenan por separado en los tres componentes del formato yuv. , Y luego usa la fórmula general de yuv a rgb:

R = Y + 0 * U + 1.140 * V
G = Y - 0.395 * U - 0.581 * V
B = Y + 2.032 * U + 0 * V

La fórmula anterior está en forma decimal no cuantificada. ¿Qué es la cuantificación?
La cuantificación es hacer Y o U o V dentro de un cierto rango mediante transformación lineal, asumiendo que Y / U / V ~ (0-255) antes de la cuantificación, y los parámetros después de la cuantificación: Y ~ (16,235) U ~ (16-240) V ~ (16-240), deja que Y (0,255) cambie a Y '(16,235) y hazlo así: Y' = Y * [(235-16) / 255] +16.

Otro punto es que la matriz OpenGL es la primera columna, por lo que cuando personaliza la matriz, debe prestar atención al intercambio de filas y columnas. 

 

Una vez que el Shader está listo, el siguiente paso es cómo renderizar para NV21.

void GpuFilterRender::renderOnDraw(double elpasedInMilliSec)
{
    if (mEglCore == NULL || mWindowSurface == NULL) {
        LOGW("Skipping drawFrame after shutdown");
        return;
    }
    pthread_mutex_lock(&mutex);
    ByteBuffer* item = mNV21Pool.get();
    if(item == NULL) {
        pthread_mutex_unlock(&mutex);
        return;
    } else { // item!=NULL,i420BufferY/U/V也!=NULL
        int8_t * nv21_buffer = item->data();
        int            y_len = item->param1;
        int            u_len = item->param2;
        int            v_len = item->param3;
        // 装填y u v数据。
        int8_t * dst_y = i420BufferY->data();
        int8_t * dst_u = i420BufferU->data();
        int8_t * dst_v = i420BufferV->data();
        memcpy(dst_y, nv21_buffer, (size_t) y_len);
        for (int i = 0; i < u_len; i++) {
            //NV21 先v后u
            *(dst_v + i) = (uint8_t) *(nv21_buffer + y_len + i * 2);
            *(dst_u + i) = (uint8_t) *(nv21_buffer + y_len + i * 2 + 1);
        }
        // 删除BufferPool当中的引用。
        delete item;
        pthread_mutex_unlock(&mutex);//#赶紧解锁
        // 画面渲染
        mWindowSurface->makeCurrent();
        yTextureId = updateTexture(dst_y, yTextureId, mFrameWidth, mFrameHeight);
        uTextureId = updateTexture(dst_u, uTextureId, mFrameWidth/2, mFrameHeight/2);
        vTextureId = updateTexture(dst_v, vTextureId, mFrameWidth/2, mFrameHeight/2);
        if( mFilter!=NULL) {
            mFilter->onDraw(yTextureId, uTextureId, vTextureId, positionCords, textureCords);
        }
        mWindowSurface->swapBuffers();
    }
}
GLuint GpuFilterRender::updateTexture(int8_t *src, int texId, int width, int height)
{
    GLuint mTextureID;
    if( texId == -1) {
        glGenTextures(1, &mTextureID);
        glBindTexture(GL_TEXTURE_2D, mTextureID);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height,
                     0, GL_LUMINANCE, GL_UNSIGNED_BYTE, src);
    } else {
        glBindTexture(GL_TEXTURE_2D, texId);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
                        GL_LUMINANCE, GL_UNSIGNED_BYTE, src);
        mTextureID = texId;
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    return mTextureID;
}

Recuerde que llenamos los datos de nv21 en el grupo de búfer mNV21Pool cuando alimentamosVideoData. En la devolución de llamada de GLThread-> renderOnDraw, seguimos obteniendo nv21 y luego lo descomponemos en Y / U / V de acuerdo con el formato NV21 (YYYYVU ...) Tres búferes y luego actualice el objeto de textura a tiempo mediante el método updateTexture. Hay un lugar propenso a errores al que prestar atención. La longitud y el ancho de Ybuffer es FrameSize normal, pero la longitud y el ancho de UV es FrameSize / 2. Si no hay una longitud y un ancho correctos correspondientes, glTexImage2D / glTexSubImage2D informará el error SIGSEGV y se bloqueará. Más tarde, se puede renderizar a través de GpuBaseFilter.onDraw.

virtual void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(mGLProgId);
        //绑定顶点坐标 和 纹理坐标
        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);
        // 把Y/U/V分别绑定三个纹理对象
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
        glUniform1i(mGLUniformSampleY, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
        glUniform1i(mGLUniformSampleU, 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
        glUniform1i(mGLUniformSampleV, 2);
        // 经过shader的渲染
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }

 

Todo el proceso pasa por aquí, y aquí ya podemos ver la fuente de video de la cámara que se adapta a la relación de la pantalla. El contenido principal del siguiente capítulo es proporcionar un método universal para el cambio de filtros sin problemas mediante el establecimiento de tres efectos de filtro simples.

Dirección del proyecto: https://github.com/MrZhaozhirong/NativeCppApp

Supongo que te gusta

Origin blog.csdn.net/a360940265a/article/details/104299424
Recomendado
Clasificación