
บั๊กหูฟังอินเอียร์: เมื่ออีกฝ่ายหายไปจากบันทึกถอดเสียงของเรา
การถอดเสียงแบบสองช่องทำงานได้อย่างสมบูรณ์แบบบนลำโพงแล็ปท็อปและหูฟังครอบหู แล้ววันหนึ่งมีคนเสียบหูฟังอินเอียร์ และคนที่พวกเขากำลังคุยด้วยก็หายไปจากบันทึกถอดเสียงเฉย ๆ — เพราะ Chromium ปิดเสียงของเราอย่างเงียบ ๆ ในเสี้ยววินาทีที่พวกเขาพูด การแก้ไขสิบเอ็ดครั้งล้มเหลว ครั้งที่สิบสองเปลี่ยนสถาปัตยกรรมทั้งหมด
GeekBye ถอดเสียงบทสนทนาสองฝั่ง: ไมโครโฟนของคุณอยู่ช่องหนึ่ง เสียงของอีกฝ่าย (สิ่งที่คอมพิวเตอร์ของคุณกำลังเล่นอยู่) อยู่อีกช่องหนึ่ง มันทำงานได้อย่างไร้ที่ติบนลำโพงแล็ปท็อป มันทำงานได้บนหูฟังครอบหู แล้วผู้ใช้คนหนึ่งเสียบหูฟังอินเอียร์ และคนที่พวกเขากำลังคุยด้วยก็หายไปจากบันทึกถอดเสียง ไม่ใช่เพี้ยน ไม่ใช่ระบุที่มาผิด แต่หายไปเลย
นี่คือเรื่องราวของ GeekBye v1.6.12 บั๊กที่กินการแก้ไขที่ล้มเหลวไปสิบเอ็ดครั้ง และช่วงเวลาที่เราตระหนักว่าเรากำลังสู้กับเบราว์เซอร์แทนที่จะทำงานร่วมกับมัน
track ที่ยังมีชีวิตแต่ว่างเปล่า
อาการนั้นน่าขนลุก คุณเริ่มการประชุม ทุกอย่างดูปกติดี คุณพูดคำเดียว — และจากเสี้ยววินาทีนั้น ช่องเสียงของอีกฝ่ายกลายเป็นความเงียบดิจิทัลล้วน ๆ เราวัดมันได้: สัญญาณเสียงระบบอยู่ที่ระดับพลังงานปกติ (RMS ราว 0.03–0.06) และในเสี้ยววินาทีที่ไมค์รับเสียงของคุณ มันก็ตกลงไปที่ 0.0000 และค้างอยู่แบบนั้นตลอดที่เหลือของเซสชัน
นี่คือส่วนที่กินเวลาเราหลายวัน ทุก API ของเบราว์เซอร์ยืนยันว่า track เสียงนั้นสมบูรณ์ดีทุกประการ readyState: live enabled: true muted: false ตามทุกแฟล็กสถานะที่แพลตฟอร์มเปิดเผย สตรีมนั้นไม่มีปัญหา สิ่งที่ตายไปคือตัวอย่างเสียง — ท่อที่มีชีวิตซึ่งลำเลียงแต่ศูนย์ล้วน ๆ เราใช้เวลานานมากในการไม่เชื่อมิเตอร์พลังงานของเราเอง เพราะเรื่องเล่าอย่างเป็นทางการบอกว่า track นั้นทำงานได้
ตัวกระตุ้นเจาะจงอยู่ที่หูฟังอินเอียร์ และเหตุผลนั้นเป็นเรื่องกายภาพ: หูฟังอินเอียร์คืออุปกรณ์ตัวเดียวที่เป็นทั้งอินพุตไมโครโฟนและเอาต์พุตเสียงของคุณพร้อมกัน ลำโพงแล็ปท็อปและหูฟังครอบหูแยกสองสิ่งนี้ออกจากกัน — เอาต์พุตไปที่หนึ่ง ไมค์อยู่อีกที่หนึ่ง แต่เมื่ออินพุตและเอาต์พุตเป็นอุปกรณ์ตัวเดียวกัน คุณก็มีเส้นทางป้อนกลับตามนิยาม การอนุมานที่หนักแน่นของเรา (เรื่องนี้อยู่ในไส้ในของเบราว์เซอร์ เราจึงอ้างบทอ้างวรรคไม่ได้) คือ Chromium ตรวจพบรูปแบบไมค์บวกลำโพงบนอุปกรณ์ตัวเดียวนั้น ตัดสินว่ามันเป็นลูปป้อนกลับที่กำลังจะหอน แล้วปิดเสียงการจับเพื่อป้องกัน — อย่างเงียบ ๆ โดยไม่บอกแอปผ่านแฟล็กใด ๆ ที่แอปอ่านได้
สิบเอ็ดการแก้ไข หลายครั้งแย่ลงกว่าเดิม
เราจู่โจมมันด้วยวิธีที่ชัดเจนก่อน และจดทุกความพยายามไว้ ปิด echo cancellation, noise suppression และ auto gain บนทั้งสองสตรีม เริ่มสองสตรีมตามลำดับพร้อมหน่วงเวลาแทนที่จะเริ่มพร้อมกัน ให้แต่ละสตรีมมี sample rate ของตัวเอง เพิ่ม gain node ที่เงียบ ส่งทุกอย่างผ่าน audio context ที่แชร์กันตัวเดียว ย้ายไปใช้ AudioWorklet ลงลึกไปจนถึง track processor ดิบของ WebCodecs
สิบเอ็ดแนวทาง ไม่เพียงล้มเหลว — หลายครั้งทำให้แย่ลง ครั้งหนึ่งทำให้เกิดความหน่วง 30 วินาที อีกครั้งทำให้บันทึกถอดเสียงจากไมโครโฟนของคุณกลับไปติดป้ายว่าเป็นอีกคนหนึ่ง เราจมอยู่ในพงหญ้ารก รักษาไปทีละอาการ และบั๊กก็เคลื่อนที่ไปเรื่อย ๆ
จุดพลิกคือการมองใหม่: ทุกการแก้ไขที่ผ่านมาพยายามรักษาสองสตรีมที่แยกจากกันให้สมบูรณ์ แต่สองสตรีมที่แยกจากกันคือสิ่งที่ฮิวริสติกป้องกันการป้อนกลับของ Chromium มองเห็นและปิดเสียงได้พอดี ถ้าไม่มีอะไรให้เปรียบเทียบล่ะ?
การแก้ไข: ให้เบราว์เซอร์หนึ่งสตรีม ไม่ใช่สอง
v1.6.12 เลิกสู้และเปลี่ยนสถาปัตยกรรม แทนที่จะส่งเสียงไมค์และเสียงระบบเป็นสองสตรีมอิสระ มันรวมทั้งสองเข้าเป็นสตรีม mono เดียวที่ฝั่งไคลเอนต์ก่อนที่อะไรก็ตามจะออกจากแอป เมื่อทั้งสองแหล่งไหลผ่านไปป์ไลน์เดียวกัน track จึงตายแยกจากกันไม่ได้ — ไม่มีรูปแบบการป้อนกลับแบบสองอุปกรณ์ให้เบราว์เซอร์ตรวจจับและปิดเสียงอีกต่อไป เงื่อนไขตัวกระตุ้นก็หยุดมีอยู่เฉย ๆ
มีของแถมที่น่ายินดี สองสตรีมหมายถึงสอง WebSocket จากไคลเอนต์ถึงแบ็กเอนด์ และสองการเชื่อมต่อจากแบ็กเอนด์ถึงผู้ให้บริการ สตรีมที่รวมกันตัวเดียวยุบมันเหลือหนึ่งต่อหนึ่ง การแก้บั๊กหูฟังอินเอียร์ลดค่าถอดเสียงของเราลงครึ่งหนึ่งเป็นผลพลอยได้
แต่การรวมเป็น mono มีปัญหาที่ชัดเจน: ถ้าคุณผสมคนสองคนเข้าเป็นช่องเดียว คุณจะยังรู้ได้อย่างไรว่าใครพูดอะไร คุณได้โยนทิ้งการแยกที่บอกคุณไปแล้วนี่นา
สร้าง "ใครพูด" ขึ้นใหม่จากพลังงาน
คำตอบคือวัดความดังของแต่ละแหล่งก่อนรวมมันเข้าด้วยกัน แล้วแนบคำใบ้ไปกับเสียงทุกก้อนว่าแหล่งใดครอบงำ — ไมค์ ระบบ หรือความเงียบ แหล่งที่ดังกว่าชนะ ง่ายพอสมควรบนลำโพง
หูฟังอินเอียร์ต้องการอีกชั้นหนึ่ง และนี่คือจุดที่เสียงสะท้อนกลับเข้ามาในเรื่อง — ชี้ไปในทิศตรงข้ามกับที่คุณคงเดา บนหูฟังอินเอียร์ เสียงของอีกคนหนึ่งรั่วออกจากหูฟังและกลับเข้าไปในไมค์ ดังนั้นหากไม่ระวัง เสียงสะท้อนของพวกเขา ที่มาถึงบนช่องไมค์ของคุณ จะถูกระบุที่มาว่าเป็นคุณ การแก้ไขคือชุดเกณฑ์ที่รับรู้เสียงสะท้อน: เมื่อเสียงระบบกำลังเล่นสูงกว่าพื้นเล็ก ๆ เกณฑ์ที่ไมโครโฟนของคุณต้องข้ามให้พ้นเพื่อนับว่าเป็น "คุณกำลังพูด" จะพุ่งสูงขึ้นมาก — จาก 0.015 ปกติ ไปเป็น 0.1 พูดอีกอย่างคือ: ในขณะที่อีกคนกำลังพูดอยู่ ไมค์ของคุณต้องดังกว่าเสียงสะท้อนของพวกเขาอย่างชัดเจนไม่กำกวมก่อน เราถึงจะให้เครดิตคำพูดนั้นแก่คุณ เฟรมที่กำกวมจะถือเป็น "ระบบ" โดยปริยาย บนสมมติฐานว่ามันเป็นเสียงสะท้อน มันคืออคติที่จงใจ ซึ่งทำให้การระบุที่มาถูกต้องในสถานการณ์เดียวกันเป๊ะกับที่เคยผิดพลาด
ต้นแบบพิสูจน์แนวคิดนั้น — การรวมเป็น mono บวกกับการตรวจจับเสียงที่อิงพลังงานและรับรู้เสียงสะท้อน — คือบรรพบุรุษของระบบระบุที่มาที่ GeekBye ใช้อยู่ทุกวันนี้ นั่งร้านถูกรื้อออกในรีลีสเดียวกันนั้นเมื่อมันพิสูจน์ตัวเองแล้ว และคณิตศาสตร์เรื่องพลังงานภายหลังก็แข็งแรงขึ้นเป็นโมดูลที่ผ่านการทดสอบอย่างเหมาะสม แต่ v1.6.12 คือจุดที่แนวคิดนี้ถือกำเนิด ภายใต้แรงกดดัน โดยผู้ใช้ที่มีหูฟังอินเอียร์คู่หนึ่ง
สามสิ่งที่บั๊กหูฟังอินเอียร์สอน
- แฮนเดิลที่สมบูรณ์ดีอาจลำเลียงข้อมูลที่ตายแล้ว การสืบสวนทั้งหมดหมุนอยู่บนการเชื่อมิเตอร์พลังงานของเรามากกว่าแฟล็กสถานะของแพลตฟอร์ม —
live,enabled,unmutedทั้งหมดโกหกในขณะที่ตัวอย่างเสียงเป็นศูนย์ จงเฝ้าดูข้อมูลจริง ไม่ใช่รายงานตัวเองของ API ถ้าสตรีมอ้างว่ามันไม่มีปัญหา จงวัดว่ามันเป็นเช่นนั้นจริงหรือไม่ - เมื่อแพลตฟอร์มสู้กับสถาปัตยกรรมของคุณ จงเปลี่ยนสถาปัตยกรรม — ไม่ใช่แพลตฟอร์ม เราเถียงชนะการป้องกันการป้อนกลับของ Chromium ไม่ได้ และทุกความพยายามที่จะปิดมันตามขอบ ๆ ก็ล้มเหลวหรือย้อนกลับมาทำร้าย การเอาเงื่อนไขที่สังเกตได้ออกไป — สองสตรีมกลายเป็นหนึ่ง — ทำให้ทั้งปัญหาหายไป มันมักถูกกว่าเสมอที่จะเอาตัวกระตุ้นออก แทนที่จะเอาชนะการต่อสู้กับเบราว์เซอร์
- เมื่อคุณยุบช่องเพื่อความทนทาน คุณต้องสร้างสิ่งที่คุณทำลายขึ้นใหม่ การรวมเป็น mono ฆ่าการแยกช่องที่ลำเลียง "ใครพูด" ข้อมูลนั้นต้องถูกอนุมานใหม่จากพลังงานก่อนรวมและเกณฑ์ที่รับรู้เสียงสะท้อน ตรรกะชดเชยนั้นไม่ใช่สิ่งที่นึกได้ทีหลังจากการแก้ไข — มันคือครึ่งหนึ่งของการแก้ไข
นี่คือบทที่สี่ของเรื่องราวความน่าเชื่อถือที่กลายมาเป็น GeekBye v2 สำหรับบทก่อนหน้า ดู การหลุดของการเชื่อมต่อไม่ควรทำให้ทั้งแอปพัง (v1.6.8); สำหรับการเขียนไปป์ไลน์ใหม่ที่งานเสียงนี้ต่อยอดมา ดู เราลบโค้ดเสียง 5,000 บรรทัด (v1.6.0); และสำหรับเส้นโค้งทั้งเส้น ดู กายวิภาคของการส่งมอบซอฟต์แวร์สู่ความสมบูรณ์แบบ