Audioleistung mit In-Process-Decodern optimieren

Ab Android 17 (API-Level 37) und aktualisiert durch Google Play-Systemupdates (Mainline-APEX-Module) führt Android In-Process-Software-Audio-Decoder für komprimierte Audioformate ein, darunter Opus und AAC.

Wenn Apps Audio-Decoder direkt im Prozess der App ausführen, anstatt in einem separaten Systemprozess in einer Sandbox, können sie die Latenz bei der Audio-Decodierung um etwa 40% verringern, die CPU-Auslastung reduzieren und die Akkulaufzeit bei kontinuierlicher Medienwiedergabe, Sprachnachrichten und Spielen verlängern.

Out-of-Process- versus In-Process-Decodierung

Bisher hat Android alle Media-Decoder der Plattformsoftware in einem dedizierten, in einer Sandbox ausgeführten System-Daemon (mediaswcodec) ausgeführt, um Apps und das Betriebssystem vor fehlerhaften Media-Bitstreams zu schützen. Die Out-of-Process-Sandbox führt jedoch zu messbaren Leistungseinbußen:

  • Latenz bei der Inter-Process Communication (IPC): Jeder Audioeingabe-Frame, der in die Warteschlange des Decoders gestellt wird, und jeder zurückgegebene decodierte PCM-Audio-Puffer erfordert die prozessübergreifende Binder-IPC-Serialisierung und den Kontextwechsel.
  • Overhead für gemeinsam genutzten Speicher:Für die Übergabe von Puffer zwischen Prozessen sind die Synchronisierung und Cacheverwaltung von gemeinsam genutztem Speicher (C2Buffer) erforderlich.
  • Konflikte bei der Planung:Bei hoher CPU-Last können Konflikte bei der Thread-Planung zwischen dem App-Prozess und mediaswcodec zu Puffer-Starvation und Audio-Rucklern in Audio-Pipelines mit niedriger Latenz führen (z. B. DAWs, VoIP-Kommunikation und interaktive Spiele).

Wenn Sie Media-Decoder direkt im Prozess ausführen, werden diese Leistungsengpässe behoben:

  • Eliminiert die IPC-Latenz:Binder-IPC-Transaktionen und Kontextwechsel werden umgangen, wodurch die End-to-End-Decodierungslatenz um etwa 40 % reduziert wird.
  • Minimiert den Puffer-Aufwand:Audio-Puffer werden direkt im lokalen App-Arbeitsspeicherbereich übergeben, ohne dass prozessübergreifende Shared-Memory-Zuordnungen erforderlich sind.
  • Verhindert Scheduling-Jitter:Die Decodierung wird in den Prioritätsthreads der App ausgeführt, z. B. in Echtzeit-AAudio- oder Oboe-Threads. Dadurch werden Audio-Aussetzer und Puffer-Underruns bei hoher Systemlast verhindert.

Speichersicherheit mit Rust

Bisher hat Android Software-Decoder in einem separaten Prozess (mediaswcodec) isoliert, da Decoder nicht vertrauenswürdige Bitstreams aus externen Mediendateien verarbeiten. Dadurch sind Exploits für Speicherbeschädigungen (z. B. Pufferüberläufe) ein großes Sicherheitsrisiko.

Android kann Software-Decoder sicher direkt in den Prozess der aufrufenden App verschieben, indem sie in speichersicheren Sprachen wie Rust implementiert oder mit Lightweight Fault Isolation (LFI) isoliert werden.

Um den Overhead der Out-of-Process-Sandbox für AAC (c2.android.inproc.aac.decoder) sicher zu eliminieren, bietet Android einen Decoder, der in reinem Rust implementiert oder durch sichere Rust-FFI-Harnesses (Foreign Function Interface) geschützt ist.

Rust gilt aus folgenden Gründen als sicher für die In-Process-Decodierung:

  • Speichersicherheit zur Kompilierzeit:Die strengen Regeln von Rust für Eigentümerschaft, Ausleihen und Lebensdauer sorgen dafür, dass auf Speicher nicht zugegriffen werden kann, nachdem er freigegeben wurde, oder dass er nicht geändert wird, während er freigegeben ist.
  • Beseitigung häufiger Sicherheitslücken:Rust verhindert Heap-Pufferüberläufe, Stack-Smashing, Array-Lese- und ‑Schreibvorgänge außerhalb des zulässigen Bereichs, Use-After-Free-Fehler und Double-Free-Sicherheitslücken zur Kompilierzeit.
  • Keine Sandbox-Steuer für die Laufzeit:Da die Speichersicherheit zur Kompilierzeit mathematisch bewiesen und durch die Überprüfung von Grenzen bestätigt wird, werden Rust-Decoder mit nativer Geschwindigkeit im App-Prozess ausgeführt, ohne dass Seitenübergänge oder Hardware-Sandbox-Kontexte erforderlich sind.

Speichersicherheit mit Lightweight Fault Isolation (LFI)

Für komplexe C/C++-Audio-Decoder, die noch nicht in Rust neu geschrieben wurden, insbesondere den Opus-Decoder (c2.android.inproc.opus.decoder, unterstützt von libopus), bietet Android In-Process-Sicherheit mit Lightweight Fault Isolation (LFI).

LFI gilt aus folgenden Gründen als sicher für C/C++-Decoder:

  • Hardware-geschützte lineare Memory-Sandbox: LFI kompiliert die C/C++-Codec-Bibliothek in WebAssembly-abgeleiteten linearen Maschinencode, der auf einen hardware-geschützten linearen Memory-Slot mit 4 GB beschränkt ist.
  • Linux Memory Protection Keys (pku): LFI verwendet Linux Memory Protection Keys (pku / pkey_mprotect), um Berechtigungen für Adressräume zu partitionieren. Die isolierte Bibliothek kann keinen Speicher außerhalb des zugewiesenen 4‑GB-Sandbox-Slots lesen oder schreiben.
  • Abfangen von Systemaufrufen:In der Sandbox sind keine Systemaufrufe des Hostbetriebssystems (z. B. Datei-E/A, Netzwerkzugriff oder Prozesssteuerung) zulässig. Alle nicht unterstützten Bibliotheksaufrufe werden an minimale Dummy-Stubs (lfi_stubs.c) weitergeleitet.
  • Sichere Fehlerbehebung:Wenn ein fehlerhafter oder schädlicher Opus-Bitstream versucht, innerhalb der linearen Sandbox einen Lese- oder Schreibvorgang außerhalb des zulässigen Bereichs auszuführen, fängt die LFI-Laufzeit den Fehler sicher im Nutzerbereich ab und gibt einen sauberen C2_CORRUPTED-Decodierungsfehler zurück, ohne die Host-App zum Absturz zu bringen.

Unterstützte Architekturen und Geräte für LFI

Für LFI sind keine benutzerdefinierten CPU-Hardwareanweisungen erforderlich. Es wird auf den Prozessorarchitekturen ARM64 (aarch64) und x86_64 unterstützt:

  • ARM64-Geräte (aarch64):Die meisten mobilen Geräte für Endnutzer, darunter Smartphones (z. B. Pixel-Geräte mit Google Tensor-SoCs, Qualcomm Snapdragon- und MediaTek-SoCs), Falt-Smartphones, Tablets und Infotainment-Einheiten für Autos, werden mit ARM64-Prozessoren betrieben und unterstützen In-Process-Decoder für LFI.
  • x86_64-Geräte:Android-Emulatoren, die auf Entwicklerarbeitsplätzen mit Intel- oder AMD-CPUs ausgeführt werden, sowie ChromeOS-Chromebooks, auf denen Android-Apps mit ARC (Android Runtime for ChromeOS) ausgeführt werden, werden auf x86_64-Architekturen ausgeführt und unterstützen LFI-Sandboxing.

Verfügbare Audio-Decoder für die Verarbeitung

Audioformat MIME‑Typ Name der In-Process-Komponente Implementierung
Opus audio/opus c2.android.inproc.opus.decoder C/C++ libopus mit LFI
AAC audio/mp4a-latm c2.android.inproc.aac.decoder Speichersicheres Rust (C2ApexAacDec)
  • AAC (c2.android.inproc.aac.decoder): Verarbeitet ADTS-Streams (mit dynamischem ADTS-Header-Parsing) und Roh-Access Units (AUs) mit genauer Zeitstempelverfolgung (nur Verarbeitung von Einzelbild-AUs bei AAC_PACKAGING_RAW).
  • Opus (c2.android.inproc.opus.decoder): Decodiert Standard-Ogg-Opus- oder Matroska-Containerpakete mit internem Resampling auf 48 kHz PCM.

In-Process-Decoder aktivieren

Derzeit sind In-Process-Decoder nicht die Standardeinstellung des Systems, wenn MediaCodec.createDecoderByType() aufgerufen wird. Die Plattform behält eine höhere Priorität für die Auswahl von Codec 2.0 für Legacy-Out-of-Process-Decoder während der Ökosystemüberprüfung bei.

Wenn Sie heute schon einen In-Process-Decoder für die Wiedergabe mit geringer Latenz oder für Tests verwenden möchten, instanziieren Sie den Codec explizit nach Komponentennamen mit MediaCodec.createByCodecName().

Instanziieren nach Komponentenname

Das folgende Beispiel zeigt, wie der In-Process-AAC-Decoder explizit instanziiert wird. Wenn die In-Process-Komponente auf dem Gerät nicht verfügbar ist, wird auf den Standarddecoder zurückgegriffen:

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 oder ExoPlayer konfigurieren

Wenn Ihre App Jetpack Media3 oder ExoPlayer verwendet, können Sie eine benutzerdefinierte MediaCodecSelector erstellen, um In-Process-Decoder zu priorisieren, wenn sie auf dem Gerät verfügbar sind:

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;
    }
}

Roadmap für Standard-Systemdecoder

Android bereitet aktiv die Umstellung dieser speichersicheren In-Process-Decoder auf die Systemstandarddecoder für die jeweiligen Audio-MIME-Typen in zukünftigen Plattformversionen vor (beginnend mit Opus LFI und AAC Rust in Android 18 oder anstehenden Mainline-Modul-Updates).

Sobald ein In-Process-Decoder die Standardkomponente für Codec 2.0 für seinen MIME-Typ wird, werden Standardaufrufe von MediaCodec.createDecoderByType(...) automatisch über die In-Process-Implementierung weitergeleitet. Dadurch wird die Decodierungslatenz um etwa 40% gesenkt, ohne dass Änderungen am App-Code erforderlich sind.