xHE-AAC で高品質かつ低ビットレートの音声を録音する

Android 17 QPR1(API レベル 37.1 または Build.VERSION_CODES_FULL.CINNAMON_BUN_1)以降、Android では Extended High Efficiency Advanced Audio Coding(xHE-AAC)エンコードのプラットフォーム サポートが導入されています。

ISO/IEC 23003-3(MPEG-D USAC)と ISO/IEC 23003-4(MPEG-D DRC)で定義されている xHE-AAC は、Unified Speech and Audio Coding と、必須のラウドネス正規化および組み込みのダイナミック レンジ コントロールを組み合わせたものです。xHE-AAC のデコードは、複数のプラットフォーム バージョンで Android 互換性定義ドキュメント(CDD)によってサポートされ、義務付けられてきましたが、xHE-AAC のエンコーダ(c2.android.xheaac.encoder と OEM ハードウェア実装)が追加されたことで、低ビットレートの音声メッセージやオーディオ メッセージ、ライブ オーディオ ストリーム、通信ペイロードを、優れた音声の明瞭さとデバイス間での一貫した音量で録音できるようになりました。

xHE-AAC のメリット

Android メッセージ アプリの従来の音声録音では、通常、古い AAC-LC(AAC Low Complexity)を 28 kbps で使用します。ビットレートが低い場合(32 kbps 未満)、AAC-LC では帯域制限アーティファクトが目立ち、音声品質がぼやけ、圧縮効率が最適でなくなります。エンドユーザーにとっては、子音が聞き取りにくい、こもったような音やキンキンした音になり、メッセージを再生し直したり、騒がしい環境で音声を聞き取ろうと苦労したりすることになります。また、音声クリップ間で音量が一定でないと、ユーザーはデバイスの音量を頻繁に調整しなければなりません。

xHE-AAC は、より高度なオーディオ コーディング アルゴリズムを適用することで、これらの制限を克服しています。たとえば、専用の音声コーディング ツールと一般的なオーディオ コーディング ツールを動的に切り替えながら、拡張スペクトル帯域複製(eSBR)で周波数範囲を拡張し、パラメトリック ステレオ コーディング機能をわずかな追加コーディング オーバーヘッドで提供します。

組み込みの MPEG-D DRC は、xHE-AAC の必須ラウドネス制御を提供して、コンテンツを一定の音量で再生します。また、ダイナミック レンジ制御処理を提供して、あらゆるプラットフォームや環境で最高のユーザー エクスペリエンスを実現します。

対象のユースケース

  • Rich Communication Services(RCS)と音声メッセージ: 低ビットレートの音声メッセージに xHE-AAC を義務付ける GSMA RCC.71 Universal Profile バージョン 3.1 以降に準拠しています。
  • ライブ音声ストリーミングとポッドキャスト: モバイル ネットワークや衛星ネットワークの接続が低下した場合でも、安定した音質で配信します。

プラットフォームの対応状況(API レベル 37.1 以降)

プラットフォーム xHE-AAC エンコードのサポートには、API レベル 37.1(Build.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 エンコーダが提供される場合があります。
  • 古い OS リリースやカスタマイズされた 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 のサポートを確認したら、KEY_AAC_PROFILE を MediaCodecInfo.CodecProfileLevel.AACObjectXHE(値 42)に設定して MediaFormat を構成します。

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)に対応しています。