A partir de Android 17 (nivel de API 37) y con las actualizaciones del sistema de Google Play (módulos APEX de Mainline), Android presenta decodificadores de audio de software en proceso para formatos de audio comprimidos, incluidos Opus y AAC.
Cuando se ejecutan decodificadores de audio directamente en el proceso de la app, en lugar de en un proceso del sistema aislado, las apps pueden reducir la latencia de decodificación de audio en un 40% aproximadamente, disminuir el uso de CPU y extender la duración de la batería durante la reproducción continua de contenido multimedia, los mensajes de voz y los juegos.
Decodificación fuera del proceso frente a decodificación en el proceso
Históricamente, Android ejecutaba todos los decodificadores de contenido multimedia de software de la plataforma dentro de un daemon del sistema dedicado y de zona de pruebas (mediaswcodec) para proteger las apps y el SO de los flujos de bits de contenido multimedia con formato incorrecto. Sin embargo, la zona de pruebas fuera de proceso genera una sobrecarga de rendimiento medible:
- Latencia de comunicación entre procesos (IPC): Cada fotograma de entrada de audio en cola para el decodificador y cada búfer de audio PCM decodificado que se devuelve requieren la serialización de IPC de Binder entre procesos y el cambio de contexto.
- Sobrecarga de memoria compartida: Las transferencias de búfer entre procesos requieren
sincronización de memoria compartida (
C2Buffer) y administración de caché. - Contención de programación: En condiciones de carga de CPU intensa, la contención de programación de subprocesos entre el proceso de la app y
mediaswcodecpuede causar falta de búfer y tartamudeo de audio en las canalizaciones de audio de baja latencia (como DAW, comunicación de VoIP y juegos interactivos).
Ejecutar decodificadores de contenido multimedia en el proceso resuelve directamente estos cuellos de botella de rendimiento:
- Elimina la latencia de IPC: Omite las transacciones de IPC de Binder y los cambios de contexto, lo que reduce la latencia de decodificación de extremo a extremo en aproximadamente un 40%.
- Minimiza la sobrecarga del búfer: Pasa los búferes de audio directamente dentro del espacio de memoria de la app local sin requerir asignaciones de memoria compartida entre procesos.
- Evita la inestabilidad de la programación: Ejecuta la decodificación en los subprocesos de prioridad propios de la app (como los subprocesos de AAudio o Oboe en tiempo real), lo que evita las interrupciones de audio y los vaciamientos de búfer con una carga pesada del sistema.
Seguridad de la memoria con Rust
Históricamente, Android aislaba los decodificadores de software en un proceso independiente (mediaswcodec) porque estos procesan flujos de bits no confiables de archivos multimedia externos, lo que hace que los exploits de corrupción de memoria (como los desbordamientos de búfer) sean un problema de seguridad importante.
Android puede mover de forma segura los decodificadores de software directamente al proceso de la app de llamadas implementándolos en lenguajes seguros para la memoria, como Rust, o aislándolos con el aislamiento de fallas ligero (LFI).
Para eliminar de forma segura la sobrecarga de la zona de pruebas fuera de proceso para AAC (c2.android.inproc.aac.decoder), Android proporciona un decodificador implementado en Rust puro o protegido por arneses seguros de la interfaz de función externa (FFI) de Rust.
Rust se considera seguro para la decodificación en proceso por los siguientes motivos:
- Seguridad de la memoria en el tiempo de compilación: La propiedad estricta, el préstamo y la verificación de la vida útil de Rust garantizan que no se pueda acceder a la memoria después de que se libere o se mute mientras se comparte.
- Eliminación de vulnerabilidades comunes: Rust evita por completo los desbordamientos del búfer de montón, los desbordamientos de pila, las lecturas y escrituras de arrays fuera de los límites, los errores de uso después de la liberación y las vulnerabilidades de doble liberación en el tiempo de compilación.
- Cero impuestos de zona de pruebas en el entorno de ejecución: Debido a que la seguridad de la memoria se demuestra matemáticamente en el tiempo de compilación y se verifica a través de la verificación de límites, los decodificadores de Rust se ejecutan a velocidad nativa dentro del proceso de la app sin requerir transiciones de tablas de páginas ni contextos de zona de pruebas de hardware.
Seguridad de la memoria con aislamiento de fallas ligero (LFI)
Para los decodificadores de audio heredados complejos de C/C++ que aún no se reescribieron en
Rust, específicamente el decodificador Opus (c2.android.inproc.opus.decoder
con tecnología de libopus), Android proporciona seguridad en el proceso con
el aislamiento de fallas ligero (LFI).
La LFI se considera segura para los decodificadores de C/C++ por los siguientes motivos:
- Zona de pruebas de memoria lineal protegida por hardware: LFI compila la biblioteca de códecs de C/C++ en código máquina lineal derivado de WebAssembly confinado a una ranura de memoria lineal de 4 GB protegida por hardware.
- Claves de protección de memoria de Linux (
pku): LFI usa claves de protección de memoria de Linux (pku/pkey_mprotect) para particionar los permisos de espacio de direcciones. La biblioteca aislada no puede leer ni escribir memoria fuera de su espacio de zona de pruebas de 4 GB asignado. - Intercepción de llamadas al sistema: No se permiten llamadas al sistema del SO host (como E/S de archivos,
acceso a la red o control de procesos) dentro de la zona de pruebas. Cualquier llamada a la biblioteca no compatible se enruta a stubs ficticios mínimos (
lfi_stubs.c). - Recuperación segura de fallas: Si una transmisión de bits de Opus con formato incorrecto o adversario
intenta realizar una lectura o escritura fuera de los límites dentro de la zona de pruebas lineal, el entorno de ejecución de LFI
captura la falla de forma segura en el espacio del usuario y devuelve un error de decodificación
C2_CORRUPTEDlimpio sin que falle la app host.
Arquitecturas y dispositivos compatibles con LFI
La LFI no requiere instrucciones de hardware de CPU personalizadas y es compatible con la arquitectura de procesador ARM64 (aarch64). La mayoría de los dispositivos móviles de consumo, incluidos los teléfonos celulares (como los dispositivos Pixel con SoC de Google Tensor, Qualcomm Snapdragon y SoC de MediaTek), los dispositivos plegables, las tablets y las unidades de infoentretenimiento para automóviles, se ejecutan en procesadores ARM64 y admiten decodificadores en proceso de LFI.
Decodificadores de audio en proceso disponibles
| Formato de audio | Tipo de MIME | Nombre del componente en proceso | Implementación |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
libopus de C/C++ con LFI |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
Rust con seguridad de memoria (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder): Controla transmisiones de ADTS (con análisis dinámico de encabezados de ADTS) y unidades de acceso (AU) sin procesar con un seguimiento preciso de las marcas de tiempo de presentación (solo procesamiento de AU de un solo fotograma en el caso deAAC_PACKAGING_RAW). - Opus (
c2.android.inproc.opus.decoder): Decodifica paquetes de contenedores estándar de Ogg Opus o Matroska con un nuevo muestreo interno a PCM de 48 kHz.
Habilita los decodificadores en proceso
Actualmente, los decodificadores en proceso no son los predeterminados del sistema cuando se llama a MediaCodec.createDecoderByType().
La plataforma conserva una prioridad de selección de Codec 2.0 más alta para los decodificadores fuera de proceso heredados durante la verificación del ecosistema.
Para habilitar un decodificador en proceso hoy para la reproducción o las pruebas de baja latencia, crea una instancia del códec de forma explícita por nombre de componente con MediaCodec.createByCodecName().
Crea una instancia por nombre de componente
En el siguiente ejemplo, se muestra cómo crear una instancia de forma explícita del decodificador de AAC en proceso y recurrir al decodificador predeterminado si el componente en proceso no está disponible en el 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); } }
Configura Jetpack Media3 o ExoPlayer
Si tu app usa Jetpack Media3 o ExoPlayer, puedes crear un MediaCodecSelector personalizado para priorizar los decodificadores en proceso cuando estén disponibles en el 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; } }
Hoja de ruta para los decodificadores predeterminados del sistema
Android se está preparando de forma activa para realizar la transición de estos decodificadores en proceso con seguridad de memoria para que se conviertan en los decodificadores predeterminados del sistema para sus respectivos tipos de MIME de audio en las próximas versiones de la plataforma (comenzando con Opus LFI y AAC Rust en Android 18 o las próximas actualizaciones del módulo de Mainline).
Una vez que un decodificador en proceso se convierte en el componente predeterminado de Codec 2.0 para su tipo de MIME, las llamadas estándar a MediaCodec.createDecoderByType(...) se enrutarán automáticamente a través de la implementación en proceso, lo que proporcionará una latencia de decodificación aproximadamente un 40% menor sin necesidad de realizar cambios en el código de la app.