A partir de Android 17 (nivel de API 37) y actualizado a través de las actualizaciones del sistema de Google Play (módulos APEX de Mainline), Android introduce decodificadores de audio de software en el proceso para formatos de audio comprimido, incluidos Opus y AAC.
Al ejecutar decodificadores de audio directamente dentro del proceso de la app en lugar de en un proceso del sistema aislado separado, las apps pueden reducir la latencia de decodificación de audio en aproximadamente un 40%, reducir la utilización de la CPU y extender la duración de la batería durante la reproducción continua de contenido multimedia, la mensajería de voz y los juegos.
Decodificación fuera del proceso versus dentro del proceso
Históricamente, Android ejecutó todos los decodificadores de medios de software de la plataforma dentro de un daemon del sistema dedicado y aislado (mediaswcodec) para proteger las apps y el SO de los flujos de bits de medios con formato incorrecto. Sin embargo, el aislamiento de procesos introduce 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 serialización de IPC de Binder entre procesos y cambio de contexto.
- Sobrecarga de memoria compartida: Las transferencias de búferes entre procesos requieren sincronización de memoria compartida (
C2Buffer) y administración de caché. - Contención de programación: Con una carga de CPU alta, la contención de programación de subprocesos entre el proceso de la app y
mediaswcodecpuede provocar inanición de búfer y tartamudeo de audio en las canalizaciones de audio de baja latencia (como DAW, comunicación VoIP y juegos interactivos).
La ejecución de decodificadores de medios 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 fluctuación de la programación: Ejecuta la decodificación en los propios subprocesos prioritarios de la app (como los subprocesos en tiempo real de AAudio o Oboe), lo que evita interrupciones de audio y desbordamientos inferiores del búfer con una carga pesada del sistema.
Seguridad de la memoria con Rust
Históricamente, Android aisló los decodificadores de software en un proceso separado (mediaswcodec) porque los decodificadores 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 una preocupación importante para la seguridad.
Android puede trasladar de forma segura los decodificadores de software directamente al proceso de la app que realiza la llamada 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 del 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.
Se considera que Rust es seguro para la decodificación en el proceso por los siguientes motivos:
- Seguridad de la memoria en tiempo de compilación: La estricta verificación de propiedad, préstamo y ciclo de vida de Rust garantiza que no se pueda acceder a la memoria después de que se libera o se muta mientras se comparte.
- Eliminación de vulnerabilidades comunes: Rust evita por completo los desbordamientos del búfer del montón, los desbordamientos de la pila, las lecturas y escrituras de arrays fuera de los límites, los errores de uso después de liberación y las vulnerabilidades de doble liberación en el tiempo de compilación.
- Impuesto de zona de pruebas en tiempo de ejecución cero: Dado 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 la velocidad nativa dentro del proceso de la app sin necesidad de transiciones de la tabla de páginas ni contextos de zona de pruebas de hardware.
Seguridad de la memoria con aislamiento de errores 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 potenciado por libopus), Android proporciona seguridad en el proceso con Aislamiento de fallas ligero (LFI).
La LFI se considera segura para los decodificadores de C/C++ por los siguientes motivos:
- Aislamiento de 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 del espacio de direcciones. La biblioteca aislada no puede leer ni escribir en la memoria fuera de la ranura de zona de pruebas de 4 GB asignada. - Interceptació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 una biblioteca no compatible se enruta a stubs mínimos simulados (
lfi_stubs.c). - Recuperación segura de errores: Si un flujo de bits de Opus con formato incorrecto o adversarial intenta una lectura o escritura fuera de los límites dentro de la zona de pruebas lineal, el tiempo de ejecución de la LFI detecta el error de forma segura en el espacio del usuario y devuelve un error de decodificación
C2_CORRUPTEDlimpio sin fallar la app host.
Arquitecturas y dispositivos compatibles con LFI
La LFI no requiere instrucciones de hardware de CPU personalizadas y es compatible con las arquitecturas de procesador ARM64 (aarch64) y x86_64:
- Dispositivos ARM64 (
aarch64): La mayoría de los dispositivos móviles para el consumidor, incluidos los teléfonos móviles (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 LFI en proceso. - Dispositivos x86_64: Los emuladores de Android que se ejecutan en estaciones de trabajo para desarrolladores con CPU Intel o AMD, así como las Chromebooks con ChromeOS que ejecutan apps para Android con ARC (Android Runtime for ChromeOS), se ejecutan en arquitecturas x86_64 y admiten el aislamiento de LFI.
Decodificadores de audio disponibles en el proceso
| 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 la marca 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 integrados en el proceso
Actualmente, los decodificadores integrados no son el valor predeterminado del sistema cuando se llama a MediaCodec.createDecoderByType().
La plataforma conserva una mayor prioridad de selección de Codec 2.0 para los decodificadores heredados fuera de proceso durante la verificación del ecosistema.
Para habilitar un decodificador en proceso hoy mismo 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().
Instanciación por nombre de componente
En el siguiente ejemplo, se muestra cómo crear una instancia explícita del decodificador AAC integrado, con una reversión al decodificador predeterminado si el componente integrado 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 activamente para realizar la transición de estos decodificadores en proceso seguros para la 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 (a partir de 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 enrutan automáticamente a través de la implementación en proceso, lo que proporciona una latencia de decodificación aproximadamente un 40% menor sin necesidad de cambiar el código de la app.