אודיו ו-MMAP

AAudio הוא ממשק API של אודיו שהושקה בגרסה 8.0 של Android. במהדורה 8.1 של Android יש שיפורים שמפחיתים את זמן האחזור כשמשתמשים בה בשילוב עם HAL ועם מנהל שמתמכים ב-MMAP. במסמך הזה מתוארים השינויים בשכבת החומרה הווירטואלית (HAL) ובנהגים הנדרשים כדי לתמוך בתכונה MMAP של AAudio ב-Android.

כדי לתמוך ב-AAudio MMAP נדרשים:

  • דיווח על יכולות ה-MMAP של ה-HAL
  • הטמעת פונקציות חדשות ב-HAL
  • אפשרות להטמיע ioctl() בהתאמה אישית למאגר במצב EXCLUSIVE
  • מתן נתיב נוסף לנתוני חומרה
  • הגדרת מאפייני מערכת שמפעילים את התכונה MMAP

הארכיטקטורה של AAudio

AAudio הוא ממשק API מקורי חדש ב-C, שמספק חלופה ל-Open SL ES. הוא משתמש בתבנית עיצוב של Builder כדי ליצור מקורות אודיו.

AAudio מספק נתיב נתונים בזמן אחזור קצר. במצב EXCLUSIVE, התכונה מאפשרת לקוד של אפליקציית הלקוח לכתוב ישירות למאגר שממופה לזיכרון, שמשותף עם מנהל ה-ALSA. במצב SHARED, מאגר ה-MMAP משמש מיקסר שפועל ב-AudioServer. במצב EXCLUSIVE, זמן האחזור קצר יותר באופן משמעותי כי הנתונים עוקפים את המיקסר.

במצב EXCLUSIVE, השירות מבקש את מאגר ה-MMAP מ-HAL ומנהל את המשאבים. מאגר ה-MMAP פועל במצב NOIRQ, כך שאין ספירות משותפות של קריאה/כתיבה לניהול הגישה למאגר. במקום זאת, הלקוח שומר על מודל תזמון של החומרה ומנבא מתי המאגר ייקרא.

בתרשים הבא אפשר לראות את נתוני ה-Pulse-code modulation‏ (PCM) שזורמים דרך MMAP FIFO אל מנהל ההתקן ALSA. חותמות הזמן מתקבלות מדי פעם על ידי שירות AAudio, ולאחר מכן מועברות למודל התזמון של הלקוח דרך תור הודעות אטומי.

תרשים זרימה של נתוני PCM.
איור 1. זרימת נתוני 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 לערך 2 (AAUDIO_POLICY_AUTO) כדי שמערכת האודיו תדע ש-HAL של האודיו תומך במצב MMAP. (ראו בהמשך בקטע 'הפעלת נתיב הנתונים של 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 של שאילתה

חותמת הזמן שמועברת חזרה למודל התזמון מכילה את מיקום המסגרת ואת הזמן הרצוי במיקרו-שניות:

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

ה-HAL יכול לקבל את המידע הזה מהדריבר של ALSA באמצעות קריאה לפונקציה חדשה של Tinyalsa:

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

מתארי קבצים לזיכרון משותף

נתיב הנתונים של AAudio MMAP משתמש באזור זיכרון שמשותף בין החומרה לבין שירות האודיו. הפניה לזיכרון המשותף מתבצעת באמצעות מתאר קובץ שנוצר על ידי מנהל ה-ALSA.

שינויים בליבה

אם מתאר הקובץ משויך ישירות לקובץ הנהג /dev/snd/, שירות AAudio יכול להשתמש בו במצב SHARED. אבל לא ניתן להעביר את המתאר לקוד הלקוח במצב EXCLUSIVE. מתאר הקובץ /dev/snd/ יספק ללקוח גישה רחבה מדי, ולכן הוא חסום על ידי SELinux.

כדי לתמוך במצב EXCLUSIVE, צריך להמיר את מתאר /dev/snd/ למתאר קובץ anon_inode:dmabuf. SELinux מאפשר להעביר את מתאר הקובץ הזה ללקוח. אפשר להשתמש בו גם ב-AAudioService.

אפשר ליצור מתאר קובץ anon_inode:dmabuf באמצעות ספריית הזיכרון של Android Ion.

מידע נוסף זמין במקורות המידע החיצוניים הבאים:

  1. "The Android ION memory allocator" https://lwn.net/Articles/480055/
  2. 'סקירה כללית על Android ION' https://wiki.linaro.org/BenjaminGaignard/ion
  3. "שילוב מנהל הזיכרון ION" https://lwn.net/Articles/565469/

שינויים ב-HAL

שירות AAudio צריך לדעת אם יש תמיכה ב-anon_inode:dmabuf הזה. לפני Android 10.0, הדרך היחידה לעשות זאת הייתה להעביר את הגודל של מאגר הנתונים הזמני של MMAP כמספר שלילי, למשל: -2048 במקום 2048, אם יש תמיכה. ב-Android מגרסה 10.0 ואילך אפשר להגדיר את הדגל AUDIO_MMAP_APPLICATION_SHAREABLE.

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

שינויים במערכת המשנה של האודיו

כדי ש-AAudio יוכל לפעול במקביל לנתיב המקורי של AudioFlinger, נדרש נתיב נתונים נוסף בחזית האודיו של מערכת המשנה האודיו. הנתיב הקודם משמש לכל שאר הצלילים של המערכת והאפליקציות. הפונקציונליות הזו יכולה להיות זמינה באמצעות מיקסר תוכנה ב-DSP או מיקסר חומרה ב-SOC.

הפעלת נתיב הנתונים של AAudio MMAP

אם MMAP לא נתמך או שלא ניתן לפתוח שידור, המערכת של AAudio תשתמש בנתיב הנתונים הקודם של AudioFlinger. לכן, AAudio יפעל עם מכשיר אודיו שלא תומך בנתיב MMAP/NOIRQ.

כשבודקים את התמיכה ב-MMAP ל-AAudio, חשוב לדעת אם בעצם בודקים את נתיב הנתונים של MMAP או רק את נתיב הנתונים הקודם. בהמשך מוסבר איך מפעילים או מאלצים נתיבי נתונים ספציפיים, ואיך שולחים שאילתה לגבי הנתיב שבו נעשה שימוש במקור נתונים.

מאפייני מערכת

אפשר להגדיר את מדיניות ה-MMAP באמצעות מאפייני מערכת:

  • 1 = AAUDIO_POLICY_NEVER – שימוש רק בנתיב הקודם. אל תנסו אפילו להשתמש ב-MMAP.
  • 2 = AAUDIO_POLICY_AUTO – ניסיון להשתמש ב-MMAP. אם הניסיון הזה נכשל או שהוא לא זמין, צריך להשתמש בנתיב הקודם.
  • 3 = AAUDIO_POLICY_ALWAYS – שימוש רק בנתיב MMAP. לא לעבור לנתיב הקודם.

אפשר להגדיר אותם ב-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

אפשר לשנות את הערכים האלה גם אחרי שהמכשיר יופעל. כדי שהשינוי ייכנס לתוקף, צריך להפעיל מחדש את שרת האודיו. לדוגמה, כדי להפעיל את מצב AUTO ל-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);