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

使用MediaCodec解码重编码缩小时视频绿屏错乱,求问题排查

问题:MediaCodec解码重编码后视频错乱

我尝试通过MediaCodec解码并重新编码视频以缩小文件体积,但复用器(MediaMuxer)生成的新视频完全错乱。能否告知我问题出在哪里?我的代码基于CTS修改,存在两个主要差异:

  • 采用带回调的异步模式编解码器;
  • 不使用辅助类,直接将encoder.createInputSurface()生成的Surface传入decoder.configure()。

我曾尝试将surfaceView.getHolder().getSurface()传入decoder.configure(),将视频解码至SurfaceView中,播放显示正常。我认为问题可能出在编码器/复用器环节。以下是我的代码:

解码器回调

private class VideoDecodeCallBack extends MediaCodec.Callback {
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
        ByteBuffer buffer = codec.getInputBuffer(index);
        if (extractorDone) {
            return;
        }
        int size = videoExtractor.readSampleData(buffer, 0);
        long pts = videoExtractor.getSampleTime();
        if (size >= 0) {
            codec.queueInputBuffer(index, 0, size, pts, videoExtractor.getSampleFlags());
        }
        extractorDone = !videoExtractor.advance();
        if (extractorDone) {
            codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
        }
    }

    @Override
    public void onOutputBufferAvailable(
            MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            codec.releaseOutputBuffer(index, false);
            return;
        }
        boolean render = info.size != 0;
        codec.releaseOutputBuffer(index, render);
        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            videoEncoder.signalEndOfInputStream();
        }
    }

    @Override
    public void onError(
            MediaCodec codec, MediaCodec.CodecException e) {
    }

    @Override
    public void onOutputFormatChanged(
            MediaCodec codec, MediaFormat format) {
    }
}

编码器回调

private class VideoEncodeCallBack extends MediaCodec.Callback {
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
    }

    @Override
    public void onOutputBufferAvailable(
            MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        ByteBuffer buffer = codec.getOutputBuffer(index);
        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            codec.releaseOutputBuffer(index, false);
            return;
        }
        if (info.size != 0) {
            muxer.writeSampleData(outputVideoTrack, buffer, info);
        }
        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            muxer.stop();
            muxer.release();
        }
        codec.releaseOutputBuffer(index, false);
    }

    @Override
    public void onError(
            MediaCodec codec, MediaCodec.CodecException e) {
    }

    @Override
    public void onOutputFormatChanged(
            MediaCodec codec, MediaFormat format) {
        outputVideoTrack = muxer.addTrack(format);
        muxer.start();
    }
}

编解码器配置方式

public void configure(String input, String output) throws IOException {
    // Extractors
    videoExtractor = new MediaExtractor();
    videoExtractor.setDataSource(input);
    int videoTrack = getAndSelectVideoTrackIndex(videoExtractor);
    MediaFormat videoFormat = videoExtractor.getTrackFormat(videoTrack);
    String mime = videoFormat.getString(MediaFormat.KEY_MIME);

    // Codecs
    videoDecoder = MediaCodec.createDecoderByType(mime);
    videoEncoder = MediaCodec.createEncoderByType(mime);
    videoDecoder.setCallback(new VideoDecodeCallBack(), videoDecodeThreadHandler);
    videoEncoder.setCallback(new VideoEncodeCallBack(), videoEncodeThreadHandler);

    videoEncoder.configure(createOutputFormat(), null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    Surface surface = videoEncoder.createInputSurface();
    videoDecoder.configure(videoFormat, surface, null, 0);

    // Muxer
    muxer = new MediaMuxer(output, OutputFormat.MUXER_OUTPUT_MPEG_4);
}

目标输出格式

private static MediaFormat createOutputFormat() {
    MediaFormat result = MediaFormat.createVideoFormat("video/avc", 540, 960);
    result.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
    result.setInteger(MediaFormat.KEY_BIT_RATE, 1300000);
    result.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
    result.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
    return result;
}

问题分析与解决方案

你遇到的视频错乱问题,核心原因是帧时间戳(PTS)没有正确传递给编码器,另外还有几处细节需要调整,我帮你逐一说明:

1. 关键修复:传递正确的帧时间戳

当你把解码器输出直接绑定到编码器的InputSurface时,解码器的帧PTS不会自动同步给编码器。你当前调用codec.releaseOutputBuffer(index, render)的方式,仅告知解码器渲染帧到Surface,但没有传递编码器必需的时间戳——缺少PTS的话,编码器生成的帧时序会完全混乱,最终导致复用后的视频错乱。

修改解码器回调的onOutputBufferAvailable方法:

@Override
public void onOutputBufferAvailable(
        MediaCodec codec, int index, MediaCodec.BufferInfo info) {
    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
        codec.releaseOutputBuffer(index, false);
        return;
    }
    boolean render = info.size != 0;
    // 关键修改:将微秒单位的PTS转为纳秒,传递给Surface
    if (render) {
        codec.releaseOutputBuffer(index, info.presentationTimeUs * 1000);
    } else {
        codec.releaseOutputBuffer(index, false);
    }
    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        videoEncoder.signalEndOfInputStream();
    }
}

这样编码器就能从Surface获取到每个帧的正确PTS,生成时序正常的编码帧。

2. 补充编解码器启动调用

你的configure方法仅完成了编解码器的配置,但没有调用start()方法!MediaCodec必须在configure之后调用start()才能进入工作状态,否则编解码器不会处理任何数据。在configure方法末尾添加以下代码:

videoDecoder.start();
videoEncoder.start();

3. 其他细节检查

  • Surface生命周期:确保编码器的InputSurface在整个编解码过程中不会被提前释放,你当前的创建方式是合理的,只要编解码器正常运行,Surface会被自动管理。
  • 复用器停止时机:你在编码器收到EOS后停止复用器的逻辑是正确的,但如果出现复用器异常,可以考虑延迟停止(比如收到EOS后等待所有输出帧写入完成)。

验证逻辑

你之前用SurfaceView显示正常,是因为SurfaceView内部会自行处理显示时序,不需要依赖手动传递PTS;但编码器必须依赖正确的PTS来生成有序的视频流,所以修复PTS传递是解决问题的核心。先调整时间戳传递代码,再补充启动调用,应该就能解决视频错乱的问题了。

内容的提问来源于stack exchange,提问作者GGWP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:17:04