GL C++显示相机YUV视频数据使用帧缓冲FBO后期处理,实现滤镜功能。

        

    一.前言:

        
                GitHub地址:GitHub - wangyongyao1989/WyFFmpeg: 音视频相关基础实现

        系列文章:

        1.   OpenGL Texture C++ 预览Camera视频;

        2.  OpenGL Texture C++ Camera Filter滤镜;

        3.  OpenGL 自定义SurfaceView Texture C++预览Camera视频;

        4.  OpenGL Texture C++ Camera Filter滤镜视频录制;

        5.  OpenGL C++ 视频中添加图片及文字水印播放录制;

        6.  OpenGL C++使用帧缓冲FBO显示相机YUV视频数据;

    显示效果:
————————————————

         

OpenGL帧缓冲FBO后期处理,实现滤镜功能。

二、功能实现的前置知识储备:

  • 本系列文章中1.  OpenGL Texture C++ 预览Camera视频;基于GLSurfaceView创建OpenGL运行环境为基础,把Camera的YUV数据传入OpenGL的片段着色器所创建的三个纹理(Texture)中对视频图像的显示;
  • 本系列文章中2.  OpenGL Texture C++ Camera Filter滤镜;在文章1的基础上对render过程进行优化,传入不同滤镜类型的片段着色器程序。来实现Camera滤镜视频切换显示。
  • 本系列文章中3.  OpenGL 自定义SurfaceView Texture C++预览Camera视频;在文章1的基础上,用自定义的类GLSurfaceView方式创建一个GLThread的OpenGL运行环境,把Camera的YUV数据传入OpenGL的片段着色器所创建的三个纹理(Texture)中对视频图像的显示。
  • 本系列文章中4.  OpenGL Texture C++ Camera Filter滤镜视频录制; 基于文章1/2/3的代码,结合Google开源项目grafika中的WindowSurface.java/Coregl.java/TextureMovieEncoder2.java/VideoEncoderCore.java创建出录制视频的surface并根据此切换GLContext上下文交换(swapBuffer)渲染与显示的视频数据,最终在VideoEncoderCore中通过MediaCodec获取出encodeData写入MediaMuxer成为MP4文件格式的视频。
  • 本系列文章中5:OpenGL C++视频中添加图片及文字水印播放并录制;基于文章1/2/3/4的代码,视频图片水印以另一个着色器程序的形式加入图片纹理,纹理相关的知识可参考本博主的另一篇文章:LearnOpenGL之入门基础-CSDN博客的知识储备;而视频水印文字在前面的基础上再加上另外一个着色器程序的形式加入文字渲染,OpenGL文字渲染可参考本博主的一篇文章:LearnOpenGL之文字渲染_opengl文字渲染接口-CSDN博客。
  • 本篇6:实现使用OpenGL的帧缓存FBO技术,将多个渲染结果合成到最终的图像中,在高级OpenGL的本博主练习集中LearnOpenGL之高级OpenGL(1)有关OpenGL帧缓冲FBO的详细介绍实现。

三、帧缓冲对象的简介:

        帧缓冲对象(Framebuffer Object, FBO)是 OpenGL 中用于管理渲染目标的核心工具。它允许你将渲染结果输出到纹理、渲染缓冲对象(Renderbuffer)或其他自定义的缓冲区,而不是默认的屏幕缓冲区。帧缓冲在图形渲染中具有重要作用。

        以下是帧缓冲的主要作用和应用场景:

      离屏渲染(Off-screen Rendering):

        帧缓冲允许你将场景渲染到一个离屏的缓冲区(例如纹理),而不是直接渲染到屏幕。这在以下场景中非常有用:

  • 反射和折射:将场景渲染到纹理,然后将纹理应用到反射或折射表面(例如镜子、水面)。
  • 环境贴图:生成动态的环境贴图(例如立方体贴图),用于实现动态反射或天空盒。
  • GUI渲染:将复杂的 GUI 渲染到纹理,然后将其作为纹理应用到屏幕上。

        后期处理(Post-processing):

            帧缓冲是实现后期处理的核心工具。通过将场景渲染到纹理,然后对纹理进行处理,可以实现各种视觉效果:

  •  模糊效果(Blur):使用高斯模糊对渲染结果进行处理,实现景深或运动模糊效果。
  •  色调映射(Tone Mapping):将 HDR 渲染结果映射到 LDR 显示范围。

  •  颜色校正:调整亮度、对比度、饱和度等。

  •  边缘检测:使用 Sobel 或 Canny 算法检测边缘,实现卡通渲染或轮廓效果。

        在个人的github的AndroidLearnOpenGL练习集中,GLFBOPostProcessing.cpp实现了运用帧缓冲的后期处理(Post-processing)滤镜效果。

        阴影映射(Shadow Mapping):

                帧缓冲用于生成深度贴图,从而实现动态阴影效果:

  • 阴影映射:从光源视角渲染场景,将深度值存储到帧缓冲的深度附件。
  • 软阴影:通过模糊深度贴图实现软阴影效果。

        多目标渲染(Mulitpe Render Targets,MRT):

                帧缓冲可以附加多个颜色附件,用于多目标渲染(MRT)。这在以下场景中非常有用:

  •  延迟渲染(Deferred Rendering):将几何信息(位置、法线、颜色等)存储到多个纹理,然后在光照阶段使用这些纹理进行计算。
  • G-Buffer:生成几何缓冲区(G-Buffer),用于存储场景的几何和材质信息。

        抗锯齿(Anti-aliasing):

         帧缓冲可以用于实现抗锯齿效果,尤其是多采样抗锯齿(MSAA):

  • 多采样纹理:将场景渲染到多采样纹理,然后解析到普通纹理。
  • 多采样渲染缓冲对象:将场景渲染到多采样渲染缓冲对象,然后解析到普通纹理。

        动态纹理生成:

        帧缓冲可以用于动态生成纹理,例如程序化纹理或动态更新的纹理:

  • 程序化纹理:通过渲染生成程序化纹理(例如噪声纹理、渐变纹理)。
  • 动态贴图:实时更新纹理内容(例如动态水面、动态天空盒)。

        VR渲染:

                在虚拟现实(VR)中,帧缓冲用于分别渲染左右眼的视图:

  • 立体渲染:分别为左右眼生成不同的视图。
  • VR后期处理:对左右眼的渲染结果分别进行后期处理。

        粒子系统和特效:

                帧缓冲可以用于实现复杂的粒子系统和特效:

  • 粒子系统:将粒子渲染到纹理,然后对纹理进行混合或模糊。
  • 屏幕空间特效:例如屏幕空间反射(SSR)、屏幕空间环境光遮蔽(SSAO)。

        图像合成:

                帧缓冲可以用于将多个渲染结果合成到最终的图像中:

  • 分层渲染:将不同的场景元素(例如背景、角色、特效)分别渲染到不同的纹理,然后合成。
  • UI叠加:将 UI 元素渲染到纹理,然后叠加到场景上。

        科学可视化:

                帧缓冲可以用于科学数据的可视化,例如体渲染、流场可视化等:

  • 体渲染:将体数据渲染到纹理,然后进行混合和光照计算。
  • 流场可视化:将流场数据渲染到纹理,然后生成箭头或流线。

        帧缓冲的核心组件:

                帧缓冲由以下组件组成:

  • 颜色附件:用于存储颜色信息(通常是纹理)。
  • 深度附件:用于存储深度信息(可以是纹理或渲染缓冲对象)。
  • 模版附件:用于存储模板信息(可以是纹理或渲染缓冲对象)。

        

 四、GL C++显示相机YUV视频数据使用帧缓冲FBO后期处理,实现滤镜功能的代码实现

        接下的代码来源于系列文章中5中的GLDrawTextVideoRender.cpp的代码改造,并结合练习集AndroidLearnOpenGL中的GLFBOPostProcessing.cpp,实现的帧缓冲FBO后期处理代码合成的。

        以下的全部代码都提交在:   WyFFmpeg/glplay at main · wangyongyao1989/WyFFmpeg · GitHub

        GLFBOPostProcessing.cpp的代码:

//  Author : wangyongyao https://github.com/wangyongyao1989
// Created by MMM on 2025/1/23.
//

#include <android/native_window.h>
#include <android/asset_manager.h>
#include <GLES3/gl3.h>
#include "GLFBOPostProcessing.h"

bool GLFBOPostProcessing::setupGraphics(int w, int h) {
    screenW = w;
    screenH = h;
    LOGI("setupGraphics(%d, %d)", w, h);
    GLuint fBOProgram = fBOShader->createProgram();
    if (!fBOProgram) {
        LOGE("Could not create fBOProgram shaderId.");
        return false;
    }


    glViewport(0, 0, w, h);
    checkGlError("glViewport");
    LOGI("glViewport successed!");

    //清屏
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    checkGlError("glClear");

    //开启深度测试
    glEnable(GL_DEPTH_TEST);

    useYUVProgram();
    createYUVTextures();

    // cube VAO
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(PostProcessingVertices), &PostProcessingVertices,
                 GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
                          (void *) (3 * sizeof(float)));
    glBindVertexArray(0);

    // plane VAO
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(PostProcessingPlaneVertices), &PostProcessingPlaneVertices,
                 GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
                          (void *) (3 * sizeof(float)));
    glBindVertexArray(0);

    // screen quad VAO
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(PostProcessingQuadVertices),
                 &PostProcessingQuadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                          (void *) (2 * sizeof(float)));


    // load and create a texture
    LOGI("load and create a texture!");
    GLenum format;
    if (nrChannels1 == 1) {
        format = GL_RED;
    } else if (nrChannels1 == 3) {
        format = GL_RGB;
    } else if (nrChannels1 == 4) {
        format = GL_RGBA;
    }
//    LOGI("texture1 format==%d", format);
    if (data1) {
        cubeTexture = loadTexture(data1, width1, height1, format);
    }

    if (nrChannels2 == 1) {
        format = GL_RED;
    } else if (nrChannels2 == 3) {
        format = GL_RGB;
    } else if (nrChannels2 == 4) {
        format = GL_RGBA;
    }
    if (data2) {
        floorTexture = loadTexture(data2, width2, height2, format);
    }


    // shader configuration
    // --------------------
    fBOShader->use();
    fBOShader->setInt("texture1", 0);

    createPostProcessingProgram();

    //1.首先要创建一个帧缓冲对象,并绑定它,这些都很直观
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    //2.接下来我们需要创建一个纹理图像,我们将它作为一个颜色附件附加到帧缓冲上。
    // 我们将纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据
    glGenTextures(1, &textureColorbuffer);
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screenW , screenH , 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer,
                           0);

    //3.创建渲染缓冲对象
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenW,
                          screenH);
    //4.将渲染缓冲对象附加到帧缓冲的深度和模板附件上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              rbo);
    //5.最后,我们希望检查帧缓冲是否是完整的,如果不是,我们将打印错误信息
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        LOGE("ERROR::FRAMEBUFFER:: Framebuffer is not complete!");
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    return true;
}

void GLFBOPostProcessing::renderFrame() {

    if (m_filter != m_prevFilter) {
        m_prevFilter = m_filter;
        if (m_filter >= 0 && m_filter < m_fragmentStringPathes.size()) {
            delete_program(screenProgram);
            LOGI("render---m_filter:%d", m_filter);
            screenShader->getSharderStringPath(m_vertexStringPath,
                                               m_fragmentStringPathes.at(m_prevFilter));
            createPostProcessingProgram();
        }
    }

    //绑定到帧缓冲区,像往常一样绘制场景以着色纹理
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    //启用深度测试(禁用渲染屏幕空间四边形)
    glEnable(GL_DEPTH_TEST);

    // 确保清除帧缓冲区的内容
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


    //todo 为甚注释掉这段代码YUV数据绘制不显示?????
    fBOShader->use();
    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 view = mCamera.GetViewMatrix();
    glm::mat4 projection = glm::perspective(glm::radians(mCamera.Zoom),
                                            (float) screenW / (float) screenH, 0.1f, 100.0f);
    fBOShader->setMat4("view", view);
    fBOShader->setMat4("projection", projection);
    // cubes
    glBindVertexArray(cubeVAO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, cubeTexture);
    model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
    fBOShader->setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
    fBOShader->setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    // floor
    glBindVertexArray(planeVAO);
    glBindTexture(GL_TEXTURE_2D, floorTexture);
    fBOShader->setMat4("model", glm::mat4(1.0f));
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);

    //绘制YUV视频数据纹理
    yuvGLShader->use();
    glm::mat4 model1 = glm::mat4(1.0f);
    glm::mat4 view1 = mCamera.GetViewMatrix();
    glm::mat4 projection1 = glm::perspective(glm::radians(mCamera.Zoom),
                                             (float) screenW / (float) screenH, 0.1f, 100.0f);
    yuvGLShader->setMat4("view", view1);
    yuvGLShader->setMat4("projection", projection1);
    if (!updateYUVTextures() || !useYUVProgram()) return;
    model1 = glm::translate(model1, glm::vec3(-1.0f, 0.0f, -1.0f));
    yuvGLShader->setMat4("model", model1);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    //现在绑定回默认帧缓冲区,并使用附加的帧缓冲区颜色纹理绘制一个四边形平面
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //禁用深度测试,这样屏幕空间四边形就不会因为深度测试而被丢弃。
    glDisable(GL_DEPTH_TEST);

    // 清除所有相关缓冲区
    // 将透明颜色设置为白色(实际上并没有必要,因为我们无论如何都看不到四边形后面)
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    screenShader->use();
    glBindVertexArray(quadVAO);
    //使用颜色附着纹理作为四边形平面的纹理
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    checkGlError("glDrawArrays");


    //切换到m_WindowSurface
    m_WindowSurface->makeCurrent();
    m_WindowSurface->swapBuffers();
}

bool GLFBOPostProcessing::setSharderPath(const char *vertexPath, const char *fragmentPath) {
    fBOShader->getSharderPath(vertexPath, fragmentPath);
    return 0;
}

bool GLFBOPostProcessing::setYUVSharderPath(const char *vertexPath, const char *fragmentPath) {
    yuvGLShader->getSharderPath(vertexPath, fragmentPath);
    return 0;
}

bool
GLFBOPostProcessing::setSharderScreenPathes(string vertexScreenPath,
                                            vector<string> fragmentScreenPathes) {
    screenShader->getSharderStringPath(vertexScreenPath, fragmentScreenPathes.front());
    m_vertexStringPath = vertexScreenPath;
    m_fragmentStringPathes = fragmentScreenPathes;
    return 0;
}

void GLFBOPostProcessing::setPicPath(const char *pic1, const char *pic2) {
    LOGI("setPicPath pic1==%s", pic1);
    LOGI("setPicPath pic2==%s", pic2);
    data1 = stbi_load(pic1, &width1, &height1, &nrChannels1, 0);
    data2 = stbi_load(pic2, &width2, &height2, &nrChannels2, 0);
}


GLFBOPostProcessing::GLFBOPostProcessing() {
    fBOShader = new OpenGLShader();
    screenShader = new OpenGLShader;
    yuvGLShader = new OpenGLShader();
}

GLFBOPostProcessing::~GLFBOPostProcessing() {
    cubeTexture = 0;
    floorTexture = 0;
    textureColorbuffer = 0;

    //析构函数中释放资源
    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &planeVAO);
    glDeleteVertexArrays(1, &quadVAO);

    glDeleteBuffers(1, &cubeVBO);
    glDeleteBuffers(1, &planeVBO);
    glDeleteBuffers(1, &quadVBO);

    glDeleteRenderbuffers(1, &rbo);
    glDeleteFramebuffers(1, &framebuffer);

    if (winsurface) {
        winsurface = nullptr;
    }

    if (m_EglCore) {
        delete m_EglCore;
        m_EglCore = nullptr;
    }

    if (m_WindowSurface) {
        delete m_WindowSurface;
        m_WindowSurface = nullptr;
    }


    if (m_pDataY) {
        m_pDataY = nullptr;
    }
    if (m_pDataU) {
        delete m_pDataU;
        m_pDataU = nullptr;
    }
    if (m_pDataV) {
        delete m_pDataV;
        m_pDataV = nullptr;
    }

    fBOShader = nullptr;
    screenShader = nullptr;

    screenProgram = 0;
    m_filter = 0;

    if (data1) {
        stbi_image_free(data1);
        data1 = nullptr;
    }

    if (data2) {
        stbi_image_free(data2);
        data2 = nullptr;
    }

    colorVertexCode.clear();
    colorFragmentCode.clear();

    deleteYUVTextures();

    if (yuvGLShader) {
        delete yuvGLShader;
        yuvGLShader = nullptr;
    }

}

void GLFBOPostProcessing::printGLString(const char *name, GLenum s) {
    const char *v = (const char *) glGetString(s);
    LOGI("OpenGL %s = %s\n", name, v);
}

void GLFBOPostProcessing::checkGlError(const char *op) {
    for (GLint error = glGetError(); error; error = glGetError()) {
        LOGI("after %s() glError (0x%x)\n", op, error);
    }
}

/**
 * 加载纹理
 * @param path
 * @return
 */
int GLFBOPostProcessing::loadTexture(unsigned char *data, int width, int height, GLenum format) {
    unsigned int textureID;
    glGenTextures(1, &textureID);
//    LOGI("loadTexture format =%d", format);
    if (data) {
        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        stbi_image_free(data);
    } else {
        checkGlError("Texture failed to load at path: ");
        stbi_image_free(data);
    }

    return textureID;
}

void GLFBOPostProcessing::setParameters(uint32_t i) {
    m_filter = i;
    LOGI("setParameters---m_filter:%d", m_filter);

}

jint GLFBOPostProcessing::getParameters() {
    return m_filter;
}

void GLFBOPostProcessing::createPostProcessingProgram() {

    screenProgram = screenShader->createProgram();
    if (!screenProgram) {
        LOGE("Could not create screenProgram shaderId.");
        return;
    }
    screenShader->use();
    screenShader->setInt("screenTexture", 0);
}

void GLFBOPostProcessing::delete_program(GLuint &program) {
    if (program) {
        glUseProgram(0);
        glDeleteProgram(program);
        program = 0;
    }
}

void GLFBOPostProcessing::OnSurfaceCreated() {
    m_EglCore = new EglCore(eglGetCurrentContext(), FLAG_RECORDABLE);
    if (!m_EglCore) {
        LOGE("new EglCore failed!");
        return;
    }

    LOGE("OnSurfaceCreated m_ANWindow:%p", m_ANWindow);

    m_WindowSurface = new WindowSurface(m_EglCore, m_ANWindow);
    if (!m_EglCore) {
        LOGE("new WindowSurface failed!");
        return;
    }
    m_WindowSurface->makeCurrent();
}


void GLFBOPostProcessing::surfaceCreated(ANativeWindow *window, AAssetManager *assetManager) {
    m_ANWindow = window;
    postMessage(MSG_PS_SurfaceCreated, false);
}

void GLFBOPostProcessing::surfaceChanged(size_t width, size_t height) {
    postMessage(MSG_PS_SurfaceChanged, width, height);
}

void GLFBOPostProcessing::render() {
    postMessage(MSG_PS_DrawFrame, false);
}

void GLFBOPostProcessing::release() {
    postMessage(MSG_PS_SurfaceDestroyed, false);
}

void GLFBOPostProcessing::handleMessage(LooperMessage *msg) {
    Looper::handleMessage(msg);
    switch (msg->what) {
        case MSG_PS_SurfaceCreated: {
            OnSurfaceCreated();
        }
            break;
        case MSG_PS_SurfaceChanged:
            setupGraphics(msg->arg1, msg->arg2);
            break;
        case MSG_PS_DrawFrame:
            renderFrame();
            break;
        case MSG_PS_SurfaceDestroyed:
//            OnSurfaceDestroyed();
            break;
        default:
            break;
    }
}

void
GLFBOPostProcessing::draw(uint8_t *buffer, size_t length, size_t width, size_t height,
                          float rotation) {
    ps_video_frame frame{};
    frame.width = width;
    frame.height = height;
    frame.stride_y = width;
    frame.stride_uv = width / 2;
    frame.y = buffer;
    frame.u = buffer + width * height;
    frame.v = buffer + width * height * 5 / 4;

    updateFrame(frame);
}

void GLFBOPostProcessing::updateFrame(const ps_video_frame &frame) {
    m_sizeY = frame.width * frame.height;
    m_sizeU = frame.width * frame.height / 4;
    m_sizeV = frame.width * frame.height / 4;

    if (m_pDataY == nullptr || m_width != frame.width || m_height != frame.height) {
        m_pDataY = std::make_unique<uint8_t[]>(m_sizeY + m_sizeU + m_sizeV);
        m_pDataU = m_pDataY.get() + m_sizeY;
        m_pDataV = m_pDataU + m_sizeU;
    }

    m_width = frame.width;
    m_height = frame.height;

    if (m_width == frame.stride_y) {
        memcpy(m_pDataY.get(), frame.y, m_sizeY);
    } else {
        uint8_t *pSrcY = frame.y;
        uint8_t *pDstY = m_pDataY.get();

        for (int h = 0; h < m_height; h++) {
            memcpy(pDstY, pSrcY, m_width);

            pSrcY += frame.stride_y;
            pDstY += m_width;
        }
    }

    if (m_width / 2 == frame.stride_uv) {
        memcpy(m_pDataU, frame.u, m_sizeU);
        memcpy(m_pDataV, frame.v, m_sizeV);
    } else {
        uint8_t *pSrcU = frame.u;
        uint8_t *pSrcV = frame.v;
        uint8_t *pDstU = m_pDataU;
        uint8_t *pDstV = m_pDataV;

        for (int h = 0; h < m_height / 2; h++) {
            memcpy(pDstU, pSrcU, m_width / 2);
            memcpy(pDstV, pSrcV, m_width / 2);

            pDstU += m_width / 2;
            pDstV += m_width / 2;

            pSrcU += frame.stride_uv;
            pSrcV += frame.stride_uv;
        }
    }

    isDirty = true;
}


bool GLFBOPostProcessing::createYUVTextures() {
    auto widthY = (GLsizei) m_width;
    auto heightY = (GLsizei) m_height;

    glActiveTexture(GL_TEXTURE0);
    glGenTextures(1, &m_textureIdY);
    glBindTexture(GL_TEXTURE_2D, m_textureIdY);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, widthY, heightY, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                 nullptr);

    if (!m_textureIdY) {
        LOGE("OpenGL Error Create Y texture");
        return false;
    }

    GLsizei widthU = (GLsizei) m_width / 2;
    GLsizei heightU = (GLsizei) m_height / 2;

    glActiveTexture(GL_TEXTURE1);
    glGenTextures(1, &m_textureIdU);
    glBindTexture(GL_TEXTURE_2D, m_textureIdU);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, widthU, heightU, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                 nullptr);

    if (!m_textureIdU) {
        LOGE("OpenGL Error Create U texture");
        return false;
    }

    GLsizei widthV = (GLsizei) m_width / 2;
    GLsizei heightV = (GLsizei) m_height / 2;

    glActiveTexture(GL_TEXTURE2);
    glGenTextures(1, &m_textureIdV);
    glBindTexture(GL_TEXTURE_2D, m_textureIdV);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, widthV, heightV, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                 nullptr);

    if (!m_textureIdV) {
        LOGE("OpenGL Error Create V texture");
        return false;
    }

    return true;
}

bool GLFBOPostProcessing::updateYUVTextures() {

    if (!m_textureIdY && !m_textureIdU && !m_textureIdV) return false;
//    LOGE("updateTextures m_textureIdY:%d,m_textureIdU:%d,m_textureIdV:%d,===isDirty:%d",
//         m_textureIdY,
//         m_textureIdU, m_textureIdV, isDirty);

    if (isDirty) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_textureIdY);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, (GLsizei) m_width, (GLsizei) m_height, 0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pDataY.get());

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_textureIdU);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, (GLsizei) m_width / 2, (GLsizei) m_height / 2,
                     0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pDataU);

        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_textureIdV);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, (GLsizei) m_width / 2, (GLsizei) m_height / 2,
                     0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pDataV);

        isDirty = false;
        return true;
    }

    return false;
}

void GLFBOPostProcessing::deleteYUVTextures() {
    if (m_textureIdY) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glDeleteTextures(1, &m_textureIdY);

        m_textureIdY = 0;
    }

    if (m_textureIdU) {
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, 0);
        glDeleteTextures(1, &m_textureIdU);

        m_textureIdU = 0;
    }

    if (m_textureIdV) {
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, 0);
        glDeleteTextures(1, &m_textureIdV);
        m_textureIdV = 0;
    }
}

int
GLFBOPostProcessing::createYUVProgram() {

    //创建YUV视频通道着色器程序
    m_yuv_program = yuvGLShader->createProgram();
    LOGI("GLFboDrawTextVideoRender createProgram m_yuv_program:%d", m_yuv_program);

    if (!m_yuv_program) {
        LOGE("Could not create program.");
        return 0;
    }

    //Get Uniform Variables Location
    m_yuv_vertexPos = (GLuint) glGetAttribLocation(m_yuv_program, "position");
    m_textureYLoc = glGetUniformLocation(m_yuv_program, "s_textureY");
    m_textureULoc = glGetUniformLocation(m_yuv_program, "s_textureU");
    m_textureVLoc = glGetUniformLocation(m_yuv_program, "s_textureV");
    m_yuv_textureCoordLoc = (GLuint) glGetAttribLocation(m_yuv_program, "texcoord");

    return m_yuv_program;
}

GLuint GLFBOPostProcessing::useYUVProgram() {
    if (!m_yuv_program && !createYUVProgram()) {
        LOGE("Could not use program.");
        return 0;
    }

    glUseProgram(m_yuv_program);
    glVertexAttribPointer(m_yuv_vertexPos, 3, GL_FLOAT, GL_FALSE, 0, FboPsVerticek);
    glEnableVertexAttribArray(m_yuv_vertexPos);

    glUniform1i(m_textureYLoc, 0);
    glUniform1i(m_textureULoc, 1);
    glUniform1i(m_textureVLoc, 2);
    glVertexAttribPointer(m_yuv_textureCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, FboPsTextureCoord);
    glEnableVertexAttribArray(m_yuv_textureCoordLoc);
    return m_yuv_program;
}

Github地址:

                https://github.com/wangyongyao1989/AndroidLearnOpenGL 

                WyFFmpeg/glplay at main · wangyongyao1989/WyFFmpeg · GitHub

 参考资料:

        中文版LearnOpenGL

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/958350.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3 项目打包并部署到Nginx

一、安装Nginx 官网下载链接&#xff1a; nginx: downloadhttps://nginx.org/en/download.htmlhttps://nginx.org/en/download.html 下载后解压并双击 nginx.exe 启动服务&#xff1a; 打开浏览器&#xff0c;访问 http://localhost/ &#xff0c;若出现如下页面&#xff0c…

二叉树(了解)c++

二叉树是一种特殊的树型结构&#xff0c;它的特点是: 每个结点至多只有2棵子树(即二叉树中不存在度大于2的结点) 并且二叉树的子树有左右之分&#xff0c;其次序不能任意颠倒&#xff0c;因此是一颗有序树 以A结点为例&#xff0c;左边的B是它的左孩子&#xff0c;右边的C是…

会议签到系统的架构和实现

会议签到系统的架构和实现 摘要:通过定制安卓会议机开机APP呈现签到界面&#xff0c;并且通过W/B结构采集管理签到信息&#xff0c;实现会议签到的功能。为达到此目标本文将探讨使用Redis提供后台数据支持&#xff1b;使用SocketIo处理适时消息&#xff1b;使用Flask进行原型开…

PIC单片机HEX文件格式分析

在调试PIC单片机在bootloader程序时&#xff0c;需要将hex文件转换为bin文件&#xff0c;在转换之前先了解一下hex文件中数据是如何定义的。 直接打开一个LED灯闪烁的程序生成的hex文件&#xff0c;芯片型号为PIC18F46K80 可以看到每条数据都是由6部分组成的&#xff0c;下面分…

17-使用椭圆制作鼻子

17-使用椭圆制作鼻子_哔哩哔哩_bilibili17-使用椭圆制作鼻子是一次性学会 Canvas 动画绘图&#xff08;核心精讲50个案例&#xff09;2023最新教程的第18集视频&#xff0c;该合集共计53集&#xff0c;视频收藏或关注UP主&#xff0c;及时了解更多相关视频内容。https://www.bi…

通过 Visual Studio Code 启动 IPython

在Visual Studio Code 中&#xff0c;你可以使用内置的终端来启动 ipython&#xff0c;当然首先要安装好ipython。 安装ipython的方法是在cmd里面输入以下命令安装&#xff1a; pip install ipython 启动ipython的步骤如下&#xff1a; 打开 VSCode 终端&#xff1a; 在 VSCo…

网络(三) 协议

目录 1. IP协议; 2. 以太网协议; 3. DNS协议, ICMP协议, NAT技术. 1. IP协议: 1.1 介绍: 网际互连协议, 网络层是进行数据真正传输的一层, 进行数据从一个主机传输到另一个主机. 网络层可以将数据主机进行传送, 那么传输层保证数据可靠性, 一起就是TCP/IP协议. 路径选择: 确…

Qt基础项目篇——Qt版Word字处理软件

一、核心功能 本软件为多文档型程序&#xff0c;界面是标准的 Windows 主从窗口 拥有&#xff1a;主菜单、工具栏、文档显示区 和 状态栏。 所要实现的东西&#xff0c;均在下图了。 开发该软件&#xff0c;主要分为下面三个阶段 1&#xff09;界面设计开发 多窗口 MDI 程序…

Python+langchain+通义千问qwen(大模型实现自己的聊天机器人)

Langchain langchain是一个用于开发由语言模型驱动的应用程序的框架&#xff0c;致力于简化AI模型应用的开发。简单来说&#xff0c;langchain就是一个&#xff08;帮助开发者轻松完成AI模型应用开发的&#xff09;框架,现在支持python和js两个版本&#xff0c;它集成多种大语…

FPGA中场战事

2023年10月3日,英特尔宣布由桑德拉里维拉(Sandra Rivera)担任“分拆”后独立运营的可编程事业部首席执行官。 从数据中心和人工智能(DCAI)部门总经理,转身为执掌该业务的CEO,对她取得像AMD掌门人苏姿丰博士类似的成功,无疑抱以厚望。 十年前,英特尔花费167亿美元真金白银…

【超详细】ELK实现日志采集(日志文件、springboot服务项目)进行实时日志采集上报

本文章介绍&#xff0c;Logstash进行自动采集服务器日志文件&#xff0c;并手把手教你如何在springboot项目中配置logstash进行日志自动上报与日志自定义格式输出给logstash。kibana如何进行配置索引模式&#xff0c;可以在kibana中看到采集到的日志 日志流程 logfile-> l…

探索Linux中的进程控制:从启动到退出的背后原理

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; 进程控制是操作系统对进程的创建、运行、调度、中止等活动进行管理和协调的行为。它是操作系统中至关重要的一部分&#xff0c;保证多任务处理环境下的资源分配和系统稳定性。 进程创建 fork( ) fork() 调…

【ComfyUI】python调用生图API,实现批量出图

官方给的示例&#xff1a; https://github.com/comfyanonymous/ComfyUI/blob/master/script_examples/websockets_api_example.pyhttps://github.com/comfyanonymous/ComfyUI/blob/master/script_examples/websockets_api_example.pyhttps://github.com/comfyanonymous/ComfyU…

推箱子游戏

java小游戏2 一游戏介绍 二图像准备 墙、箱子、人、箱子目的地&#xff0c;人左边、人右边、人上边、人下边 三结构准备 地图是什么&#xff0c;我们把地图想象成一个网格&#xff0c;每个格子就是工人每次移动的步长&#xff0c;也是箱子移动的距离&#xff0c;设置一个二维数…

软件鉴定测试重要性和流程分享

在当今快速发展的数字时代&#xff0c;软件系统的可靠性和安全性变得至关重要。越来越多的企业意识到&#xff0c;进行专业的软件鉴定与测试不仅是保证产品质量的必要步骤&#xff0c;也是提升市场竞争力的重要手段。软件鉴定测试指通过对软件进行评估和验证&#xff0c;以确保…

Linux C\C++编程-建立文件和内存映射

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 Linu…

15-spring整合mybatis方式一

spring整合mybatis 方式一【重要】 步骤: 1.导入相关jar包 junit mybatis mysql数据库 spring相关的 aop织入 mybatis-spring 【new】 junit junit 4.12 mysql mysql-connector-java 8.0.23 org.mybatis mybatis 3.5.2 org.springframework spring-webmvc 5…

基于深度学习的视觉检测小项目(十五) 用户的登录界面

用户管理离不开的是消息框&#xff08;QMessageBox&#xff09;和对话框&#xff08;QDialog&#xff09;&#xff0c;比如对话框用于用户名和密码输入&#xff0c;消息框用于提示登录成功、密码错误。 • 基础知识&#xff1a;PySide6&#xff08;PyQT5&#xff09;的常用对话…

什么是COLLATE排序规则?

在当今数字化世界中&#xff0c;数据的整理、比较和排序是至关重要的。在数据库管理和编程语言中&#xff0c;我们经常需要对字符串进行排序&#xff0c;以展示或处理信息。为了实现这一点&#xff0c;各种系统和工具提供了排序规则&#xff0c;其中COLLATE排序规则就是其中的一…

Linux:信号的保存[2]

1.信号在内核中的表示 因为是三种内核的数据结构&#xff0c;操作系统就可以通过操作这些数据结构提供接口。 如果一个信号没有产生&#xff0c;并不妨碍它可以先被阻塞。 当同一时刻发送大量相同信号时&#xff0c;会丢失。 2.信号的捕捉&#xff08;重点&#xff09; 信号产…