‫AAudio و MMAP

‫AAudio یک میانای برنامه‌سازی کاربردی صوتی است که در نسخه Android 8.0 معرفی شده است. نسخه Android 8.1 دارای بهبودهایی برای کاهش تأخیر هنگام استفاده همراه با HAL و درایوری است که از MMAP پشتیبانی می‌کند. این سند لایه انتزاعی سخت‌افزار (HAL) و تغییرات درایور موردنیاز برای پشتیبانی از ویژگی MMAP در AAudio در Android را شرح می‌دهد.

پشتیبانی از AAudio MMAP به موارد زیر نیاز دارد:

  • گزارش قابلیت‌های MMAP در HAL
  • پیاده‌سازی عملکردهای جدید در HAL
  • به‌صورت اختیاری، پیاده‌سازی ioctl() سفارشی برای بافر حالت EXCLUSIVE
  • ارائه مسیر داده سخت‌افزاری اضافی
  • تنظیم ویژگی‌های سیستم که ویژگی MMAP را فعال می‌کند

معماری AAudio

AAudio یک API بومی جدید C است که جایگزینی برای Open SL ES ارائه می‌دهد. این کلاس از الگوی طراحی Builder برای ایجاد جاری‌سازی‌های صوتی استفاده می‌کند.

AAudio مسیر داده‌ای با تأخیر کم ارائه می‌دهد. در حالت «انحصاری»، این ویژگی به کد برنامه مشتری اجازه می‌دهد مستقیماً در بافر نگاشت‌شده حافظه که با درایور ALSA هم‌رسانی می‌شود بنویسد. در حالت «مشترک»، میان‌گیر MMAP توسط میکسر درحال اجرا در AudioServer استفاده می‌شود. در حالت «انحصاری»، تأخیر به‌طور قابل‌توجهی کمتر است زیرا داده‌ها میکسر را دور می‌زنند.

در حالت «انحصاری»، سرویس بافر MMAP را از HAL درخواست می‌کند و منابع را مدیریت می‌کند. بافر MMAP در حالت NOIRQ اجرا می‌شود، بنابراین هیچ شمارنده خواندن/نوشتن مشترکی برای مدیریت دسترسی به بافر وجود ندارد. درعوض، کارخواه مدل زمان‌بندی سخت‌افزار را حفظ می‌کند و پیش‌بینی می‌کند که بافر چه زمانی خوانده خواهد شد.

در نمودار زیر، می‌توانیم داده‌های مدولاسیون کد پالس (PCM) را ببینیم که ازطریق MMAP FIFO به درایور ALSA جریان می‌یابد. مُهرهای زمان به‌طور دوره‌ای توسط سرویس AAudio درخواست می‌شود و سپس ازطریق صف پیام اتمی به مدل زمان‌بندی کارخواه منتقل می‌شود.

نمودار جریان داده PCM.
شکل ۱. جریان داده PCM ازطریق FIFO به ALSA

در حالت SHARED (مشترک)، از مدل زمان‌بندی نیز استفاده می‌شود، اما این مدل در AAudioService قرار دارد.

برای ضبط صدا، از مدل مشابهی استفاده می‌شود، اما داده‌های PCM در جهت مخالف جریان می‌یابد.

تغییرات HAL

برای tinyALSA، به این مراجعه کنید:

external/tinyalsa/include/tinyalsa/asoundlib.h
external/tinyalsa/include/tinyalsa/pcm.c
int pcm_start(struct pcm *pcm);
int pcm_stop(struct pcm *pcm);
int pcm_mmap_begin(struct pcm *pcm, void **areas,
           unsigned int *offset,
           unsigned int *frames);
int pcm_get_poll_fd(struct pcm *pcm);
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset,
           unsigned int frames);
int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr,
           struct timespec *tstamp);

برای HAL قدیمی، به این لینک مراجعه کنید:

hardware/libhardware/include/hardware/audio.h
hardware/qcom/audio/hal/audio_hw.c
int start(const struct audio_stream_out* stream);
int stop(const struct audio_stream_out* stream);
int create_mmap_buffer(const struct audio_stream_out *stream,
                        int32_t min_size_frames,
                        struct audio_mmap_buffer_info *info);
int get_mmap_position(const struct audio_stream_out *stream,
                        struct audio_mmap_position *position);

برای HAL صدای HIDL:

hardware/interfaces/audio/2.0/IStream.hal
hardware/interfaces/audio/2.0/types.hal
hardware/interfaces/audio/2.0/default/Stream.h
start() generates (Result retval);
stop() generates (Result retval) ;
createMmapBuffer(int32_t minSizeFrames)
       generates (Result retval, MmapBufferInfo info);
getMmapPosition()
       generates (Result retval, MmapPosition position);

گزارش پشتیبانی MMAP

دارایی سیستم «aaudio.mmap_policy» باید روی ۲ (AAUDIO_POLICY_AUTO) تنظیم شود تا چارچوب صوتی بداند که حالت MMAP توسط HAL صوتی پشتیبانی می‌شود. (به «فعال کردن مسیر داده AAudio MMAP» در زیر مراجعه کنید.)

فایل audio_policy_configuration.xml باید حاوی برونداد و ورودی نمایه ویژه حالت MMAP/NO IRQ باشد تا «مدیر خط‌مشی صوتی» بداند وقتی کارخواهان MMAP ایجاد می‌شوند کدام جاری‌سازی را باز کند:

<mixPort name="mmap_no_irq_out" role="source"
            flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>

<mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>

باز و بسته کردن جاری‌سازی MMAP

createMmapBuffer(int32_t minSizeFrames)
            generates (Result retval, MmapBufferInfo info);

جریان MMAP را می‌توان با فراخوانی توابع Tinyalsa باز و بسته کرد.

جایگاه MMAP پُرسمان

مُهر زمان ارسال‌شده به «مدل زمان‌بندی» حاوی موقعیت قاب و زمان MONOTONIC برحسب نانوثانیه است:

getMmapPosition()
        generates (Result retval, MmapPosition position);

«لایه انتزاعی سخت‌افزار» می‌تواند این اطلاعات را با فراخوانی تابع جدید Tinyalsa از درایور ALSA دریافت کند:

int pcm_mmap_get_hw_ptr(struct pcm* pcm,
                        unsigned int *hw_ptr,
                        struct timespec *tstamp);

توصیف‌گرهای فایل برای حافظه مشترک

مسیر داده AAudio MMAP از ناحیه حافظه‌ای استفاده می‌کند که بین سخت‌افزار و سرویس صوتی هم‌رسانی می‌شود. حافظه مشترک بااستفاده از توصیفگر فایل ارجاع داده می‌شود که توسط درایور ALSA تولید می‌شود.

تغییرات هسته

اگر توصیفگر فایل مستقیماً با فایل درایور /dev/snd/ مرتبط باشد، می‌تواند توسط سرویس AAudio در حالت «مشترک» استفاده شود. اما توصیف‌گر نمی‌تواند برای حالت EXCLUSIVE به کد کارخواه منتقل شود. توصیفگر فایل /dev/snd/ دسترسی بسیار گسترده‌ای به کارخواه ارائه می‌دهد، بنابراین SELinux آن را مسدود می‌کند.

برای پشتیبانی از حالت «انحصاری»، لازم است توصیفگر /dev/snd/ به توصیفگر فایل anon_inode:dmabuf تبدیل شود. SELinux اجازه می‌دهد آن توصیف‌گر فایل به کارخواه ارسال شود. AAudioService نیز می‌تواند از آن استفاده کند.

بااستفاده از کتابخانه حافظه Android Ion می‌توان توصیف‌گر فایل anon_inode:dmabuf تولید کرد.

برای اطلاعات بیشتر، این منابع خارجی را ببینید:

  1. «تخصیص‌دهنده حافظه Android ION» https://lwn.net/Articles/480055/
  2. «نمای کلی Android ION» https://wiki.linaro.org/BenjaminGaignard/ion
  3. «ادغام تخصیص‌دهنده حافظه ION» https://lwn.net/Articles/565469/

تغییرات لایه انتزاع سخت‌افزار

سرویس AAudio باید بداند که آیا این anon_inode:dmabuf پشتیبانی می‌شود یا نه. قبل‌از Android 10.0، تنها راه انجام این کار این بود که اندازه بافر MMAP را به‌عنوان یک عدد منفی ارسال کنید، مثلاً. ‫-۲۰۴۸ به‌جای ۲۰۴۸، درصورت پشتیبانی. در Android 10.0 و نسخه‌های جدیدتر می‌توانید پرچم AUDIO_MMAP_APPLICATION_SHAREABLE را تنظیم کنید.

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

تغییرات زیرسیستم صوتی

‫AAudio به مسیر داده اضافی در قسمت جلویی صدای زیرسیستم صوتی نیاز دارد تا بتواند به‌موازات مسیر اصلی AudioFlinger کار کند. از آن مسیر قدیمی برای همه صداهای سیستم و صداهای برنامه استفاده می‌شود. این کارکرد می‌تواند توسط یک میکسر نرم‌افزاری در DSP یا یک میکسر سخت‌افزاری در SOC ارائه شود.

فعال کردن مسیر داده MMAP در AAudio

اگر MMAP پشتیبانی نشود یا نتواند جریانی را باز کند، AAudio از مسیر داده قدیمی AudioFlinger استفاده خواهد کرد. بنابراین AAudio با دستگاه صوتی که از مسیر MMAP/NOIRQ پشتیبانی نمی‌کند کار خواهد کرد.

هنگام آزمایش پشتیبانی MMAP برای AAudio، مهم است بدانید که آیا واقعاً مسیر داده MMAP را آزمایش می‌کنید یا صرفاً مسیر داده قدیمی را آزمایش می‌کنید. در زیر توضیح داده شده است که چگونه مسیرهای داده خاص را فعال یا اجباری کنید و چگونه مسیر استفاده‌شده توسط یک جاری‌سازی را پُرسمان کنید.

خصوصیات سیستم

می‌توانید خط‌مشی MMAP را ازطریق ویژگی‌های سیستم تنظیم کنید:

  • ‫۱ = AAUDIO_POLICY_NEVER - فقط از مسیر قدیمی استفاده کنید. حتی سعی نکنید از MMAP استفاده کنید.
  • ‫۲ = AAUDIO_POLICY_AUTO - سعی کنید از MMAP استفاده کنید. اگر این کار انجام نشد یا دردسترس نبود، از مسیر قدیمی استفاده کنید.
  • ‫۳ = AAUDIO_POLICY_ALWAYS - فقط از مسیر MMAP استفاده شود. Do not fall back to legacy path.

این‌ها ممکن است در Makefile دستگاه‌ها تنظیم شوند، مانند:

# Enable AAudio MMAP/NOIRQ data path.
# 2 is AAUDIO_POLICY_AUTO so it will try MMAP then fallback to Legacy path.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2
# Allow EXCLUSIVE then fall back to SHARED.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_exclusive_policy=2

همچنین می‌توانید این مقادیر را پس‌از راه‌اندازی دستگاه ملغی کنید. برای اعمال تغییر، باید سرور صوتی را بازراه‌اندازی کنید. برای مثال، برای فعال کردن حالت «خودکار» برای MMAP:

adb root
adb shell setprop aaudio.mmap_policy 2
adb shell killall audioserver

توابعی در ndk/sysroot/usr/include/aaudio/AAudioTesting.h ارائه شده است که به شما امکان می‌دهد خط‌مشی استفاده از مسیر MMAP را لغو کنید:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

برای اینکه بدانید آیا جاری‌سازی از مسیر MMAP استفاده می‌کند، با این تماس بگیرید:

bool AAudioStream_isMMapUsed(AAudioStream* stream);