限时福利领取


MediaCodec工作流程

作为Android平台硬件加速编解码的核心组件,MediaCodec的高效使用一直是音视频开发的关键难点。本文将结合实战经验,带你避开那些让我掉过坑的雷区。

一、为什么你的MediaCodec总出问题?

初次接触MediaCodec时,我遇到过三大经典问题:

  • ANR弹窗:在主线程执行configure()/start()导致界面卡死
  • 帧率跳水:输入输出缓冲区处理不当造成视频卡顿
  • 内存泄漏:未及时释放编解码器和Surface资源

这些问题的根源在于对MediaCodec状态机理解不足。比如当调用flush()后立即提交数据,很可能触发"CodecStoppedException"——因为编解码器此时处于Flushing状态而非Running状态。

二、横向对比:MediaCodec的优劣势

通过实测华为P40和小米11设备,得到以下数据:

| 方案 | 1080P解码延迟 | 功耗(mAh/min) | 格式支持 | |---------------|--------------|---------------|----------------| | MediaCodec | 38ms | 120 | 厂商依赖 | | FFmpeg软解 | 210ms | 480 | 全格式 | | ExoPlayer | 45ms | 150 | 自适应 |

MediaCodec在延迟和功耗上优势明显,但需要注意:

  1. 高通平台对HEVC支持较好,联发科可能需fallback到AVC
  2. 部分低端机存在色域转换bug(YUV420->RGB异常)

三、手把手实现编解码流程

1. 初始化配置关键点

val codec = MediaCodec.createDecoderByType("video/avc").apply {
    // 必须使用兼容格式!建议从CodecCapabilities获取
    val format = MediaFormat.createVideoFormat(
        "video/avc", 
        1920, 
        1080
    ).apply {
        setInteger(MediaFormat.KEY_BIT_RATE, 4000000)
        setInteger(MediaFormat.KEY_FRAME_RATE, 30)
        setByteBuffer("csd-0", csd0) // H.264的SPS/PPS
    }

    // 使用Surface可启用零拷贝渲染
    configure(format, surfaceView.holder.surface, null, 0)
}

2. 异步回调处理

codec.setCallback(object : MediaCodec.Callback() {
    override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
        // 从环形队列取数据填充
        val buffer = codec.getInputBuffer(index)!!
        buffer.put(nalUnit)
        codec.queueInputBuffer(index, 0, nalUnit.size, timestamp, 0)
    }

    override fun onOutputBufferAvailable(
        codec: MediaCodec, 
        index: Int, 
        info: MediaCodec.BufferInfo
    ) {
        // 立即释放缓冲区避免阻塞
        codec.releaseOutputBuffer(index, render)
        if (render) updateSurfaceTexture()
    }
})

3. 渲染优化技巧

使用GLSurfaceView比TextureView节省20%GPU资源:

<android.opengl.GLSurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

四、性能调优实录

在Redmi Note 11上测试不同策略:

  1. 线程优先级:解码线程设为THREAD_PRIORITY_VIDEO(-16)后,丢帧率从5%降至0.3%
  2. 缓冲区数量:InputBuffer从4增至8可提升20%吞吐量,但内存占用翻倍
  3. 关键帧间隔:GOP从30调整为60,编码速度提升15%

五、血泪避坑指南

  • 厂商兼容性
  • 三星设备需用OMX.Exynos.avc.decoder而非通用类型
  • 华为某些机型要求先start()再getInputBuffers()

  • 复位操作

    fun resetCodec() {
        codec.stop()
        codec.flush() // 必须!否则残留数据会导致花屏
        codec.start()
    }

六、扩展实践建议

尝试用MediaMuxer录制屏幕时,注意: 1. 使用PresentationTime = System.nanoTime()/1000保持时间戳连续 2. 建议开启异步模式防止写入阻塞 3. 对于4K视频,优先选用HEVC编码节省50%存储空间

性能对比图

经过三个版本的迭代,我们的播放器解码延迟从最初的200ms优化到50ms以内。记住:MediaCodec就像一头猛兽,驯服它需要理解其习性,希望这篇笔记能帮你少走弯路。

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐