其实自己也是个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数据,需要通过交错读取才可。