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
Steven2 นาทีที่อ่าน

การหลุดของการเชื่อมต่อครั้งเดียวไม่ควรทำให้ทั้งแอปพัง — แต่ของเราพัง

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

วิศวกรรม
ความน่าเชื่อถือ
Electron
กายวิภาคของการส่งซอฟต์แวร์ให้สมบูรณ์แบบ: เมื่อ Code Review จับสิ่งที่ Test จับไม่ได้
Steven
Steven2 นาทีที่อ่าน

กายวิภาคของการส่งซอฟต์แวร์ให้สมบูรณ์แบบ: เมื่อ Code Review จับสิ่งที่ Test จับไม่ได้

ตลอดซีรีส์ GeekBye v2 เรื่องเดิมเกิดขึ้นซ้ำแล้วซ้ำเล่า: การแก้ไขผ่านทุกเทสต์บนเครื่องของนักพัฒนา แล้ว code review ก็พิสูจน์ว่ามันจะพังสำหรับเกือบทุกคน นี่คือเวิร์กโฟลว์เบื้องหลังเก้ารีลีส — ด่านรีวิว การจับปัญหาแบบแก้-ก่อน และวินัยเทสต์-ก่อน-ส่ง ที่เปลี่ยน "มันทำงานบนเครื่องผม" ให้เป็น "มันทำงานได้จริง"

วิศวกรรม
กระบวนการ
การรีวิวโค้ด
วิธีสตรีมรายงานสดโดยไม่ให้หน้าจอกระพริบ
Steven
Steven2 นาทีที่อ่าน

วิธีสตรีมรายงานสดโดยไม่ให้หน้าจอกระพริบ

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

วิศวกรรม
ฟรอนต์เอนด์
การสตรีม