Android编码器入门指南:从MediaCodec基础到实战避坑

1次阅读
没有评论

共计 2548 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

MediaCodec 工作流程全景图

MediaCodec 是 Android 多媒体框架中的核心编码组件,其工作流程可以概括为 ” 生产者 - 消费者 ” 模型。我们用一张流程图来理解这个抽象概念:

Android 编码器入门指南:从 MediaCodec 基础到实战避坑

start
: 创建 MediaCodec 实例;
: 配置编码参数(分辨率 / 帧率等);
if (Surface 模式?) then (是)
  : 创建 Surface 输入通道;
else (否)
  : 准备 ByteBuffer 输入队列;
endif
: 启动编码器;
repeat
  : 获取空闲输入缓冲区;
  : 填充原始数据(YUV);
  : 提交到编码器;
  : 获取输出缓冲区;
  : 处理编码后数据;
repeat while (还有数据?) 
: 释放编码器资源;
stop

两种输入模式深度解析

1. Surface 模式

  • 适用场景:Camera 预览或 OpenGL 渲染管线
  • 优势
  • 自动处理帧率同步
  • 零拷贝内存传输
  • 支持硬件加速
  • 典型代码
    val codec = MediaCodec.createEncoderByType("video/avc")
    val surface = codec.createInputSurface()

2. ByteBuffer 模式

  • 适用场景
  • 已有 YUV 数据需要处理
  • 需要精确控制每帧时间戳
  • 关键步骤
    // 获取输入缓冲区索引
    val inputBufferIdx = codec.dequeueInputBuffer(TIMEOUT_US)
    if (inputBufferIdx >= 0) {val inputBuffer = codec.getInputBuffer(inputBufferIdx)
        // 填充 YUV 数据
        yuvConverter.fillYUVData(inputBuffer)
        // 提交编码器
        codec.queueInputBuffer(inputBufferIdx, ...)
    }

编码参数配置黄金法则

  1. 比特率(Bitrate)
  2. 计算公式:width * height * fps * motionFactor * 0.07
  3. 示例:1080p30 视频推荐3500-5000kbps

  4. 帧率(FPS)

  5. 必须与输入源帧率一致
  6. 错误设置会导致:

    • 帧率过高:编码器丢帧
    • 帧率过低:视频卡顿
  7. 关键帧间隔(I-frame Interval)

  8. 直播场景推荐 2 秒
  9. 点播视频可以 10-30 秒
  10. 设置方法:
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2)

YUV420P 格式转换实战

这是最常见的色彩空间转换场景:

fun NV21toYUV420Planar(nv21: ByteArray, width: Int, height: Int): ByteArray {val yuv420p = ByteArray(width * height * 3 / 2)
    // Y 分量直接拷贝
    System.arraycopy(nv21, 0, yuv420p, 0, width * height)

    // UV 分量处理
    val uvSize = width * height / 4
    for (i in 0 until uvSize) {yuv420p[width * height + i] = nv21[width * height + i * 2 + 1] // U 分量
        yuv420p[width * height + uvSize + i] = nv21[width * height + i * 2] // V 分量
    }
    return yuv420p
}

异步回调处理技巧

现代 Android 版本推荐使用异步模式:

codec.setCallback(object : MediaCodec.Callback() {override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {// 获取并填充输入缓冲区}

    override fun onOutputBufferAvailable(
        codec: MediaCodec,
        index: Int,
        info: MediaCodec.BufferInfo
    ) {val outputBuffer = codec.getOutputBuffer(index)
        // 处理编码后的 H.264 数据
        codec.releaseOutputBuffer(index, false)
    }

    // 错误处理不可少!override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {Log.e(TAG, "编码器错误: ${e.errorCode}")
    }
})

高频踩坑点排查

1. 颜色空间不匹配

  • 症状:视频发绿或颜色失真
  • 解决方案
  • 确认 YUV 格式与 MediaFormat.KEY_COLOR_FORMAT 一致
  • 华为设备需要特殊处理COLOR_FormatYUV420SemiPlanar

2. 编码器初始化失败

  • 排查步骤
  • 检查 MIME 类型支持MediaCodecList.findEncoderForFormat
  • 验证分辨率是否是 16 的倍数
  • 某些设备要求设置 KEY_PROFILEKEY_LEVEL

3. 内存泄漏

  • 预防措施
    try {// 使用编码器} finally {codec.stop()
        codec.release()
        surface?.release()}

性能优化三板斧

  1. 线程模型优化
  2. 建议使用双线程架构:

    • 生产者线程:处理输入数据
    • 消费者线程:处理输出数据
  3. 内存复用

  4. 复用 YUV 转换缓冲区
  5. 输出 ByteBuffer 池化

  6. 动态参数调整

  7. 根据设备温度调整帧率
  8. 网络环境变化时动态修改比特率

版本兼容性处理

不同 Android 版本的特殊处理:

  • Android 5.0+:必须处理MediaCodec.CRYPTO_EXCEPTION
  • Android 7.0+createInputSurface()行为变化
  • Android 10+:要求动态设置KEY_MAX_B_FRAMES

实战建议

最后给新手开发者的三个建议:

  1. 始终添加 codec.release() 的异常保护
  2. 关键节点添加 MediaCodec.BufferInfo.presentationTimeUs 日志
  3. 使用 adb shell dumpsys media.codec 调试编码状态

通过本指南的系统学习,相信你已经掌握了 Android 视频编码的核心要点。编码器的使用就像学习骑自行车 – 刚开始可能会摔几次,但一旦掌握平衡,就能自由驰骋。遇到问题时,多查阅官方文档和源码注释,它们往往藏着解决问题的钥匙。

正文完
 0
评论(没有评论)