一.前言:
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