OpenGL如何渲染NV12数据

OpenGL如何渲染NV12数据

    • 引言
    • 参考资料
    • 创建纹理
    • shader脚本
    • 如何readpixel回yuvByte流

引言

其实自己也是个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

shader脚本

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

如何readpixel回yuvByte流

写完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数据,需要通过交错读取才可。

你可能感兴趣的:(OpenGL)