W Androidzie 17 (API na poziomie 37) i nowszych wersjach aktualizowanych za pomocą aktualizacji systemu Google Play (moduły APEX Mainline) wprowadzono wewnętrzne dekodery audio dla skompresowanych formatów audio, w tym Opus i AAC.
Dzięki uruchamianiu dekoderów audio bezpośrednio w procesie aplikacji, a nie w osobnym procesie systemowym w piaskownicy, aplikacje mogą zmniejszyć opóźnienie dekodowania dźwięku o około 40%, zmniejszyć wykorzystanie procesora i wydłużyć czas pracy na baterii podczas ciągłego odtwarzania multimediów, przesyłania wiadomości głosowych i grania.
Dekodowanie poza procesem a w procesie
W przeszłości Android uruchamiał wszystkie dekodery multimediów w ramach oprogramowania platformy w dedykowanym, odizolowanym procesie systemowym (mediaswcodec), aby chronić aplikacje i system operacyjny przed nieprawidłowymi strumieniami bitów multimediów. Jednak piaskownica poza procesem
ma zauważalny wpływ na wydajność:
- Opóźnienie komunikacji międzyprocesowej (IPC): każda ramka wejściowa audio umieszczona w kolejce dekodera i każdy zdekodowany bufor audio PCM wymaga serializacji IPC Binder i przełączania kontekstu między procesami.
- Narzucone obciążenie pamięci współdzielonej: przekazywanie buforów między procesami wymaga synchronizacji pamięci współdzielonej (
C2Buffer) i zarządzania pamięcią podręczną. - Konflikt planowania: przy dużym obciążeniu procesora konflikt planowania wątków między procesem aplikacji a
mediaswcodecmoże powodować brak bufora i zacinanie się dźwięku w potokach audio o niskim opóźnieniu (takich jak cyfrowe stacje robocze, komunikacja VoIP i gry interaktywne).
Uruchamianie dekoderów multimediów w procesie bezpośrednio rozwiązuje te problemy z wydajnością:
- Eliminuje opóźnienie IPC: pomija transakcje IPC w Binderze i przełączanie kontekstu, co zmniejsza opóźnienie dekodowania end-to-end o około 40%.
- Minimalizuje narzut bufora: przekazuje bufory audio bezpośrednio w przestrzeni pamięci lokalnej aplikacji bez konieczności mapowania pamięci współdzielonej między procesami.
- Zapobiega zakłóceniom w harmonogramie: wykonuje dekodowanie na własnych wątkach priorytetowych aplikacji (np. wątkach AAudio lub Oboe w czasie rzeczywistym), zapobiegając przerwom w dźwięku i niedoborom bufora przy dużym obciążeniu systemu.
Bezpieczeństwo pamięci w Rust
W przeszłości Android izolował dekodery oprogramowania w osobnym procesie (mediaswcodec), ponieważ dekodery przetwarzają niezaufane strumienie bitów z zewnętrznych plików multimedialnych, co sprawia, że wykorzystywanie luk w zabezpieczeniach pamięci (takich jak przepełnienie bufora) stanowi poważne zagrożenie dla bezpieczeństwa.
Android może bezpiecznie przenosić dekodery oprogramowania bezpośrednio do procesu aplikacji wywołującej, implementując je w językach bezpiecznych pod względem pamięci, takich jak Rust, lub izolując je za pomocą lekkiej izolacji błędów (LFI).
Aby bezpiecznie wyeliminować narzut związany z piaskownicą poza procesem w przypadku AAC (c2.android.inproc.aac.decoder), Android udostępnia dekoder zaimplementowany w czystym języku Rust lub chroniony przez bezpieczne interfejsy FFI w języku Rust.
Rust jest uważany za bezpieczny w przypadku dekodowania w procesie z tych powodów:
- Bezpieczeństwo pamięci w czasie kompilacji: ścisłe zasady własności, pożyczania i sprawdzania czasu życia w Rust zapewniają, że nie można uzyskać dostępu do pamięci po jej zwolnieniu ani zmodyfikować jej, gdy jest udostępniana.
- Eliminacja typowych luk w zabezpieczeniach: Rust całkowicie zapobiega przepełnieniom bufora sterty, przepełnieniom stosu, odczytom i zapisom tablic poza zakresem, błędom użycia po zwolnieniu i lukom w zabezpieczeniach związanym z podwójnym zwolnieniem pamięci w czasie kompilacji.
- Brak podatku od piaskownicy w czasie działania: ponieważ bezpieczeństwo pamięci jest matematycznie udowodnione w czasie kompilacji i weryfikowane przez sprawdzanie granic, dekodery Rusta działają z natywną szybkością w procesie aplikacji bez konieczności przełączania tabeli stron ani kontekstów piaskownicy sprzętowej.
Bezpieczeństwo pamięci dzięki lekkiej izolacji błędów (LFI)
W przypadku złożonych starszych dekoderów audio C/C++, które nie zostały jeszcze przepisane w języku Rust, a w szczególności dekodera Opus (c2.android.inproc.opus.decoder
zasilanego przez libopus), Android zapewnia bezpieczeństwo w procesie za pomocą lekkiej izolacji błędów (LFI).
LFI jest uważany za bezpieczny w przypadku dekoderów C/C++ z tych powodów:
- Piaskownica pamięci liniowej chroniona sprzętowo: LFI kompiluje kodek C/C++ do postaci liniowego kodu maszynowego pochodzącego z WebAssembly, który jest ograniczony do chronionego sprzętowo 4-gigabajtowego gniazda pamięci liniowej.
- Klucze ochrony pamięci w systemie Linux (
pku): LFI używa kluczy ochrony pamięci w systemie Linux (pku/pkey_mprotect) do dzielenia uprawnień przestrzeni adresowej. Odizolowana biblioteka nie może odczytywać ani zapisywać pamięci poza przypisanym jej 4-gigabajtowym miejscem w piaskownicy. - Przechwytywanie wywołań systemowych: w piaskownicy nie są dozwolone żadne wywołania systemowe systemu operacyjnego hosta (takie jak operacje wejścia/wyjścia plików, dostęp do sieci czy kontrola procesów). Wszelkie nieobsługiwane wywołania biblioteki są kierowane do minimalnych atrap (
lfi_stubs.c). - Bezpieczne odzyskiwanie po błędach: jeśli nieprawidłowy lub złośliwy strumień bitów Opus próbuje odczytać lub zapisać dane poza zakresem w liniowej piaskownicy, środowisko wykonawcze LFI bezpiecznie przechwytuje błąd w przestrzeni użytkownika i zwraca czysty błąd dekodowania
C2_CORRUPTEDbez powodowania awarii aplikacji hosta.
Obsługiwane architektury i urządzenia w przypadku LFI
LFI nie wymaga niestandardowych instrukcji sprzętowych procesora i jest obsługiwane w przypadku architektur procesora ARM64 (aarch64) i x86_64:
- Urządzenia ARM64 (
aarch64): większość urządzeń mobilnych dla konsumentów, w tym telefony komórkowe (np. urządzenia Pixel z układami SoC Google Tensor, Qualcomm Snapdragon i MediaTek), urządzenia składane, tablety i samochodowe systemy infotainment, działa na procesorach ARM64 i obsługuje dekodery LFI w procesie. - Urządzenia x86_64: emulatory Androida działające na stacjach roboczych deweloperów z procesorami Intel lub AMD, a także Chromebooki z ChromeOS, na których działają aplikacje na Androida korzystające ze środowiska wykonawczego Androida dla ChromeOS (ARC), działają na architekturach x86_64 i obsługują piaskownicę LFI.
Dostępne dekodery audio w procesie
| Format dźwięku | Typ MIME | Nazwa komponentu w procesie | Implementacja |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
C/C++ libopus z LFI |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
Rust z bezpiecznym zarządzaniem pamięcią (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder): obsługuje strumienie ADTS (z dynamiczną analizą nagłówka ADTS) i surowe jednostki dostępu (AU) z dokładnym śledzeniem sygnatury czasowej prezentacji (w przypadkuAAC_PACKAGING_RAWprzetwarzanie tylko jednostek AU z jedną klatką). - Opus (
c2.android.inproc.opus.decoder): dekoduje standardowe pakiety kontenera Ogg Opus lub Matroska z wewnętrznym próbkowaniem do 48 kHz PCM.
Włączanie dekoderów w procesie
Obecnie dekodery w trakcie przetwarzania nie są domyślnym systemem podczas dzwonienia na numery MediaCodec.createDecoderByType().
Podczas weryfikacji ekosystemu platforma zachowuje wyższy priorytet wyboru kodeka 2.0 w przypadku starszych dekoderów działających poza procesem.
Aby włączyć dekoder w procesie na potrzeby odtwarzania z niskimi opóźnieniami lub testowania, utwórz instancję kodeka w sposób jawny według nazwy komponentu za pomocą MediaCodec.createByCodecName().
Tworzenie instancji według nazwy komponentu
Poniższy przykład pokazuje, jak jawnie utworzyć instancję dekodera AAC w procesie, a w razie niedostępności na urządzeniu komponentu w procesie użyć domyślnego dekodera:
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); } }
Konfigurowanie Jetpack Media3 lub ExoPlayer
Jeśli Twoja aplikacja korzysta z biblioteki Jetpack Media3 lub ExoPlayer, możesz utworzyć niestandardowy element MediaCodecSelector, aby w przypadku dostępności na urządzeniu priorytetowo traktować dekodery w procesie:
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; } }
Plan działania dotyczący domyślnych dekoderów systemowych
Android aktywnie przygotowuje się do przejścia na te bezpieczne pod względem pamięci dekodery w procesie, aby stały się domyślnymi dekoderami systemowymi dla odpowiednich typów MIME audio w nadchodzących wersjach platformy (począwszy od Opus LFI i AAC Rust w Androidzie 18 lub nadchodzących aktualizacjach modułu Mainline).
Gdy dekoder w procesie stanie się domyślnym komponentem Codec 2.0 dla swojego typu MIME, standardowe wywołania MediaCodec.createDecoderByType(...) będą automatycznie kierowane przez implementację w procesie, co zapewni o około 40% niższe opóźnienie dekodowania bez konieczności wprowadzania zmian w kodzie aplikacji.