A partir do Android 17 (nível 37 da API) e atualizado pelas atualizações do sistema do Google Play (módulos APEX do Mainline), o Android apresenta decodificadores de áudio de software em processo para formatos de áudio compactados, incluindo Opus e AAC.
Ao executar decodificadores de áudio diretamente no processo do app, em vez de em um processo de sistema separado em sandbox, os apps podem reduzir a latência de decodificação de áudio em aproximadamente 40%, reduzir o uso da CPU e prolongar a duração da bateria durante a reprodução contínua de mídia, mensagens de voz e jogos.
Decodificação fora do processo x em processo
Historicamente, o Android executava todos os decodificadores de mídia de software da plataforma em um
daemon de sistema dedicado e em sandbox (mediaswcodec) para proteger os apps e o
SO contra bitstreams de mídia malformados. No entanto, o sandbox fora do processo
introduz uma sobrecarga de desempenho mensurável:
- Latência de comunicação entre processos (IPC, na sigla em inglês): cada frame de entrada de áudio enfileirado para o decodificador e cada buffer de áudio PCM decodificado retornado requer serialização de IPC do Binder entre processos e troca de contexto.
- Sobrecarga de memória compartilhada: as transferências de buffer entre processos exigem
sincronização de memória compartilhada (
C2Buffer) e gerenciamento de cache. - Contenção de programação: sob carga pesada de CPU, a contenção de programação de linhas de execução
entre o processo do app e o
mediaswcodecpode causar falta de buffer e interrupções de áudio em pipelines de áudio de baixa latência, como DAWs, comunicação VoIP e jogos interativos.
A execução de decodificadores de mídia em processo resolve diretamente esses gargalos de desempenho:
- Elimina a latência de IPC: ignora as transações de IPC do Binder e as trocas de contexto, reduzindo a latência de decodificação de ponta a ponta em aproximadamente 40%.
- Minimiza a sobrecarga do buffer: transmite buffers de áudio diretamente no espaço de memória do app local, sem exigir mapeamentos de memória compartilhada entre processos.
- Evita o jitter de programação: executa a decodificação nas linhas de execução prioritárias do app (como as linhas de execução em tempo real do AAudio ou do Oboe), evitando interrupções de áudio e underruns de buffer sob carga pesada do sistema.
Segurança da memória com Rust
Historicamente, o Android isolava decodificadores de software em um processo separado
(mediaswcodec) porque eles processam bitstreams não confiáveis de arquivos de mídia
externos, o que torna as explorações de corrupção de memória (como estouros de buffer) uma
grande preocupação de segurança.
O Android pode mover decodificadores de software diretamente para o processo do app de ligação implementando-os em linguagens seguras para a memória, como Rust, ou isolando-os com o Lightweight Fault Isolation (LFI).
Para eliminar com segurança a sobrecarga de sandbox fora do processo para AAC
(c2.android.inproc.aac.decoder), o Android oferece um decodificador implementado em
Rust puro ou protegido por harnesses de interface de função externa (FFI, na sigla em inglês)
seguros do Rust.
O Rust é considerado seguro para decodificação em processo pelos seguintes motivos:
- Segurança de memória em tempo de compilação: a propriedade estrita, o empréstimo e a verificação de ciclo de vida do Rust garantem que a memória não possa ser acessada depois de ser liberada ou modificada enquanto compartilhada.
- Eliminação de vulnerabilidades comuns: o Rust impede completamente overflows de buffer de heap, destruições de pilha, leituras e gravações de matrizes fora dos limites, erros de uso após a liberação e vulnerabilidades de liberação dupla no tempo de compilação.
- Taxa de sandbox de ambiente de execução zero: como a segurança da memória é matematicamente comprovada no tempo de compilação e verificada por meio de verificação de limites, os decodificadores do Rust são executados em velocidade nativa dentro do processo do app sem exigir transições de tabela de páginas ou contextos de sandbox de hardware.
Segurança de memória com isolamento de falhas leve (LFI, na sigla em inglês)
Para decodificadores de áudio legados complexos em C/C++ que ainda não foram reescritos em
Rust, especificamente o decodificador Opus (c2.android.inproc.opus.decoder
com tecnologia libopus), o Android oferece segurança em processo usando o
Lightweight Fault Isolation (LFI).
A LFI é considerada segura para decodificadores C/C++ pelos seguintes motivos:
- Sandbox de memória linear protegida por hardware: o LFI compila a biblioteca de codec C/C++ em código de máquina linear derivado do WebAssembly, confinado a um slot de memória linear de 4 GB protegido por hardware.
- Chaves de proteção de memória do Linux (
pku): o LFI usa chaves de proteção de memória do Linux (pku/pkey_mprotect) para particionar as permissões de espaço de endereço. A biblioteca isolada não pode ler ou gravar memória fora do slot de sandbox de 4 GB atribuído. - Intercepção de chamadas do sistema: nenhuma chamada do sistema do SO host (como E/S de arquivos,
acesso à rede ou controle de processos) é permitida no sandbox. Qualquer
chamada de biblioteca não compatível é roteada para stubs fictícios mínimos (
lfi_stubs.c). - Recuperação segura de falhas: se um bitstream Opus malformado ou adversário
tentar uma leitura ou gravação fora dos limites dentro do sandbox linear, o ambiente de execução
da LFI vai interceptar a falha com segurança no espaço do usuário e retornar um erro de decodificação
C2_CORRUPTEDlimpo sem causar falhas no app host.
Arquiteturas e dispositivos compatíveis com a LFI
A LFI não requer instruções personalizadas de hardware de CPU e é compatível com a
arquitetura de processador ARM64 (aarch64). A maioria dos dispositivos móveis
de consumo, incluindo smartphones (como dispositivos Pixel com SoCs do Google Tensor, Qualcomm Snapdragon e MediaTek), dobráveis, tablets e unidades de infoentretenimento
automotivo, é executada em processadores ARM64 e oferece suporte a decodificadores em processo LFI.
Decodificadores de áudio em processo disponíveis
| Formato de áudio | Tipo MIME | Nome do componente em processo | Implementação |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
libopus em C/C++ com LFI |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
Rust com segurança de memória (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder): processa streams ADTS (com análise dinâmica de cabeçalho ADTS) e unidades de acesso (AUs, na sigla em inglês) brutas com rastreamento preciso de carimbo de data/hora de apresentação (somente processamento de AU de frame único em caso deAAC_PACKAGING_RAW). - Opus (
c2.android.inproc.opus.decoder): decodifica pacotes de contêineres padrão Ogg Opus ou Matroska com reamostragem interna para PCM de 48 kHz.
Ativar decodificadores em processo
No momento, os decodificadores em processo não são o padrão do sistema ao chamar
MediaCodec.createDecoderByType().
A plataforma mantém uma prioridade de seleção mais alta do Codec 2.0 para decodificadores
legados fora do processo durante a verificação do ecossistema.
Para ativar um decodificador em processo para reprodução ou teste de baixa latência,
instancie o codec explicitamente pelo nome do componente usando
MediaCodec.createByCodecName().
Instanciar por nome do componente
O exemplo a seguir demonstra como instanciar explicitamente o decodificador AAC em processo, voltando para o decodificador padrão se o componente em processo não estiver disponível no 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); } }
Configurar o Jetpack Media3 ou o ExoPlayer
Se o app usa o Jetpack Media3 ou o ExoPlayer, é possível criar um
MediaCodecSelector personalizado para priorizar decodificadores em processo quando eles estiverem disponíveis
no 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; } }
Roteiro para decodificadores padrão do sistema
O Android está se preparando ativamente para fazer a transição desses decodificadores em processo com segurança de memória para que se tornem os decodificadores padrão do sistema para os respectivos tipos MIME de áudio nas próximas versões da plataforma (começando com o Opus LFI e o AAC Rust no Android 18 ou nas próximas atualizações do módulo Mainline).
Quando um decodificador em processo se torna o componente padrão do Codec 2.0 para o
tipo MIME, as chamadas padrão para MediaCodec.createDecoderByType(...) são
roteadas automaticamente pela implementação em processo, o que reduz
em aproximadamente 40% a latência de decodificação sem exigir mudanças no código
do app.