A partire da Android 17 (livello API 37) e aggiornato tramite gli aggiornamenti del sistema Google Play (moduli APEX Mainline), Android introduce decoder audio software in-process per i formati audio compressi, inclusi Opus e AAC.
Eseguendo i decoder audio direttamente all'interno del processo dell'app anziché in un processo di sistema sandbox separato, le app possono ridurre la latenza di decodifica audio di circa il 40%, ridurre l'utilizzo della CPU e prolungare la durata della batteria durante la riproduzione continua di contenuti multimediali, la messaggistica vocale e i giochi.
Decodifica out-of-process e in-process
In passato, Android eseguiva tutti i decoder multimediali software della piattaforma all'interno di un
daemon di sistema dedicato e in sandbox (mediaswcodec) per proteggere le app e il
sistema operativo da bitstream multimediali non validi. Tuttavia, il sandboxing out-of-process
introduce un overhead di prestazioni misurabile:
- Latenza della comunicazione interprocesso (IPC): ogni frame di input audio accodato al decodificatore e ogni buffer audio PCM decodificato restituito richiede la serializzazione IPC di Binder tra processi e il cambio di contesto.
- Overhead della memoria condivisa: i trasferimenti di buffer tra processi richiedono
la sincronizzazione della memoria condivisa (
C2Buffer) e la gestione della cache. - Contesa di pianificazione:in caso di carico elevato della CPU, la contesa di pianificazione dei thread tra il processo dell'app e
mediaswcodecpuò causare l'esaurimento del buffer e l'interruzione dell'audio nelle pipeline audio a bassa latenza (come DAW, comunicazione VoIP e giochi interattivi).
L'esecuzione dei decoder multimediali in-process risolve direttamente questi colli di bottiglia delle prestazioni:
- Elimina la latenza IPC: ignora le transazioni IPC Binder e i cambi di contesto, riducendo la latenza di decodifica end-to-end di circa il 40%.
- Riduce al minimo l'overhead del buffer: passa i buffer audio direttamente nello spazio di memoria dell'app locale senza richiedere mappature della memoria condivisa tra processi.
- Impedisce il jitter di pianificazione:esegue la decodifica sui thread di priorità dell'app (ad esempio thread AAudio o Oboe in tempo reale), impedendo interruzioni audio e buffer underrun in caso di carico elevato del sistema.
Sicurezza della memoria con Rust
Storicamente, Android ha isolato i decoder software in un processo separato
(mediaswcodec) perché i decoder elaborano bitstream non attendibili da file multimediali
esterni, rendendo gli exploit di corruzione della memoria (come i buffer overflow) un
importante problema di sicurezza.
Android può spostare in modo sicuro i decoder software direttamente nel processo dell'app chiamante implementandoli in linguaggi sicuri per la memoria come Rust o isolandoli con Lightweight Fault Isolation (LFI).
Per eliminare in modo sicuro l'overhead della sandbox out-of-process per AAC
(c2.android.inproc.aac.decoder), Android fornisce un decodificatore implementato in
Rust puro o protetto da interfacce FFI (Foreign Function Interface) Rust sicure.
Rust è considerato sicuro per la decodifica in-process per i seguenti motivi:
- Sicurezza della memoria in fase di compilazione:i controlli rigorosi di proprietà, prestito e durata di Rust garantiscono che non sia possibile accedere alla memoria dopo che è stata liberata o modificata durante la condivisione.
- Eliminazione delle vulnerabilità comuni: Rust impedisce completamente gli overflow del buffer heap, gli stack smash, le letture e le scritture di array fuori dai limiti, gli errori di tipo use-after-free e le vulnerabilità di tipo double-free in tempo di compilazione.
- Tassa di sandboxing zero runtime:poiché la sicurezza della memoria è dimostrata matematicamente in tempo di compilazione e verificata tramite il controllo dei limiti, i decoder Rust vengono eseguiti alla velocità nativa all'interno del processo dell'app senza richiedere transizioni della tabella delle pagine o contesti sandbox hardware.
Sicurezza della memoria con l'isolamento degli errori leggero (LFI)
Per i decodificatori audio C/C++ legacy complessi che non sono ancora stati riscritti in
Rust, in particolare il decodificatore Opus (c2.android.inproc.opus.decoder
basato su libopus), Android fornisce la sicurezza in-process utilizzando
Lightweight Fault Isolation (LFI).
LFI è considerato sicuro per i decoder C/C++ per i seguenti motivi:
- Sandbox della memoria lineare protetta dall'hardware: LFI compila la libreria di codec C/C++ in codice macchina lineare derivato da WebAssembly confinato in uno slot di memoria lineare da 4 GB protetto dall'hardware.
- Chiavi di protezione della memoria di Linux (
pku): LFI utilizza le chiavi di protezione della memoria di Linux (pku/pkey_mprotect) per partizionare le autorizzazioni dello spazio degli indirizzi. La libreria isolata non può leggere o scrivere nella memoria al di fuori dello slot sandbox da 4 GB assegnato. - Intercettazione delle chiamate di sistema:all'interno della sandbox sono consentite chiamate di sistema del sistema operativo host zero (ad esempio I/O di file,
accesso alla rete o controllo dei processi). Qualsiasi
chiamata di libreria non supportata viene indirizzata a stub fittizi minimi (
lfi_stubs.c). - Recupero sicuro dagli errori:se un flusso di bit Opus malformato o ostile tenta una lettura o una scrittura fuori dai limiti all'interno della sandbox lineare, il runtime LFI intercetta l'errore in modo sicuro nello spazio utente e restituisce un errore di decodifica
C2_CORRUPTEDpulito senza arrestare l'app host.
Architetture e dispositivi supportati per LFI
LFI non richiede istruzioni hardware della CPU personalizzate ed è supportato su
architetture del processore ARM64 (aarch64) e x86_64:
- Dispositivi ARM64 (
aarch64):la maggior parte dei dispositivi mobili di consumo, inclusi i telefoni cellulari (come i dispositivi Pixel con SoC Google Tensor, Qualcomm Snapdragon e MediaTek), i dispositivi pieghevoli, i tablet e le unità di infotainment per auto, vengono eseguiti su processori ARM64 e supportano i decoder LFI in-process. - Dispositivi x86_64: gli emulatori Android in esecuzione su workstation di sviluppo con CPU Intel o AMD, nonché i Chromebook ChromeOS che eseguono app Android utilizzando ARC (Android Runtime for ChromeOS), vengono eseguiti su architetture x86_64 e supportano il sandboxing LFI.
Decodificatori audio in-process disponibili
| Formato audio | Tipo MIME | Nome del componente in-process | Implementazione |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
C/C++ libopus con LFI |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
Rust con sicurezza della memoria (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder): gestisce i flussi ADTS (con analisi dinamica dell'intestazione ADTS) e le unità di accesso (AU) non elaborate con un monitoraggio accurato del timestamp di presentazione (solo elaborazione AU a singolo frame in caso diAAC_PACKAGING_RAW). - Opus (
c2.android.inproc.opus.decoder): decodifica i pacchetti contenitore Ogg Opus o Matroska standard con ricampionamento interno a 48 kHz PCM.
Attivare i decoder in-process
Al momento, i decodificatori in fase di elaborazione non sono l'impostazione predefinita del sistema quando si chiama
MediaCodec.createDecoderByType().
La piattaforma mantiene una priorità di selezione più elevata per Codec 2.0 per i decoder out-of-process legacy durante la verifica dell'ecosistema.
Per attivare oggi un decodificatore in fase di elaborazione per la riproduzione o il test a bassa latenza,
crea un'istanza del codec in modo esplicito in base al nome del componente utilizzando
MediaCodec.createByCodecName().
Creare un'istanza in base al nome del componente
Il seguente esempio mostra come creare un'istanza esplicita del decodificatore AAC in-process, eseguendo il fallback al decodificatore predefinito se il componente in-process non è disponibile sul dispositivo:
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); } }
Configurare Jetpack Media3 o ExoPlayer
Se la tua app utilizza Jetpack Media3 o ExoPlayer, puoi creare un MediaCodecSelector personalizzato per dare la priorità ai decoder in-process quando sono disponibili sul dispositivo:
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 per i decodificatori di sistema predefiniti
Android si sta preparando attivamente alla transizione di questi decoder in-process sicuri per la memoria per farli diventare i decoder di sistema predefiniti per i rispettivi tipi MIME audio nelle prossime release della piattaforma (a partire da Opus LFI e AAC Rust in Android 18 o nei prossimi aggiornamenti dei moduli Mainline).
Una volta che un decodificatore in-process diventa il componente Codec 2.0 predefinito per il suo tipo MIME, le chiamate standard a MediaCodec.createDecoderByType(...) vengono instradate automaticamente tramite l'implementazione in-process, fornendo una latenza di decodifica inferiore di circa il 40% senza richiedere modifiche al codice dell'app.