限时福利领取


背景与痛点

在Android音视频开发中,解码与渲染是核心环节,但开发者常遇到以下问题:

  • 帧率不稳定:因解码线程阻塞或渲染不及时导致卡顿
  • 内存泄漏:MediaCodec实例或Surface未正确释放
  • 同步问题:音视频时钟不同步造成唇音不同步现象

音视频处理流程

技术选型对比

| 方案 | 优点 | 缺点 | |------|------|------| | MediaCodec | 硬件加速、低功耗、系统级支持 | 兼容性需适配不同芯片组 | | FFmpeg软解 | 格式支持广泛、跨平台 | CPU占用高、功耗大 | | ExoPlayer | 封装完善、功能全面 | 定制灵活性较低 |

选择MediaCodec+SurfaceView组合的关键理由:

  1. 直接访问硬件解码器,性能最优
  2. SurfaceView提供独立的渲染图层,避免UI线程阻塞
  3. 系统原生支持,API稳定

核心实现细节

1. MediaCodec初始化

val codec = MediaCodec.createDecoderByType("video/avc")
val format = MediaFormat.createVideoFormat("video/avc", width, height)
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
codec.configure(format, surfaceView.holder.surface, null, 0)

关键参数说明:

  • video/avc:H.264格式标识
  • Surface绑定后自动处理YUV->RGB转换
  • 建议在子线程启动编解码器

2. SurfaceView配置

<SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

必须等待Surface创建完成:

surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
        // 在此触发解码初始化
    }
})

3. 解码循环实现

解码流程

标准处理流程:

  1. 从MediaExtractor获取样本数据
  2. 将输入数据送入解码器:
    val inputBufferId = codec.dequeueInputBuffer(TIMEOUT_US)
    if (inputBufferId >= 0) {
        val buffer = codec.getInputBuffer(inputBufferId)
        buffer?.put(sampleData)
        codec.queueInputBuffer(inputBufferId, ...)
    }
  3. 获取并渲染输出帧:
    val bufferInfo = MediaCodec.BufferInfo()
    val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
    if (outputBufferId >= 0) {
        codec.releaseOutputBuffer(outputBufferId, true) // true表示渲染到Surface
    }

完整代码示例

class VideoDecoder(private val surfaceView: SurfaceView) {
    private lateinit var codec: MediaCodec

    fun startDecoding(dataSource: DataSource) {
        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                initCodec(holder.surface)
                startDecodeThread(dataSource)
            }
            // 其他回调省略...
        })
    }

    private fun initCodec(surface: Surface) {
        codec = MediaCodec.createDecoderByType("video/avc")
        val format = createMediaFormat()
        codec.configure(format, surface, null, 0)
        codec.start()
    }

    private fun startDecodeThread(dataSource: DataSource) {
        thread {
            while (isActive) {
                // 实现上述解码循环
            }
            releaseResources()
        }
    }
}

性能与安全考量

内存管理

  • 使用try-with-resources确保资源释放
  • 解码器停止后必须调用release()
  • 监控onError回调处理异常状态

线程优化

  • 解码循环单独线程运行
  • 使用Handler与主线程通信
  • 避免在解码线程执行UI操作

避坑指南

常见问题解决

  1. Surface未就绪
  2. 检查SurfaceHolder.Callback注册时机
  3. 添加surfaceCreated等待机制

  4. 解码器状态错误

  5. 捕获IllegalStateException
  6. 实现状态机控制流程

  7. 内存泄漏

  8. Activity.onDestroy()中释放资源
  9. 使用弱引用持有Context

下一步挑战

尝试实现以下进阶功能:

  1. 动态分辨率切换处理
  2. 自适应帧率控制
  3. 硬件Buffer复用优化

欢迎在评论区分享你遇到的独特案例或优化方案!

Logo

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

更多推荐