You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
            }
        }
    }
}

问题

  1. 为何输出视频会出现画面分块?
  2. 需要修改代码中的哪些部分才能解决该问题,生成正常视频?

问题原因分析

画面分块的核心原因有两个:

  1. 缺少SPS/PPS关键序列参数:H.264视频必须以序列参数集(SPS)和图像参数集(PPS)开头,这些参数包含解码器所需的画面尺寸、编码模式等基础信息。原代码直接写入编码器输出的裸H.264数据,但未保存初始化时输出的SPS/PPS,导致解码器无法正确解析画面结构,引发分块错乱。
  2. 输入帧数据格式/对齐问题:指定的颜色格式为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:52:32