با شروع از اندروید ۱۷ (سطح API ۳۷) و بهروزرسانی از طریق بهروزرسانیهای سیستم گوگل پلی (ماژولهای اصلی APEX)، اندروید رمزگشاهای صوتی نرمافزاری درونپردازشی را برای فرمتهای صوتی فشرده، از جمله Opus و AAC ، معرفی میکند.
با اجرای مستقیم رمزگشاهای صوتی در داخل فرآیند برنامه به جای یک فرآیند سیستمی جداگانه و تحت سندباکس، برنامهها میتوانند تأخیر رمزگشایی صدا را تقریباً ۴۰٪ کاهش دهند، استفاده از CPU را کم کنند و عمر باتری را در حین پخش مداوم رسانه، پیامرسانی صوتی و بازی افزایش دهند.
رمزگشایی خارج از فرآیند در مقابل رمزگشایی در حین فرآیند
از نظر تاریخی، اندروید تمام رمزگشاهای رسانه نرمافزار پلتفرم را درون یک سرویس سیستمی اختصاصی و سندباکسشده ( mediaswcodec ) اجرا میکرد تا از برنامهها و سیستمعامل در برابر جریانهای بیتی رسانهای ناقص محافظت کند. با این حال، سندباکس خارج از فرآیند، سربار عملکردی قابل اندازهگیری را ایجاد میکند:
- تأخیر ارتباط بین فرآیندی (IPC): هر فریم ورودی صوتی که در صف رمزگشایی قرار میگیرد و هر بافر صوتی PCM رمزگشایی شده که بازگردانده میشود، نیاز به سریالسازی IPC بین فرآیندی Binder و سوئیچینگ زمینه دارد.
- سربار حافظه مشترک: انتقال بافر بین فرآیندی نیاز به همگامسازی حافظه مشترک (
C2Buffer) و مدیریت حافظه پنهان دارد. - رقابت در زمانبندی: تحت بار سنگین پردازنده، رقابت در زمانبندی نخها بین فرآیند برنامه و
mediaswcodecمیتواند باعث کمبود بافر و لکنت صدا در خطوط لوله صوتی با تأخیر کم (مانند DAWها، ارتباطات VoIP و بازیهای تعاملی) شود.
اجرای رمزگشاهای رسانه در حین پردازش، مستقیماً این تنگناهای عملکرد را برطرف میکند:
- تأخیر IPC را از بین میبرد: تراکنشهای IPC مربوط به Binder و سوئیچهای زمینه را دور میزند و تأخیر رمزگشایی سرتاسری را تقریباً 40٪ کاهش میدهد.
- سربار بافر را به حداقل میرساند: بافرهای صوتی را مستقیماً در فضای حافظه محلی برنامه بدون نیاز به نگاشتهای حافظه مشترک بین پردازشی عبور میدهد.
- جلوگیری از لرزش زمانبندی: رمزگشایی را روی رشتههای اولویتدار خود برنامه (مانند رشتههای AAudio یا Oboe در زمان واقعی) اجرا میکند و از افت صدا و کاهش سرعت بافر در زیر بار سنگین سیستم جلوگیری میکند.
امنیت حافظه با Rust
از نظر تاریخی، اندروید، رمزگشاهای نرمافزار را در یک فرآیند جداگانه ( mediaswcodec ) ایزوله میکرد، زیرا رمزگشاها جریانهای بیتی غیرقابل اعتماد را از فایلهای رسانه خارجی پردازش میکردند و همین امر، سوءاستفادههای مربوط به خرابی حافظه (مانند سرریز بافر) را به یک نگرانی امنیتی عمده تبدیل میکرد.
اندروید میتواند با پیادهسازی رمزگشاهای نرمافزاری در زبانهای حافظهمحور امن مانند Rust یا ایزوله کردن آنها با استفاده از Lightweight Fault Isolation (LFI)، آنها را با خیال راحت مستقیماً به فرآیند فراخوانی برنامه منتقل کند.
برای حذف ایمن سربار سندباکسینگ خارج از فرآیند برای AAC ( c2.android.inproc.aac.decoder )، اندروید یک رمزگشا ارائه میدهد که با Rust خالص پیادهسازی شده یا توسط مهارهای رابط تابع خارجی Rust (FFI) ایمن محافظت میشود.
زبان Rust به دلایل زیر برای رمزگشایی در حین فرآیند امن تلقی میشود:
- امنیت حافظه در زمان کامپایل: مالکیت، قرض گرفتن و بررسی طول عمر سختگیرانه Rust تضمین میکند که پس از آزادسازی یا تغییر حافظه در حین اشتراکگذاری، نمیتوان به آن دسترسی پیدا کرد.
- رفع آسیبپذیریهای رایج: Rust به طور کامل از سرریز بافر هیپ، خرابی پشته، خواندن و نوشتن آرایه خارج از محدوده، خطاهای استفاده پس از آزادسازی و آسیبپذیریهای آزادسازی مجدد در زمان کامپایل جلوگیری میکند.
- بدون مالیات بر سندباکسینگ زمان اجرا: از آنجا که ایمنی حافظه در زمان کامپایل به صورت ریاضی اثبات شده و از طریق بررسی مرزها تأیید میشود، رمزگشاهای Rust با سرعت بومی در داخل فرآیند برنامه اجرا میشوند بدون اینکه نیازی به انتقال جدول صفحه یا زمینههای سندباکسینگ سختافزاری داشته باشند.
ایمنی حافظه با جداسازی خطای سبک (LFI)
برای رمزگشاهای صوتی پیچیده قدیمی C/C++ که هنوز در Rust بازنویسی نشدهاند - به ویژه رمزگشای Opus ( c2.android.inproc.opus.decoder ارائه شده توسط libopus ) - اندروید با استفاده از جداسازی خطای سبک (LFI) ایمنی در حین پردازش را فراهم میکند.
LFI به دلایل زیر برای رمزگشاهای C/C++ امن در نظر گرفته میشود:
- سندباکسینگ حافظه خطی محافظتشده توسط سختافزار: LFI کتابخانه کدک C/C++ را به کد ماشین خطی مشتقشده از WebAssembly کامپایل میکند که به یک اسلات حافظه خطی ۴ گیگابایتی محافظتشده توسط سختافزار محدود شده است.
- کلیدهای حفاظت از حافظه لینوکس (
pku): LFI از کلیدهای حفاظت از حافظه لینوکس (pku/pkey_mprotect) برای پارتیشنبندی مجوزهای فضای آدرس استفاده میکند. این کتابخانه ایزوله نمیتواند حافظهای خارج از اسلات 4 گیگابایتی اختصاص داده شده به آن را بخواند یا بنویسد. - رهگیری فراخوانی سیستم: فراخوانیهای سیستم عامل میزبان (مانند ورودی/خروجی فایل، دسترسی به شبکه یا کنترل فرآیند) در داخل جعبه شنی مجاز نیستند. هرگونه فراخوانی کتابخانه پشتیبانی نشده به حداقل فایلهای خام (
lfi_stubs.c) هدایت میشود. - بازیابی خطای ایمن: اگر یک جریان بیتی Opus ناقص یا مخرب اقدام به خواندن یا نوشتن خارج از محدوده در داخل جعبه شنی خطی کند، زمان اجرای LFI خطا را به طور ایمن در فضای کاربر به دام میاندازد و یک خطای رمزگشایی
C2_CORRUPTEDبدون از کار انداختن برنامه میزبان برمیگرداند.
معماریها و دستگاههای پشتیبانیشده برای LFI
LFI به هیچ دستورالعمل سختافزاری CPU سفارشی نیاز ندارد و در معماریهای پردازنده ARM64 ( aarch64 ) و x86_64 پشتیبانی میشود:
- دستگاههای ARM64 (
aarch64): اکثر دستگاههای تلفن همراه مصرفی - از جمله تلفنهای همراه (مانند دستگاههای پیکسل با SoCهای Google Tensor، Qualcomm Snapdragon و MediaTek SoCs)، دستگاههای تاشو، تبلتها و واحدهای سرگرمی خودرو - بر روی پردازندههای ARM64 اجرا میشوند و از رمزگشاهای درونپردازشی LFI پشتیبانی میکنند. - دستگاههای x86_64: شبیهسازهای اندروید که روی ایستگاههای کاری توسعهدهندگان با پردازندههای Intel یا AMD اجرا میشوند، و همچنین کرومبوکهای ChromeOS که برنامههای اندروید را با استفاده از ARC (زمان اجرای اندروید برای ChromeOS) اجرا میکنند، روی معماریهای x86_64 اجرا میشوند و از سندباکسینگ LFI پشتیبانی میکنند.
رمزگشاهای صوتی در حال پردازش موجود
| فرمت صوتی | نوع MIME | نام قطعه در حال پردازش | پیادهسازی |
|---|---|---|---|
| اپوس | audio/opus | c2.android.inproc.opus.decoder | libopus C/C++ با LFI |
| آآک | audio/mp4a-latm | c2.android.inproc.aac.decoder | زنگ زدگی ایمن در حافظه ( 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() پیشفرض سیستم نیستند. این پلتفرم در طول تأیید اکوسیستم، اولویت انتخاب Codec 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) } }
جاوا
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 } } }
جاوا
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; } }
نقشه راه برای رمزگشاهای پیشفرض سیستم
اندروید به طور فعال در حال آمادهسازی برای انتقال این رمزگشاهای درونپردازشیِ ایمن از حافظه به رمزگشاهای پیشفرض سیستم برای انواع MIME صوتی مربوطه در نسخههای بعدی پلتفرم است (با شروع از Opus LFI و AAC Rust در اندروید ۱۸ یا بهروزرسانیهای ماژول Mainline در آینده).
زمانی که یک دیکدر در حال پردازش به کامپوننت پیشفرض Codec 2.0 برای نوع MIME خود تبدیل میشود، فراخوانیهای استاندارد MediaCodec.createDecoderByType(...) به طور خودکار از طریق پیادهسازی در حال پردازش هدایت میشوند و تقریباً 40٪ تأخیر رمزگشایی کمتری را بدون نیاز به هیچ گونه تغییر کد برنامه فراهم میکنند.