
L'error dels auriculars: quan l'altra persona va desaparèixer de la nostra transcripció
La transcripció de doble canal funcionava perfectament amb els altaveus del portàtil i amb auriculars de diadema. Llavors algú va endollar uns auriculars intraauriculars, i la persona amb qui parlava simplement va desaparèixer de la transcripció — perquè Chromium silenciava el nostre àudio sense avisar en l'instant que ella parlava. Onze correccions van fallar. La dotzena va canviar tota l'arquitectura.
GeekBye transcriu dues bandes d'una conversa: el teu micròfon en un canal, l'àudio de l'altre participant (el que reprodueix el teu ordinador) en un altre. Funcionava impecablement amb els altaveus del portàtil. Funcionava amb auriculars de diadema. Llavors un usuari va endollar uns auriculars intraauriculars, i la persona amb qui parlava va desaparèixer de la transcripció. No distorsionada. No mal atribuïda. Desapareguda.
Aquesta és la història de GeekBye v1.6.12, l'error que va necessitar onze correccions fallides, i el moment en què ens vam adonar que lluitàvem contra el navegador en comptes de treballar-hi.
La pista que era viva i buida
El símptoma era esgarrifós. Iniciaves una reunió, tot semblava bé, deies una paraula — i des d'aquell instant, el canal d'àudio de l'altra persona anava al silenci digital pur. Ho vam mesurar: el senyal d'àudio del sistema estava en un nivell d'energia normal (un RMS al voltant de 0.03–0.06), i en el moment que el micròfon captava la teva veu, queia a 0.0000 i s'hi quedava la resta de la sessió.
Aquí ve la part que ens va costar dies. Cada API del navegador insistia que la pista d'àudio estava perfectament sana. readyState: live. enabled: true. muted: false. Segons cada indicador d'estat que exposava la plataforma, el flux estava bé. Eren les mostres les que estaven mortes — una canonada viva que no transportava res més que zeros. Vam passar molt de temps sense creure el nostre propi mesurador d'energia, perquè la versió oficial deia que la pista funcionava.
El desencadenant era específic dels auriculars intraauriculars, i la raó és física: un auricular intraauricular és un únic dispositiu que és alhora la teva entrada de micròfon i la teva sortida d'àudio. Els altaveus del portàtil i els auriculars de diadema els separen — la sortida va a un lloc, el micròfon és en un altre. Però quan l'entrada i la sortida són el mateix dispositiu, tens, per definició, un camí de retroalimentació. La nostra inferència forta (això viu a les entranyes del navegador, així que no en podem citar capítol i vers) és que Chromium detecta aquesta forma de micròfon-més-altaveu-en-un-dispositiu, decideix que és un bucle de retroalimentació a punt de xiular, i silencia la captura de manera protectora — en silenci, sense dir-ho a l'aplicació a través de cap dels indicadors que una aplicació pot llegir.
Onze correccions, diverses d'elles pitjors
L'hi vam atacar primer per les vies òbvies, i vam anotar cada intent. Desactivar la cancel·lació d'eco, la supressió de soroll i l'auto gain a tots dos fluxos. Iniciar els dos fluxos de manera seqüencial amb un retard en comptes de junts. Donar a cada flux la seva pròpia sample rate. Afegir un node de guany silenciós. Encaminar-ho tot a través d'un únic context d'àudio compartit. Passar a un AudioWorklet. Baixar del tot fins al processador de pistes cru de WebCodecs.
Onze enfocaments. No només van fallar — diversos ho van empitjorar. Un va introduir un retard de 30 segons. Un altre feia que la transcripció del teu micròfon aparegués etiquetada com l'altra persona. Estàvem ficats fins al coll, tractant cada símptoma, i l'error no parava de moure's.
L'avenç va ser un replantejament: cada correcció fins llavors intentava mantenir sanes dues fluxos separats. Però dos fluxos separats eren exactament allò que l'heurística de retroalimentació de Chromium podia veure i silenciar. I si no hi hagués res per comparar?
La correcció: dona-li al navegador un flux, no dos
v1.6.12 va deixar de lluitar i va canviar l'arquitectura. En comptes d'enviar el micròfon i l'àudio del sistema com dos fluxos independents, els barreja en un únic flux mono al client abans que res surti de l'aplicació. Quan totes dues fonts flueixen per una única canonada, les pistes no poden morir de manera independent — ja no hi ha una forma de retroalimentació de dos dispositius perquè el navegador la detecti i la silenciï. La condició desencadenant simplement deixa d'existir.
Hi va haver una bonificació deliciosa. Dos fluxos volien dir dos WebSockets de client a backend i dues connexions de backend a proveïdor; un únic flux barrejat ho va col·lapsar a un i un. Corregir l'error dels auriculars va reduir a la meitat el nostre cost de transcripció com a efecte secundari.
Però la barreja mono té un problema evident: si barreges totes dues persones en un canal, com saps encara qui va dir què? Has llençat justament la separació que t'ho deia.
Reconstruir "qui parlava" a partir de l'energia
La resposta va ser mesurar la sonoritat de cada font abans de barrejar-les, i adjuntar a cada fragment d'àudio una pista dient quina font dominava — micròfon, sistema o silenci. La font més forta guanya. Prou senzill amb altaveus.
Els auriculars intraauriculars necessitaven una capa més, i aquí és on l'eco torna a la història — apuntant en la direcció oposada del que suposaries. Amb auriculars intraauriculars, la veu de l'altra persona s'escapa de l'auricular i torna a entrar al micròfon. Així que sense cura, el seu eco, arribant al teu canal de micròfon, s'atribueix a tu. La correcció és un conjunt de llindars conscients de l'eco: quan l'àudio del sistema sona per sobre d'un petit terra, el llistó que el teu micròfon ha de superar per comptar com "tu parlant" puja moltíssim — d'un 0.015 normal a 0.1. Dit d'una altra manera: mentre l'altra persona parla, el teu micròfon ha de ser clarament, inequívocament més fort que el seu eco abans no atribuïm les paraules a tu. Els fotogrames ambigus tornen per defecte a "sistema", suposant que són eco. És un biaix deliberat que encerta l'atribució en exactament la situació que abans l'errava.
Aquella prova de concepte — barreja mono més detecció de veu basada en energia i conscient de l'eco — és l'avantpassat del sistema d'atribució que GeekBye executa avui. La bastida es va arrencar en la mateixa versió un cop es va demostrar, i la matemàtica d'energia es va endurir més tard en un mòdul degudament provat. Però v1.6.12 és on va néixer la idea, sota pressió, per un usuari amb un parell d'auriculars intraauriculars.
Tres coses que va ensenyar l'error dels auriculars
- Un identificador sa pot transportar dades mortes. Tota la investigació va girar a l'entorn de confiar en el nostre mesurador d'energia per sobre dels indicadors d'estat de la plataforma —
live,enabled,unmuted, tots mentint mentre les mostres eren zeros. Monitoritza la càrrega útil, no l'autoinforme de l'API. Si un flux afirma estar bé, mesura si realment ho està. - Quan la plataforma lluita contra la teva arquitectura, canvia l'arquitectura — no la plataforma. No podíem guanyar la discussió a la protecció de retroalimentació de Chromium, i cada intent de desactivar-la pels marges va fallar o va sortir malament. Eliminar la condició observable — dos fluxos convertint-se en un — va fer desaparèixer tot el problema. Gairebé sempre és més barat eliminar el desencadenant que guanyar una baralla amb el navegador.
- Quan col·lapses canals per robustesa, has de reconstruir el que has destruït. Barrejar a mono va matar la separació de canals que transportava "qui parlava". Aquella informació s'havia de tornar a derivar de l'energia prèvia a la barreja i de llindars conscients de l'eco. La lògica compensatòria no és una idea de darrera hora afegida a la correcció — és la meitat de la correcció.
Aquest és el quart capítol de la història de fiabilitat que esdevé GeekBye v2. Per al capítol anterior, vegeu una connexió caiguda no hauria de fer caure tota la teva aplicació (v1.6.8); per a la reescriptura del pipeline sobre la qual es va construir aquesta feina d'àudio, vam esborrar 5.000 línies de codi d'àudio (v1.6.0); i per a tot l'arc, l'anatomia de publicar programari fins a la perfecció.