Начиная с Android 17 (уровень API 37) и обновляясь через системные обновления Google Play (модули Mainline APEX), Android внедряет встроенные программные декодеры аудио для сжатых аудиоформатов, включая Opus и AAC .
Запуская аудиодекодеры непосредственно в процессе приложения, а не в отдельном изолированном системном процессе, приложения могут сократить задержку декодирования звука примерно на 40% , снизить загрузку процессора и продлить время работы батареи при непрерывном воспроизведении мультимедиа, голосовых сообщениях и играх.
Декодирование вне процесса и внутри процесса
Исторически сложилось так, что Android запускал все программные декодеры мультимедиа внутри выделенного изолированного системного демона ( mediaswcodec ), чтобы защитить приложения и ОС от некорректных потоков медиаданных. Однако использование изолированных процессов вне основного процесса приводит к ощутимым накладным расходам на производительность:
- Задержка межпроцессного взаимодействия (IPC): каждый входной аудиокадр, поставленный в очередь декодеру, и каждый возвращенный декодированный аудиобуфер PCM требуют сериализации IPC между процессами Binder и переключения контекста.
- Накладные расходы на общую память: Передача буферов между процессами требует синхронизации общей памяти (
C2Buffer) и управления кэшем. - Конфликты при планировании: При высокой нагрузке на ЦП конфликты при планировании потоков между процессом приложения и
mediaswcodecмогут вызывать нехватку буфера и заикание звука в аудиосистемах с низкой задержкой (таких как DAW, VoIP-связь и интерактивные игры).
Запуск медиадекодеров в рамках одного процесса напрямую решает эти проблемы с производительностью:
- Устраняет задержку межпроцессного взаимодействия: обходит транзакции межпроцессного взаимодействия Binder и переключения контекста, снижая задержку сквозного декодирования примерно на 40%.
- Минимизирует накладные расходы на буферизацию: передает аудиобуферы непосредственно в локальное пространство памяти приложения, не требуя межпроцессного сопоставления общей памяти.
- Предотвращает сбои в планировании: декодирование выполняется в собственных приоритетных потоках приложения (например, в потоках AAudio или Oboe в реальном времени), что предотвращает прерывания звука и переполнение буфера при высокой системной нагрузке.
Безопасность памяти в Rust
Исторически сложилось так, что Android изолировал программные декодеры в отдельном процессе ( mediaswcodec ), поскольку декодеры обрабатывают ненадежные битовые потоки из внешних медиафайлов, что делало уязвимости, связанные с повреждением памяти (например, переполнением буфера), серьезной проблемой безопасности.
Android может безопасно перемещать программные декодеры непосредственно в процесс вызывающего приложения, реализуя их на языках с безопасной памятью, таких как Rust, или изолируя их с помощью легковесной изоляции ошибок (LFI).
Для безопасного устранения накладных расходов на песочницу вне процесса для AAC ( c2.android.inproc.aac.decoder ) Android предоставляет декодер, реализованный на чистом Rust или защищенный безопасными интерфейсами внешних функций Rust (FFI).
Язык Rust считается безопасным для внутрипроцессного декодирования по следующим причинам:
- Безопасность памяти на этапе компиляции: строгие правила владения, заимствования и проверки времени жизни памяти в Rust гарантируют, что доступ к памяти после ее освобождения или изменения в процессе совместного использования будет невозможен.
- Устранение распространенных уязвимостей: Rust полностью предотвращает переполнение буфера в куче, сбой стека, чтение и запись массивов за пределы допустимого диапазона, ошибки использования памяти после освобождения и уязвимости двойного освобождения памяти на этапе компиляции.
- Отсутствие затрат на изоляцию во время выполнения: поскольку безопасность памяти математически доказана во время компиляции и проверена с помощью проверки границ, декодеры Rust выполняются со скоростью нативного кода внутри процесса приложения, не требуя переходов между таблицами страниц или контекстов аппаратной песочницы.
Безопасность памяти с помощью облегченной изоляции ошибок (LFI)
Для сложных устаревших аудиодекодеров на C/C++, которые еще не были переписаны на Rust — в частности, для декодера Opus ( c2.android.inproc.opus.decoder на базе libopus ) — Android обеспечивает внутрипроцессную безопасность с помощью облегченной изоляции ошибок (LFI) .
Протокол LFI считается безопасным для декодеров на C/C++ по следующим причинам:
- Аппаратная защита линейной памяти: LFI компилирует библиотеку кодеков C/C++ в линейный машинный код, производный от WebAssembly, ограниченный аппаратно защищенным слотом линейной памяти объемом 4 ГБ.
- Ключи защиты памяти Linux (
pku): LFI использует ключи защиты памяти Linux (pku/pkey_mprotect) для разделения разрешений адресного пространства. Изолированная библиотека не может читать или записывать данные в память за пределами выделенного ей 4 ГБ изолированного пространства. - Перехват системных вызовов: Внутри песочницы запрещены любые системные вызовы хост-системы (такие как ввод-вывод файлов, доступ к сети или управление процессами). Любые неподдерживаемые вызовы библиотек перенаправляются на минимальные заглушки (
lfi_stubs.c). - Безопасное восстановление после сбоя: если некорректный или вредоносный битовый поток Opus пытается выполнить чтение или запись за пределами допустимого диапазона внутри линейной песочницы, среда выполнения LFI безопасно перехватывает ошибку в пользовательском пространстве и возвращает корректную ошибку декодирования
C2_CORRUPTEDбез сбоя хост-приложения.
Поддерживаемые архитектуры и устройства для LFI
Для работы LFI не требуются специальные аппаратные инструкции ЦП, и эта функция поддерживается на процессорах архитектур ARM64 ( aarch64 ) и x86_64 :
- Устройства на базе ARM64 (
aarch64): Большинство потребительских мобильных устройств, включая мобильные телефоны (такие как устройства Pixel с процессорами Google Tensor, Qualcomm Snapdragon и MediaTek), складные устройства, планшеты и автомобильные информационно-развлекательные системы, работают на процессорах ARM64 и поддерживают внутрипроцессные декодеры LFI. - Устройства x86_64: эмуляторы Android, работающие на рабочих станциях разработчиков с процессорами Intel или AMD, а также Chromebook на ChromeOS, запускающие приложения Android с использованием ARC (Android Runtime for ChromeOS), выполняющиеся на архитектурах x86_64 и поддерживающие песочницу LFI.
Доступные встроенные аудиодекодеры
| Аудиоформат | MIME-тип | Название обрабатываемого компонента | Выполнение |
|---|---|---|---|
| Опус | audio/opus | c2.android.inproc.opus.decoder | libopus на C/C++ с LFI |
| ААК | audio/mp4a-latm | c2.android.inproc.aac.decoder | Безопасный для памяти Rust ( C2ApexAacDec ) |
- AAC (
c2.android.inproc.aac.decoder): Обрабатывает потоки ADTS (с динамическим анализом заголовка ADTS) и необработанные блоки доступа (AU) с точным отслеживанием временной метки представления (обработка AU только для одного кадра в случаеAAC_PACKAGING_RAW). - Opus (
c2.android.inproc.opus.decoder): Декодирует стандартные пакеты Ogg Opus или Matroska с внутренней передискретизацией в PCM 48 кГц.
Включите декодеры, работающие в процессе обработки.
В настоящее время декодеры, созданные непосредственно в процессе обработки, не являются системными декодерами по умолчанию при вызове MediaCodec.createDecoderByType() . Платформа сохраняет более высокий приоритет выбора кодека 2.0 для устаревших декодеров, созданных вне процесса обработки, во время проверки экосистемы.
Чтобы сегодня включить встроенный декодер для воспроизведения с низкой задержкой или тестирования, явно создайте экземпляр кодека по имени компонента, используя MediaCodec.createByCodecName() .
Создать экземпляр по имени компонента.
В следующем примере показано, как явно создать экземпляр декодера AAC в процессе выполнения, используя в качестве резервного варианта декодер по умолчанию, если компонент, созданный в процессе выполнения, недоступен на устройстве:
Котлин
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); } }
Настройте Jetpack Media3 или ExoPlayer.
Если ваше приложение использует Jetpack Media3 или ExoPlayer, вы можете создать пользовательский MediaCodecSelector , чтобы отдавать приоритет декодерам, находящимся в процессе обработки, когда они доступны на устройстве:
Котлин
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; } }
Дорожная карта для декодеров системы по умолчанию
В будущих обновлениях платформы Android активно готовится к тому, чтобы эти безопасные для памяти внутрипроцессные декодеры стали системными декодерами по умолчанию для соответствующих аудио MIME-типов (начиная с Opus LFI и AAC Rust в Android 18 или в будущих обновлениях модулей Mainline).
Как только встроенный декодер становится компонентом Codec 2.0 по умолчанию для своего MIME-типа, стандартные вызовы MediaCodec.createDecoderByType(...) будут автоматически перенаправляться через встроенную реализацию, обеспечивая примерно на 40% меньшую задержку декодирования без необходимости внесения каких-либо изменений в код приложения.