使用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

