MediaCodec 异步编码 + Buffer 管理:Claude Code 写防抖生产者消费者模型

MediaCodec 异步编码 + Buffer 管理:Claude Code 写防抖生产者消费者模型

💡 原文中文,约11600字,阅读约需28分钟。
📝

内容提要

本文介绍Android MediaCodec异步视频编码架构,对比同步模式性能优势,提供Claude Code生成的异步编码器代码,涵盖输入队列背压防丢帧、输出Buffer自动回收、优雅停止及错误恢复。并总结五大陷阱:忘记releaseOutputBuffer、线程操作错误、Muxer时序问题、EOS处理不当、重建清理缺失,附性能对比数据。

🔎

延伸解读

异步模式的核心收益:停止延迟与CPU占用

文章对比了同步与异步模式,指出同步模式因dequeueOutputBuffer超时导致停止延迟200-2000ms,而异步模式通过事件驱动可立即响应EOS,停止延迟低于50ms。同时,异步模式CPU占用从15%降至8%,因为避免了空转等待。这些数据表明,异步模式不仅提升用户体验,还降低了资源消耗,适合对响应速度和功耗敏感的场景。

背压队列:主动丢帧而非被动阻塞

文章提出的EncoderInputQueue采用容量为3的队列,当队列满时主动丢弃最旧帧,并记录丢弃计数。这种策略避免了输入队列满导致的编码器阻塞或丢帧,使丢帧行为可控且可监控。相比同步模式下的偶发丢帧,背压机制提供了更稳定的编码流程,适合实时性要求高的应用,如视频录制。

优雅停止与错误恢复的关键步骤

文章强调优雅停止需先signalEndOfInputStream,等待EOS回调后再停止Muxer和编码器,避免丢失最后几帧。错误恢复则需在onError中释放旧编码器和Muxer,并重建,因为Muxer的track index会变化。这些步骤是异步编码中容易出错的地方,正确实现能提升应用的健壮性。

五大陷阱:从代码层面规避常见问题

文章总结了五个常见陷阱:忘记releaseOutputBuffer导致Buffer池耗尽、在错误线程操作编码器引发ANR、Muxer时序错误导致崩溃、EOS处理不当丢失帧、重建时未清理旧资源。每个陷阱都给出了错误和正确示例,帮助开发者避免类似问题,确保异步编码的稳定性和性能。

Q&A

MediaCodec 同步模式和异步模式的主要区别是什么?

同步模式使用 dequeueInputBuffer()/dequeueOutputBuffer() 阻塞等待,代码简单但停止时需等待超时(如200ms),CPU占用高;异步模式通过 setCallback(Handler) 事件驱动,零等待,停止响应快,CPU占用低,但线程模型复杂,需手动管理 Buffer。

在 MediaCodec 异步编码中,如何防止输入队列满导致丢帧?

使用背压策略:维护一个容量有限的输入队列(如容量3),当队列满时,丢弃最旧的帧(poll 出队),并增加丢弃计数,从而避免编码器输入阻塞。

MediaCodec 异步编码中,为什么必须调用 releaseOutputBuffer?不调用会怎样?

releaseOutputBuffer 用于释放输出缓冲区,如果不调用,缓冲区池会耗尽,导致编码器卡死或崩溃。必须在每次 onOutputBufferAvailable 回调中,无论是否写入 Muxer,都要在 finally 块中调用 releaseOutputBuffer。

在 MediaCodec 异步编码中,如何优雅地停止编码器而不丢失最后几帧?

先调用 signalEndOfInputStream() 发送 EOS 信号,然后等待 onOutputBufferAvailable 回调收到 BUFFER_FLAG_END_OF_STREAM 标志,再停止 Muxer 和编码器。避免直接 stop() 导致最后几帧丢失。

MediaCodec 异步编码中,Muxer 启动的时机是什么?为什么不能提前写入?

Muxer 必须在 onOutputFormatChanged 回调中调用 addTrack 和 start() 之后才能写入数据。因为此时编码器输出格式才确定,提前写入会导致崩溃。

MediaCodec 异步编码器异常时如何自动重建?

在 onError 回调中,先释放旧编码器和 Muxer(因为 track index 可能变化),然后重新调用 configureAndStart() 创建新的编码器和 Muxer。

MediaCodec 异步编码相比同步模式在性能上有哪些提升?

停止延迟从 200-2000ms 降低到 <50ms,CPU 占用从 15% 降低到 8%,编码丢帧从偶发变为可控(通过背压队列主动丢弃)。

🏷️

标签

➡️

继续阅读