บทความนี้อธิบายเคล็ดลับและคำแนะนำในการแก้ไขข้อบกพร่องเกี่ยวกับเสียงของ Android
อ่างล้างจาน
"tee sink" คือฟีเจอร์การแก้ไขข้อบกพร่องของ AudioFlinger ซึ่งใช้ได้ในบิลด์ที่กำหนดเองเท่านั้น เพื่อเก็บรักษาเสียงสั้นๆ ล่าสุดไว้สำหรับการวิเคราะห์ในภายหลัง ซึ่งช่วยให้เปรียบเทียบระหว่างสิ่งที่เล่นหรือบันทึกจริงกับสิ่งที่คาดไว้ได้
เพื่อการคุ้มครองความเป็นส่วนตัว Sink แบบ Tee จะปิดอยู่โดยค่าเริ่มต้น ทั้งในช่วงคอมไพล์และรันไทม์ หากต้องการใช้อ่างอาบน้ำแบบเท คุณต้องเปิดใช้โดยคอมไพล์อีกครั้ง รวมถึงตั้งค่าพร็อพเพอร์ตี้ อย่าลืมปิดใช้ฟีเจอร์นี้หลังจากแก้ไขข้อบกพร่องเสร็จแล้ว ไม่ควรเปิดใช้ Tee Sink ในบิลด์เวอร์ชันที่ติดตั้งใช้งานจริง
วิธีการในส่วนนี้มีไว้สำหรับ Android 7.x ขึ้นไป สำหรับ Android
5.x และ 6.x ให้แทนที่ /data/misc/audioserver
ด้วย
/data/misc/media
นอกจากนี้ คุณต้องใช้รุ่น userdebug หรือ eng หากคุณใช้บิลด์ 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 ซึ่งแสดงผลรวมของหลายบิต โดย 1 บิตต่อฟีเจอร์
ดูคำอธิบายของแต่ละบิตโดยสังเขปได้ที่โค้ด 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
ไฟล์นี้เป็นไฟล์ .wav รูปแบบ PCM - จากนั้นให้ระบุ
adb pull
ไฟล์/data/misc/audioserver/*.wav
ที่ต้องการ โปรดทราบว่าชื่อไฟล์ดัมพ์เฉพาะแทร็กจะไม่ปรากฏในdumpsys
เอาต์พุต แต่ระบบจะยังคงบันทึกไฟล์เหล่านั้นไว้ใน/data/misc/audioserver
เมื่อปิดแทร็ก - ตรวจสอบไฟล์การถ่ายโอนข้อมูลเพื่อหาข้อกังวลด้านความเป็นส่วนตัวก่อนแชร์กับผู้อื่น
คำแนะนำ
ลองใช้แนวคิดต่อไปนี้เพื่อให้ได้ผลลัพธ์ที่เป็นประโยชน์มากขึ้น
- ปิดเสียงการสัมผัสและการคลิกแป้นพิมพ์เพื่อลดการหยุดชะงักของเอาต์พุตการทดสอบ
- เพิ่มระดับเสียงทั้งหมดสูงสุด
- ปิดใช้แอปที่ส่งเสียงหรือบันทึกจากไมโครโฟนหากไม่เกี่ยวข้องกับการทดสอบ
- ระบบจะบันทึกการถ่ายโอนข้อมูลเฉพาะแทร็กเมื่อปิดแทร็กเท่านั้น คุณอาจต้องบังคับปิดแอปเพื่อถ่ายโอนข้อมูลเฉพาะของแทร็ก
- ดำเนินการ
dumpsys
ทันทีหลังจากทดสอบ เนื่องจากพื้นที่บันทึกมีจำกัด - อัปโหลดไฟล์ดัมพ์ไปยังโฮสต์เป็นระยะๆ เพื่อไม่ให้ไฟล์หายไป ระบบจะเก็บไฟล์การถ่ายโอนข้อมูลไว้เพียงจำนวนจำกัดเท่านั้น ระบบจะนำไฟล์การถ่ายโอนข้อมูลเก่าออกเมื่อถึงขีดจำกัดดังกล่าว
คืนค่า
ตามที่ระบุไว้ข้างต้น คุณไม่ควรเปิดใช้ฟีเจอร์นี้ไว้ กู้คืนบิลด์และอุปกรณ์โดยทำดังนี้
- เปลี่ยนซอร์สโค้ดกลับเป็น
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" อย่างหลวมๆ เพื่ออ้างอิงทั้ง 3 รายการ แต่ที่จริงแล้ว NBLOG
คือ API การบันทึก C++, media.log
คือชื่อกระบวนการ Linux และ MediaLogService
คือบริการ Binder ของ 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();
เนื่องจากมีเพียงชุดข้อความ FastMixer
และ FastCapture
เท่านั้นที่ใช้ไทม์ไลน์ NBLog
นี้ จึงไม่จำเป็นต้องใช้การยกเว้นซึ่งกันและกัน
ในชุดข้อความอื่นๆ ของ AudioFlinger ให้ใช้ mNBLogWriter
ดังนี้
mNBLogWriter->log("string"); mNBLogWriter->logf("format", parameters); mNBLogWriter->logTimestamp();
สำหรับเธรดอื่นๆ นอกเหนือจาก FastMixer
และ FastCapture
ทั้งเธรดเองและการดำเนินการของ Binder จะใช้ไทม์ไลน์ NBLog
ของเธรดได้ NBLog::Writer
ไม่ได้ระบุการยกเว้นซึ่งกันและกันอย่างเป็นนัยตามไทม์ไลน์ ดังนั้นโปรดตรวจสอบว่าบันทึกทั้งหมดเกิดขึ้นภายในบริบทที่มีการถือครอง Mutex mLock
ของเธรด
หลังจากเพิ่มบันทึกแล้ว ให้สร้าง AudioFlinger อีกครั้ง
ข้อควรระวัง: ต้องสร้างไทม์ไลน์ NBLog::Writer
แยกต่างหากสำหรับแต่ละเธรดเพื่อให้เธรดปลอดภัย เนื่องจากไทม์ไลน์จะละเว้นมิวเทคส์โดยการออกแบบ หากต้องการให้หลายเธรดใช้ไทม์ไลน์เดียวกัน คุณสามารถปกป้องด้วย Mutex ที่มีอยู่ (ตามที่อธิบายไว้ข้างต้นสำหรับ mLock
) หรือจะใช้ Wrapper NBLog::LockedWriter
แทน NBLog::Writer
ก็ได้
อย่างไรก็ตาม การดำเนินการนี้จะทำให้เสียประโยชน์หลักของ API นี้ ซึ่งเป็นลักษณะการทำงานแบบไม่บล็อก
ดู NBLog
API ฉบับเต็มได้ที่ 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
จะแสดง 2 กระบวนการ ดังนี้
- media.log
- mediaserver
จดรหัสกระบวนการ mediaserver
ไว้ใช้ในภายหลัง
แสดงไทม์ไลน์
คุณขอการถ่ายโอนข้อมูลบันทึกด้วยตนเองได้ทุกเมื่อ คำสั่งนี้จะแสดงบันทึกจากไทม์ไลน์ล่าสุดและไทม์ไลน์ที่ใช้งานอยู่ทั้งหมด จากนั้นล้างบันทึก
dumpsys media.log
โปรดทราบว่าไทม์ไลน์แต่ละรายการจะแยกกันโดยการออกแบบ และไม่มีเครื่องมือสำหรับการผสานไทม์ไลน์
กู้คืนบันทึกหลังจาก Media Server หยุดทำงาน
ตอนนี้ลองหยุดกระบวนการ mediaserver
: kill -9 #
โดยที่ # คือรหัสกระบวนการที่คุณจดไว้ก่อนหน้านี้ คุณควรเห็นการดัมพ์จาก media.log
ใน logcat
หลัก ซึ่งแสดงบันทึกทั้งหมดที่นำไปสู่การขัดข้อง
dumpsys media.log