
Błąd słuchawek dousznych: gdy druga osoba znikała z naszej transkrypcji
Dwukanałowa transkrypcja działała bez zarzutu na głośnikach laptopa i słuchawkach nausznych. Potem ktoś podłączył słuchawki douszne i osoba, z którą rozmawiał, po prostu znikała z transkrypcji — bo Chromium po cichu wyciszał nasze audio w chwili, gdy tamten się odezwał. Jedenaście napraw zawiodło. Dwunasta zmieniła całą architekturę.
GeekBye transkrybuje dwie strony rozmowy: twój mikrofon na jednym kanale, dźwięk drugiej osoby (to, co odtwarza twój komputer) na drugim. Działało bezbłędnie na głośnikach laptopa. Działało na słuchawkach nausznych. Potem użytkownik podłączył słuchawki douszne i osoba, z którą rozmawiał, znikała z transkrypcji. Nie zniekształcona. Nie źle przypisana. Zniknęła.
To historia GeekBye v1.6.12, błędu, który wymagał jedenastu nieudanych napraw, i chwili, gdy uświadomiliśmy sobie, że walczymy z przeglądarką zamiast z nią współpracować.
Ścieżka, która była żywa i pusta
Objaw był niesamowity. Zaczynałeś spotkanie, wszystko wyglądało dobrze, mówiłeś jedno słowo — i od tej chwili kanał dźwięku drugiej osoby wpadał w czystą cyfrową ciszę. Zmierzyliśmy to: sygnał dźwięku systemowego siedział na normalnym poziomie energii (RMS około 0.03–0.06), a w chwili, gdy mikrofon wychwytywał twój głos, spadał do 0.0000 i pozostawał tam do końca sesji.
Oto część, która kosztowała nas dni. Każde API przeglądarki upierało się, że ścieżka audio jest doskonale zdrowa. readyState: live. enabled: true. muted: false. Według każdej flagi statusu, jaką platforma wystawiała, strumień był w porządku. To próbki były martwe — żywa rura niosąca same zera. Długo nie wierzyliśmy własnemu miernikowi energii, bo oficjalna wersja mówiła, że ścieżka działa.
Wyzwalacz był specyficzny dla słuchawek dousznych, a przyczyna jest fizyczna: słuchawka douszna to jedno urządzenie, które jest jednocześnie twoim wejściem mikrofonowym i wyjściem audio. Głośniki laptopa i słuchawki nauszne to rozdzielają — wyjście idzie w jedno miejsce, mikrofon jest gdzie indziej. Ale gdy wejście i wyjście to to samo urządzenie, masz z definicji ścieżkę sprzężenia. Nasz mocny wniosek (to żyje w bebechach przeglądarki, więc nie możemy zacytować rozdziału i wersu) jest taki, że Chromium wykrywa ten kształt mikrofon-plus-głośnik-na-jednym-urządzeniu, uznaje, że to pętla sprzężenia bliska zapiszczenia, i ochronnie wycisza przechwytywanie — po cichu, nie mówiąc o tym aplikacji przez żadną z flag, które aplikacja może odczytać.
Jedenaście napraw, kilka z nich gorszych
Zaatakowaliśmy to najpierw w oczywisty sposób i spisaliśmy każdą próbę. Wyłączyć kasowanie echa, tłumienie szumu i automatyczne wzmocnienie na obu strumieniach. Uruchamiać oba strumienie sekwencyjnie z opóźnieniem zamiast razem. Dać każdemu strumieniowi własną częstotliwość próbkowania. Dodać cichy węzeł wzmocnienia. Puścić wszystko przez jeden współdzielony kontekst audio. Przejść na AudioWorklet. Zejść aż do surowego procesora ścieżki WebCodecs.
Jedenaście podejść. Nie tylko zawiodły — kilka pogorszyło sprawę. Jedno wprowadziło 30-sekundowe opóźnienie. Inne sprawiło, że transkrypcja twojego mikrofonu pojawiała się oznaczona jako druga osoba. Byliśmy głęboko w chaszczach, lecząc każdy objaw, a błąd wciąż się przesuwał.
Przełomem było przeformułowanie: każda dotychczasowa naprawa próbowała utrzymać dwa oddzielne strumienie zdrowe. Ale dwa oddzielne strumienie były dokładnie tym, co heurystyka sprzężenia Chromium mogła zobaczyć i wyciszyć. A gdyby nie było czego porównywać?
Naprawa: daj przeglądarce jeden strumień, nie dwa
v1.6.12 przestał walczyć i zmienił architekturę. Zamiast wysyłać mikrofon i dźwięk systemowy jako dwa niezależne strumienie, miksuje je w jeden strumień mono po stronie klienta, zanim cokolwiek opuści aplikację. Gdy oba źródła płyną przez jeden potok, ścieżki nie mogą umrzeć niezależnie — nie ma już dwuurządzeniowego kształtu sprzężenia, który przeglądarka mogłaby wykryć i wyciszyć. Warunek wyzwalający po prostu przestaje istnieć.
Był i rozkoszny bonus. Dwa strumienie oznaczały dwa WebSockety klient-do-backendu i dwa połączenia backend-do-dostawcy; jeden zmiksowany strumień zwinął to do jednego i jednego. Naprawa błędu słuchawek dousznych zmniejszyła nasz koszt transkrypcji o połowę jako efekt uboczny.
Ale miksowanie do mono ma oczywisty problem: jeśli zlewasz obie osoby w jeden kanał, jak wciąż wiesz, kto co powiedział? Wyrzuciłeś dokładnie tę separację, która ci to mówiła.
Odbudowa „kto mówił" z energii
Odpowiedzią było zmierzenie głośności każdego źródła przed ich zmiksowaniem i dołączenie do każdego kawałka audio wskazówki mówiącej, które źródło dominowało — mikrofon, system czy cisza. Głośniejsze źródło wygrywa. Wystarczająco proste na głośnikach.
Słuchawki douszne wymagały jeszcze jednej warstwy i tu echo wraca do opowieści — skierowane przeciwnie do tego, co byś zgadł. Na słuchawkach dousznych głos drugiej osoby wycieka ze słuchawki z powrotem do mikrofonu. Więc bez uwagi jej echo, docierające na twój kanał mikrofonowy, zostaje przypisane tobie. Naprawą jest zestaw świadomych echa progów: gdy dźwięk systemowy gra powyżej małego progu, poprzeczka, którą twój mikrofon musi przeskoczyć, by liczyć się jako „ty mówisz", skacze wysoko w górę — z normalnego 0.015 do 0.1. Innymi słowy: dopóki druga osoba mówi, twój mikrofon musi być wyraźnie, jednoznacznie głośniejszy od jej echa, zanim przypiszemy słowa tobie. Niejednoznaczne klatki domyślnie idą do „systemu", przy założeniu, że to echo. To celowe nastawienie, które trafia z atrybucją w dokładnie tej sytuacji, która kiedyś ją myliła.
Ten proof-of-concept — miksowanie do mono plus oparte na energii, świadome echa wykrywanie głosu — jest przodkiem systemu atrybucji, na którym GeekBye działa dziś. Rusztowanie wyrwano w tym samym wydaniu, gdy tylko się sprawdziło, a matematyka energii później stwardniała w porządnie przetestowany moduł. Ale v1.6.12 to miejsce, gdzie idea się narodziła, pod presją, za sprawą użytkownika ze słuchawkami dousznymi.
Trzy rzeczy, których nauczył błąd słuchawek dousznych
- Zdrowy uchwyt może nieść martwe dane. Całe śledztwo obróciło się wokół zaufania naszemu miernikowi energii ponad flagami statusu platformy —
live,enabled,unmuted, wszystkie kłamiące, gdy próbki były zerami. Monitoruj ładunek, nie samoocenę API. Jeśli strumień twierdzi, że jest w porządku, zmierz, czy naprawdę jest. - Gdy platforma walczy z twoją architekturą, zmień architekturę — nie platformę. Nie mogliśmy przegadać ochrony sprzężeniowej Chromium, a każda próba wyłączenia jej na obrzeżach zawodziła lub odbijała się rykoszetem. Usunięcie obserwowalnego warunku — dwa strumienie stające się jednym — sprawiło, że cały problem zniknął. Niemal zawsze taniej jest usunąć wyzwalacz niż wygrać walkę z przeglądarką.
- Gdy zwijasz kanały dla niezawodności, musisz odbudować to, co zniszczyłeś. Miksowanie do mono zabiło separację kanałów, która niosła „kto mówił". Tę informację trzeba było wyprowadzić na nowo z energii sprzed miksu i świadomych echa progów. Kompensująca logika nie jest dodatkiem do naprawy — ona jest połową naprawy.
To czwarty rozdział opowieści o niezawodności, która staje się GeekBye v2. Po poprzedni rozdział zobacz zerwane połączenie nie powinno wywalać całej aplikacji (v1.6.8); po przepisanie potoku, na którym ta praca nad audio się oparła, usunęliśmy 5000 linii kodu audio (v1.6.0); a po cały łuk, anatomię dostarczania oprogramowania do perfekcji.