Steven
Steven6 min čtení

Bug se sluchátky: když druhý účastník zmizel z našeho přepisu

Dvoukanálový přepis fungoval bezchybně na reproduktorech notebooku i na náhlavních sluchátkách. Pak někdo zapojil pecky do uší a člověk, se kterým mluvil, prostě zmizel z přepisu — protože Chromium tiše ztlumil náš zvuk v okamžiku, kdy promluvil. Jedenáct oprav selhalo. Dvanáctá změnila celou architekturu.

Inženýrství
Zvuk
Přepis
Vydání GeekBye
Bug se sluchátky: když druhý účastník zmizel z našeho přepisu

GeekBye přepisuje dvě strany konverzace: váš mikrofon na jednom kanálu, zvuk druhého účastníka (to, co přehrává váš počítač) na druhém. Fungovalo to bezchybně na reproduktorech notebooku. Fungovalo to na náhlavních sluchátkách. Pak jeden uživatel zapojil pecky do uší a člověk, se kterým mluvil, zmizel z přepisu. Ne zkomolený. Ne špatně přiřazený. Pryč.

Tohle je příběh GeekBye v1.6.12, bugu, který si vyžádal jedenáct neúspěšných oprav, a okamžiku, kdy jsme si uvědomili, že bojujeme proti prohlížeči, místo abychom s ním spolupracovali.

Track, který byl živý a prázdný

Symptom byl zvláštní. Spustili jste schůzku, všechno vypadalo v pořádku, řekli jste jedno slovo — a od toho okamžiku přešel zvukový kanál druhého člověka do čistého digitálního ticha. Změřili jsme to: signál systémového zvuku ležel na normální úrovni energie (RMS kolem 0.03–0.06) a v okamžiku, kdy mikrofon zachytil váš hlas, spadl na 0.0000 a zůstal tam po zbytek relace.

Tady je ta část, která nás stála dny. Každé API prohlížeče trvalo na tom, že zvukový track je naprosto v pořádku. readyState: live. enabled: true. muted: false. Podle každého status příznaku, který platforma vystavovala, byl stream v pořádku. Byly to vzorky, které byly mrtvé — živá roura nesoucí jen nuly. Dlouho jsme nevěřili vlastnímu měřiči energie, protože oficiální verze říkala, že track funguje.

Spouštěč byl specifický pro pecky do uší a důvod je fyzikální: pecka do ucha je jedno zařízení, které je zároveň vaším mikrofonovým vstupem a vaším zvukovým výstupem. Reproduktory notebooku a náhlavní sluchátka je oddělují — výstup jde na jedno místo, mikrofon je jinde. Ale když jsou vstup a výstup stejné zařízení, máte z definice cestu zpětné vazby. Náš silný závěr (tohle žije v útrobách prohlížeče, takže nemůžeme citovat kapitolu a verš) je, že Chromium detekuje ten tvar mikrofon-plus-reproduktor-na-jednom-zařízení, rozhodne, že jde o smyčku zpětné vazby, která se chystá zapískat, a ochranně ztlumí zachytávání — tiše, aniž by to aplikaci řeklo přes kterýkoli z příznaků, jež aplikace může přečíst.

Jedenáct oprav, několik z nich horších

Nejprve jsme to napadli zjevnými způsoby a každý pokus si zapsali. Vypni potlačení ozvěny, potlačení šumu a automatické zesílení na obou streamech. Spusť oba streamy postupně se zpožděním místo současně. Dej každému streamu vlastní vzorkovací frekvenci. Přidej tichý gain uzel. Veď všechno přes jeden sdílený audio context. Přejdi na AudioWorklet. Sestup až dolů k surovému track procesoru z WebCodecs.

Jedenáct přístupů. Nejen že selhaly — několik situaci zhoršilo. Jeden zavedl 30sekundové zpoždění. Jiný způsobil, že se přepis vašeho mikrofonu zobrazoval označený jako druhý člověk. Byli jsme hluboko v plevelu, léčili každý symptom a bug se pořád přesouval.

Průlom byl v přerámování: každá dosavadní oprava se snažila udržet dva samostatné streamy zdravé. Ale dva samostatné streamy byly přesně to, co heuristika zpětné vazby Chromia dokázala vidět a ztlumit. Co kdyby nebylo co porovnávat?

Oprava: dej prohlížeči jeden stream, ne dva

v1.6.12 přestala bojovat a změnila architekturu. Místo aby posílala mikrofon a systémový zvuk jako dva nezávislé streamy, smíchá je do jednoho mono streamu na klientovi dřív, než cokoli opustí aplikaci. Když oba zdroje tečou jedním potrubím, tracky nemohou umřít nezávisle — už neexistuje dvouzařízenový tvar zpětné vazby, který by prohlížeč detekoval a ztlumil. Spouštěcí podmínka prostě přestane existovat.

Byl tu i příjemný bonus. Dva streamy znamenaly dva WebSockety klient-k-backendu a dvě spojení backend-k-poskytovateli; jeden smíchaný stream to zredukoval na jeden a jeden. Oprava bugu se sluchátky jako vedlejší efekt snížila náklady na přepis na polovinu.

Mono míchání má ale zjevný problém: když smísíte oba lidi do jednoho kanálu, jak pořád víte, kdo co řekl? Zahodili jste zrovna to oddělení, které vám to říkalo.

Znovuvybudování „kdo mluvil" z energie

Odpovědí bylo změřit hlasitost každého zdroje předtím, než je smícháte, a připojit ke každému kousku zvuku nápovědu o tom, který zdroj dominoval — mikrofon, systém nebo ticho. Hlasitější zdroj vyhrává. Na reproduktorech dost jednoduché.

Pecky do uší potřebovaly ještě jednu vrstvu a tady se ozvěna vrací do příběhu — namířená opačným směrem, než byste hádali. Na peckách do uší hlas druhého člověka uniká z pecky zpátky do mikrofonu. Takže bez opatrnosti se jeho ozvěna, přicházející na váš mikrofonový kanál, přiřadí vám. Opravou je sada prahů s ohledem na ozvěnu: když systémový zvuk hraje nad malou hranicí, laťka, kterou musí váš mikrofon překonat, aby se počítal jako „vy mluvíte", vyskočí vysoko — z normálních 0.015 na 0.1. Jinými slovy: dokud mluví druhý člověk, musí být váš mikrofon jasně, jednoznačně hlasitější než jeho ozvěna, než vám slova připíšeme. Nejednoznačné snímky připadnou ve výchozím nastavení „systému", za předpokladu, že jde o ozvěnu. Je to záměrné zkreslení, které trefí přiřazení správně přesně v té situaci, v níž ho dřív pletlo.

Ten proof-of-concept — mono míchání plus rozpoznávání hlasu založené na energii s ohledem na ozvěnu — je předchůdcem systému přiřazení, který GeekBye provozuje dnes. Lešení bylo vytrženo ve stejném vydání, jakmile se osvědčilo, a energetická matematika se později zpevnila do pořádně otestovaného modulu. Ale v1.6.12 je místem, kde se ta myšlenka zrodila, pod tlakem, uživatelem s párem pecek do uší.

Tři věci, které nás bug se sluchátky naučil

  1. Zdravý handle může nést mrtvá data. Celé vyšetřování se otočilo kolem toho, že jsme důvěřovali svému měřiči energie víc než status příznakům platformy — live, enabled, unmuted, všechny lhaly, zatímco vzorky byly nuly. Sleduj užitečná data, ne sebe-hlášení API. Když stream tvrdí, že je v pořádku, změř, jestli opravdu je.
  2. Když platforma bojuje proti tvé architektuře, změň architekturu — ne platformu. Nedokázali jsme argumentačně přebít ochranu Chromia proti zpětné vazbě a každý pokus vypnout ji po okrajích selhal nebo se obrátil proti nám. Odstranění pozorovatelné podmínky — dva streamy se stanou jedním — celý problém odstranilo. Skoro vždycky je levnější odstranit spouštěč než vyhrát bitvu s prohlížečem.
  3. Když kvůli robustnosti sléváš kanály, musíš znovu vybudovat, cos zničil. Míchání do mono zabilo oddělení kanálů, které neslo „kdo mluvil". Tuhle informaci bylo třeba znovu odvodit z energie před mícháním a prahů s ohledem na ozvěnu. Kompenzační logika není dodatečná myšlenka k opravě — je polovinou opravy.

Tohle je čtvrtá kapitola příběhu o spolehlivosti, který se stává GeekBye v2. Předchozí kapitolu najdete v přerušené spojení by nemělo shodit celou vaši aplikaci (v1.6.8); přepis pipeline, na kterém tahle práce se zvukem stavěla, v smazali jsme 5000 řádků zvukového kódu (v1.6.0); a celý oblouk v anatomie dodávání softwaru k dokonalosti.

Související články