במאמר הזה מפורטים כמה טיפים וטריקים לניפוי באגים באודיו ב-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
הגדרה בזמן הידור
cd frameworks/av/services/audioflinger- עורכים את
Configuration.h. - מסירים את ההערה
#define TEE_SINK. - יוצרים מחדש את
libaudioflinger.so. adb rootadb remount- מעבירים או מסנכרנים את
libaudioflinger.soהחדש ל-/system/libשל המכשיר.
הגדרה בזמן ריצה
adb shell getprop | grep ro.debuggable
מוודאים שהפלט הוא:[ro.debuggable]: [1]adb shellls -ld /data/misc/audioserver
מוודאים שהפלט הוא:
drwx------ media media ... media
אם הספרייה לא קיימת, יוצרים אותה באופן הבא:
mkdir /data/misc/audioserverchown media:media /data/misc/audioserverecho af.tee=# > /data/local.prop
כאשר הערך שלaf.teeהוא מספר שמתואר בהמשך.chmod 644 /data/local.propreboot
ערכים למאפיין af.tee
הערך של af.tee הוא מספר בין 0 ל-7, שמציג את הסכום של כמה ביטים, אחד לכל מאפיין.
הסבר על כל ביט מופיע בקוד שב-AudioFlinger::AudioFlinger() ב-AudioFlinger.cpp, אבל באופן כללי:
- 1 = קלט
- 2 = פלט FastMixer
- 4 = AudioRecord ו-AudioTrack לכל טראק
עדיין אין ביט למאגר עומק או למיקסר רגיל, אבל אפשר לקבל תוצאות דומות באמצעות '4'.
בדיקה ורכישה של נתונים
- מריצים את בדיקת האודיו.
adb shell dumpsys media.audio_flinger- מחפשים שורה בפלט של
dumpsysכמו זו:
tee copied to /data/misc/audioserver/20131010101147_2.wav
זהו קובץ PCM .wav. - לאחר מכן
adb pullכל קובץ/data/misc/audioserver/*.wavשרוצים. חשוב לזכור ששמות של קובצי dump ספציפיים לטראק לא מופיעים בפלט שלdumpsys, אבל הם עדיין נשמרים ב-/data/misc/audioserverכשהטראק נסגר. - לפני שמשתפים את קובצי הדמפ עם אחרים, חשוב לבדוק אם יש בהם בעיות שקשורות לפרטיות.
הצעות
כדי לקבל תוצאות מועילות יותר, אפשר לנסות את הרעיונות הבאים:
- משביתים את צלילי המגע ואת הקליקים על המקש כדי לצמצם את ההפרעות בפלט של הבדיקה.
- מגדילים את כל הנפחים.
- משביתים אפליקציות שמפיקות צלילים או מקליטות מהמיקרופון, אם הן לא רלוונטיות לבדיקה.
- נתונים ספציפיים לטראק נשמרים רק כשהטראק נסגר. יכול להיות שתצטרכו לסגור אפליקציה בכוח כדי לדגום את הנתונים הספציפיים לטראק שלה.
- צריך לבצע את
dumpsysמיד אחרי הבדיקה, כי נפח ההקלטה מוגבל. - כדי לוודא שלא תאבדו את קובצי ה-dump, כדאי להעלות אותם לארח מדי פעם. נשמר רק מספר מוגבל של קובצי dump. קובצי dump ישנים יותר יוסרו אחרי שמגיעים למגבלה הזו.
שחזור
כפי שצוין למעלה, לא מומלץ להשאיר את התכונה tee sink מופעלת. משחזרים את הגרסה המאוחדת ואת המכשיר באופן הבא:
- משנים את השינויים בקוד המקור ל-
Configuration.h. - יוצרים מחדש את
libaudioflinger.so. - מעבירים או מסנכרנים את
libaudioflinger.soששוחזר אל/system/libשל המכשיר. adb shellrm /data/local.proprm /data/misc/audioserver/*.wavreboot
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:
איור 1. הארכיטקטורה לפני media.log
נקודות חשובות:
initforks ו-execsmediaserver.initמזהה את מותו שלmediaserverומבצע יצירת ענף מחדש לפי הצורך.- הרישום ביומן של
ALOGxלא מוצג.
בתרשים הבא מוצג הקשר החדש בין הרכיבים, אחרי ש-media.log נוסף לארכיטקטורה:
איור 2. הארכיטקטורה אחרי media.log
שינויים חשובים:
-
לקוחות משתמשים ב-
NBLOGAPI כדי ליצור רשומות ביומן ולהוסיף אותן למאגר עגול בזיכרון משותף. -
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 rootadb shellecho ro.test_harness=1 > /data/local.propchmod 644 /data/local.propreboot
החיבור התנתק במהלך ההפעלה מחדש, לכן:
adb shell
ps media תציג עכשיו שני תהליכים:
- media.log
- mediaserver
חשוב לשים לב למזהה התהליך של mediaserver, לשימוש מאוחר יותר.
הצגת צירי הזמן
אפשר לבקש באופן ידני גרסת dump של יומן בכל שלב. הפקודה הזו מציגה יומנים מכל צירי הזמן הפעילים והעדכניים, ולאחר מכן מנקה אותם:
dumpsys media.log
חשוב לזכור שלוחות הזמנים הם עצמאיים מטבעם, ואין אפשרות למזג לוחות זמנים.
שחזור יומנים אחרי שהמכשיר mediaserver הפסיק לפעול
עכשיו ננסה להרוג את התהליך mediaserver: kill -9 #, כאשר # הוא מזהה התהליך שציינתם קודם. אמור להופיע דמפ מ-media.log ב-logcat הראשי, שבו מוצגים כל היומנים שהובילו לקריסה.
dumpsys media.log