インプロセス デコーダを使用してオーディオ パフォーマンスを最適化する

Android 17(API レベル 37)以降では、Google Play システム アップデート(Mainline APEX モジュール)を通じて、Android に Opus や AAC などの圧縮オーディオ形式用のインプロセス ソフトウェア オーディオ デコーダが導入されています。

アプリは、オーディオ デコーダを個別のサンドボックス化されたシステム プロセスではなく、アプリのプロセス内で直接実行することで、オーディオ デコードのレイテンシを約 40% 削減し、CPU 使用率を下げ、メディアの連続再生、音声メッセージ、ゲーム中のバッテリー駆動時間を延長できます。

プロセス外デコードとプロセス内デコード

これまで、Android は、アプリと OS を不正な形式のメディア ビットストリームから保護するため、専用のサンドボックス化されたシステム デーモン(mediaswcodec)内で、すべてのプラットフォーム ソフトウェア メディア デコーダを実行していました。ただし、アウトプロセス サンドボックスには、測定可能なパフォーマンス オーバーヘッドがあります。

  • プロセス間通信(IPC)レイテンシ: デコーダにキューイングされるすべての音声入力フレームと、返されるすべてのデコード済み PCM 音声バッファには、プロセス間 Binder IPC シリアル化とコンテキスト切り替えが必要です。
  • 共有メモリのオーバーヘッド: プロセス間バッファのハンドオフには、共有メモリ(C2Buffer)の同期とキャッシュ管理が必要です。
  • スケジューリング競合: CPU 負荷が高い場合、アプリ プロセスと mediaswcodec の間のスレッド スケジューリング競合により、低レイテンシ オーディオ パイプライン(DAW、VoIP 通信、インタラクティブ ゲームなど)でバッファの枯渇やオーディオの途切れが発生する可能性があります。

メディア デコーダをインプロセスで実行すると、次のパフォーマンス ボトルネックが直接解消されます。

  • IPC レイテンシを解消: Binder IPC トランザクションとコンテキスト切り替えをバイパスし、エンドツーエンドのデコード レイテンシを約 40% 削減します。
  • バッファのオーバーヘッドを最小限に抑える: プロセス間の共有メモリ マッピングを必要とせずに、ローカルアプリのメモリ空間内でオーディオ バッファを直接渡します。
  • スケジューリング ジッターを防止: アプリ独自の優先度スレッド(リアルタイム AAudio スレッドや Oboe スレッドなど)でデコードを実行し、システム負荷が高い状態での音声の途切れやバッファ アンダーランを防止します。

Rust によるメモリ安全性

これまで、Android ではソフトウェア デコーダを別のプロセス(mediaswcodec)で分離してきました。これは、デコーダが外部メディア ファイルから信頼できないビットストリームを処理するため、メモリ破損エクスプロイト(バッファ オーバーフローなど)が大きなセキュリティ上の懸念事項となっていたためです。

Android は、Rust などのメモリセーフな言語で実装するか、Lightweight Fault Isolation(LFI)で分離することで、ソフトウェア デコーダを呼び出し元のアプリプロセスに安全に直接移動できます。

AAC(c2.android.inproc.aac.decoder)のプロセス外サンドボックスのオーバーヘッドを安全に排除するため、Android は 純粋な Rust で実装されたデコーダ、または安全な Rust Foreign Function Interface(FFI)ハーネスで保護されたデコーダを提供します。

Rust は、次の理由により、インプロセス デコードに安全であると見なされます。

  • コンパイル時のメモリ安全性: Rust の厳格な所有権、借用、ライフタイム チェックにより、メモリが解放された後や、共有中に変更された後にメモリにアクセスできないことが保証されます。
  • 一般的な脆弱性の排除: Rust は、コンパイル時にヒープ バッファ オーバーフロー、スタック スマッシュ、配列の境界外の読み取りと書き込み、解放後使用(Use After Free)エラー、二重解放の脆弱性を完全に防ぎます。
  • 実行時のサンドボックス化のオーバーヘッドがゼロ: メモリの安全性がコンパイル時に数学的に証明され、境界チェックによって検証されるため、Rust デコーダはページテーブルの切り替えやハードウェア サンドボックス コンテキストを必要とせずに、アプリ プロセス内でネイティブ速度で実行されます。

軽量フォールト分離(LFI)によるメモリ安全性

まだ Rust で書き換えられていない複雑なレガシー C/C++ オーディオ デコーダ(特に Opus デコーダ(libopus を搭載した c2.android.inproc.opus.decoder))の場合、Android は 軽量フォールト分離(LFI)を使用してプロセス内安全を提供します。

LFI は、次の理由から C/C++ デコーダに対して安全であると見なされます。

  • ハードウェアで保護された線形メモリ サンドボックス: LFI は、C/C++ コーデック ライブラリを、ハードウェアで保護された 4 GB の線形メモリ スロットに制限された WebAssembly 由来の線形マシンコードにコンパイルします。
  • Linux メモリ保護キー(pku): LFI は、Linux メモリ保護キー(pku / pkey_mprotect)を使用してアドレス空間の権限をパーティション分割します。分離されたライブラリは、割り当てられた 4 GB のサンドボックス スロット外のメモリを読み書きできません。
  • システム呼び出しのインターセプト: サンドボックス内では、ホスト OS のシステム呼び出し(ファイル I/O、ネットワーク アクセス、プロセス制御など)は許可されません。サポートされていないライブラリ呼び出しは、最小限のダミー スタブ(lfi_stubs.c)にルーティングされます。
  • 安全な障害復旧: 形式が正しくない、または敵対的な Opus ビットストリームが線形サンドボックス内で範囲外の読み取りまたは書き込みを試みた場合、LFI ランタイムはユーザー空間で障害を安全にトラップし、ホストアプリをクラッシュさせることなく、クリーンな C2_CORRUPTED デコードエラーを返します。

LFI でサポートされているアーキテクチャとデバイス

LFI にはカスタム CPU ハードウェア命令は必要なく、ARM64(aarch64)と x86_64 プロセッサ アーキテクチャでサポートされています。

  • ARM64(aarch64)デバイス: モバイル デバイス(Google Tensor SoC、Qualcomm Snapdragon、MediaTek SoC を搭載した Google Pixel デバイスなど)、折りたたみ式デバイス、タブレット、車載インフォテインメント ユニットなど、ほとんどの消費者向けモバイル デバイスは ARM64 プロセッサで動作し、LFI インプロセス デコーダをサポートしています。
  • x86_64 デバイス: Intel または AMD CPU を搭載したデベロッパー ワークステーションで実行される Android エミュレータ、および ARC(Android Runtime for ChromeOS)を使用して Android アプリを実行する ChromeOS Chromebook は、x86_64 アーキテクチャで実行され、LFI サンドボックスをサポートします。

利用可能なインプロセス音声デコーダ

オーディオ形式 MIME タイプ インプロセス コンポーネント名 実装
Opus audio/opus c2.android.inproc.opus.decoder LFI を使用した C/C++ libopus
AAC audio/mp4a-latm c2.android.inproc.aac.decoder メモリセーフな Rust(C2ApexAacDec)
  • AAC(c2.android.inproc.aac.decoder): ADTS ストリーム(動的 ADTS ヘッダー解析あり)と、正確なプレゼンテーション タイムスタンプ トラッキング(AAC_PACKAGING_RAW の場合はシングルフレーム AU 処理のみ)を含む未加工のアクセス ユニット(AU)を処理します。
  • Opus(c2.android.inproc.opus.decoder): 標準の Ogg Opus または Matroska コンテナ パケットをデコードし、内部で 48 kHz PCM にリサンプリングします。

プロセス内デコーダを有効にする

現在、MediaCodec.createDecoderByType() を呼び出す場合、インプロセス デコーダはシステムのデフォルトではありません。エコシステムの検証中、プラットフォームは以前のプロセス外デコーダに対して Codec 2.0 の選択優先度を高く維持します。

低遅延再生またはテストのために、今日からインプロセス デコーダを有効にするには、MediaCodec.createByCodecName() を使用してコンポーネント名でコーデックを明示的にインスタンス化します。

コンポーネント名でインスタンス化する

次の例は、インプロセス AAC デコーダを明示的にインスタンス化し、デバイスでインプロセス コンポーネントが使用できない場合はデフォルトのデコーダにフォールバックする方法を示しています。

Kotlin

val INPROC_AAC_CODEC = "c2.android.inproc.aac.decoder"

fun createAudioDecoder(): MediaCodec {
    return try {
        // Attempt to opt in to the low-latency in-process AAC decoder
        MediaCodec.createByCodecName(INPROC_AAC_CODEC)
    } catch (e: IllegalArgumentException) {
        // Fall back to the default platform AAC decoder
        MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
    }
}

Java

private static final String INPROC_AAC_CODEC = "c2.android.inproc.aac.decoder";

public MediaCodec createAudioDecoder() throws IOException {
    try {
        // Attempt to opt in to the low-latency in-process AAC decoder
        return MediaCodec.createByCodecName(INPROC_AAC_CODEC);
    } catch (IllegalArgumentException e) {
        // Fall back to the default platform AAC decoder
        return MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
    }
}

Jetpack Media3 または ExoPlayer を構成する

アプリで Jetpack Media3 または ExoPlayer を使用している場合は、デバイスで利用可能な場合にインプロセス デコーダを優先するカスタム MediaCodecSelector を作成できます。

Kotlin

class InprocPreferredMediaCodecSelector : MediaCodecSelector {
    override fun getDecoderInfos(
        mimeType: String,
        requiresSecureDecoder: Boolean,
        requiresTunnelingDecoder: Boolean
    ): List<MediaCodecInfo> {
        val defaultInfos = MediaCodecUtil.getDecoderInfos(
            mimeType,
            requiresSecureDecoder,
            requiresTunnelingDecoder
        )
        val inprocNames = setOf(
            "c2.android.inproc.opus.decoder",
            "c2.android.inproc.aac.decoder"
        )
        // Sort in-process decoders to the top of the selection list
        return defaultInfos.sortedByDescending { it.name in inprocNames }
    }
}

Java

public class InprocPreferredMediaCodecSelector implements MediaCodecSelector {
    private static final Set<String> INPROC_NAMES = new HashSet<>(Arrays.asList(
        "c2.android.inproc.opus.decoder",
        "c2.android.inproc.aac.decoder"
    ));

    @Override
    public List<MediaCodecInfo> getDecoderInfos(
            String mimeType,
            boolean requiresSecureDecoder,
            boolean requiresTunnelingDecoder) throws MediaCodecUtil.DecoderQueryException {
        List<MediaCodecInfo> defaultInfos = new ArrayList<>(
            MediaCodecUtil.getDecoderInfos(mimeType, requiresSecureDecoder, requiresTunnelingDecoder)
        );
        // Sort in-process decoders to the top of the selection list
        defaultInfos.sort((a, b) -> {
            boolean aInproc = INPROC_NAMES.contains(a.name);
            boolean bInproc = INPROC_NAMES.contains(b.name);
            return Boolean.compare(bInproc, aInproc);
        });
        return defaultInfos;
    }
}

デフォルトのシステム デコーダへのロードマップ

Android は、これらのメモリセーフなインプロセス デコーダを、今後のプラットフォーム リリース(Android 18 または今後の Mainline モジュール アップデートの Opus LFI と AAC Rust から)でそれぞれのオーディオ MIME タイプのシステム デフォルト デコーダに移行する準備を積極的に進めています。

インプロセス デコーダがその MIME タイプのデフォルトの Codec 2.0 コンポーネントになると、MediaCodec.createDecoderByType(...) への標準呼び出しは自動的にインプロセス実装を介してルーティングされ、アプリコードの変更を必要とせずにデコード レイテンシが約 40% 削減されます。