ניפוי באגים באודיו

במאמר הזה מפורטים כמה טיפים וטריקים לניפוי באגים באודיו ב-Android.

בור ספיגה

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

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

ההוראות בקטע הזה מיועדות ל-Android מגרסה 7.x ואילך. ב-Android 5.x ו-6.x, מחליפים את /data/misc/audioserver ב-/data/misc/media. בנוסף, צריך להשתמש ב-userdebug או ב-eng build. אם אתם משתמשים ב-build של userdebug, משביתים את Verity באמצעות:

adb root && adb disable-verity && adb reboot

הגדרה בזמן הידור

  1. cd frameworks/av/services/audioflinger
  2. עורכים את Configuration.h.
  3. מסירים את ההערה #define TEE_SINK.
  4. יוצרים מחדש את libaudioflinger.so.
  5. adb root
  6. adb remount
  7. מעבירים או מסנכרנים את libaudioflinger.so החדש ל-/system/lib של המכשיר.

הגדרה בזמן ריצה

  1. adb shell getprop | grep ro.debuggable
    מוודאים שהפלט הוא: [ro.debuggable]: [1]
  2. adb shell
  3. ls -ld /data/misc/audioserver

    מוודאים שהפלט הוא:

    drwx------ media media ... media
    

    אם הספרייה לא קיימת, יוצרים אותה באופן הבא:

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    כאשר הערך של af.tee הוא מספר שמתואר בהמשך.
  5. chmod 644 /data/local.prop
  6. reboot

ערכים למאפיין af.tee

הערך של af.tee הוא מספר בין 0 ל-7, שמציג את הסכום של כמה ביטים, אחד לכל מאפיין. הסבר על כל ביט מופיע בקוד שב-AudioFlinger::AudioFlinger() ב-AudioFlinger.cpp, אבל באופן כללי:

  • 1 = קלט
  • 2 = פלט FastMixer
  • 4 = AudioRecord ו-AudioTrack לכל טראק

עדיין אין ביט למאגר עומק או למיקסר רגיל, אבל אפשר לקבל תוצאות דומות באמצעות '4'.

בדיקה ורכישה של נתונים

  1. מריצים את בדיקת האודיו.
  2. adb shell dumpsys media.audio_flinger
  3. מחפשים שורה בפלט של dumpsys כמו זו:
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    זהו קובץ PCM ‎ .wav.
  4. לאחר מכן adb pull כל קובץ /data/misc/audioserver/*.wav שרוצים. חשוב לזכור ששמות של קובצי dump ספציפיים לטראק לא מופיעים בפלט של dumpsys, אבל הם עדיין נשמרים ב-/data/misc/audioserver כשהטראק נסגר.
  5. לפני שמשתפים את קובצי הדמפ עם אחרים, חשוב לבדוק אם יש בהם בעיות שקשורות לפרטיות.

הצעות

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

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

שחזור

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

  1. משנים את השינויים בקוד המקור ל-Configuration.h.
  2. יוצרים מחדש את libaudioflinger.so.
  3. מעבירים או מסנכרנים את libaudioflinger.so ששוחזר אל /system/lib של המכשיר.
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

פקודות המאקרו ALOGx

ממשק ה-API הסטנדרטי של Java לתיעוד ביומן ב-Android SDK הוא android.util.Log.

ממשק ה-API התואם בשפת C ב-Android NDK __android_log_print מוצהר ב-<android/log.h>.

בחלק המקורי של מסגרת Android, אנחנו מעדיפים להשתמש במאקרוסים בשמות ALOGE,‏ ALOGW,‏ ALOGI,‏ ALOGV וכו'. הם מוצהרים ב-<utils/Log.h>, ולצורכי המאמר הזה נתייחס אליהם ביחד בתור ALOGx.

כל ממשקי ה-API האלה קלים לשימוש וברורים, ולכן הם נפוצים בפלטפורמת Android. באופן ספציפי, התהליך mediaserver, שכולל את שרת האודיו AudioFlinger, משתמש ב-ALOGx באופן נרחב.

עם זאת, יש כמה מגבלות על ALOGx ועל החברים:

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

NBLOG,‏ media.log ו-MediaLogService

ממשקי ה-API של NBLOG, התהליך media.log והשירות MediaLogService המשויכים אליהם יוצרים יחד מערכת חדשה יותר של רישום ביומן למדיה, והם תוכננו במיוחד כדי לטפל בבעיות שמפורטות למעלה. נשתמש במונח 'media.log' באופן רופף כדי להתייחס לכל השלושה, אבל באופן מדויק, NBLOG הוא ה-API לתיעוד ב-C++, ‏ media.log הוא שם תהליך ב-Linux ו-MediaLogService הוא שירות קישור של Android לבדיקה של היומנים.

'ציר זמן' של media.log הוא סדרה של רשומות ביומן שהסדר היחסי שלהן נשמר. לפי הסכמה, בכל שיחה צריך להשתמש בציר זמן משלו.

יתרונות

היתרונות של מערכת media.log הם:

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

ארכיטקטורה

בתרשים הבא מוצג הקשר בין התהליך mediaserver לבין התהליך init, לפני ההוספה של media.log:

הארכיטקטורה לפני media.log

איור 1. הארכיטקטורה לפני media.log

נקודות חשובות:

  • init forks ו-execs mediaserver.
  • init מזהה את מותו של mediaserver ומבצע יצירת ענף מחדש לפי הצורך.
  • הרישום ביומן של ALOGx לא מוצג.

בתרשים הבא מוצג הקשר החדש בין הרכיבים, אחרי ש-media.log נוסף לארכיטקטורה:

הארכיטקטורה אחרי media.log

איור 2. הארכיטקטורה אחרי media.log

שינויים חשובים:

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

איפה משתמשים

החל מ-Android 4.4, יש רק כמה נקודות ביומן ב-AudioFlinger שמשתמשות במערכת media.log. השימוש בממשקי ה-API החדשים לא קל כמו השימוש ב-ALOGx, אבל הוא גם לא קשה במיוחד. מומלץ ללמוד את מערכת הרישום ביומן החדשה למקרים שבהם היא חיונית. במיוחד מומלץ להשתמש בזה לשרשראות של AudioFlinger שצריכות לפעול בתדירות גבוהה, מדי פעם ובלי חסימה, כמו השרשורים FastMixer ו-FastCapture.

איך משתמשים

הוספת יומנים

קודם צריך להוסיף יומנים לקוד.

בשרשור FastMixer ובשרשור FastCapture, משתמשים בקוד כמו זה:

logWriter->log("string");
logWriter->logf("format", parameters);
logWriter->logTimestamp();

ציר הזמן NBLog משמש רק את השרשור FastMixer ואת השרשור FastCapture, ולכן אין צורך בהחרגה הדדית.

בשרשור אחר של AudioFlinger, משתמשים ב-mNBLogWriter:

mNBLogWriter->log("string");
mNBLogWriter->logf("format", parameters);
mNBLogWriter->logTimestamp();

בשרשור שאינו FastMixer או FastCapture, אפשר להשתמש בציר הזמן NBLog של השרשור גם על ידי השרשור עצמו וגם על ידי פעולות של ה-binder. NBLog::Writer לא מספק החרגה משותפת משתמשים משתמעת לכל ציר זמן, לכן חשוב לוודא שכל הרישומים ביומן מתרחשים בהקשר שבו מנעול ה-mutex mLock של השרשור נעול.

אחרי שמוסיפים את היומנים, צריך לבנות מחדש את AudioFlinger.

זהירות: כדי להבטיח את הבטיחות של השרשור, נדרש ציר זמן NBLog::Writer נפרד לכל שרשור, כי צירי זמן לא כוללים מנעולים מרובים לשימוש בו-זמנית (mutexes) מעצם הגדרתם. אם רוצים שיותאם יותר מסגרת זמן אחת ליותר משרשור אחד, אפשר להגן עליה באמצעות מנעול נעילה (mutex) קיים (כפי שמתואר למעלה לגבי mLock). לחלופין, אפשר להשתמש ב-wrapper של NBLog::LockedWriter במקום ב-NBLog::Writer. עם זאת, הפעולה הזו מבטלת את אחד מהיתרונות העיקריים של ה-API הזה: ההתנהגות הלא חוסמת שלו.

ממשק ה-API המלא של NBLog נמצא בכתובת frameworks/av/include/media/nbaio/NBLog.h.

הפעלת media.log

כברירת מחדל, האפשרות media.log מושבתת. הוא פעיל רק כשהנכס ro.test_harness הוא 1. כדי להפעיל אותה:

adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot

החיבור התנתק במהלך ההפעלה מחדש, לכן:

adb shell
הפקודה ps media תציג עכשיו שני תהליכים:
  • media.log
  • mediaserver

חשוב לשים לב למזהה התהליך של mediaserver, לשימוש מאוחר יותר.

הצגת צירי הזמן

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

dumpsys media.log

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

שחזור יומנים אחרי שהמכשיר mediaserver הפסיק לפעול

עכשיו ננסה להרוג את התהליך mediaserver: kill -9 #, כאשר # הוא מזהה התהליך שציינתם קודם. אמור להופיע דמפ מ-media.log ב-logcat הראשי, שבו מוצגים כל היומנים שהובילו לקריסה.

dumpsys media.log