限时福利领取


在语音识别、实时通话等场景中,经常需要将AAC编码的音频流转换为PCM格式。今天就来分享下我在项目中实现的完整方案,包含踩坑经验和性能优化技巧。

音频处理流程

一、为什么需要自己实现转换?

Android系统虽然提供了MediaPlayer等高级API,但遇到这些场景时就会很头疼:

  • 需要实时获取PCM数据做语音分析
  • 系统播放器在不同厂商ROM上有兼容问题
  • 连续处理长音频时内存暴涨导致OOM

二、技术方案选型

尝试过三种主流方案后,得出以下对比结论:

  1. FFmpeg方案
  2. 优点:兼容性最好
  3. 缺点:CPU占用高(实测多出30%),体积增加5-8MB

  4. 第三方库(如ExoPlayer)

  5. 优点:API友好
  6. 缺点:黑箱操作,无法精细控制缓冲区

  7. MediaCodec原生方案

  8. 优点:硬件加速,延迟最低(实测80ms)
  9. 缺点:需要处理版本差异(重点!)

最终选择MediaCodec+AudioTrack组合,下面是核心实现:

三、MediaCodec配置要点

// 1. 初始化解码器(注意try-catch块)
val codec = MediaCodec.createDecoderByType("audio/mp4a-latm").apply {
    configure(
        MediaFormat().apply {
            setString(MediaFormat.KEY_MIME, "audio/mp4a-latm")
            setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100)
            setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1)
            setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024*1024) // 关键!避免BufferOverflow
        },
        null,  // Surface
        null,  // Crypto
        0      // flags
    )
    start()
}

编解码流程

四、数据流转发核心逻辑

  1. 输入阶段
  2. 用dequeueInputBuffer()获取可用缓冲区索引
  3. 注意处理BUFFER_FLAG_END_OF_STREAM标志

  4. 输出阶段

  5. 关键代码:
    val info = MediaCodec.BufferInfo()
    when (val outIndex = codec.dequeueOutputBuffer(info, 10000)) {
        MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
            // 处理采样率变化
            audioTrack.sampleRate = codec.outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)
        }
        MediaCodec.INFO_TRY_AGAIN_LATER -> { /* 重试逻辑 */ }
        else -> {
            val buffer = codec.getOutputBuffer(outIndex)!!
            audioTrack.write(buffer, info.size, AudioTrack.WRITE_BLOCKING)
            codec.releaseOutputBuffer(outIndex, false)
        }
    }

五、避坑指南

  • 内存泄漏:必须在Activity的onDestroy中调用

    fun release() {
        codec.stop()
        codec.release()
        audioTrack.stop()
        audioTrack.release()
    }
  • 版本适配

  • Android 8.0+建议用异步模式(setCallback)
  • 华为EMUI需要额外设置format参数

  • 性能数据

  • Redmi Note 11:平均延迟92ms
  • 内存占用稳定在15MB以内

六、延伸思考

如果要做更极致的优化,可以考虑: 1. 用Native层实现避免JNI开销 2. 研究OMX_StoreMetaDataInBuffers扩展 3. 尝试直接DSP处理(需要厂商支持)

完整项目代码已上传GitHub,欢迎讨论优化方案~

Logo

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

更多推荐