במאמר הזה מפורטים כמה טיפים וטריקים לניפוי באגים באודיו ב-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 root
adb remount
- מעבירים או מסנכרנים את
libaudioflinger.so
החדש ל-/system/lib
של המכשיר.
הגדרה בזמן ריצה
adb shell getprop | grep ro.debuggable
מוודאים שהפלט הוא:[ro.debuggable]: [1]
adb shell
ls -ld /data/misc/audioserver
מוודאים שהפלט הוא:
drwx------ media media ... media
אם הספרייה לא קיימת, יוצרים אותה באופן הבא:
mkdir /data/misc/audioserver
chown media:media /data/misc/audioserver
echo af.tee=# > /data/local.prop
כאשר הערך שלaf.tee
הוא מספר שמתואר בהמשך.chmod 644 /data/local.prop
reboot
ערכים למאפיין 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 shell
rm /data/local.prop
rm /data/misc/audioserver/*.wav
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
:

איור 1. הארכיטקטורה לפני media.log
נקודות חשובות:
init
forks ו-execsmediaserver
.init
מזהה את מותו שלmediaserver
ומבצע יצירת ענף מחדש לפי הצורך.- הרישום ביומן של
ALOGx
לא מוצג.
בתרשים הבא מוצג הקשר החדש בין הרכיבים, אחרי ש-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