กำลังแชร์อินพุตเสียง

อินพุตเสียงมักจะมาจากไมโครโฟนในตัว ไมค์ภายนอก หรือ อินเทอร์เฟซเสียงที่เชื่อมต่อกับอุปกรณ์ อินพุตเสียงยังอาจมาจาก การสนทนาทางโทรศัพท์

บางครั้ง แอปตั้งแต่ 2 แอปขึ้นไปอาจต้องการ "จับภาพ" อินพุตเสียงเดียวกัน โดยพวกเขาอาจทำงานที่ต่างกัน เช่น แอปบางแอปที่รับเสียงอาจ "กำลังบันทึกเสียง" เหมือนโปรแกรมอัดเสียงธรรมดา ในขณะที่แอปอื่นๆ อาจ "กำลังฟัง" เช่น Google Assistant หรือบริการการช่วยเหลือพิเศษที่ ตอบสนองต่อคำสั่งเสียง

แต่ไม่ว่าในกรณีใด แอปเหล่านี้จะต้องการรับอินพุตเสียง ตลอดทั้งหน้านี้ เราจะใช้คำว่า "capture" โดยไม่คำนึงว่า แอปกำลังบันทึกเสียงหรือเพียงแค่ฟัง

อาจเกิดปัญหาหากมีแอปตั้งแต่ 2 แอปขึ้นไปเพื่อบันทึกเสียงพร้อมกัน ส่งสัญญาณเสียงจาก แหล่งเดียวกัน ให้กับทุกอุปกรณ์ หน้านี้อธิบายถึง วิธีที่ระบบ Android แชร์อินพุตเสียงระหว่างแอปต่างๆ ที่บันทึกเสียง

ลักษณะการทำงานก่อน Android 10

ก่อน Android 10 สตรีมเสียงอินพุตสามารถบันทึกได้โดยแอปเพียง 1 แอปที่ หากมีบางแอปบันทึกหรือฟังเสียงอยู่แล้ว แอปอาจ สร้างออบเจ็กต์ AudioRecord แต่ระบบจะแสดงข้อผิดพลาดเมื่อคุณเรียกใช้ AudioRecord.startRecording() และการบันทึกจะไม่เริ่มขึ้น

กฎนี้มีข้อยกเว้นอย่างหนึ่งคือ เมื่อแอปที่ได้รับสิทธิ์ (เช่น Google Assistant หรือ บริการการเข้าถึง) ได้รับสิทธิ์ android.permission.CAPTURE_AUDIO_HOTWORD และใช้แหล่งที่มาของเสียงในประเภท HOTWORD ในกรณีนี้ แอปอื่นอาจเริ่มบันทึก เมื่อเกิดเหตุการณ์ดังกล่าวขึ้น แอปที่เป็นสิทธิ์เฉพาะบุคคลสิ้นสุดลง และแอปใหม่ได้เก็บบันทึกอินพุตแล้ว

มีการเพิ่มการเปลี่ยนแปลงอีก 1 รายการใน Android 9: เฉพาะแอปที่ทํางานในเบื้องหน้า (หรือ บริการที่ทำงานอยู่เบื้องหน้า) สามารถจับอินพุตเสียงได้ เมื่อแอปที่ไม่มี บริการที่ทำงานอยู่เบื้องหน้าหรือคอมโพเนนต์ UI ที่ทำงานอยู่เบื้องหน้าเริ่มจับภาพแล้ว, แอป ยังคงทำงานต่อไปแต่ยังคงไม่มีเสียงใดๆ แม้ว่าจะเป็นเพียงแอปเดียวที่จับภาพ เสียงในช่วงเวลานั้น

ลักษณะการทำงานของ Android 10

ลักษณะการทำงานที่เกิดขึ้นก่อน Android 10 คือ "มาก่อนได้ก่อน" เมื่อแอปเริ่มบันทึกเสียง แอปอื่นๆ จะไม่สามารถเข้าถึงแอป อินพุตเสียงจนกว่าแอปที่กำลังบันทึกเสียงอยู่จะหยุดทำงาน

Android 10 กำหนดรูปแบบลำดับความสำคัญที่เปลี่ยนสตรีมเสียงอินพุตได้ ระหว่างแอปขณะทำงานอยู่ได้ ในกรณีส่วนใหญ่ หากแอปใหม่ได้อินพุตเสียงมา แอปการจับภาพก่อนหน้านี้จะยังคงทำงานต่อไป แต่จะได้รับการปิดเสียง ในบางส่วน เพื่อให้ระบบสามารถนำส่งเสียงไปยังทั้ง 2 แอปได้ต่อไป ฟีเจอร์ต่างๆ สถานการณ์ในการแชร์ได้อธิบายไว้ด้านล่างนี้

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

สำหรับวัตถุประสงค์ในการจับเสียง Android จะจำแนกแอป 2 ประเภทต่อไปนี้

  • "ปกติ" ที่ผู้ใช้ติดตั้ง
  • "เป็นสิทธิ์เฉพาะบุคคล" มีการติดตั้งแอปไว้ล่วงหน้าแล้วในอุปกรณ์ ซึ่งรวมถึง Google Assistant และบริการการช่วยเหลือพิเศษทั้งหมด

นอกจากนี้ ระบบยังดำเนินการกับแอปแตกต่างออกไป หากวิดีโอนั้นใช้เนื้อหาที่ "คำนึงถึงความเป็นส่วนตัว" แหล่งที่มาของเสียง: CAMCORDER หรือ VOICE_COMMUNICATION

กฎการจัดลำดับความสำคัญในการใช้และแชร์อินพุตเสียงมีดังนี้

  • แอปที่ได้รับสิทธิ์มีลำดับความสำคัญสูงกว่าแอปทั่วไป
  • แอปที่มี UI ในเบื้องหน้าที่มองเห็นได้จะมีความสำคัญสูงกว่าแอปเบื้องหลัง
  • แอปที่บันทึกเสียงจากแหล่งที่มาที่มีความละเอียดอ่อนด้านความเป็นส่วนตัวจะมีลำดับความสำคัญสูงกว่าแอปที่ไม่มี
  • แอปทั่วไป 2 แอปบันทึกเสียงพร้อมกันไม่ได้
  • ในบางสถานการณ์ แอปที่ได้รับสิทธิ์สามารถแชร์อินพุตเสียงกับแอปอื่นได้
  • หากแอปพื้นหลัง 2 แอปที่มีลำดับความสำคัญเท่ากันกำลังบันทึกเสียง แอปสุดท้ายที่เริ่มใช้งานจะมีลำดับความสำคัญสูงกว่า

สถานการณ์การแชร์

เมื่อแอป 2 แอปพยายามบันทึกเสียง ทั้งคู่สามารถรับสัญญาณอินพุตได้ หรือหนึ่งในนั้นอาจได้รับสัญญาณอินพุต เงียบๆ

สถานการณ์หลักๆ มี 4 กรณีดังนี้

  • Assistant + แอปทั่วไป
  • บริการการช่วยเหลือพิเศษ + แอปทั่วไป
  • แอปทั่วไป 2 แอป
  • การโทรด้วยเสียง + แอปทั่วไป

Assistant + แอปทั่วไป

Assistant เป็นแอปที่ได้รับสิทธิ์เพราะมีการติดตั้งไว้ล่วงหน้าและเก็บเอาไว้ RoleManager.ROLE_ASSISTANT แอปอื่นๆ ที่ติดตั้งไว้ล่วงหน้าที่มีบทบาทนี้จะได้รับการดำเนินการในลักษณะเดียวกัน

Android จะใช้อินพุตเสียงตามกฎต่อไปนี้

  • Assistant รับเสียงได้ (ไม่ว่าจะอยู่เบื้องหน้าหรือเบื้องหลัง) เว้นแต่ว่าแอปอื่นที่ใช้แหล่งที่มาของเสียงที่ละเอียดอ่อนด้านความเป็นส่วนตัวกำลังบันทึกอยู่

  • แอปจะได้รับเสียง เว้นแต่ Assistant จะมี UI ที่มองเห็นได้ ที่ด้านบนของหน้าจอ

โปรดทราบว่าทั้ง 2 แอปจะได้รับเสียงเฉพาะเมื่อ Assistant อยู่ในเบื้องหลัง และอีกแอปไม่ได้บันทึกจากแหล่งเสียงที่มีความละเอียดอ่อนต่อความเป็นส่วนตัว

บริการการช่วยเหลือพิเศษ + แอปทั่วไป

AccessibilityService ต้องมีการประกาศที่เข้มงวด

Android จะแชร์เสียงอินพุตตามกฎต่อไปนี้

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

  • หากบริการไม่ได้อยู่ด้านบนสุด ระบบจะถือว่ากรณีนี้เป็นกรณีปกติของ 2 แอปด้านล่าง

แอปทั่วไป 2 แอป

เมื่อแอป 2 แอปกำลังจับภาพพร้อมกัน จะมีเพียงแอปเดียวที่ได้รับเสียงและอีกแอปหนึ่งปิดเสียง

Android จะแชร์เสียงอินพุตตามกฎต่อไปนี้

  • หากไม่มีแอปใดที่คำนึงถึงความเป็นส่วนตัว แอปที่มี UI ที่ด้านบนจะรับเสียงได้ หากแอปใดไม่มี UI แอปที่เริ่มจับแอปล่าสุดจะได้รับเสียง
  • หากแอปใดแอปหนึ่งมีความละเอียดอ่อนด้านความเป็นส่วนตัว แอปจะรับเสียงและ แอปอื่นๆ จะปิดเสียงแม้ว่าจะมี UI อยู่ด้านบนหรือเริ่มจับภาพแล้วก็ตาม ล่าสุด
  • หากทั้ง 2 แอปคำนึงถึงความเป็นส่วนตัว แอปที่ทำให้เกิด Conversion มากที่สุด ได้รับเสียงเมื่อเร็วๆ นี้และอีกรายการหนึ่งไม่มีเสียง

การโทรด้วยเสียง + แอปทั่วไป

การโทรด้วยเสียงจะทำงานเมื่อโหมดเสียงแสดงผลโดย AudioManager.getMode() คือ MODE_IN_CALL หรือ MODE_IN_COMMUNICATION

Android จะแชร์เสียงอินพุตตามกฎต่อไปนี้

ลักษณะการทำงานของ Android 11

Android 11 (API ระดับ 30) ดำเนินการตามรูปแบบลำดับความสำคัญของ Android 10 ที่อธิบายไว้ข้างต้น นอกจากนี้ยังมีเมธอดใหม่ๆ ใน AudioRecord, MediaRecorder และ AAudioStream ที่เปิดและปิดความสามารถในการบันทึกเสียงพร้อมกัน โดยไม่คำนึงถึง Use Case ที่เลือก

วิธีการใหม่มีดังนี้

เมื่อ setPrivacySensitive() คือ true กรณีการใช้งานการจับภาพจะเป็นแบบส่วนตัวและ Assistant ที่ได้รับสิทธิ์จะไม่สามารถจับภาพพร้อมกัน การตั้งค่านี้จะลบล้าง การทำงานเริ่มต้นโดยขึ้นอยู่กับแหล่งที่มาของเสียง ตัวอย่างเช่น VOICE_COMMUNICATION เป็นแบบส่วนตัวโดยค่าเริ่มต้น แต่ UNPROCESSED ไม่ได้เป็นแบบส่วนตัว

การเปลี่ยนแปลงการกำหนดค่า

เมื่อหลายแอปบันทึกเสียงพร้อมกัน จะมีเพียง 1 หรือ 2 แอปเท่านั้น "ใช้งานอยู่" (การรับเสียง) ส่วนคนอื่นๆ จะถูกปิดเสียง (เปิดเสียง) เมื่อ แอปที่ใช้งานอยู่เปลี่ยนแปลง เฟรมเวิร์กเสียงอาจกำหนดค่าเส้นทางเสียงใหม่ ตามกฎเหล่านี้

  • อุปกรณ์อินพุตเสียงสำหรับแต่ละแอปที่ใช้งานอยู่อาจเปลี่ยนไป (เช่น จาก ไมโครโฟนในตัวกับชุดหูฟังบลูทูธที่เชื่อมต่ออยู่)
  • มีการเปิดใช้การประมวลผลล่วงหน้าที่เชื่อมโยงกับแอปที่มีลำดับความสำคัญสูงสุดที่ใช้งานอยู่ ทั้งหมด การประมวลผลล่วงหน้าอื่นๆ จะไม่มีผล

เนื่องจากแอปที่ใช้งานอยู่อาจถูกปิดเสียงเมื่อแอปที่มีลำดับความสำคัญสูงกว่าเปิดใช้งาน คุณสามารถลงทะเบียน AudioManager.AudioRecordingCallback ในAudioRecord หรือ MediaRecorder ที่จะมีการแจ้งเตือนเมื่อการกำหนดค่ามีการเปลี่ยนแปลง การเปลี่ยนแปลงที่เป็นไปได้มีดังนี้

  • จับภาพเสียงที่ไม่ปิดเสียงหรือไม่ปิดเสียง
  • เปลี่ยนอุปกรณ์แล้ว
  • เปลี่ยนการประมวลผลล่วงหน้าแล้ว
  • เปลี่ยนแปลงพร็อพเพอร์ตี้ของสตรีม (อัตราการสุ่มตัวอย่าง มาสก์ช่อง รูปแบบตัวอย่าง)

คุณต้องโทร AudioRecord.registerAudioRecordingCallback() ก่อนจะเริ่มจับภาพ การเรียกกลับจะดำเนินการเมื่อแอปได้รับเสียงเท่านั้นและมีการเปลี่ยนแปลงเกิดขึ้น

เมธอด onRecordingConfigChanged() จะแสดงผล AudioRecordingConfiguration ที่มีสถานะการบันทึกเสียงปัจจุบัน ใช้รายการต่อไปนี้ เพื่อเรียนรู้เกี่ยวกับการเปลี่ยนแปลง

isClientSilenced()
แสดงผลเป็น "จริง" หากเสียงที่ส่งกลับไปยังไคลเอ็นต์กำลังปิดเสียงอยู่เนื่องจากนโยบายการบันทึก
getAudioDevice()
ส่งคืนอุปกรณ์เสียงที่ใช้งานอยู่
getEffects()
แสดงผลเอฟเฟกต์การประมวลผลล่วงหน้าที่ใช้งานอยู่ โปรดทราบว่าผลกระทบที่ใช้งานอยู่อาจไม่เหมือนกับผลลัพธ์ที่ getClientEffects() แสดงผล หากไคลเอ็นต์ไม่ใช่แอปที่มีลำดับความสำคัญสูงสุด
getFormat()
แสดงผลพร็อพเพอร์ตี้ของสตรีม โปรดทราบว่าข้อมูลเสียงจริงที่ลูกค้าได้รับจะยึดตามรูปแบบที่กำหนดซึ่งแสดงผลโดย getClientFormat() เสมอ เฟรมเวิร์กจะทำการแปลงตัวอย่าง ช่องทาง และการเปลี่ยนรูปแบบที่จำเป็นโดยอัตโนมัติจากรูปแบบที่ใช้ในอินเทอร์เฟซของฮาร์ดแวร์ให้อยู่ในรูปแบบที่ลูกค้าระบุ
AudioRecord.getActiveRecordingConfiguration()
แสดงผลการกำหนดค่าการบันทึกที่ใช้งานอยู่

คุณสามารถดูมุมมองทั่วไปของไฟล์บันทึกเสียงที่ใช้งานอยู่ทั้งหมดในอุปกรณ์โดยการโทร AudioManager.getActiveRecordingConfigurations()