当前位置: 首页 > news >正文

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 VAOglGenVertexArrays(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 VAOglGenVertexArrays(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 VAOglGenVertexArrays(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 textureLOGI("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);// cubesglBindVertexArray(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);// floorglBindVertexArray(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_WindowSurfacem_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 Locationm_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

        

相关文章:

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

一.前言&#xff1a; GitHub地址&#xff1a;GitHub - wangyongyao1989/WyFFmpeg: 音视频相关基础实现 系列文章&#xff1a; 1. OpenGL Texture C 预览Camera视频&#xff1b; 2. OpenGL Texture C Camera Filter滤镜; 3. OpenGL 自定义SurfaceView Texture C预览Camera视…...

一文了解树与森林基础

文章目录 树和森林1树的存储结构1.1双亲表示法1.2孩子表示法1.3孩子兄弟表示法 2树、森林与二叉树的转换2.1森林与二叉树的转换2.2 树与二叉树的转换 3树和森林的遍历3.1树的遍历3.2森林的遍历3.3 树和森林的遍历与二叉树的遍历关系 4树的应用——并查集4.1并查集及其相关操作4…...

在Docker 容器中安装 Oracle 19c

在 Docker 容器中安装 Oracle 19c 是可行的&#xff0c;但它相较于其他数据库&#xff08;如 MySQL、PostgreSQL 等&#xff09;会复杂一些&#xff0c;因为 Oracle 数据库有一些特定的要求&#xff0c;如操作系统和库的依赖&#xff0c;以及许可证问题。 不过&#xff0c;Ora…...

Java TCP协议(2)

TCP可靠传输 五. 流量控制 用来控制发送方的窗口大小&#xff0c;通过接收方返回来的ACK进行反制。 接收方把自己能够处理的数据量主动告诉发送方&#xff0c;从而让发送方动态调整窗口大小。 如果窗口大小为0表示没有空间去接收数据了&#xff0c;主机A就不发数据了&#xf…...

JS基础-操作数组(7)

一.增删改查 1.改 重新赋值 2.增 arr.puch() 末尾追加 arr.unshift() 开头追加 a)案例&#xff1a;数组筛选 3.删除 arr.pop() 删除最后一个元素 arr.shift() 删除第一个元素 splice&#xff08;&#xff09; 删除指定元素...

(长期更新)《零基础入门 ArcGIS(ArcScene) 》实验七----城市三维建模与分析(超超超详细!!!)

城市三维建模与分析 三维城市模型已经成为一种非常普遍的地理空间数据资源,成为城市的必需品,对城市能化管理至关重要。语义信息丰富的三维城市模型可以有效实现不同领域数据与IS相信息的高层次集成及互操作,从而在城市规划、环境模拟、应急响应和辅助决策等众多领域公挥作用、…...

大数据技术笔记

大数据技术概述 本章初步介绍大数据领域技术涉及的一些基础理论&#xff0c;如分布式、存储、网络等知识。 分布式理论 大数据意味数据量大&#xff0c;那么存储和计算数据的节点就不大可能只有一个&#xff0c;而是采用分而治之的思想在多个节点中存储和计算&#xff0c;提…...

【JAVA 基础 第(20)课】JDBC JAVA 连接 MySql 数据库

pom.xml 导入 MySql jar 包 <!-- 导入Mysql数据库链接jar包 --> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.30</version> </dependency> 数据库驱动、连接封装成…...

如何将使用unsloth微调的模型部署到ollama?

目录 一、将模型保存为gguf格式 二、下载llama.cpp 三、生成 llama-quantize 可执行文件 四、使用llama-quantize 五、训练模型 六、将模型部署到ollama 一、将模型保存为gguf格式 在你的训练代码 trainer.train() 之后添加&#xff1a; model.save_pretrained_gguf(&q…...

Go语言中的值类型和引用类型特点

一、值类型 值类型的数据直接包含值&#xff0c;当它们被赋值给一个新的变量或者作为参数传递给函数时&#xff0c;实际上是创建了原值的一个副本。这意味着对新变量的修改不会影响原始变量的值。 Go中的值类型包括&#xff1a; 基础类型&#xff1a;int&#xff0c;float64…...

grafana新增email告警

选择一个面板 比如cpu 新增一个临界点表达式 input选A 就是A的值达到某个临界点 触发告警 我这边IS ABOVE0.15就是cpu大于0.15%就触发报警&#xff0c;这个值怎么填看指标的值显示 这里要设置一下报警条件 这边随便配置下 配置标签和通知&#xff0c;选择你的邮件 看下告警…...

基于Spring Security 6的OAuth2 系列之六 - 授权服务器--自定义授权页面

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…...

MyBatis-Plus的插件

一、分页插件 1.自带的 启动类 在启动类里配置分页相关内容 package com.qcby;import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inne…...

基于Redis实现短信验证码登录

目录 1 基于Session实现短信验证码登录 2 配置登录拦截器 3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效 4 Session集群共享问题 5 基于Redis实现短信验证码登录 6 Hash 结构与 String 结构类型的比较 7 Redis替代Session需要考虑的问题 8 …...

HarmonyOS Next构建工具 lycium 原理介绍

HarmonyOS Next构建工具 lycium 原理介绍 背景介绍 HarmonyOS Next中很多系统API是以C接口提供&#xff0c;如果要使用C接口&#xff0c;必须要使用NAPI在ArkTS与C间交互&#xff0c;这种场景在使用DevEco-Studio中集成的交叉编译工具&#xff0c;以及cmake构建工具就完全够用…...

蓝桥杯例题一

不管遇到多大的困难&#xff0c;我们都要坚持下去。每一次挫折都是我们成长的机会&#xff0c;每一次失败都是我们前进的动力。路漫漫其修远兮&#xff0c;吾将上下而求索。只有不断努力奋斗&#xff0c;才能追逐到自己的梦想。不要害怕失败&#xff0c;害怕的是不敢去尝试。只…...

MySQL可直接使用的查询表的列信息

文章目录 背景实现方案模板SQL如何查询列如何转大写如何获取字符位置如何拼接字段 SQL适用场景 背景 最近产品找来&#xff0c;想让帮忙出下表的信息&#xff0c;字段驼峰展示&#xff0c;每张表信息show create table全部展示&#xff0c;再逐个粘贴&#xff0c;有点太耗费时…...

输入网址到网页显示,发生了什么--讲述

输入www.baidu.com作为网址&#xff0c; 孤身的人-HTTP 浏览器要做的第一步就是 解析URL&#xff0c;根据url里面的资源路径&#xff0c;确认服务器资源和路径&#xff0c;生成http请求消息&#xff0c;包括请求消息&#xff08;请求行 消息头 请求体&#xff09; 举例&am…...

npm install 报错:Command failed: git checkout 2.2.0-c

[TOC](npm install 报错&#xff1a;Command failed: git checkout 2.2.0-c) npm install 报错&#xff1a;Command failed: git checkout 2.2.0-c export NODE_HOME/usr/local/node-v14.14.0-linux-x64 npm config set registry https://registry.npmmirror.com 使用如上环…...

[Day 15]54.螺旋矩阵(简单易懂 有画图)

今天我们来看这道螺旋矩阵&#xff0c;和昨天发的题很类似。没有技巧&#xff0c;全是循环。小白也能懂~ 力扣54.螺旋矩阵 题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; …...

react antd点击table单元格文字下载指定的excel路径

在使用 Ant Design (antd) 的 Table 组件时&#xff0c;如果想点击表格单元格中的文字来触发下载指定路径的 Excel 文件&#xff0c;可以通过以下步骤实现&#xff1a; 1. 确保有一个可供下载的 Excel 文件&#xff1a;需要有一个服务器端点或者一个可以直接访问的 URL&#xf…...

什么是数据结构

数据结构 如何有效的存储数据。 数据存储方式 物理结构又称存储结构 在内存中存储状态&#xff0c;数据可以选择集中存放&#xff08;顺序存储结构&#xff09;&#xff0c;也可以选择分散存放&#xff08;链式存储结构&#xff09;。 逻辑结构 数据之间的逻辑关系&#…...

把 PVE 下的机械硬盘(非SSD系统盘)分配给虚拟机使用

PVE 挂在硬盘 参考 Ubuntu 24.04 LTS 空闲硬盘挂载到 文件管理器的 other locations。 在 PVE shell 中根据上面教程挂在硬盘 新建分享目录 参考 Proxmox VE&#xff08;PVE&#xff09;添加硬盘做存储 虚拟机新增硬盘 虚拟机 关机&#xff0c;按下图新增硬盘 新增硬盘…...

HTML5 Web Worker 的使用与实践

引言 在现代 Web 开发中&#xff0c;用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应&#xff0c;用户很可能会流失。HTML5 引入了 Web Worker&#xff0c;它允许我们在后台运行 JavaScript 代码&#xff0c;从而避免阻塞主线程&#xff0c;保…...

把网站程序数据上传到服务器的方法和注意事项

将网站程序数据上传到服务器是一个常见的网站开发和部署流程。主要涉及到FTP上传、FileZilla、rsync(在Linux下)、或其他相关的文件同步工具。以下是一般步骤和方法&#xff1a; 使用FTP&#xff1a; 1. 选择FTP客户端软件&#xff1a; - 常见的FTP客户端包括FileZilla(开源)、…...

YOLOv5训练自己的数据及rknn部署

YOLOv5训练自己的数据及rknn部署 一、下载源码二、准备自己的数据集2.1 标注图像2.2 数据集结构 三、配置YOLOv5训练3.1 修改配置文件3.2 模型选择 四、训练五、测试六、部署6.1 pt转onnx6.2 onnx转rknn 七、常见错误7.1 训练过程中的错误7.1.1 cuda: out of memory7.1.2 train…...

李沐vscode配置+github管理+FFmpeg视频搬运+百度API添加翻译字幕

终端输入nvidia-smi查看cuda版本 我的是12.5&#xff0c;在网上没有找到12.5的torch&#xff0c;就安装12.1的。torch&#xff0c;torchvision&#xff0c;torchaudio版本以及python版本要对应 参考&#xff1a;https://blog.csdn.net/FengHanI/article/details/135116114 创…...

Python 在Word中添加、或删除超链接

在Word文档中&#xff0c;超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超链接&#xff0c;用户可以轻松地导航到相关信息&#xff0c;从而增强文档的互动性和可读性。本文将介绍如何使用Python在Word中添加超链接、或删除Word文档中的超…...

在K8S中使用Values文件定制不同环境下的应用配置详解

在Kubernetes&#xff08;简称K8s&#xff09;环境中&#xff0c;应用程序的配置管理是一项关键任务。为了确保应用程序在不同环境&#xff08;如开发、测试、预发布和生产&#xff09;中都能稳定运行&#xff0c;我们需要为每个环境定制相应的配置。Values文件是在使用Helm管理…...

elementUI Table组件实现表头吸顶效果

需求描述 当 table 内容过多的时候&#xff0c;页面上滑滚动&#xff0c;表头的信息也会随着被遮挡&#xff0c;无法将表头信息和表格内容对应起来&#xff0c;需要进行表头吸顶 开始编码&#x1f4aa; 环境&#xff1a;vue2.6、element UI step1&#xff1a; 给el-table__h…...

JS-Web API -day06

一、正则表达式 正则表达式测试工具: http://tool.oschina.net/regex 1.1 正则表达式介绍与语法 正则表达式&#xff1a; 正则表达式&#xff08;Regular Expression&#xff09;是用于匹配字符串中字符组合的模式。在 JavaScript中&#xff0c;正则表达式也是对象。通常用来查…...

qml MenuItem详解

1、概述 MenuItem 是 QML&#xff08;Qt Modeling Language&#xff09;中用于表示菜单项的组件。它通常作为 Menu 组件的子项出现&#xff0c;用于提供用户可点击的菜单选项。MenuItem 可以包含文本、图标&#xff0c;甚至可以是其他 MenuItem 或 Menu 的容器&#xff0c;从而…...

汇编实验·系统调用

一、实验目的: 1.掌握基于特定操作系统中调用API或者SYSTEMCALL的基本方法。 2.进一步理解高级语言中函数调用的相关规定和约定(stdcall,cdec,fastcall等) 3.IA-32架构下API参数在汇编中的实现方式和约定。 二、实验内容 1.在课程设定的VS2022社区版的汇编开发环境下,完…...

ubuntu调用图形化网络测试工具

在 Ubuntu 中&#xff0c;除了命令行工具外&#xff0c;还有一些图形化的网络测试工具可以帮助你更直观地测试和分析网络性能。以下是几款常用的图形化网络测试工具及其使用方法&#xff1a; 1. gnome-nettool gnome-nettool 是一个简单的图形化网络工具集&#xff0c;包含 pi…...

【Qt】05-菜单栏

做菜单 前言一、创建文件二、菜单栏 QMenuBar2.1 示例代码2.2 运行结果 三、工具栏 QToolBar3.1 运行代码3.2 结果分析 四、状态栏 QStatusBar4.1 运行代码4.2 运行结果 五、文本编辑框 QTextEdit5.1 运行代码5.2 运行结果 六、浮动窗口 addDockWidget6.1 运行代码6.2 运行结果…...

Git知识分享

一、理解git首先要理清楚下面五个概念&#xff1a; 1、工作区(git add 命令之前的样子) 2、stash 暂存(暂存工作区和暂存区的更改) 3、暂存区(git add 命令之后的存储区, 4、本地仓库(git commit提交的位置) 5、远程仓库(git push提交的位置) 二、git常用命令&#xff1a; 1、g…...

细说STM32F407单片机电源低功耗StandbyMode待机模式及应用示例

目录 一、待机模式基础知识 1、进入待机模式 2、待机模式的状态 3、退出待机模式 二、待机模式应用示例 1、示例功能和CubeMX项目设置 &#xff08;1&#xff09; 时钟 &#xff08;2&#xff09; DEBUG、LED1、KeyRight、USART6、CodeGenerator &#xff08;3&#x…...

独立站运营新突破:Clock斗篷技术助力商家降本增效

一、引言 在当今竞争激烈的电商市场中&#xff0c;独立站运营已成为众多商家拓展业务、打造品牌的重要途径。然而&#xff0c;推广成本高企一直是困扰独立站商家的难题。许多商家在推广过程中&#xff0c;由于缺乏有效的策略&#xff0c;往往面临高昂的费用和有限的回报。但事实…...

【python】subprocess.Popen执行adb shell指令进入linux系统后连续使用指令,出现cmd窗口阻塞问题

问题描述 subprocess.Popen执行adb shell指令进入linux系统后出现cmd窗口阻塞问题&#xff0c;需要手动关闭cmd才会继续执行其他指令。 解决方案 1、cmd指令后面加入exit\n关闭exe进程 2、subprocess.Popen()添加内置参数creationflagssubprocess.CREATE_NO_WINDOW隐藏窗口弹…...

10天学会flutter DAY2 玩转dart 类

print(point.y); * 使用 ?. 代替. 可以避免因为左边表达式为null 而导致的问题 (这个是flutter 2.0 之后新增的空认证功能)print(point?.x); print(point?.y); * 如下代码所示p1.y 6; **setter** 写入方法, print(p1.y); **getter** 读取方法p1.y 6; print(p1.y); […...

【C++】string类模拟实现

目录 &#x1f495;1.模拟string类构造函数 &#x1f495;2.模拟构造函数实现 &#x1f495;3.拷贝构造函数模拟实现 &#x1f495;4.析构函数模拟实现 &#x1f495;5.size函数&#xff0c;capacity函数模拟实现 &#x1f495;6.begin函数,end函数&#xff0c;模拟实…...

2025发文新方向:AI+量化 人工智能与金融完美融合!

2025深度学习发论文&模型涨点之——AI量化 人工智能的融入&#xff0c;使量化交易实现了质的突破。借助机器学习、深度学习等先进技术&#xff0c;人工智能可高效处理并剖析海量市场数据&#xff0c;挖掘出数据背后错综复杂的模式与趋势&#xff0c;从而不仅提升了数据分析…...

eniops库中reduce函数使用方法

reduce 是 eniops 中的一个常用函数&#xff0c;用于对张量进行降维操作。它允许你通过指定维度名称和操作类型&#xff08;如求和、均值等&#xff09;来简化张量的形状。 import eniops import torch# 创建一个示例张量 x torch.randn(2, 3, 4)# 使用 reduce 进行降维操作 …...

第03章 02 VTK中的智能指针

在VTK&#xff08;Visualization Toolkit&#xff09;中&#xff0c;智能指针用于管理对象的生命周期&#xff0c;避免内存泄漏和悬空指针等问题。VTK提供了几种不同类型的智能指针&#xff0c;包括vtkNew、vtkSmartPointer和vtkWeakPtr。以下是它们的区别和作用&#xff1a; …...

03垃圾回收篇(D4_彻底理解GC)

目录 一、浅析大促备战过程中出现的 fullGc&#xff0c;我们能做什么&#xff1f; 1. 什么是 JVM 的 GC? 2. 写代码的时候能做什么&#xff1f; 3. 测试能做啥 4. 知识小结 二、MinorGC、MajorGC、FullGC垃圾回收介绍 1. MinorGC &#xff08;新生代垃圾回收&#xff09…...

C语言小项目——通讯录

功能介绍&#xff1a; 1.联系人信息&#xff1a;姓名年龄性别地址电话 2.通讯录中可以存放100个人的信息 3.功能&#xff1a; 1>增加联系人 2>删除指定联系人 3>查找指定联系人的信息 4>修改指定联系人的信息 5显示所有联系人的信息 6>排序&#xff08;名字&…...

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…...

【理论】测试开发工程师进阶路线

一、腾讯与阿里的质量保证服务参考 阿里云效测试能力与架构 腾讯 WeTest 测试能力全景图 二、测试开发技术体系 1.用户端测试&#xff1a; Web/App 测试 Web/App 自动化测试 用户端专项测试 用户端安全测试 2.服务端测试&#xff1a; 接口协议与 Mock 接口自动化测试 服务端…...

能源新动向:智慧能源平台助力推动新型电力负荷管理系统建设

背景 国家能源局近日发布《关于支持电力领域新型经营主体创新发展的指导意见》&#xff0c;鼓励支持具备条件的工业企业、工业园区等开展智能微电网建设&#xff0c;通过聚合分布式光伏、分散式风电、新型储能、可调节负荷等资源&#xff0c;为电力系统提供灵活调节能力&#x…...

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

WPF基础 | 深入 WPF 事件机制&#xff1a;路由事件与自定义事件处理 一、前言二、WPF 事件基础概念2.1 事件的定义与本质2.2 常见的 WPF 事件类型 三、路由事件3.1 路由事件的概念与原理3.2 路由事件的三个阶段3.3 路由事件的标识与注册3.4 常见的路由事件示例 四、自定义事件处…...