OpenGL.Shader: Чжигэ учит писать живого клиента фильтра (3) OpenGL рендеринг NV21 (YUV)

OpenGL.Shader: Жиге учит писать живого клиента с фильтрами (3)

 

В последней главе говорилось о двух моментах: первый - это пул буферов данных кадра, который кэширует данные кадра NV21 (стек вызовов - JClass Activity-> JClass CFEScheduler-> JCallback Camera.onPreviewFrame-> JNIMethod feedVideoData) ; второй - setRotationCamera, Отрегулируйте горизонтальное и вертикальное перевороты в соответствии с углом поворота (стек вызовов - JClass Activity-> JClass CFEScheduler-> JMethod setUpCamera-> JNIMethod setRotationCamera) ; оба в конечном итоге запускают adjustFrameScaling, настраивают масштабирование изображения кадра и генерируют вершины для рендеринга OpenGL. Данные координат и данные координат текстуры.

Следующим шагом является фактическая визуализация данных изображения на носителе экрана. Этот раз несколько отличается от записи водяного знака, представленной в предыдущей статье.Метод, представленный в предыдущей статье, заключается в использовании объекта текстуры, подготовленного системой, и мы можем использовать идентификатор текстуры для непосредственного управления им. Но на этот раз есть только самые примитивные данные кадра изображения формата NV21, ключевой проблемой стало то, как эффективно визуализировать данные кадра изображения на выходной поверхности носителя.

Еще один вопрос, на который стоит обратить внимание: как плавно переключаться после добавления различных эффектов фильтра? (А пока поставьте здесь точку) Без лишних слов покажите код.

Сначала взгляните на содержимое трех обратных вызовов жизненного цикла GLThread + GLRender: (Если у вас есть вопросы, обратитесь к предыдущей статье 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;
    }
}

Очевидно, что этот GpuBaseFilter будет ключевым классом, играющим жизненно важную роль во всем процессе рендеринга фильтра.
Так что быстро взгляните на то, что делает этот 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

Длина немного велика. Это базовый класс, созданный стандартным C ++. Функции, начинающиеся с ключевого слова virtual, будут переписаны унаследованным классом (см. Соответствующие сведения в Baidu). Сосредоточьтесь на конструкторе шейдера и методе onDraw для рендеринга.

Сначала посмотрите на вершинный шейдер NO_FILTER_VERTEX_SHADER:

позиция атрибута vec4;
атрибут vec4 inputTextureCoordinate;
варьирующаяся vec2 textureCoordinate;
void main ()
{     gl_Position = position;     textureCoordinate = inputTextureCoordinate.xy; }


Контент несложный, координаты вершин выводятся по правилам, а координаты текстуры передаются во фрагментный шейдер.

Затем посмотрите на NO_FILTER_FRAGMENT_SHADER, который является одним из основных в этой статье.

прецизионный поплавок среднего размера;
различная текстура highp vec2 textureCoordinate;
унифицированный сэмплер 2D SamplerRGB;
однородный пробоотборник 2D SamplerY;
однородный сэмплер2D SamplerU;
равномерный пробоотборник 2D SamplerV;
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 = texture2D (SamplerY, pos) .r;     yuv.y = texture2D (SamplerU, pos) .r - 0,5;     yuv.z = texture2D (SamplerV, pos) .r - 0,5;     вернуть colorConversionMatrix * yuv; } void main () {








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

Очевидно, здесь делается подготовка для двух рук. SamplerRGB - это цветовая текстура. Код, используемый для комментирования gl_FragColor, напрямую получен из координат текстуры; SamplerY / U / V можно увидеть по имени, чтобы знать, что три компонента хранятся отдельно в трех компонентах формата yuv. , А затем используйте общую формулу yuv to rgb:

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

Приведенная выше формула представлена ​​в десятичной форме без определения количества. Что такое количественная оценка?
Квантование заключается в том, чтобы сделать Y, U или V в определенном диапазоне посредством линейного преобразования, предполагая, что Y / U / V ~ (0-255) до квантования, а параметры после квантования: Y ~ (16,235) U ~ (16-240) V ~ (16-240), пусть Y (0,255) изменится на Y '(16,235) и сделайте это так: Y' = Y * [(235-16) / 255] +16.

Другой момент заключается в том, что матрица OpenGL - это сначала столбцы, поэтому при настройке матрицы необходимо обращать внимание на чередование строк и столбцов. 

 

После того, как шейдер готов, следующим шагом будет рендеринг для 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;
}

Напомним, что мы заполняем данные nv21 в пуле буферов mNV21Pool, когда передаем VideoData. В обратном вызове GLThread-> renderOnDraw мы продолжаем получать nv21, а затем разлагаем его на Y / U / V в соответствии с форматом NV21 (YYYYVU ...) Три буфера, а затем своевременно обновлять объект текстуры с помощью метода updateTexture. Здесь есть место, подверженное ошибкам. Длина и ширина Ybuffer нормальные FrameSize, но длина и ширина UV равны FrameSize / 2. Если нет правильной соответствующей длины и ширины, glTexImage2D / glTexSubImage2D сообщит об ошибке SIGSEGV и о сбое. Позже его можно будет отобразить с помощью 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);
    }

 

Здесь проходит весь процесс, и здесь мы уже можем видеть источник видео с камеры, который адаптируется к соотношению сторон экрана.Основное содержание следующей главы - предоставить универсальный метод плавного переключения фильтров путем установки трех простых эффектов фильтра.

Адрес проекта: https://github.com/MrZhaozhirong/NativeCppApp

рекомендация

отblog.csdn.net/a360940265a/article/details/104299424