ตั้งแต่ Android 17 (ระดับ API 37) เป็นต้นไปและอัปเดตผ่านการอัปเดตระบบ Google Play (โมดูล APEX หลัก) Android จะเปิดตัวตัวถอดรหัสเสียงซอฟต์แวร์ในกระบวนการสำหรับรูปแบบเสียงที่บีบอัด ซึ่งรวมถึง Opus และ AAC
การเรียกใช้ตัวถอดรหัสเสียงภายในกระบวนการของแอปโดยตรงแทนที่จะใช้กระบวนการของระบบแซนด์บ็อกซ์แยกต่างหากจะช่วยให้แอปสามารถลดเวลาในการตอบสนองของการถอดรหัสเสียงได้ประมาณ 40% ลดการใช้ CPU และยืดระยะเวลาการใช้งานแบตเตอรี่ในระหว่างการเล่นสื่อ การส่งข้อความเสียง และการเล่นเกมอย่างต่อเนื่อง
การถอดรหัสแบบนอกกระบวนการเทียบกับการถอดรหัสแบบในกระบวนการ
ในอดีต Android เรียกใช้ตัวถอดรหัสสื่อซอฟต์แวร์แพลตฟอร์มทั้งหมดภายใน
daemon ของระบบที่แยกไว้และอยู่ในแซนด์บ็อกซ์ (mediaswcodec) เพื่อปกป้องแอปและ
ระบบปฏิบัติการจากบิตสตรีมสื่อที่มีรูปแบบไม่ถูกต้อง อย่างไรก็ตาม แซนด์บ็อกซ์นอกกระบวนการ
ทำให้เกิดค่าใช้จ่ายด้านประสิทธิภาพที่วัดได้ ดังนี้
- เวลาในการตอบสนองของการสื่อสารระหว่างกระบวนการ (IPC): ทุกเฟรมอินพุตเสียง ที่จัดคิวไว้ในตัวถอดรหัสและทุกบัฟเฟอร์เสียง PCM ที่ถอดรหัสแล้วที่ส่งคืนต้องมีการ ซีเรียลไลซ์ IPC ของ Binder ข้ามกระบวนการและการสลับบริบท
- ค่าใช้จ่ายของหน่วยความจำที่ใช้ร่วมกัน: การส่งต่อบัฟเฟอร์ระหว่างกระบวนการต้องมีการซิงโครไนซ์หน่วยความจำที่ใช้ร่วมกัน (
C2Buffer) และการจัดการแคช - การแย่งชิงการจัดกำหนดการ: เมื่อ CPU มีภาระงานหนัก การแย่งชิงการจัดกำหนดการเธรด
ระหว่างกระบวนการของแอปกับ
mediaswcodecอาจทำให้บัฟเฟอร์ ขาดแคลนและเสียงขาดหายในไปป์ไลน์เสียงที่มีเวลาในการตอบสนองต่ำ (เช่น DAW, การสื่อสารผ่าน VoIP และเกมแบบอินเทอร์แอกทีฟ)
การเรียกใช้ตัวถอดรหัสสื่อในกระบวนการจะช่วยแก้ปัญหาคอขวดด้านประสิทธิภาพต่อไปนี้ได้โดยตรง
- ลดเวลาในการตอบสนองของ IPC: ข้ามธุรกรรม IPC ของ Binder และการสลับบริบท ซึ่งจะช่วยลดเวลาในการตอบสนองของการถอดรหัสตั้งแต่ต้นจนจบได้ประมาณ 40%
- ลดค่าใช้จ่ายของบัฟเฟอร์: ส่งบัฟเฟอร์เสียงโดยตรงภายในพื้นที่หน่วยความจำของแอปในเครื่องโดยไม่ต้องมีการแมปหน่วยความจำที่ใช้ร่วมกันข้ามกระบวนการ
- ป้องกันการสั่นของกำหนดการ: ดำเนินการถอดรหัสในเธรดที่มีลำดับความสำคัญของแอปเอง (เช่น เธรด AAudio หรือ Oboe แบบเรียลไทม์) เพื่อป้องกันไม่ให้เสียงขาดหาย และบัฟเฟอร์ขาดหายเมื่อระบบมีภาระงานหนัก
ความปลอดภัยของหน่วยความจำด้วย Rust
ในอดีต Android แยกตัวถอดรหัสซอฟต์แวร์ในกระบวนการแยกต่างหาก
(mediaswcodec) เนื่องจากตัวถอดรหัสประมวลผลบิตสตรีมที่ไม่น่าเชื่อถือจากไฟล์สื่อภายนอก
จึงทำให้การใช้ประโยชน์จากการเสียหายของหน่วยความจำ (เช่น การบัฟเฟอร์เกินขอบเขตที่กำหนด) เป็น
ข้อกังวลด้านความปลอดภัยที่สำคัญ
Android สามารถย้ายตัวถอดรหัสซอฟต์แวร์ไปยังกระบวนการของแอปการโทรได้อย่างปลอดภัย โดยการนำไปใช้ในภาษาที่ปลอดภัยต่อหน่วยความจำ เช่น Rust หรือแยกด้วย การแยกข้อบกพร่องแบบเบา (LFI)
Android มีตัวถอดรหัสที่ใช้ใน Rust แบบเพียวหรือได้รับการปกป้องโดย Safe Rust Foreign Function Interface (FFI)
harnesses เพื่อลดค่าใช้จ่ายของแซนด์บ็อกซ์แบบแยกกระบวนการสำหรับ AAC (c2.android.inproc.aac.decoder) อย่างปลอดภัย
เราถือว่า Rust ปลอดภัยสำหรับการถอดรหัสในกระบวนการเนื่องจากเหตุผลต่อไปนี้
- ความปลอดภัยของหน่วยความจำขณะคอมไพล์: การตรวจสอบความเป็นเจ้าของ การยืม และอายุการใช้งานที่เข้มงวดของ Rust ช่วยให้มั่นใจได้ว่าจะเข้าถึงหน่วยความจำไม่ได้หลังจากที่ระบบปล่อยหน่วยความจำนั้นแล้วหรือมีการเปลี่ยนแปลงขณะที่แชร์
- การกำจัดช่องโหว่ที่พบบ่อย: Rust ป้องกันไม่ให้เกิดข้อผิดพลาดเกี่ยวกับบัฟเฟอร์ล้นฮีป การโจมตีสแต็ก การอ่านและการเขียนอาร์เรย์ที่อยู่นอกขอบเขต ข้อผิดพลาดในการใช้หลังจากปล่อย และช่องโหว่ที่ทำให้เกิดการปล่อยหน่วยความจำซ้ำในเวลาคอมไพล์
- ไม่มีค่าใช้จ่ายในการแซนด์บ็อกซ์รันไทม์: เนื่องจากความปลอดภัยของหน่วยความจำได้รับการพิสูจน์ทางคณิตศาสตร์ ในเวลาคอมไพล์และได้รับการยืนยันผ่านการตรวจสอบขอบเขต ดีโคดเดอร์ Rust จึง ทำงานด้วยความเร็วแบบเนทีฟภายในกระบวนการของแอปโดยไม่ต้องมีการเปลี่ยนตารางหน้า หรือบริบทแซนด์บ็อกซ์ของฮาร์ดแวร์
ความปลอดภัยของหน่วยความจำด้วยการแยกข้อบกพร่องแบบเบา (LFI)
สำหรับตัวถอดรหัสเสียง C/C++ รุ่นเดิมที่ซับซ้อนซึ่งยังไม่ได้เขียนใหม่ใน Rust โดยเฉพาะตัวถอดรหัส Opus (c2.android.inproc.opus.decoder
ขับเคลื่อนโดย libopus) Android จะให้ความปลอดภัยในกระบวนการโดยใช้การแยกข้อบกพร่องแบบ Lightweight (LFI)
LFI ถือว่าปลอดภัยสำหรับตัวถอดรหัส C/C++ ด้วยเหตุผลต่อไปนี้
- การแซนด์บ็อกซ์หน่วยความจำเชิงเส้นที่ได้รับการป้องกันด้วยฮาร์ดแวร์: LFI จะคอมไพล์ไลบรารีโคเดก C/C++ เป็นรหัสเครื่องเชิงเส้นที่ได้มาจาก WebAssembly ซึ่งจำกัดไว้ในสล็อตหน่วยความจำเชิงเส้น 4 GB ที่ได้รับการป้องกันด้วยฮาร์ดแวร์
- คีย์การป้องกันหน่วยความจำของ Linux (
pku): LFI ใช้คีย์การป้องกันหน่วยความจำของ Linux (pku/pkey_mprotect) เพื่อแบ่งสิทธิ์พื้นที่ที่อยู่ ไลบรารีที่แยกไว้จะอ่านหรือเขียนหน่วยความจำนอกสล็อตแซนด์บ็อกซ์ขนาด 4 GB ที่กำหนดไว้ไม่ได้ - การดักจับการเรียกใช้ระบบ: ไม่อนุญาตให้มีการเรียกใช้ระบบปฏิบัติการของโฮสต์ (เช่น I/O ของไฟล์
การเข้าถึงเครือข่าย หรือการควบคุมกระบวนการ) ภายในแซนด์บ็อกซ์ ระบบจะกำหนดเส้นทางการเรียกใช้ไลบรารีที่ไม่รองรับไปยัง Stub ดัมมี่ขั้นต่ำ (
lfi_stubs.c) - การกู้คืนข้อผิดพลาดอย่างปลอดภัย: หากบิตสตรีม Opus ที่มีรูปแบบไม่ถูกต้องหรือเป็นอันตรายพยายามอ่านหรือเขียนนอกขอบเขตภายในแซนด์บ็อกซ์เชิงเส้น รันไทม์ LFI
จะดักจับข้อผิดพลาดอย่างปลอดภัยในพื้นที่ผู้ใช้และแสดงข้อผิดพลาดในการถอดรหัสที่สะอาด
C2_CORRUPTEDโดยไม่ทำให้แอปโฮสต์ขัดข้อง
สถาปัตยกรรมและอุปกรณ์ที่รองรับสำหรับ LFI
LFI ไม่ต้องใช้ชุดคำสั่งฮาร์ดแวร์ CPU ที่กำหนดเองและรองรับสถาปัตยกรรมโปรเซสเซอร์ ARM64 (aarch64) และ x86_64
- อุปกรณ์ ARM64 (
aarch64): อุปกรณ์เคลื่อนที่สำหรับผู้บริโภคส่วนใหญ่ ซึ่งรวมถึงโทรศัพท์มือถือ (เช่น อุปกรณ์ Pixel ที่มี SoC ของ Google Tensor, Qualcomm Snapdragon และ MediaTek SoC), อุปกรณ์พับได้, แท็บเล็ต และหน่วยสาระบันเทิงในรถยนต์ จะทำงานบนโปรเซสเซอร์ ARM64 และรองรับตัวถอดรหัสในกระบวนการ LFI - อุปกรณ์ x86_64: โปรแกรมจำลอง Android ที่ทำงานบนเวิร์กสเตชันสำหรับนักพัฒนาซอฟต์แวร์ ที่มี CPU ของ Intel หรือ AMD รวมถึง Chromebook ที่ใช้ ChromeOS ซึ่งเรียกใช้แอป Android โดยใช้ ARC (Android Runtime สำหรับ ChromeOS) จะทำงานบนสถาปัตยกรรม x86_64 และรองรับแซนด์บ็อกซ์ LFI
ตัวถอดรหัสเสียงในกระบวนการที่พร้อมใช้งาน
| รูปแบบเสียง | ประเภท MIME | ชื่อคอมโพเนนต์ในกระบวนการ | การใช้งาน |
|---|---|---|---|
| Opus | audio/opus |
c2.android.inproc.opus.decoder |
C/C++ libopus ที่มี LFI |
| AAC | 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 kHz
เลือกใช้ตัวถอดรหัสในกระบวนการ
ปัจจุบันตัวถอดรหัสในกระบวนการไม่ใช่ค่าเริ่มต้นของระบบเมื่อโทรหา 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) } }
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 ที่กำหนดเองเพื่อจัดลำดับความสำคัญของตัวถอดรหัสในกระบวนการเมื่อพร้อมใช้งานในอุปกรณ์ได้โดยทำดังนี้
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; } }
แผนงานสำหรับตัวถอดรหัสระบบเริ่มต้น
Android กำลังเตรียมพร้อมที่จะเปลี่ยนตัวถอดรหัสในกระบวนการที่ปลอดภัยด้านหน่วยความจำเหล่านี้ ให้เป็นตัวถอดรหัสเริ่มต้นของระบบสำหรับ MIME ของเสียงที่เกี่ยวข้อง ในแพลตฟอร์มรุ่นที่จะเปิดตัว (เริ่มจาก Opus LFI และ AAC Rust ใน Android 18 หรือการอัปเดตโมดูล Mainline ที่กำลังจะมาถึง)
เมื่อตัวถอดรหัสในกระบวนการกลายเป็นคอมโพเนนต์ Codec 2.0 เริ่มต้นสำหรับประเภท MIME ของตัวเอง การเรียกมาตรฐานไปยัง MediaCodec.createDecoderByType(...) จะกำหนดเส้นทางผ่านการติดตั้งใช้งานในกระบวนการโดยอัตโนมัติ ซึ่งจะช่วยลดเวลาในการตอบสนองในการถอดรหัสลงประมาณ 40% โดยไม่ต้องเปลี่ยนแปลงโค้ดแอป