赞
踩
其实自己也是个GL小白,刚入门没多久。最近接手一个项目,由于种种原因吧,对接同学提供给我的是YUV(NV12)格式的byte流数据。之前在GL渲染时只渲染过RGB或者RGBA的数据,还真没渲染过NV12格式的。好在有前人已经整理过相关资料了,这里只是整理记录一下,并纠正下前人的笔误。
本篇文章只是粘贴了部分关键代码。
在此由衷感谢大佬们的相关分享:
1.https://blog.csdn.net/jaccen2012/article/details/78832383#commentBox
这篇博客比较完整简明,但有几处笔误的地方。
2.https://xiaodongxie1024.github.io/2019/06/18/20190618_ios_video_preview/
这篇博客比较完整准确一些。
首先,也是最为关键的一步,我们需要把NV12的byte流数据转成GL纹理。
//Y通道
glGenTextures(2, m_yuvTextureID);
glBindTexture(GL_TEXTURE_2D, m_yuvTextureID[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, nWidth, nHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pY);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(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);
//UV通道
glBindTexture(GL_TEXTURE_2D, m_yuvTextureID[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, nWidth / 2, nHeight / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pUV);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(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);
我们首先生成两个纹理的ID,这两个纹理ID分别对应Y和UV通道。如果是RGBA的数据的话没什么说的,宽高就是图像的尺寸。划重点!这里需要注意的是,Y通道的宽高是UV的2倍,并且Y通道对应GL_LUMINANCE,UV通道对应GL_LUMINANCE_ALPHA
static char strFragmentShader[] = "#ifdef GL_ES \n" "precision highp float; \n" "#else \n" "#define highp \n" "#define mediump \n" "#define lowp \n" "#endif \n" "uniform sampler2D textureY; \n" "uniform sampler2D textureUV; \n" "varying vec2 texcoordOut; \n" " \n" "void main() \n" "{ \n" " vec3 CurResult; \n" " highp vec3 yuv; \n" " yuv.x = texture2D(textureY, texcoordOut).r; \n" " yuv.y = texture2D(textureUV, texcoordOut).r; \n" " yuv.z = texture2D(textureUV, texcoordOut).a; \n" " \n" " gl_FragColor.r = yuv.x; \n" " gl_FragColor.g = yuv.y; \n" " gl_FragColor.b = yuv.z; \n" " gl_FragColor.a = 1.0; \n" "} \n" ; static char strVertexShader[] = "attribute vec3 position; \n" "attribute vec2 texcoord; \n" "varying vec2 texcoordOut; \n" "uniform mat4 mvpMatrix; \n" " \n" "void main() \n" "{ \n" " texcoordOut = texcoord; \n" " gl_Position = mvpMatrix * vec4(position,1.0); \n" "} \n" ;
vs脚本与渲染RGBA基本一致,都是传入纹理坐标顶点坐标即可。不同的是fs脚本,需要关联我们之前创建的Y纹理和UV纹理,具体写法参考如上。**y通道对应Y纹理的r,u通道对应UV纹理的r,v通道对应UV纹理的a。**最后统一赋值给gl_FragColor
写完shader、创建纹理后。中间还有些创建framebuffer、绑定framebuffer、绑定纹理、绘制顶点坐标等步骤。
这些都做完以后,我们需要把绘制好的纹理通过readpixel读出来并返回给NV12的byte流。
读取的过程如下:
byte* pRGBA = ReadPixelBuffer(); byte* pYTempPtr = pY; byte* pRGBATempPtr = pRGBA; for(int i = 0; i < nWidth * nHeight; i++) { *(pYTempPtr++) = pRGBATempPtr[0]; pRGBATempPtr += 4; } byte* pUVTempPtr = pUV; pRGBATempPtr = pRGBA; int width_4 = nWidth * 4; for(int i = 0; i < nHeight / 2; i++) { for(int j = 0; j < nWidth / 2; j++) { pUVTempPtr[0] = pRGBATempPtr[1]; pUVTempPtr[1] = pRGBATempPtr[2]; pUVTempPtr+=2; pRGBATempPtr+=8; } pRGBATempPtr+=width_4; }
经过渲染后,NV12存到了RGBA格式中,r通道存的是y数据,直接1:1读取就行。g通道存的是u数据,b通道存的是v数据,需要通过交错读取才可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。