Android 17 (API 수준 37)부터 시작하여 Google Play 시스템 업데이트 (Mainline APEX 모듈)를 통해 업데이트되는 Android는 Opus 및 AAC를 비롯한 압축 오디오 형식의 인 프로세스 소프트웨어 오디오 디코더를 도입합니다.
별도의 샌드박스 시스템 프로세스가 아닌 앱 프로세스 내에서 직접 오디오 디코더를 실행하면 앱이 오디오 디코딩 지연 시간을 약 40% 줄이고, CPU 사용률을 줄이며, 연속 미디어 재생, 음성 메시지, 게임 중에 배터리 수명을 연장할 수 있습니다.
프로세스 외부 디코딩과 프로세스 내부 디코딩 비교
이전에는 Android에서 전용 샌드박스 시스템 데몬 (mediaswcodec) 내에서 모든 플랫폼 소프트웨어 미디어 디코더를 실행하여 앱과 OS를 잘못된 미디어 비트스트림으로부터 보호했습니다. 하지만 프로세스 외부 샌드박스는 측정 가능한 성능 오버헤드를 도입합니다.
- 프로세스 간 통신 (IPC) 지연 시간: 디코더에 대기열로 추가된 모든 오디오 입력 프레임과 반환된 모든 디코딩된 PCM 오디오 버퍼에는 교차 프로세스 바인더 IPC 직렬화와 컨텍스트 전환이 필요합니다.
- 공유 메모리 오버헤드: 프로세스 간 버퍼 핸드오프에는 공유 메모리 (
C2Buffer) 동기화 및 캐시 관리가 필요합니다. - 스케줄링 경합: CPU 부하가 심한 경우 앱 프로세스와
mediaswcodec간의 스레드 스케줄링 경합으로 인해 지연 시간이 짧은 오디오 파이프라인 (예: DAW, VoIP 통신, 대화형 게임)에서 버퍼 부족과 오디오 끊김 현상이 발생할 수 있습니다.
미디어 디코더를 인프로세스로 실행하면 다음과 같은 성능 병목 현상이 직접 해결됩니다.
- IPC 지연 시간 제거: 바인더 IPC 트랜잭션과 컨텍스트 전환을 우회하여 엔드 투 엔드 디코딩 지연 시간을 약 40% 줄입니다.
- 버퍼 오버헤드 최소화: 교차 프로세스 공유 메모리 매핑 없이 로컬 앱 메모리 공간 내에서 오디오 버퍼를 직접 전달합니다.
- 일정 잡음 방지: 앱 자체 우선순위 스레드 (예: 실시간 AAudio 또는 Oboe 스레드)에서 디코딩을 실행하여 과도한 시스템 부하에서 오디오 드롭아웃과 버퍼 언더런을 방지합니다.
Rust를 사용한 메모리 안전
이전에는 디코더가 외부 미디어 파일에서 신뢰할 수 없는 비트스트림을 처리했기 때문에 Android는 별도의 프로세스(mediaswcodec)에서 소프트웨어 디코더를 격리했습니다. 이로 인해 버퍼 오버플로와 같은 메모리 손상 악용이 주요 보안 문제가 되었습니다.
Android는 Rust와 같은 메모리 안전 언어로 구현하거나 경량 오류 격리 (LFI)로 격리하여 소프트웨어 디코더를 호출 앱 프로세스로 직접 안전하게 이동할 수 있습니다.
AAC(c2.android.inproc.aac.decoder)의 프로세스 외부 샌드박스 오버헤드를 안전하게 제거하기 위해 Android는 순수 Rust로 구현되거나 안전한 Rust 외부 함수 인터페이스 (FFI) 하네스로 보호되는 디코더를 제공합니다.
다음과 같은 이유로 Rust는 인프로세스 디코딩에 안전한 것으로 간주됩니다.
- 컴파일 시간 메모리 안전성: Rust의 엄격한 소유권, 차용, 수명 검사를 통해 메모리가 해제된 후 액세스하거나 공유 중에 변경할 수 없습니다.
- 일반적인 취약점 제거: Rust는 컴파일 시간에 힙 버퍼 오버플로우, 스택 스매시, 범위를 벗어난 배열 읽기 및 쓰기, use-after-free 오류, double-free 취약점을 완전히 방지합니다.
- 런타임 샌드박스 세금 없음: 메모리 안전은 컴파일 시간에 수학적으로 증명되고 경계 검사를 통해 확인되므로 Rust 디코더는 페이지 테이블 전환이나 하드웨어 샌드박스 컨텍스트가 필요 없이 앱 프로세스 내에서 네이티브 속도로 실행됩니다.
경량 오류 격리 (LFI)를 사용한 메모리 안전
아직 Rust로 다시 작성되지 않은 복잡한 기존 C/C++ 오디오 디코더(특히 Opus 디코더(libopus로 구동되는 c2.android.inproc.opus.decoder))의 경우 Android는 경량 장애 격리(LFI)를 사용하여 프로세스 내 안전을 제공합니다.
LFI는 다음과 같은 이유로 C/C++ 디코더에 안전한 것으로 간주됩니다.
- 하드웨어로 보호되는 선형 메모리 샌드박스: LFI는 C/C++ 코덱 라이브러리를 하드웨어로 보호되는 4GB 선형 메모리 슬롯으로 제한된 WebAssembly 파생 선형 기계어 코드로 컴파일합니다.
- Linux 메모리 보호 키 (
pku): LFI는 Linux 메모리 보호 키 (pku/pkey_mprotect)를 사용하여 주소 공간 권한을 파티셔닝합니다. 격리된 라이브러리는 할당된 4GB 샌드박스 슬롯 외부의 메모리를 읽거나 쓸 수 없습니다. - 시스템 호출 가로채기: 샌드박스 내에서는 호스트 OS 시스템 호출 (예: 파일 I/O, 네트워크 액세스 또는 프로세스 제어)이 허용되지 않습니다. 지원되지 않는 라이브러리 호출은 최소 더미 스텁 (
lfi_stubs.c)으로 라우팅됩니다. - 안전한 오류 복구: 형식이 잘못되었거나 적대적인 Opus 비트스트림이 선형 샌드박스 내에서 범위를 벗어난 읽기 또는 쓰기를 시도하면 LFI 런타임이 사용자 공간에서 오류를 안전하게 트랩하고 호스트 앱을 비정상 종료하지 않고 깨끗한
C2_CORRUPTED디코딩 오류를 반환합니다.
LFI 지원 아키텍처 및 기기
LFI에는 맞춤 CPU 하드웨어 명령어가 필요하지 않으며 ARM64 (aarch64) 및 x86_64 프로세서 아키텍처에서 지원됩니다.
- ARM64 (
aarch64) 기기: 휴대전화 (예: Google Tensor SoC, Qualcomm Snapdragon, MediaTek SoC가 적용된 Pixel 기기), 폴더블, 태블릿, 자동차 인포테인먼트 장치 등 대부분의 소비자 모바일 기기는 ARM64 프로세서에서 실행되고 LFI 인프로세스 디코더를 지원합니다. - x86_64 기기: Intel 또는 AMD CPU가 있는 개발자 워크스테이션에서 실행되는 Android Emulator와 ARC (ChromeOS용 Android 런타임)를 사용하여 Android 앱을 실행하는 ChromeOS Chromebook은 x86_64 아키텍처에서 실행되고 LFI 샌드박싱을 지원합니다.
사용 가능한 인프로세스 오디오 디코더
| 오디오 형식 | MIME 유형 | 인프로세스 구성요소 이름 | 구현 |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
LFI가 있는 C/C++ libopus |
| AAC | audio/mp4a-latm |
c2.android.inproc.aac.decoder |
메모리 안전 Rust (C2ApexAacDec) |
- AAC (
c2.android.inproc.aac.decoder): ADTS 스트림 (동적 ADTS 헤더 파싱 포함)과 정확한 프레젠테이션 타임스탬프 추적이 있는 원시 액세스 단위 (AU)(AAC_PACKAGING_RAW의 경우 단일 프레임 AU 처리만 해당)를 처리합니다. - Opus (
c2.android.inproc.opus.decoder): 내부 리샘플링을 통해 표준 Ogg Opus 또는 Matroska 컨테이너 패킷을 48kHz PCM으로 디코딩합니다.
인프로세스 디코더 선택
현재 MediaCodec.createDecoderByType() 호출 시 인프로세스 디코더가 시스템 기본값이 아닙니다.
플랫폼은 생태계 확인 중에 기존 프로세스 외부 디코더에 더 높은 Codec 2.0 선택 우선순위를 유지합니다.
지연 시간이 짧은 재생 또는 테스트를 위해 오늘 인프로세스 디코더를 선택하려면 MediaCodec.createByCodecName()를 사용하여 구성요소 이름으로 코덱을 명시적으로 인스턴스화하세요.
구성요소 이름으로 인스턴스화
다음 예에서는 인프로세스 AAC 디코더를 명시적으로 인스턴스화하여 기기에서 인프로세스 구성요소를 사용할 수 없는 경우 기본 디코더로 대체하는 방법을 보여줍니다.
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) } }
자바
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를 만들 수 있습니다.
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 } } }
자바
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는 이러한 메모리 안전 인프로세스 디코더가 향후 플랫폼 출시 (Android 18 또는 향후 Mainline 모듈 업데이트에서 Opus LFI 및 AAC Rust부터 시작)에서 각 오디오 MIME 유형의 시스템 기본 디코더가 되도록 적극적으로 준비하고 있습니다.
인 프로세스 디코더가 MIME 유형의 기본 코덱 2.0 구성요소가 되면 MediaCodec.createDecoderByType(...)에 대한 표준 호출이 인 프로세스 구현을 통해 자동으로 라우팅되어 앱 코드 변경 없이 디코딩 지연 시간이 약 40% 단축됩니다.