Android中使用OpenGL ES渲染MediaCodec输出Image异常排查
问题分析:MediaCodec解码YUV通过EGL渲染异常
问题背景
使用MediaCodec解码视频时,因无法为Codec配置Surface,改用EGL手动渲染YUV数据。解码逻辑为从MediaCodec获取输出Image,传入YuvDrawer后触发渲染,但运行画面异常。
解码核心代码:
videoDecoder.setStateListener(object : DefDecoderStateListener { override fun decodeOneFrame( codec: MediaCodec, // 已配置格式为MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible bufferInfo: MediaCodec.BufferInfo?, outputBuffers: Array<ByteBuffer>, index: Int ) { val image = codec.getOutputImage(index)!! yuvDrawer.setImage(image) mRenderer.notifySwap(bufferInfo?.presentationTimeUs ?: 0) } })
YuvDrawer存在的核心问题
1. 纹理参数设置错误
在activateTexture()方法中,错误地为GLES11Ext.GL_TEXTURE_EXTERNAL_OES类型纹理设置参数,但实际使用的是普通GL_TEXTURE_2D纹理,导致纹理采样规则不生效,画面会出现拉伸或失真。
2. YUV转RGB公式错误
片段着色器中对U、V通道的采样和转换公式存在问题:
- 采样U、V时错误取了g/b通道,而
GL_LUMINANCE格式的纹理,r/g/b通道值完全相同,应统一取r通道 - 转换系数不符合标准YUV420转RGB的公式,会导致颜色偏移、失真
3. 忽略Image缓冲区的行步长(rowStride)
MediaCodec输出的Image Plane存在rowStride(每行字节数),其值可能大于视频宽度,直接将原始ByteBuffer传入glTexImage2D会因数据不连续导致画面扭曲、错位。
4. 着色器属性与Java代码不匹配
- 顶点着色器中纹理属性名为
aTexture,但Java代码中获取的是aCoordinate,导致纹理坐标无法正确传递,画面可能全黑或错乱 - 顶点着色器未使用
uMatrix矩阵,导致投影矩阵完全无效,画面拉伸比例错误
5. Alpha属性处理错误
Java代码中将alpha作为attribute传递,但片段着色器未接收使用,且glVertexAttrib1f的调用属于无效操作;若需支持透明度,应将alpha改为uniform变量。
6. 纹理资源泄漏
release()方法中仅删除了mTextureId,但实际生成了3个纹理(textures数组),导致GL资源泄漏。
修复建议
针对上述问题,可逐一修正:
- 修正纹理参数:将
activateTexture()中的GLES11Ext.GL_TEXTURE_EXTERNAL_OES替换为GLES20.GL_TEXTURE_2D - 修正YUV转RGB公式:更新片段着色器代码为标准转换逻辑:
y = texture(textureY, vTexture).r; u = texture(textureU, vTexture).r - 0.5; v = texture(textureV, vTexture).r - 0.5; rgb.r = y + 1.402 * v; rgb.g = y - 0.344 * u - 0.714 * v; rgb.b = y + 1.772 * u;
- 处理行步长:通过
glPixelStorei(GLES20.GL_UNPACK_ROW_LENGTH, rowStride)指定非连续缓冲区的行步长,或复制数据到连续ByteBuffer中 - 对齐着色器属性:将顶点着色器中的
aTexture改为aCoordinate,并添加矩阵应用逻辑:
uniform mat4 uMatrix; void main() { gl_Position = uMatrix * a_Position; vTexture = aCoordinate; }
- 修复Alpha处理:将
alpha改为uniform变量,在片段着色器中控制透明度:
uniform float alpha; // ... FragColor = vec4(rgb, alpha);
Java代码中用glUniform1f(mAlphaHandler, mAlpha)设置值
6. 修复资源泄漏:在release()中删除全部3个纹理:
GLES20.glDeleteTextures(3, textures, 0);
替代方案
如果手动处理YUV渲染的成本过高,可考虑:
- 使用SurfaceTexture中转:创建
SurfaceTexture并将其对应的Surface传给MediaCodec,在EGL线程中更新SurfaceTexture并渲染,避免手动处理YUV缓冲区 - 复用成熟渲染模块:直接使用ExoPlayer等开源库的渲染组件,或基于FFmpeg实现解码+渲染流程,减少手动实现的错误率
内容的提问来源于stack exchange,提问作者White Three
相关产品推荐
相关产品推荐

