使用 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 傳送低位元率音訊訊息。
  • 直播音訊串流和 Podcast:在行動或衛星網路連線品質不佳時,仍能提供穩定的音質。

平台適用情形 (API 級別 37.1 以上)

平台 xHE-AAC 編碼支援功能需要 API 級別 37.1 (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_1/SDK 3700001 以上版本)。

查詢 xHE-AAC 編碼器支援

您不得透過呼叫 MediaCodec.createByCodecName(...) (使用 "c2.android.xheaac.encoder") 假設每部裝置都支援 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 Hz、48000 Hz。
  • 位元率範圍: 12,000 bps 到 400,000 bps (12 kbps – 400 kbps)。
  • 容器多工:支援原始 MP4A 基本串流,以及平台 MP4/M4A 容器多工 (MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)。