אודיו ו-MMAP

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

כדי לתמוך ב-AAudio MMAP, אתם צריכים:

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

ארכיטקטורת אודיו

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

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

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

בתרשים שלמטה ניתן לראות את זרימה של נתוני אפנון קוד הדופק (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);

לאודיו HIDL עם HAL:

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) מערכת האודיו יודעת שמצב MMAP נתמך על ידי ה-HAL של האודיו. (ראו "הפעלת נתיב נתוני AAudio MMAP" below.)

הקובץ 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);

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

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

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

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

שינויים בליבה (kernel)

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

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

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

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

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

שינויים ב-HAL

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

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

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

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

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

האודיו ישתמש בנתיב הנתונים AudioFlinger הקודם אם MMAP לא נתמך או לא ניתן לפתוח את השידור. לכן האודיו יפעל עם התקן אודיו שלא תומך תמיכה בנתיב 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

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