使用 xHE-AAC 录制高品质、低比特率的音频

从 Android 17 QPR1(API 级别 37.1 或 Build.VERSION_CODES_FULL.CINNAMON_BUN_1)开始,Android 引入了对增强型高效高级音频编码 (xHE-AAC) 编码的平台支持。

xHE-AAC 在 ISO/IEC 23003-3 (MPEG-D USAC)ISO/IEC 23003-4 (MPEG-D DRC) 中定义,它将统一语音和音频编码与强制性音量归一化和内置动态范围控制相结合。虽然 xHE-AAC 解码已在多个平台版本中受到 Android 兼容性定义文档 (CDD) 的支持和强制要求,但添加 xHE-AAC 编码器c2.android.xheaac.encoder 和 OEM 硬件实现)后,您便可以录制低比特率语音和音频消息、实时音频流以及通信载荷,从而在各种设备上实现出色的语音清晰度和一致的音量。

xHE-AAC 的优势

Android 即时通讯应用中的传统语音录制功能通常使用旧版 AAC-LC(低复杂度 AAC),比特率为 28 kbps。在低比特率(低于 32 kbps)下,AAC-LC 会出现明显的频带限制伪影、模糊的语音质量和次优的压缩效率。对于最终用户,这会导致音频失真、声音尖细,辅音难以区分,需要收听者重放消息或在嘈杂的环境中费力地理解语音。 此外,各个语音片段的音量不一致,会迫使用户频繁调整设备音量。

xHE-AAC 通过应用更先进的音频编码算法克服了这些限制,例如在专用语音编码和通用音频编码工具之间动态切换,同时通过增强型频谱带复制 (eSBR) 扩展频率范围,并提供参数化立体声编码功能,而几乎不会增加额外的编码开销。

内置的 MPEG-D DRC 为 xHE-AAC 提供强制性音量控制,以确保以一致的音量播放内容,并提供动态范围控制处理,从而在任何平台和任何环境下都能提供尽可能最佳的聆听体验。

目标应用场景

  • 富通讯解决方案 (RCS) 和语音信息:符合 GSMA RCC.71 通用配置文件版本 3.1 及更高版本的要求,该要求强制规定了低比特率音频信息采用 xHE-AAC。
  • 实时音频串流和播客:即使在移动网络或卫星网络连接不佳的情况下,也能提供出色的音质。

平台可用性(API 级别 37.1 及更高版本)

平台 xHE-AAC 编码支持需要 API 级别 37.1Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_1/SDK 3700001 或更高版本)。

查询 xHE-AAC 编码器支持

您不得通过调用 "c2.android.xheaac.encoder"MediaCodec.createByCodecName(...) 假定每台设备上都提供 xHE-AAC 编码。

  • 某些设备可能提供来自芯片组供应商的硬件加速 xHE-AAC 编码器,而不是平台软件编码器。
  • 较旧的操作系统版本或自定义 ROM 缺少 xHE-AAC 编码支持。
  • 在对附件进行编码以用于点对点消息传递(例如 RCS 或聊天)之前,应用还应检查接收客户端是否通告了 USAC/xHE-AAC 解码支持(例如,使用 SDP 功能发现 profile-level-id=55 / object=42),以防止旧版接收器出现互操作性故障。

检查 MediaCodecList 功能

如需检查当前设备是否支持 xHE-AAC 编码,请检查支持 MediaFormat.MIMETYPE_AUDIO_AAC 的编码器的 profileLevels

Kotlin

import android.media.MediaCodecInfo.CodecProfileLevel
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
fun isXheAacEncodingSupported(): Boolean {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false
    }

    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    for (codecInfo in codecList.codecInfos) {
        if (!codecInfo.isEncoder) continue

        if (MediaFormat.MIMETYPE_AUDIO_AAC in codecInfo.supportedTypes) {
            val capabilities =
                codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
            // Check if AACObjectXHE (42) is present in profileLevels
            val supportsXhe = capabilities.profileLevels.any { profileLevel ->
                profileLevel.profile == CodecProfileLevel.AACObjectXHE
            }
            if (supportsXhe) {
                return true
            }
        }
    }
    return false
}

Java

import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Build;

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
public boolean isXheAacEncodingSupported() {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false;
    }

    MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
        if (!codecInfo.isEncoder()) continue;

        for (String type : codecInfo.getSupportedTypes()) {
            if (MediaFormat.MIMETYPE_AUDIO_AAC.equals(type)) {
                MediaCodecInfo.CodecCapabilities capabilities =
                        codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC);
                for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
                    if (profileLevel.profile == CodecProfileLevel.AACObjectXHE) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

配置并实例化 xHE-AAC 编码器

验证 xHE-AAC 支持后,请将 MediaFormat 配置为 KEY_AAC_PROFILE 设置为 MediaCodecInfo.CodecProfileLevel.AACObjectXHE(值 42)。

Kotlin

fun setupXheAacEncoder(): MediaCodec? {
    if (!isXheAacEncodingSupported()) {
        return null
    }

    val mimeType = MediaFormat.MIMETYPE_AUDIO_AAC
    val sampleRate = 48000
    val channelCount = 1 // Mono voice recording
    val bitRate = 20000  // 20 kbps delivers superior speech clarity

    val format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount).apply {
        setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
        setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE)
    }

    // Find the hardware or software encoder supporting this format and profile
    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    val encoderName = codecList.findEncoderForFormat(format) ?: return null
    val encoder = MediaCodec.createByCodecName(encoderName)
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    return encoder
}

Java

public MediaCodec setupXheAacEncoder() throws IOException {
    if (!isXheAacEncodingSupported()) {
        return null;
    }

    String mimeType = MediaFormat.MIMETYPE_AUDIO_AAC;
    int sampleRate = 48000;
    int channelCount = 1; // Mono voice recording
    int bitRate = 20000;  // 20 kbps delivers superior speech clarity

    MediaFormat format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount);
    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
    format.setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE);

    // Find the hardware or software encoder supporting this format and profile
    MediaCodecList codecList =
            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    String encoderName = codecList.findEncoderForFormat(format);
    if (encoderName == null) {
        return null;
    }

    MediaCodec encoder = MediaCodec.createByCodecName(encoderName);
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    return encoder;
}

支持的软件编码器限制 (c2.android.xheaac.encoder)

使用系统提供的软件 xHE-AAC 编码器 (c2.android.xheaac.encoder) 时,该组件会通告以下功能限制:

  • 声道数:最多 2 个声道(单声道 / 立体声)。
  • 采样率44100 Hz48000 Hz
  • 比特率范围12,000 bps400,000 bps(12 kbps - 400 kbps)。
  • 容器混合:与原始 MP4A 基本码流和平台 MP4/M4A 容器混合 (MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) 兼容。