MediaCodec异步转码:捕获Callback中MediaExtractor异常的方法
当然可以实现!
你遇到的这个问题在MediaCodec异步转码场景里很常见——子线程中抛出的异常如果不妥善捕获,很容易导致资源泄漏或者主线程无法感知错误状态。下面是一套完整的方案,能帮你把Callback中的异常传递到主线程的awaitEncode函数,进而安全关闭所有资源:
核心思路
我们需要一套线程安全的异常存储机制,配合同步等待工具,把HandlerThread中Callback抛出的异常传递到主线程;同时在Callback的所有回调方法中统一捕获异常,触发资源清理并通知主线程。
具体实现步骤
1. 准备线程安全的异常存储与同步工具
在你的转码类中定义两个核心变量:
AtomicReference<Throwable>:用来存储第一个发生的异常(保证线程安全,避免多线程覆盖)CountDownLatch:让主线程的awaitEncode等待转码完成或异常发生
private final AtomicReference<Throwable> mTranscodeException = new AtomicReference<>(); // 如果是音视频双解码,这里计数设为2 private final CountDownLatch mTranscodeLatch = new CountDownLatch(1);
2. 在MediaCodec.Callback中统一捕获异常
给Callback的所有回调方法(onInputBufferAvailable、onOutputBufferAvailable、onError、onOutputFormatChanged)都加上try-catch块,捕获所有Throwable(不要只抓Exception,避免漏掉Error类的异常),然后统一处理:
private MediaCodec.Callback mVideoCodecCallback = new MediaCodec.Callback() { @Override public void onInputBufferAvailable(MediaCodec codec, int index) { try { // 你的正常处理逻辑:读取Extractor样本、输入到Codec等 if (!mVideoExtractor.advance()) { codec.signalEndOfInputStream(); return; } long sampleTime = mVideoExtractor.getSampleTime(); ByteBuffer inputBuffer = codec.getInputBuffer(index); // 写入数据到inputBuffer... codec.queueInputBuffer(index, 0, inputBuffer.limit(), sampleTime, 0); } catch (Throwable t) { handleTranscodeException(t, codec); } } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { try { // 你的输出处理逻辑:写入Muxer等 ByteBuffer outputBuffer = codec.getOutputBuffer(index); mMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, info); codec.releaseOutputBuffer(index, false); // 处理结束逻辑... } catch (Throwable t) { handleTranscodeException(t, codec); } } // 其他回调方法(onError、onOutputFormatChanged)同样加try-catch private void handleTranscodeException(Throwable t, MediaCodec codec) { // 只存储第一个异常,避免后续异常覆盖 if (mTranscodeException.compareAndSet(null, t)) { // 立即停止并释放当前Codec try { codec.stop(); codec.release(); } catch (Exception e) { // 释放时的异常可以记录日志,不影响主异常处理 Log.e("Transcode", "Failed to release codec", e); } // 通知主线程转码异常终止 mTranscodeLatch.countDown(); } } };
音频解码器的Callback也需要完全一样的异常处理逻辑,确保任何一条轨道出问题都能及时通知主线程。
3. 主线程awaitEncode函数处理异常与资源清理
在awaitEncode中,先等待Latch触发,然后检查是否有异常,最后统一关闭所有资源:
public void awaitEncode() throws InterruptedException, Throwable { // 等待转码完成或异常发生 mTranscodeLatch.await(); // 检查是否有异常 Throwable exception = mTranscodeException.get(); if (exception != null) { // 可以在这里重新抛出异常,让上层处理,或者直接处理 throw exception; } // 正常完成时的资源清理 cleanUpAllResources(); } private void cleanUpAllResources() { // 统一关闭所有资源:Extractor、Muxer、剩余Codec等 try { if (mVideoExtractor != null) { mVideoExtractor.release(); } if (mAudioExtractor != null) { mAudioExtractor.release(); } if (mMuxer != null) { mMuxer.stop(); mMuxer.release(); } // 其他资源如HandlerThread也可以在这里quit } catch (Exception e) { Log.e("Transcode", "Failed to clean up resources", e); } }
关键注意事项
- 线程安全优先:必须用线程安全的容器存储异常(比如
AtomicReference),避免多线程下的异常覆盖问题。 - 捕获所有Throwable:不要只捕获
Exception,因为MediaCodec或Extractor可能抛出Error类的异常(比如OOM导致的OutOfMemoryError)。 - 资源清理顺序:先停止Codec,再关闭Extractor和Muxer,避免资源占用或文件损坏。
- 音视频双轨处理:如果是同时处理音视频,
CountDownLatch的计数要设为2,每个Codec完成或异常时都调用countDown()。
另外,你遇到的IllegalStateException大概率是某些异常文件导致Extractor状态异常(比如文件损坏、格式不兼容),捕获异常后及时清理资源就能避免程序崩溃或资源泄漏。
内容的提问来源于stack exchange,提问作者Jon G
相关产品推荐
相关产品推荐

