אודיו

AAudio הוא ממשק API חדש ל-Android C שהושק במהדורת Android O. הוא מיועד לאפליקציות אודיו עם ביצועים גבוהים שדורשות זמן אחזור קצר. אפליקציות מתקשרות עם AAudio על ידי קריאה וכתיבה של נתונים למקורות.

ממשק ה-API של AAudio הוא מינימלי מעצם הגדרתו, והוא לא מבצע את הפונקציות הבאות:

  • ספירה של מכשירי אודיו
  • ניתוב אוטומטי בין נקודות קצה של אודיו
  • קלט/פלט של קבצים
  • פענוח של אודיו דחוס
  • הצגה אוטומטית של כל הקלט/הזרמים בקריאה חוזרת אחת.

תחילת העבודה

אפשר להפעיל את AAudio מקוד C++‎. כדי להוסיף את קבוצת התכונות של AAudio לאפליקציה, צריך לכלול את קובץ הכותרת AAudio.h:

#include <aaudio/AAudio.h>

שידורי אודיו

AAudio מעביר נתוני אודיו בין האפליקציה לבין מקורות הקלט והפלט של האודיו במכשיר Android. האפליקציה מעבירה נתונים פנימה והחוצה על ידי קריאה ממקורות אודיו וכתיבה אליהם, שמיוצגים על ידי המבנה AAudioStream. קריאה/כתיבה יכולות להיות חסימניות או לא חסימניות.

מקור נתונים מוגדר לפי הפרטים הבאים:

  • מכשיר האודיו שהוא המקור או היעד של הנתונים בסטרימינג.
  • מצב השיתוף שקובע אם לשידור יש גישה בלעדית למכשיר אודיו, שיכול להיות ששותף בין כמה שידורים.
  • הפורמט של נתוני האודיו בסטרימינג.

מכשיר אודיו

כל שידור מצורף למכשיר אודיו אחד.

מכשיר אודיו הוא ממשק חומרה או נקודת קצה וירטואלית שמשמש כמקור או כצינור הולכה לזרם רציף של נתוני אודיו דיגיטליים. חשוב לא להתבלבל בין מכשיר אודיו (מיקרופון מובנה או אוזניות Bluetooth) לבין מכשיר Android (הטלפון או השעון) שבו פועלת האפליקציה.

אפשר להשתמש בשיטה AudioManager getDevices() כדי למצוא את מכשירי האודיו שזמינים במכשיר Android. השיטה מחזירה מידע על type של כל מכשיר.

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

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

מצב שיתוף

לכל שידור יש מצב שיתוף:

  • הערך AAUDIO_SHARING_MODE_EXCLUSIVE מציין שלשידור יש גישה בלעדית למכשיר האודיו שלו. לא ניתן להשתמש במכשיר בשום שידור אודיו אחר. אם מכשיר האודיו כבר נמצא בשימוש, יכול להיות שלא תהיה לסטרימינג גישה בלעדית. סביר להניח שלשידורים בלעדיים יהיה זמן אחזור קצר יותר, אבל גם סיכוי גבוה יותר להתנתקות. מומלץ לסגור את הסטרימינג הייחודי ברגע שאתם לא צריכים אותו יותר, כדי שאפליקציות אחרות יוכלו לגשת למכשיר. בשידורים בלעדיים זמן האחזור הוא הנמוך ביותר האפשרי.
  • AAUDIO_SHARING_MODE_SHARED מאפשר ל-AAudio לערבב אודיו. AAudio מערבב את כל הסטרימינגים המשותפים שהוקצו לאותו מכשיר.

אפשר להגדיר את שיתוף המסך באופן מפורש כשיוצרים שידור. כברירת מחדל, מצב השיתוף הוא SHARED.

פורמט אודיו

הנתונים שמועברים דרך מקור נתונים כוללים את המאפיינים הרגילים של אודיו דיגיטלי. אלה הפרמטרים:

  • פורמט של נתונים לדוגמה
  • מספר הערוצים (דגימות לכל פריים)
  • תדירות הדגימה

ב-AAudio מותר להשתמש בפורמטים הבאים של טעימות:

aaudio_format_t סוג הנתונים C הערות
AAUDIO_FORMAT_PCM_I16 int16_t דגימות נפוצות של 16 ביט, פורמט Q0.15
AAUDIO_FORMAT_PCM_FLOAT float ‎-1.0 עד +1.0
AAUDIO_FORMAT_PCM_I24_PACKED uint8_t בקבוצות של 3 דגימות ארוכות של 24 ביט, פורמט Q0.23
AAUDIO_FORMAT_PCM_I32 int32_t דגימות נפוצות של 32 ביט, פורמט Q0.31
AAUDIO_FORMAT_IEC61937 uint8_t אודיו דחוס ב-IEC61937 להעברה ישירה (passthrough) ב-HDMI או ב-S/PDIF

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

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

יצירת שידור אודיו

ספריית AAudio מבוססת על תבנית עיצוב של בונה ומספקת את AAudioStreamBuilder.

  1. יוצרים אובייקט AAudioStreamBuilder:

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. מגדירים את ההגדרות של שידור האודיו ב-builder באמצעות פונקציות ה-builder שתואמות לפרמטרים של השידור. אלה הפונקציות האופציונליות להגדרה שזמינות:

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    שימו לב שהשיטות האלה לא מדווחות על שגיאות, כמו קבוע לא מוגדר או ערך מחוץ לטווח.

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

    כדי להימנע מבעיות, מומלץ לבדוק את המצב של מקור האודיו אחרי שיוצרים אותו, כפי שמוסבר בשלב 4 בהמשך.

  3. אחרי שמגדירים את AAudioStreamBuilder, משתמשים בו כדי ליצור סטרימינג:

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

  4. אחרי שיוצרים את הסטרימינג, בודקים את ההגדרות שלו. אם ציינת פורמט דגימה, קצב דגימה או דגימות לכל פריים, הם לא ישתנו. אם ציינת את מצב השיתוף או את קיבולת המאגר, הם עשויים להשתנות בהתאם ליכולות של מכשיר האודיו של השידור וליכולות של מכשיר Android שבו הוא פועל. כחלק מתכנות הגנה טובה, כדאי לבדוק את ההגדרות של הסטרימינג לפני שמשתמשים בו. יש פונקציות לאחזור הגדרת הסטרימינג שתואמת לכל הגדרת ה-builder:

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()

  5. אפשר לשמור את ה-builder ולהשתמש בו שוב בעתיד כדי ליצור עוד שידורים. אבל אם אתם לא מתכוונים להשתמש בו יותר, כדאי למחוק אותו.

    AAudioStreamBuilder_delete(builder);
    

שימוש בשידור אודיו

מעברי מצבים

בדרך כלל, סטרימינג של AAudio נמצא באחד מחמשת המצבים היציבים הבאים (מצב השגיאה 'מנותק' מתואר בסוף הקטע הזה):

  • פתיחה
  • ספרים שהתחלת
  • בהשהיה
  • פרצוף מסמיק
  • הופסק

נתונים עוברים דרך מקור נתונים רק כשהוא נמצא במצב 'הפעלה'. כדי להעביר את הסטרימינג בין מצבים, משתמשים באחת מהפונקציות שמבקשות מעבר מצב:

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

חשוב לזכור שאפשר לבקש השהיה או שטיפה רק בזרם פלט:

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

  • תאריך ההתחלה:
  • השהיה
  • שטיפה
  • מתבצעת עצירה
  • סגירה

בתרשים המצבים שבהמשך, המצבים היציבים מוצגים כריבועיים מעוגלים והמצבים הזמניים מוצגים כריבועיים מקווקווים. למרות שהמדינה לא מוצגת, אפשר להתקשר למספר close() מכל מדינה

מחזור החיים של AAudio

AAudio לא מספק קריאות חזרה (callbacks) כדי להתריע על שינויים בסטטוס. אפשר להשתמש בפונקציה מיוחדת אחת, AAudioStream_waitForStateChange(stream, inputState, nextState, timeout), כדי להמתין לשינוי במצב.

הפונקציה לא מזהה שינוי במצב בעצמה, ולא ממתינה למצב ספציפי. הפונקציה ממתינה עד שהמצב הנוכחי שונה מ-inputState שציינתם.

לדוגמה, אחרי שליחת בקשה להשהיה, השידור אמור לעבור מיד למצב זמני 'השהיה', ולהגיע למצב 'מושהה' זמן מה לאחר מכן – אבל אין ערובה לכך. מכיוון שאי אפשר להמתין לסטטוס 'מושהה', צריך להשתמש ב-waitForStateChange() כדי להמתין לסטטוס כלשהו מלבד 'השהיה'. כך עושים את זה:

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

אם הסטטוס של הסטרימינג הוא לא 'השהיה' (inputState, שהנחנו שהוא הסטטוס הנוכחי בזמן הקריאה), הפונקציה תחזיר מיד. אחרת, הוא נחסם עד שהמצב לא יהיה יותר 'השהיה' או עד שתוקף הזמן הקצוב יפוג. כשהפונקציה חוזרת, הפרמטר nextState מציג את המצב הנוכחי של הסטרימינג.

אפשר להשתמש באותה טכניקה אחרי שמפעילים את הבקשה, מפסיקים אותה או מבצעים בה שטיפה, באמצעות המצב הזמני התואם בתור inputState. אל תפעילו את waitForStateChange() אחרי שתפעילו את AAudioStream_close(), כי הסטרימינג יימחק ברגע שהוא יסתיים. וגם לא להפעיל את AAudioStream_close() בזמן ש-waitForStateChange() פועל בשרשור אחר.

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

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

לקריאה או לכתיבה חוסמות שמעבירות את מספר הפריימים שצוין, צריך להגדיר את timeoutNanos כגדול מאפס. לשיחה לא חוסמת, מגדירים את timeoutNanos לאפס. במקרה כזה, התוצאה היא מספר הפריימים שהועברו בפועל.

כשקוראים קלט, צריך לוודא שנקרא מספר הפריימים הנכון. אם לא, המאגר עשוי להכיל נתונים לא מוכרים שיכולים לגרום לבעיות באודיו. אפשר למלא את המאגר באפסים כדי ליצור השהיה שקטה:

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

אפשר להכין את מאגר הנתונים של הסטרימינג לפני שמתחילים את הסטרימינג על ידי כתיבת נתונים או שתיקה. צריך לעשות זאת בקריאה לא חוסמת עם timeoutNanos שמוגדר לאפס.

הנתונים במאגר חייבים להתאים לפורמט הנתונים שמוחזר על ידי AAudioStream_getDataFormat().

סגירת שידור אודיו

כשמסיימים להשתמש בסטרימינג, סוגרים אותו:

AAudioStream_close(stream);

אחרי שסוגרים את מקור הנתונים, אי אפשר להשתמש ב-stream pointer עם אף פונקציה מבוססת-מקור של AAudio.

סגירת מקור נתונים לא בטוחה לשרשור. אל תנסה לסגור את הסטרימינג בשרשור אחד בזמן שמשתמשים בו בשרשור אחר. אם משתמשים בכמה שרשורים, צריך לסנכרן אותם בקפידה. מומלץ להעביר את כל הקוד לטיפול בסטרימינג לשרשור אחד, ואז לשלוח לו פקודות באמצעות תור אטומי.

שידור אודיו מנותק

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

  • מכשיר האודיו המשויך כבר לא מחובר (לדוגמה, כשהאוזניות לא מחוברות).
  • מתרחשת שגיאה פנימית.
  • התקן אודיו כבר לא התקן האודיו הראשי.

כשהסטרימינג מנותק, המצב שלו הוא Disconnected (מנותק), וכל ניסיון להריץ את AAudioStream_write() או פונקציות אחרות יחזיר שגיאה. תמיד צריך להפסיק ולסגור שידור מנותק, ללא קשר לקוד השגיאה.

אם אתם משתמשים ב-data callback (בניגוד לאחת משיטות הקריאה/הכתיבה הישירה), לא תקבלו קוד החזרה כשהסטרימינג יתנתק. כדי לקבל הודעה כאשר זה קורה, כותבים פונקציה AAudioStream_errorCallback ומירשם אותה באמצעות AAudioStreamBuilder_setErrorCallback()‎.

אם אתם מקבלים הודעה על הניתוק בשרשור של קריאה חוזרת (callback) עם שגיאה, עליכם להפסיק ולסגור את הסטרימינג משרשור אחר. אחרת, יכול להיות שתהיה לכם נעילה מרובת משתמשים (deadlock).

שימו לב: אם פותחים שידור חדש, יכול להיות שההגדרות שלו יהיו שונות מההגדרות של השידור המקורי (לדוגמה, framesPerBurst):

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error) {
    // Launch a new thread to handle the disconnect.
    std::thread myThread(my_error_thread_proc, stream, userData);
    myThread.detach(); // Don't wait for the thread to finish.
}

אופטימיזציה של הביצועים

כדי לבצע אופטימיזציה של הביצועים של אפליקציית אודיו, אפשר לשנות את מאגרי הנתונים הפנימיים שלה ולהשתמש בשרשורים מיוחדים בעדיפות גבוהה.

שינוי של מאגרי נתונים כדי לצמצם את זמן האחזור

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

הקיבולת של מאגר הביניים היא כמות הנתונים הכוללת שהוא יכול להכיל. אפשר להתקשר למספר AAudioStreamBuilder_setBufferCapacityInFrames() כדי להגדיר את הקיבולת. השיטה מגבילה את הקיבולת שאפשר להקצות לערך המקסימלי שהמכשיר מאפשר. משתמשים ב-AAudioStream_getBufferCapacityInFrames() כדי לאמת את הקיבולת בפועל של המאגר.

אפליקציה לא חייבת להשתמש בכל הקיבולת של מאגר נתונים. AAudio ממלא מאגר נתונים זמני עד גודל שאפשר להגדיר. גודל מאגר הנתונים הזמני לא יכול להיות גדול מקיבולת האחסון שלו, ובדרך כלל הוא קטן יותר. שליטה בגודל המאגר מאפשרת לקבוע את מספר הפסגות הבקשות הנדרשות כדי למלא אותו, וכך לשלוט בזמן האחזור. משתמשים בשיטות AAudioStreamBuilder_setBufferSizeInFrames() ו-AAudioStreamBuilder_getBufferSizeInFrames() כדי לעבוד עם גודל המאגר הזמני.

כשאפליקציה מפעילה אודיו, היא כותבת למאגר ומחכה עד שהכתיבה תושלם. AAudio קורא מהמאגר בפרצים נפרדים. כל רצף נתונים מהיר מכיל מספר פריימים של אודיו, ובדרך כלל הוא קטן יותר מגודל המאגר שנקרא. המערכת קובעת את גודל הפסיקה ואת הקצב שלה. בדרך כלל, המאפיינים האלה מוכתבים על ידי המעגלים של מכשיר האודיו. אי אפשר לשנות את הגודל של התפרצות או את קצב ההתפרצויות, אבל אפשר להגדיר את הגודל של המאגר הפנימי בהתאם למספר ההתפרצויות שהוא מכיל. באופן כללי, זמן האחזור הנמוך ביותר מתקבל כאשר גודל המאגר הזמני של AAudioStream הוא מכפיל של גודל הפיצוץ המדווח.

      AAudio Buffering

אחת הדרכים לבצע אופטימיזציה של גודל המאגר היא להתחיל עם מאגר גדול ולהקטין אותו בהדרגה עד שמתחילים להופיע אירועי חוסר זמן עיבוד, ואז להגדיל אותו שוב. לחלופין, אפשר להתחיל עם מאגר נתונים קטן, ואם הוא יגרום לבעיות של חוסר נתונים, להגדיל את מאגר הנתונים עד שהפלט יזרום בצורה חלקה שוב.

התהליך הזה יכול להתרחש מהר מאוד, אולי עוד לפני שהמשתמש מפעיל את הצליל הראשון. מומלץ לבצע קודם את הגדרת הגודל הראשונית של המאגר באמצעות שתיקה, כדי שהמשתמש לא ישמע שיבושים באודיו. ביצועי המערכת עשויים להשתנות לאורך זמן (לדוגמה, המשתמש עשוי להשבית את מצב הטיסה). מכיוון שכוונון מאגר הנתונים מוסיף מעט מאוד זמן אחזור, האפליקציה יכולה לבצע את הפעולה הזו באופן רציף בזמן שהיא קוראת או כותבת נתונים למקור נתונים.

דוגמה ללו"פ לאופטימיזציה של מאגר:

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

אין יתרון לשימוש בשיטה הזו כדי לבצע אופטימיזציה של גודל המאגר של מקור קלט. מקורות הקלט פועלים מהר ככל האפשר, ומנסים לצמצם את כמות הנתונים שנשמרים במטמון, ולאחר מכן הם מתמלאים כשהאפליקציה מופסקת.

שימוש בשיחת חזרה בעדיפות גבוהה

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

פונקציית הקריאה החוזרת כוללת את אב הטיפוס הזה:

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

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

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

במקרה הפשוט ביותר, הסטרימינג מפעיל מדי פעם את פונקציית ה-callback כדי לקבל את הנתונים לפרץ הבא.

אסור לפונקציית ההתקשרות חזרה לבצע פעולות קריאה או כתיבה בסטרימינג שהפעיל אותה. אם פונקציית ה-callback שייכת למקור קלט, הקוד צריך לעבד את הנתונים שסופקו במאגר audioData (שצוין כארגומנטים השלישי). אם פונקציית ה-callback שייכת למקור פלט, הקוד צריך להעביר נתונים למאגר.

לדוגמה, אפשר להשתמש בקריאה חוזרת (callback) כדי ליצור באופן רציף פלט של גל סינוס, באופן הבא:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

אפשר לעבד יותר מזרם אחד באמצעות AAudio. אפשר להשתמש במקור נתונים אחד כמקור ראשי, ולהעביר את ההפניות למקורות נתונים אחרים בנתוני המשתמש. רושמים קריאה חוזרת (callback) לשידור הראשי. לאחר מכן משתמשים ב-I/O ללא חסימה בשידורים האחרים. זו דוגמה לקריאה חוזרת (callback) הלוך ושוב שמעבירה מקור קלט למקור פלט. מקור הקריאה הראשי הוא מקור הפלט. מקור הנתונים נכלל בנתוני המשתמש.

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

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

הערה: בדוגמה הזו, ההנחה היא שלזרמי הקלט והפלט יש את אותו מספר ערוצים, פורמט וקצב דגימה. הפורמט של הסטרימינג יכול להיות שונה – כל עוד הקוד מטפל בתרגומים בצורה תקינה.

הגדרת מצב הביצועים

לכל AAudioStream יש מצב ביצועים שמשפיע מאוד על התנהגות האפליקציה. יש שלושה מצבים:

  • AAUDIO_PERFORMANCE_MODE_NONE הוא מצב ברירת המחדל. הוא משתמש בשידור בסיסי שמאזן בין זמן האחזור לבין חיסכון בחשמל.
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY משתמש במאגרים קטנים יותר ובנתיב נתונים מותאם כדי לצמצם את זמן האחזור.
  • ב-AAUDIO_PERFORMANCE_MODE_POWER_SAVING נעשה שימוש במאגרי נתונים פנימיים גדולים יותר ובנתיב נתונים שמחליף את זמן האחזור בצריכת אנרגיה נמוכה יותר.

אפשר לבחור את מצב הביצועים באמצעות קריאה ל-setPerformanceMode()‎, ולגלות את המצב הנוכחי באמצעות קריאה ל-getPerformanceMode()‎.

אם זמן אחזור קצר חשוב יותר לחיסכון באנרגיה באפליקציה, כדאי להשתמש ב-AAUDIO_PERFORMANCE_MODE_LOW_LATENCY. האפשרות הזו שימושית לאפליקציות אינטראקטיביות מאוד, כמו משחקים או מקלדות סינתיסייזר.

אם חיסכון באנרגיה חשוב יותר מזמן אחזור קצר באפליקציה, צריך להשתמש ב-AAUDIO_PERFORMANCE_MODE_POWER_SAVING. המצב הזה אופייני לאפליקציות שמשמיעות מוזיקה שנוצרה בעבר, כמו נגני אודיו בסטרימינג או נגני קובצי MIDI.

בגרסה הנוכחית של AAudio, כדי להשיג את זמן האחזור הנמוך ביותר האפשרי, צריך להשתמש במצב הביצועים AAUDIO_PERFORMANCE_MODE_LOW_LATENCY יחד עם קריאה חוזרת (callback) בעדיפות גבוהה. פועלים לפי הדוגמה הבאה:

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

בטיחות בשרשור

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

כדי להימנע מבעיות, אל תפעילו את AAudioStream_waitForStateChange() או תקראו או תכתבו לאותו מקור נתונים משני שרשורים שונים. באופן דומה, אין לסגור את הסטרימינג בשרשור אחד בזמן הקריאה או הכתיבה בו בשרשור אחר.

קריאות שמחזירות הגדרות של סטרימינג, כמו AAudioStream_getSampleRate() ו-AAudioStream_getChannelCount(), בטוחות לשימוש בכמה שרשורים בו-זמנית.

הקריאות האלה גם בטוחות לשימוש בכמה שרשורים (thread-safe):

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*() מלבד AAudioStream_getTimestamp()

בעיות מוכרות

  • זמן האחזור של האודיו גבוה במקרה של חסימה של write() כי במהדורה Android O DP2 לא נעשה שימוש בטראק FAST. שימוש בקריאה חוזרת (callback) כדי לקבל זמן אחזור קצר יותר.

מקורות מידע נוספים

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

מקורות ל-API

Codelabs

סרטונים