Aaudio ו-MMAP

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

תמיכה ב-AAudio MMAP דורשת:

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

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

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

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

במצב EXCLUSIVE, השירות מבקש את מאגר ה-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

יש להגדיר את מאפיין המערכת "audio.mmap_policy" ל-2 (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);

ה-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. "מקצה הזיכרון של אנדרואיד ION" https://lwn.net/Articles/480055/
  2. "סקירה כללית של אנדרואיד ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "שילוב מקצה הזיכרון ION" https://lwn.net/Articles/565469/

שינויים ב-HAL

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

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

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

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

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

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

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