Steven
Steven2 นาทีที่อ่าน

บั๊กหูฟังอินเอียร์: เมื่ออีกฝ่ายหายไปจากบันทึกถอดเสียงของเรา

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

วิศวกรรม
เสียง
การถอดเสียง
GeekBye รีลีส
บั๊กหูฟังอินเอียร์: เมื่ออีกฝ่ายหายไปจากบันทึกถอดเสียงของเรา

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 คือจุดที่แนวคิดนี้ถือกำเนิด ภายใต้แรงกดดัน โดยผู้ใช้ที่มีหูฟังอินเอียร์คู่หนึ่ง

สามสิ่งที่บั๊กหูฟังอินเอียร์สอน

  1. แฮนเดิลที่สมบูรณ์ดีอาจลำเลียงข้อมูลที่ตายแล้ว การสืบสวนทั้งหมดหมุนอยู่บนการเชื่อมิเตอร์พลังงานของเรามากกว่าแฟล็กสถานะของแพลตฟอร์ม — live, enabled, unmuted ทั้งหมดโกหกในขณะที่ตัวอย่างเสียงเป็นศูนย์ จงเฝ้าดูข้อมูลจริง ไม่ใช่รายงานตัวเองของ API ถ้าสตรีมอ้างว่ามันไม่มีปัญหา จงวัดว่ามันเป็นเช่นนั้นจริงหรือไม่
  2. เมื่อแพลตฟอร์มสู้กับสถาปัตยกรรมของคุณ จงเปลี่ยนสถาปัตยกรรม — ไม่ใช่แพลตฟอร์ม เราเถียงชนะการป้องกันการป้อนกลับของ Chromium ไม่ได้ และทุกความพยายามที่จะปิดมันตามขอบ ๆ ก็ล้มเหลวหรือย้อนกลับมาทำร้าย การเอาเงื่อนไขที่สังเกตได้ออกไป — สองสตรีมกลายเป็นหนึ่ง — ทำให้ทั้งปัญหาหายไป มันมักถูกกว่าเสมอที่จะเอาตัวกระตุ้นออก แทนที่จะเอาชนะการต่อสู้กับเบราว์เซอร์
  3. เมื่อคุณยุบช่องเพื่อความทนทาน คุณต้องสร้างสิ่งที่คุณทำลายขึ้นใหม่ การรวมเป็น mono ฆ่าการแยกช่องที่ลำเลียง "ใครพูด" ข้อมูลนั้นต้องถูกอนุมานใหม่จากพลังงานก่อนรวมและเกณฑ์ที่รับรู้เสียงสะท้อน ตรรกะชดเชยนั้นไม่ใช่สิ่งที่นึกได้ทีหลังจากการแก้ไข — มันคือครึ่งหนึ่งของการแก้ไข

นี่คือบทที่สี่ของเรื่องราวความน่าเชื่อถือที่กลายมาเป็น GeekBye v2 สำหรับบทก่อนหน้า ดู การหลุดของการเชื่อมต่อไม่ควรทำให้ทั้งแอปพัง (v1.6.8); สำหรับการเขียนไปป์ไลน์ใหม่ที่งานเสียงนี้ต่อยอดมา ดู เราลบโค้ดเสียง 5,000 บรรทัด (v1.6.0); และสำหรับเส้นโค้งทั้งเส้น ดู กายวิภาคของการส่งมอบซอฟต์แวร์สู่ความสมบูรณ์แบบ

บทความที่เกี่ยวข้อง

ความเงียบคือสิ่งที่รับน้ำหนัก
Steven
Steven3 นาทีที่อ่าน

ความเงียบคือสิ่งที่รับน้ำหนัก

สอง release สุดท้ายของ GeekBye v1 ว่าด้วยความจริงอันน่าอึดอัดข้อเดียวกัน: การถอดเสียงแบบเรียลไทม์บนเครือข่ายจริงไม่ได้ไร้การสูญเสีย และการเดินหมากที่ซื่อสัตย์คือเลิกแกล้งทำเป็นว่ามันไร้การสูญเสีย v1.8.20 เก็บสำเนาของ audio chunk ทุกก้อนไว้บนดิสก์ก่อนจะทิ้งมันระหว่างการเชื่อมต่อใหม่ และเริ่มทำเครื่องหมายช่องว่างใน transcript ออกมาดัง ๆ v1.9.0 เลิกส่งความเงียบเพื่อประหยัด bandwidth — แล้วก็ค้นพบว่าความเงียบนั่นแหละคือสัญญาณเป๊ะ ๆ ที่เครื่องถอดเสียงใช้รู้ว่าประโยคจบแล้ว สอง release ว่าด้วยราคาของการโยนของทิ้ง

วิศวกรรม
Audio
ความน่าเชื่อถือ
สามกริยาที่ทำให้ Web Audio ยังมีชีวิต
Steven
Steven5 นาทีที่อ่าน

สามกริยาที่ทำให้ Web Audio ยังมีชีวิต

GeekBye สอง point release ห่างกันสองเดือนและอยู่คนละไฟล์ สอนโค้ดเสียงของเราด้วยบทเรียนเดียวกันจากปลายสองด้านตรงข้าม: เลิกมอง AudioContext ของ browser ว่าเป็นของใช้แล้วทิ้ง release หนึ่งเรียนรู้ที่จะ resume() context ที่ macOS แอบ suspend ไปกลางการอัด; อีกอันเรียนรู้ที่จะ suspend() แทน close() เพื่อให้ session ที่ต่อกันเป็นพรืดเลิกพุ่งชนเพดานราวหก context ของ Chromium resume, suspend, close — นั่นคือทั้งเรื่อง

วิศวกรรม
Audio
Desktop
แยกการโทรออกจากแอปที่แค่เปิดค้างไว้
Steven
Steven4 นาทีที่อ่าน

แยกการโทรออกจากแอปที่แค่เปิดค้างไว้

GeekBye สังเกตได้ว่าคุณเข้าร่วมการประชุมวิดีโอแล้ว และเสนอบันทึกมันให้ด้วยคลิกเดียว การตรวจจับกลายเป็นครึ่งที่ง่าย — Swift binary ที่อ่านชื่อหน้าต่างทุกสิบวินาที ครึ่งที่ยากคือความแม่นยำ: ไม่ยิงตอน Zoom แค่เปิดค้างอยู่, ไม่เตือนสำหรับการประชุมที่คุณกำลังบันทึกอยู่แล้ว, และไม่ mute ไมค์ในการโทรที่คุณอยู่จริง ๆ สามรีลีส และแต่ละอันคือ guard ที่ต้องเรียนรู้ที่จะไม่เอาชนะตัวเอง

วิศวกรรม
macOS
Desktop