
Earbuds-fejlen: da den anden person forsvandt fra vores transskription
Tokanals-transskription virkede fejlfrit på laptop-højttalere og over-øret-hovedtelefoner. Så satte nogen earbuds i, og personen de talte med forsvandt helt enkelt fra transskriptionen — fordi Chromium stille dæmpede vores lyd i samme øjeblik de begyndte at tale. Elleve rettelser slog fejl. Den tolvte ændrede hele arkitekturen.
GeekBye transskriberer to sider af en samtale: din mikrofon på én kanal, den anden deltagers lyd (det din computer afspiller) på en anden. Det virkede fejlfrit på laptop-højttalere. Det virkede på over-øret-hovedtelefoner. Så satte en bruger earbuds i, og personen de talte med forsvandt fra transskriptionen. Ikke forvansket. Ikke fejltilskrevet. Væk.
Dette er historien om GeekBye v1.6.12, fejlen der krævede elleve mislykkede rettelser, og øjeblikket hvor vi indså, at vi kæmpede mod browseren i stedet for at arbejde sammen med den.
Sporet der var levende og tomt
Symptomet var uhyggeligt. Du startede et møde, alt så fint ud, du sagde ét ord — og fra det øjeblik gik den anden persons lydkanal til ren digital stilhed. Vi målte det: systemlyd-signalet lå på et normalt energiniveau (et RMS omkring 0.03–0.06), og i det øjeblik mikrofonen opfangede din stemme, faldt det til 0.0000 og blev der resten af sessionen.
Her er delen der kostede os dage. Hvert browser-API insisterede på, at lydsporet var fuldstændig sundt. readyState: live. enabled: true. muted: false. Efter hvert statusflag platformen eksponerede var strømmen fin. Det var samplerne der var døde — et levende rør der ikke bar andet end nuller. Vi brugte lang tid på ikke at tro på vores egen energimåler, fordi den officielle historie sagde, at sporet virkede.
Udløseren var specifik for earbuds, og årsagen er fysisk: en earbud er én enhed, der samtidig er din mikrofonindgang og din lydudgang. Laptop-højttalere og over-øret-hovedtelefoner adskiller disse — udgangen går ét sted hen, mikrofonen er et andet sted. Men når indgang og udgang er samme enhed, har du per definition en feedback-vej. Vores stærke slutning (dette bor i browserens indvolde, så vi kan ikke citere kapitel og vers) er, at Chromium opdager denne mikrofon-plus-højttaler-på-én-enhed-form, afgør at det er et feedback-loop på vej til at hyle, og beskyttende dæmper optagelsen — stille, uden at fortælle appen det gennem nogen af de flag en app kan læse.
Elleve rettelser, flere af dem værre
Vi angreb det på de oplagte måder først og skrev hvert forsøg ned. Slå ekko-annullering, støjreduktion og autoforstærkning fra på begge strømme. Starte de to strømme sekventielt med en forsinkelse i stedet for sammen. Give hver strøm sin egen samplingfrekvens. Tilføje en stille forstærkningsnode. Dirigere alt gennem én delt lydkontekst. Skifte til en AudioWorklet. Falde helt ned til den rå WebCodecs-sporprocessor.
Elleve fremgangsmåder. De slog ikke bare fejl — flere gjorde det værre. En introducerede en 30-sekunders forsinkelse. En anden fik din mikrofons transskript til at dukke op mærket som den anden person. Vi var dybt inde i krattet, behandlede hvert symptom, og fejlen blev ved med at flytte sig.
Gennembruddet var en omformulering: hver rettelse indtil videre forsøgte at holde to separate strømme sunde. Men to separate strømme var præcis det, som Chromiums feedback-heuristik kunne se og dæmpe. Hvad nu hvis der ikke var noget at sammenligne?
Rettelsen: giv browseren én strøm, ikke to
v1.6.12 holdt op med at kæmpe og ændrede arkitekturen. I stedet for at sende mikrofonen og systemlyden som to uafhængige strømme mixer den dem til én mono-strøm på klienten, før noget forlader appen. Når begge kilder flyder gennem én pipeline, kan sporene ikke dø uafhængigt af hinanden — der er ikke længere nogen to-enheds-feedback-form, som browseren kan opdage og dæmpe. Udløsningsbetingelsen holder simpelthen op med at eksistere.
Der var en dejlig bonus. To strømme betød to klient-til-backend-WebSockets og to backend-til-udbyder-forbindelser; én mixet strøm kollapsede det til én og én. At rette earbuds-fejlen halverede vores transskriptionsomkostning som en sidegevinst.
Men mono-mixning har et oplagt problem: hvis du blander begge personer til én kanal, hvordan ved du så stadig hvem der sagde hvad? Du har jo smidt selve den adskillelse væk, der fortalte dig det.
Genopbygge "hvem der talte" ud fra energi
Svaret var at måle hver kildes lydstyrke før de blev mixet og hæfte et hint på hver lydbid om, hvilken kilde der dominerede — mikrofon, system eller stilhed. Højere kilde vinder. Simpelt nok på højttalere.
Earbuds havde brug for endnu et lag, og det er her ekkoet kommer tilbage i historien — vendt den modsatte vej af, hvad man ville gætte. På earbuds lækker den anden persons stemme ud af budden og tilbage ind i mikrofonen. Så uden omhu bliver deres ekko, som ankommer på din mikrofonkanal, tilskrevet dig. Rettelsen er et sæt ekkobevidste tærskler: når systemlyden spiller over et lille gulv, springer barren, din mikrofon skal klare for at tælle som "du taler", kraftigt op — fra et normalt 0.015 til 0.1. Med andre ord: mens den anden person taler, skal din mikrofon være tydeligt, utvetydigt højere end deres ekko, før vi krediterer ordene til dig. Tvetydige rammer havner som standard på "system" ud fra antagelsen om, at de er ekko. Det er en bevidst slagside, der får tilskrivningen rigtig i præcis den situation, der før fik den forkert.
Det proof-of-concept — mono-mixning plus energibaseret, ekkobevidst stemmedetektering — er forfaderen til det tilskrivningssystem, GeekBye kører i dag. Stilladset blev revet ud i samme udgivelse, så snart det havde bevist sig, og energimatematikken hærdede senere til et ordentligt testet modul. Men v1.6.12 er der, hvor idéen blev født — under pres, af en bruger med et par earbuds.
Tre ting earbuds-fejlen lærte os
- Et sundt håndtag kan bære død data. Hele undersøgelsen drejede sig om at stole på vores energimåler frem for platformens statusflag —
live,enabled,unmuted, alle løj, mens samplerne var nuller. Overvåg indholdet, ikke API'ets selvrapport. Hvis en strøm påstår, at den er fin, så mål, om den faktisk er det. - Når platformen kæmper mod din arkitektur, så ændr arkitekturen — ikke platformen. Vi kunne ikke argumentere Chromiums feedback-beskyttelse ihjel, og hvert forsøg på at slå den fra i kanterne slog fejl eller slog tilbage. At fjerne den observerbare betingelse — to strømme bliver til én — fik hele problemet til at forsvinde. Det er næsten altid billigere at fjerne udløseren end at vinde en kamp med browseren.
- Når du slår kanaler sammen for robusthed, skal du genopbygge det, du ødelagde. At mixe til mono dræbte kanaladskillelsen, der bar "hvem der talte". Den information måtte udledes på ny fra energi før mixning og ekkobevidste tærskler. Den kompenserende logik er ikke en eftertanke til rettelsen — den er halvdelen af rettelsen.
Dette er det fjerde kapitel i tilforladeligheds-historien, der bliver GeekBye v2. For det foregående kapitel, se en tabt forbindelse burde ikke crashe hele din app (v1.6.8); for pipeline-omskrivningen, som dette lydarbejde byggede på, vi slettede 5.000 linjer lydkode (v1.6.0); og for hele buen, anatomien af at skibe software til perfektion.