À partir d'Android 17 (niveau d'API 37) et mis à jour via les mises à jour du système Google Play (modules APEX Mainline), Android introduit des décodeurs audio logiciels intégrés pour les formats audio compressés, y compris Opus et AAC.
En exécutant les décodeurs audio directement dans le processus de l'application plutôt que dans un processus système sandbox distinct, les applications peuvent réduire la latence de décodage audio d'environ 40%, réduire l'utilisation du processeur et prolonger l'autonomie de la batterie lors de la lecture continue de contenus multimédias, de l'envoi de messages vocaux et des jeux.
Décodage hors processus et dans le processus
Historiquement, Android exécutait tous les décodeurs multimédias du logiciel de plate-forme dans un démon système dédié et sandboxé (mediaswcodec) pour protéger les applications et l'OS des flux de bits multimédias mal formés. Toutefois, le bac à sable hors processus introduit une surcharge de performances mesurable :
- Latence de la communication inter-processus (IPC) : chaque frame d'entrée audio mise en file d'attente pour le décodeur et chaque tampon audio PCM décodé renvoyé nécessitent une sérialisation IPC Binder inter-processus et un changement de contexte.
- Surcharge de mémoire partagée : les transferts de tampon inter-processus nécessitent une synchronisation de la mémoire partagée (
C2Buffer) et une gestion du cache. - Contention de planification : en cas de forte charge du processeur, la contention de planification des threads entre le processus de l'application et
mediaswcodecpeut entraîner une insuffisance de mémoire tampon et des saccades audio dans les pipelines audio à faible latence (tels que les DAW, les communications VoIP et les jeux interactifs).
L'exécution des décodeurs multimédias en cours de processus résout directement ces goulots d'étranglement des performances :
- Élimine la latence IPC : contourne les transactions et les changements de contexte Binder IPC, ce qui réduit la latence de décodage de bout en bout d'environ 40%.
- Réduit la surcharge du tampon : transmet les tampons audio directement dans l'espace mémoire de l'application locale sans nécessiter de mappages de mémoire partagée interprocessus.
- Empêche la gigue de planification : exécute le décodage sur les propres threads de priorité de l'application (tels que les threads AAudio ou Oboe en temps réel), ce qui empêche les coupures audio et les sous-dépassements de mémoire tampon en cas de forte charge système.
Sécurité de la mémoire avec Rust
Historiquement, Android isolait les décodeurs logiciels dans un processus distinct (mediaswcodec), car les décodeurs traitent des flux de bits non fiables provenant de fichiers multimédias externes, ce qui fait des exploits de corruption de mémoire (tels que les dépassements de tampon) un problème de sécurité majeur.
Android peut déplacer en toute sécurité les décodeurs logiciels directement dans le processus de l'application appelante en les implémentant dans des langages sécurisés pour la mémoire comme Rust ou en les isolant avec Lightweight Fault Isolation (LFI).
Pour éliminer en toute sécurité la surcharge du bac à sable hors processus pour AAC (c2.android.inproc.aac.decoder), Android fournit un décodeur implémenté en Rust pur ou protégé par des harnais d'interface de fonction étrangère (FFI, Foreign Function Interface) Rust sécurisés.
Rust est considéré comme sécurisé pour le décodage en cours de processus pour les raisons suivantes :
- Sécurité de la mémoire au moment de la compilation : les vérifications strictes de la propriété, de l'emprunt et de la durée de vie de Rust garantissent que la mémoire ne peut pas être consultée après sa libération ni modifiée lorsqu'elle est partagée.
- Élimination des failles courantes : Rust empêche complètement les dépassements de mémoire tampon du tas, les écrasements de pile, les lectures et écritures de tableaux hors limites, les erreurs d'utilisation après libération et les failles de double libération au moment de la compilation.
- Aucune taxe de bac à sable d'exécution : comme la sécurité de la mémoire est mathématiquement prouvée au temps de compilation et vérifiée par le biais de la vérification des limites, les décodeurs Rust s'exécutent à la vitesse native dans le processus de l'application sans nécessiter de transitions de table de pages ni de contextes de bac à sable matériel.
Sécurité de la mémoire avec l'isolation légère des défauts (LFI)
Pour les anciens décodeurs audio C/C++ complexes qui n'ont pas encore été réécrits en Rust, en particulier le décodeur Opus (c2.android.inproc.opus.decoder optimisé par libopus), Android fournit une sécurité en cours de processus à l'aide de la Lightweight Fault Isolation (LFI).
L'interface LFI est considérée comme sécurisée pour les décodeurs C/C++ pour les raisons suivantes :
- Sandboxing de la mémoire linéaire protégée par matériel : LFI compile la bibliothèque de codecs C/C++ en code machine linéaire dérivé de WebAssembly, confiné dans un emplacement de mémoire linéaire de 4 Go protégé par matériel.
- Clés de protection de la mémoire Linux (
pku) : LFI utilise les clés de protection de la mémoire Linux (pku/pkey_mprotect) pour partitionner les autorisations d'espace d'adressage. La bibliothèque isolée ne peut pas lire ni écrire de mémoire en dehors de son emplacement sandbox de 4 Go. - Interception des appels système : aucun appel système de l'OS hôte (tel que les E/S de fichier, l'accès réseau ou le contrôle des processus) n'est autorisé dans le bac à sable. Tout appel de bibliothèque non compatible est redirigé vers des stubs factices minimaux (
lfi_stubs.c). - Récupération sécurisée des erreurs : si un flux de bits Opus mal formé ou hostile tente une lecture ou une écriture hors limites dans le bac à sable linéaire, le runtime LFI intercepte l'erreur de manière sécurisée dans l'espace utilisateur et renvoie une erreur de décodage
C2_CORRUPTEDpropre sans planter l'application hôte.
Architectures et appareils compatibles avec LFI
L'LFI ne nécessite aucune instruction matérielle de processeur personnalisée et est compatible avec les architectures de processeur ARM64 (aarch64) et x86_64 :
- Appareils ARM64 (
aarch64) : la plupart des appareils mobiles grand public, y compris les téléphones mobiles (tels que les appareils Pixel avec les SoC Google Tensor, Qualcomm Snapdragon et MediaTek), les appareils pliables, les tablettes et les unités d'info-divertissement automobiles, fonctionnent sur des processeurs ARM64 et sont compatibles avec les décodeurs LFI intégrés. - Appareils x86_64 : les émulateurs Android s'exécutant sur des postes de travail de développeur avec des processeurs Intel ou AMD, ainsi que les Chromebooks ChromeOS exécutant des applications Android à l'aide d'ARC (Android Runtime for ChromeOS), s'exécutent sur des architectures x86_64 et sont compatibles avec le bac à sable LFI.
Décodeurs audio intégrés disponibles
| Format audio | Type MIME | Nom du composant intégré | Implémentation |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
libopus C/C++ avec LFI |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
Rust avec sécurité mémoire (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder) : gère les flux ADTS (avec analyse dynamique des en-têtes ADTS) et les unités d'accès (UA) brutes avec un suivi précis des codes temporels de présentation (traitement des UA à un seul frame en cas deAAC_PACKAGING_RAW). - Opus (
c2.android.inproc.opus.decoder) : décode les paquets de conteneurs Ogg Opus ou Matroska standards avec rééchantillonnage interne en PCM à 48 kHz.
Activer les décodeurs intégrés
Actuellement, les décodeurs intégrés ne sont pas le système par défaut lors de l'appel de MediaCodec.createDecoderByType().
La plate-forme conserve une priorité de sélection plus élevée pour le codec 2.0 pour les anciens décodeurs hors processus lors de la validation de l'écosystème.
Pour activer un décodeur intégré aujourd'hui pour la lecture à faible latence ou les tests, instanciez le codec de manière explicite par nom de composant à l'aide de MediaCodec.createByCodecName().
Instancier par nom de composant
L'exemple suivant montre comment instancier explicitement le décodeur AAC intégré, en revenant au décodeur par défaut si le composant intégré n'est pas disponible sur l'appareil :
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); } }
Configurer Jetpack Media3 ou ExoPlayer
Si votre application utilise Jetpack Media3 ou ExoPlayer, vous pouvez créer un MediaCodecSelector personnalisé pour donner la priorité aux décodeurs intégrés lorsqu'ils sont disponibles sur l'appareil :
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; } }
Feuille de route vers les décodeurs système par défaut
Android se prépare activement à faire de ces décodeurs in-process à mémoire sécurisée les décodeurs système par défaut pour leurs types MIME audio respectifs dans les prochaines versions de la plate-forme (à commencer par Opus LFI et AAC Rust dans Android 18 ou les prochaines mises à jour du module Mainline).
Une fois qu'un décodeur intégré devient le composant Codec 2.0 par défaut pour son type MIME, les appels standards à MediaCodec.createDecoderByType(...) sont automatiquement routés via l'implémentation intégrée, ce qui permet de réduire la latence de décodage d'environ 40% sans nécessiter de modification du code de l'application.