Xamarin.Android中MediaCodec录制视频画面分块问题求助
Xamarin.Android MediaCodec H.264编码画面分块问题排查与修复
我正在开发Xamarin.Android应用的视频录制功能,使用MediaCodec进行编码,目标是生成H.264格式视频,但输出视频出现画面分块(如图所示)。

复现步骤:
- 调用SetupEncoder方法,传入指定宽、高和输出路径初始化编码器;
- 通过EncodeFrame方法向编码器传入帧数据;
- 查看保存的视频文件。
问题代码
using Android.Media; using Android.Util; using Java.IO; using Java.Nio; using System; using System.IO; using static Android.Media.MediaCodec; namespace Record.Droid { public class VideoRecorder { private const string TAG = "VideoRecorder"; private const string MIMETYPE = "video/avc"; private const int WAITTIME = 10000; // Adjust as needed private MediaCodec codec; private DataOutputStream dos; public bool SetupEncoder(int width, int height, string outputPath) { try { try { FileStream fileStream = new FileStream(outputPath, FileMode.Create); dos = new DataOutputStream(fileStream); } catch (System.IO.IOException e) { Log.Error(TAG, "Failed to open output file: " + e.Message); return false; } MediaFormat mediaFormat = MediaFormat.CreateVideoFormat(MIMETYPE, width, height); mediaFormat.SetInteger(MediaFormat.KeyBitRate, 700000); mediaFormat.SetInteger(MediaFormat.KeyFrameRate, 30); mediaFormat.SetInteger(MediaFormat.KeyColorFormat, (int)MediaCodecCapabilities.Formatyuv420planar); mediaFormat.SetInteger(MediaFormat.KeyIFrameInterval, 5); codec = MediaCodec.CreateEncoderByType(MIMETYPE); codec.Configure(mediaFormat, null, null, MediaCodecConfigFlags.Encode); codec.Start(); return true; } catch (System.IO.IOException e) { Log.Error(TAG, "Failed to set up encoder: " + e.Message); ReleaseEncoder(); return false; } } public void EncodeFrame(byte[] data, long presentationTimeUs) { try { ByteBuffer[] inputBuffers = codec.GetInputBuffers(); ByteBuffer[] outputBuffers = codec.GetOutputBuffers(); bool sawInputEOS = false; int inputBufferIndex = -1, outputBufferIndex = -1; BufferInfo info = new BufferInfo(); inputBufferIndex = codec.DequeueInputBuffer(WAITTIME); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.Clear(); inputBuffer.Put(data); codec.QueueInputBuffer(inputBufferIndex, 0, data.Length, presentationTimeUs, 0); } outputBufferIndex = codec.DequeueOutputBuffer(info, WAITTIME); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.GetOutputBuffer(outputBufferIndex).Duplicate(); outputBuffer.Position(info.Offset); outputBuffer.Limit(info.Offset + info.Size); byte[] array = new byte[info.Size]; outputBuffer.Get(array); dos.Write(array); codec.ReleaseOutputBuffer(outputBufferIndex, false); if ((info.Flags & MediaCodecBufferFlags.EndOfStream) != 0) { ReleaseEncoder(); } } } catch (System.IO.IOException e) { Log.Error(TAG, "Error encoding frame: " + e.Message); } } public void ReleaseEncoder() { try { if (dos != null) { dos.Flush(); dos.Close(); } } catch (System.IO.IOException e) { Log.Error(TAG, "Failed to release encoder: " + e.Message); } if (codec != null) { codec.Stop(); codec.Release(); codec = null; } } } }
问题
- 为何输出视频会出现画面分块?
- 需要修改代码中的哪些部分才能解决该问题,生成正常视频?
问题原因分析
画面分块的核心原因有两个:
- 缺少SPS/PPS关键序列参数:H.264视频必须以序列参数集(SPS)和图像参数集(PPS)开头,这些参数包含解码器所需的画面尺寸、编码模式等基础信息。原代码直接写入编码器输出的裸H.264数据,但未保存初始化时输出的SPS/PPS,导致解码器无法正确解析画面结构,引发分块错乱。
- 输入帧数据格式/对齐问题:指定的颜色格式为
MediaCodecCapabilities.Formatyuv420planar,但如果传入的帧数据不是标准YUV420P格式(比如数据排列错误),或者宽高未按H.264编码器要求对齐(通常需为16的倍数),编码器无法正确处理帧数据,也会导致画面分块。
代码修复方案
1. 保存SPS/PPS配置数据
编码器启动后输出的第一个Buffer包含SPS/PPS,需优先写入文件。修改EncodeFrame方法,检测MediaCodecBufferFlags.CodecConfig标记并写入:
public void EncodeFrame(byte[] data, long presentationTimeUs) { try { BufferInfo info = new BufferInfo(); // 处理输入缓冲区 int inputBufferIndex = codec.DequeueInputBuffer(WAITTIME); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = codec.GetInputBuffer(inputBufferIndex); inputBuffer.Clear(); inputBuffer.Put(data); codec.QueueInputBuffer(inputBufferIndex, 0, data.Length, presentationTimeUs, 0); } // 循环处理所有输出缓冲区(原代码只处理一次,会导致数据积压) while (true) { int outputBufferIndex = codec.DequeueOutputBuffer(info, WAITTIME); if (outputBufferIndex == (int)MediaCodecInfo.TryAgainLater) { break; } else if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.GetOutputBuffer(outputBufferIndex); if (outputBuffer != null) { outputBuffer.Position(info.Offset); outputBuffer.Limit(info.Offset + info.Size); // 写入SPS/PPS配置数据 if ((info.Flags & MediaCodecBufferFlags.CodecConfig) != 0) { byte[] configData = new byte[info.Size]; outputBuffer.Get(configData); dos.Write(configData); } else if (info.Size > 0) { // 写入正常帧数据 byte[] frameData = new byte[info.Size]; outputBuffer.Get(frameData); dos.Write(frameData); } codec.ReleaseOutputBuffer(outputBufferIndex, false); if ((info.Flags & MediaCodecBufferFlags.EndOfStream) != 0) { ReleaseEncoder(); break; } } } else { // 处理输出格式变化(可选:从MediaFormat中获取SPS/PPS) MediaFormat newFormat = codec.OutputFormat; break; } } } catch (System.IO.IOException e) { Log.Error(TAG, "Error encoding frame: " + e.Message); } }
2. 确保输入帧数据格式与对齐
- 确认传入的
byte[] data是标准YUV420P格式(Y分量在前,接着U分量,最后V分量)。如果原始数据是NV21等其他格式,需先转换为YUV420P。 - 调整宽高为16的倍数(H.264编码器要求),修改
SetupEncoder中的宽高初始化:
int alignedWidth = (width + 15) / 16 * 16; int alignedHeight = (height + 15) / 16 * 16; MediaFormat mediaFormat = MediaFormat.CreateVideoFormat(MIMETYPE, alignedWidth, alignedHeight);
3. 修复输出缓冲区处理逻辑
原代码仅调用一次DequeueOutputBuffer,会导致编码器输出数据积压。改为循环处理直到返回TryAgainLater,确保所有输出数据被写入文件。
额外优化:正确结束录制
录制结束时,需向编码器输入带EndOfStream标记的空缓冲区,触发编码器输出剩余帧:
public void StopRecording() { int inputBufferIndex = codec.DequeueInputBuffer(WAITTIME); if (inputBufferIndex >= 0) { codec.QueueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodecBufferFlags.EndOfStream); } // 处理剩余输出数据 EncodeFrame(new byte[0], 0); }
内容的提问来源于stack exchange,提问作者Gurucharan Vijayakumar
相关产品推荐
相关产品推荐

