Android Camera视频帧解码出现水平线条失真问题求助
解决相机视频OpenGL帧处理的PNG失真问题
我之前遇到过类似的相机帧OpenGL处理失真的问题,你的情况大概率是两个核心问题导致的:相机输出的SemiPlanar YUV格式(NV12)与原有代码处理的Planar YUV(I420)不匹配,加上色彩范围(color_range)未明确导致的色彩映射错误。下面是具体的排查和解决步骤:
1. 明确MediaCodec输出的YUV格式细节
你提到相机输出的color-format=21对应的是COLOR_FormatYUV420SemiPlanar(也就是NV12格式):
- 数据布局:Y平面(单通道)在前,UV分量交替存储在同一个平面(双通道)在后
- 而你测试的在线视频大概率是I420格式(Y、U、V三个独立平面),原有
ExtractMpegFramesTest_egl14的代码可能默认按I420处理,这直接导致了UV采样错位,出现水平线条失真。
修正OpenGL纹理处理逻辑
需要调整纹理创建和采样逻辑,适配NV12格式:
- 创建两个纹理:一个用于Y平面(单通道亮度),一个用于UV混合平面(双通道)
- 更新纹理时,正确拆分MediaCodec输出缓冲区的Y和UV部分
Xamarin Android中的示例代码:
// 从MediaCodec获取输出缓冲区 var outputBuffer = mediaCodec.GetOutputBuffer(outputBufferId); var format = mediaCodec.OutputFormat; int width = format.GetInteger(MediaFormat.KeyWidth); int height = format.GetInteger(MediaFormat.KeyHeight); // 拆分Y和UV缓冲区(NV12:Y占width*height字节,UV占width*height/2字节) outputBuffer.Position = 0; ByteBuffer yBuffer = outputBuffer.Slice(); yBuffer.Limit(width * height); ByteBuffer uvBuffer = outputBuffer.Slice(); uvBuffer.Position(width * height); uvBuffer.Limit(width * height + width * height / 2); // 更新Y纹理 GLES20.GlBindTexture(GLES20.GlTexture2D, _yTextureId); GLES20.GlTexImage2D(GLES20.GlTexture2D, 0, GLES20.GlLuminance, width, height, 0, GLES20.GlLuminance, GLES20.GlUnsignedByte, yBuffer); // 更新UV纹理(用LuminanceAlpha格式适配双通道UV) GLES20.GlBindTexture(GLES20.GlTexture2D, _uvTextureId); GLES20.GlTexImage2D(GLES20.GlTexture2D, 0, GLES20.GlLuminanceAlpha, width / 2, height / 2, 0, GLES20.GlLuminanceAlpha, GLES20.GlUnsignedByte, uvBuffer);
2. 处理色彩范围的差异
相机视频的color_range=unknown,说明它可能输出全范围(Full Range)YUV(Y:0-255,UV:0-255),而标准视频使用的是有限范围(TV Range)(Y:16-235,UV:16-240)。原有代码的色彩转换矩阵默认适配有限范围,这会导致色彩拉伸失真。
修改片段着色器的色彩转换逻辑
在OpenGL片段着色器中添加色彩范围判断,调整YUV到RGB的转换:
precision mediump float; varying vec2 vTexCoord; uniform sampler2D yTex; uniform sampler2D uvTex; uniform bool isFullRange; // 传入是否为全范围的标记 void main() { float y = texture2D(yTex, vTexCoord).r; vec2 uv = texture2D(uvTex, vTexCoord).rg; // 归一化YUV值,适配色彩范围 if (isFullRange) { y = y / 255.0; uv = (uv / 255.0) - 0.5; } else { y = (y - 16.0) / 219.0; uv = (uv - 128.0) / 224.0; } // YUV转RGB矩阵(适配BT.601/BT.709,这里用通用矩阵) mat3 yuvToRgb = mat3( 1.0, 1.0, 1.0, 0.0, -0.3441, 1.772, 1.402, -0.7141, 0.0 ); vec3 rgb = yuvToRgb * vec3(y, uv.x, uv.y); gl_FragColor = vec4(clamp(rgb, 0.0, 1.0), 1.0); }
在代码中传递isFullRange参数:
int rangeLoc = GLES20.GlGetUniformLocation(_programId, "isFullRange"); // 相机视频默认按全范围处理,可根据实际测试调整 GLES20.GlUniform1i(rangeLoc, 1);
3. 适配纹理的Stride和Crop参数
相机输出格式中的stride=640和crop-*参数需要注意:
- 如果stride大于width,需要在采样时校正纹理坐标,避免拉伸:
vTexCoord.x *= (float)width / stride; - 如果存在crop区域,渲染时需要调整顶点坐标,只显示裁剪后的有效区域,避免边缘无效数据干扰
验证效果
完成上述调整后,重新测试相机视频的帧提取:
- 首先确认水平线条消失,色彩还原正常
- 测试帧率,OpenGL方案应该能轻松达到30fps的处理需求
内容的提问来源于stack exchange,提问作者Iain Stanford
相关产品推荐
相关产品推荐

