การแก้ไขข้อบกพร่องของเสียง

บทความนี้อธิบายเคล็ดลับและคำแนะนำในการแก้ไขข้อบกพร่องเกี่ยวกับเสียงของ 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

การตั้งค่าเวลาคอมไพล์

  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 ซึ่งแสดงผลรวมของหลายบิต โดย 1 บิตต่อฟีเจอร์ ดูคำอธิบายของแต่ละบิตโดยสังเขปได้ที่โค้ด 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
    ไฟล์นี้เป็นไฟล์ .wav รูปแบบ PCM
  4. จากนั้นให้ระบุadb pullไฟล์ /data/misc/audioserver/*.wav ที่ต้องการ โปรดทราบว่าชื่อไฟล์ดัมพ์เฉพาะแทร็กจะไม่ปรากฏในdumpsysเอาต์พุต แต่ระบบจะยังคงบันทึกไฟล์เหล่านั้นไว้ใน /data/misc/audioserver เมื่อปิดแทร็ก
  5. ตรวจสอบไฟล์การถ่ายโอนข้อมูลเพื่อหาข้อกังวลด้านความเป็นส่วนตัวก่อนแชร์กับผู้อื่น

คำแนะนำ

ลองใช้แนวคิดต่อไปนี้เพื่อให้ได้ผลลัพธ์ที่เป็นประโยชน์มากขึ้น

  • ปิดเสียงการสัมผัสและการคลิกแป้นพิมพ์เพื่อลดการหยุดชะงักของเอาต์พุตการทดสอบ
  • เพิ่มระดับเสียงทั้งหมดสูงสุด
  • ปิดใช้แอปที่ส่งเสียงหรือบันทึกจากไมโครโฟนหากไม่เกี่ยวข้องกับการทดสอบ
  • ระบบจะบันทึกการถ่ายโอนข้อมูลเฉพาะแทร็กเมื่อปิดแทร็กเท่านั้น คุณอาจต้องบังคับปิดแอปเพื่อถ่ายโอนข้อมูลเฉพาะของแทร็ก
  • ดำเนินการdumpsysทันทีหลังจากทดสอบ เนื่องจากพื้นที่บันทึกมีจำกัด
  • อัปโหลดไฟล์ดัมพ์ไปยังโฮสต์เป็นระยะๆ เพื่อไม่ให้ไฟล์หายไป ระบบจะเก็บไฟล์การถ่ายโอนข้อมูลไว้เพียงจำนวนจำกัดเท่านั้น ระบบจะนำไฟล์การถ่ายโอนข้อมูลเก่าออกเมื่อถึงขีดจำกัดดังกล่าว

คืนค่า

ตามที่ระบุไว้ข้างต้น คุณไม่ควรเปิดใช้ฟีเจอร์นี้ไว้ กู้คืนบิลด์และอุปกรณ์โดยทำดังนี้

  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" อย่างหลวมๆ เพื่ออ้างอิงทั้ง 3 รายการ แต่ที่จริงแล้ว NBLOG คือ API การบันทึก C++, media.log คือชื่อกระบวนการ Linux และ MediaLogService คือบริการ Binder ของ 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();

เนื่องจากมีเพียงชุดข้อความ 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