אודיו

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

AAudio API הוא מינימלי בעיצוב שלו, היא לא מבצעת את הפונקציות האלה:

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

תחילת העבודה

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

#include <aaudio/AAudio.h>

שידורי אודיו

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

סטרימינג מוגדר לפי:

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

התקן אודיו

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

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

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

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

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

מצב שיתוף

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

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

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

פורמט אודיו

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

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

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

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 לנתיב HDMI או S/PDIF

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

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

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

ספריית AAudio מתבצעת לפי דפוס עיצוב של builder ומספקת 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);
    

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

    אם לא מציינים את מזהה המכשיר, ברירת המחדל היא מכשיר הפלט הראשי. אם לא מציינים את כיוון השידור, ברירת המחדל היא זרם הפלט. לכל הפרמטרים האחרים אפשר להגדיר באופן מפורש ערך, או לאפשר למערכת להקצות את הערך האופטימלי באמצעות הימנעות מציון הפרמטר או הגדרת ערך אותו ל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_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

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

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

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

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

מחזור חיים של אודיו

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

קריאה וכתיבה בשידור אודיו

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

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

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

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);
}

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

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

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

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

AAudioStream_close(stream);

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

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

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

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

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

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

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

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

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.
}

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

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

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

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

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

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

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

      אגירת אודיו

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

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

דוגמה ללולאת אופטימיזציה של מאגר נתונים זמני:

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);
        }
    }
}

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

שימוש בקריאה חוזרת (callback) בעדיפות גבוהה

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

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

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

כדי לרשום את הקריאה החוזרת (callback), צריך להשתמש במבנה של השידור:

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

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

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

לדוגמה, אפשר להשתמש בקריאה חוזרת (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;
}

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

הקריאה החוזרת (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 יחד עם קריאה חוזרת בעדיפות גבוהה. לדוגמה:

// 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);

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

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

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

שיחות שמחזירות הגדרות שידור, כמו AAudioStream_getSampleRate() וגם AAudioStream_getChannelCount(), יישמרו בשרשורים.

גם השיחות האלה יישמרו בשרשורים:

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

בעיות מוכרות

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

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

למידע נוסף, אפשר להיעזר במקורות המידע הבאים:

מקורות ל-API

שיעורי Lab

סרטונים