Steven
Steven6 min citire

Bug-ul căștilor: când cealaltă persoană a dispărut din transcrierea noastră

Transcrierea pe două canale mergea perfect pe boxele laptopului și pe căștile over-ear. Apoi cineva a băgat niște căști intraauriculare, iar persoana cu care vorbea pur și simplu a dispărut din transcriere — pentru că Chromium ne-a mutat sunetul în tăcere în clipa în care ea a vorbit. Unsprezece reparații au eșuat. A douăsprezecea a schimbat toată arhitectura.

Inginerie
Audio
Transcriere
Lansări GeekBye
Bug-ul căștilor: când cealaltă persoană a dispărut din transcrierea noastră

GeekBye transcrie două părți ale unei conversații: microfonul tău pe un canal, sunetul celuilalt participant (ce redă computerul tău) pe altul. Mergea impecabil pe boxele laptopului. Mergea pe căștile over-ear. Apoi un utilizator a băgat niște căști intraauriculare, iar persoana cu care vorbea a dispărut din transcriere. Nu distorsionată. Nu atribuită greșit. Dispărută.

Aceasta e povestea lui GeekBye v1.6.12, a bug-ului care a cerut unsprezece reparații eșuate și a momentului în care ne-am dat seama că ne luptam cu browserul în loc să lucrăm cu el.

Track-ul care era viu și gol

Simptomul era straniu. Porneai o ședință, totul părea în regulă, spuneai un cuvânt — și din clipa aceea, canalul audio al celeilalte persoane trecea în pură tăcere digitală. Am măsurat: semnalul sunetului de sistem stătea la un nivel de energie normal (un RMS în jur de 0.03–0.06), iar în momentul în care microfonul îți capta vocea, cădea la 0.0000 și rămânea acolo pentru tot restul sesiunii.

Iată partea care ne-a costat zile. Fiecare API din browser insista că track-ul audio era perfect sănătos. readyState: live. enabled: true. muted: false. După fiecare flag de status pe care platforma îl expunea, fluxul era în regulă. Eșantioanele erau moarte — o țeavă vie care nu căra decât zerouri. Am petrecut multă vreme neîncrezând în propriul nostru contor de energie, pentru că versiunea oficială spunea că track-ul funcționa.

Declanșatorul era specific căștilor intraauriculare, iar motivul e fizic: o cască intraauriculară e un singur dispozitiv care e simultan intrarea ta de microfon și ieșirea ta de sunet. Boxele laptopului și căștile over-ear le separă — ieșirea merge într-un loc, microfonul e în altul. Dar când intrarea și ieșirea sunt același dispozitiv, ai, prin definiție, o cale de feedback. Deducția noastră fermă (asta trăiește în măruntaiele browserului, așa că nu putem cita capitol și verset) e că Chromium detectează acea formă de microfon-plus-difuzor-pe-un-singur-dispozitiv, decide că e o buclă de feedback gata să țiuie și mută captura protectiv — în tăcere, fără să spună aplicației prin niciunul dintre flag-urile pe care o aplicație le poate citi.

Unsprezece reparații, câteva mai proaste

Am atacat-o mai întâi pe căile evidente și am notat fiecare încercare. Dezactivează anularea ecoului, suprimarea zgomotului și câștigul automat pe ambele fluxuri. Pornește cele două fluxuri secvențial cu o întârziere în loc de împreună. Dă fiecărui flux propria rată de eșantionare. Adaugă un nod de câștig tăcut. Rutează totul printr-un singur context audio comun. Trece la un AudioWorklet. Coboară până la procesorul de track brut WebCodecs.

Unsprezece abordări. Nu doar că au eșuat — câteva au înrăutățit lucrurile. Una a introdus o întârziere de 30 de secunde. Alta a făcut ca transcrierea microfonului tău să apară etichetată drept cealaltă persoană. Eram adânc în bălării, tratând fiecare simptom, iar bug-ul se tot muta.

Descoperirea a fost o reîncadrare: fiecare reparație de până atunci încerca să țină două fluxuri separate sănătoase. Dar două fluxuri separate erau exact lucrul pe care euristica de feedback a lui Chromium îl putea vedea și muta. Ce-ar fi dacă n-ar exista nimic de comparat?

Reparația: dă browserului un singur flux, nu două

v1.6.12 a încetat lupta și a schimbat arhitectura. În loc să trimită microfonul și sunetul de sistem ca două fluxuri independente, le mixează într-un singur flux mono pe client înainte ca ceva să părăsească aplicația. Când ambele surse curg printr-o singură conductă, track-urile nu pot muri independent — nu mai există o formă de feedback cu două dispozitive pe care browserul s-o detecteze și s-o muteze. Condiția declanșatoare pur și simplu încetează să existe.

A fost și un bonus încântător. Două fluxuri însemnau două WebSocket-uri client-către-backend și două conexiuni backend-către-furnizor; un singur flux mixat a colapsat asta la unul și unul. Repararea bug-ului căștilor a înjumătățit costul nostru de transcriere ca efect secundar.

Dar mixarea mono are o problemă evidentă: dacă amesteci ambele persoane într-un singur canal, cum mai știi cine a spus ce? Ai aruncat chiar separarea care îți spunea asta.

Reconstruirea lui „cine a vorbit" din energie

Răspunsul a fost să măsori intensitatea fiecărei surse înainte să le mixezi și să atașezi fiecărei bucăți de audio un indiciu despre care sursă a dominat — microfon, sistem sau tăcere. Sursa mai tare câștigă. Destul de simplu pe boxe.

Căștile intraauriculare aveau nevoie de încă un strat, și aici revine ecoul în poveste — îndreptat în direcția opusă față de ce ai ghici. Pe căști intraauriculare, vocea celeilalte persoane iese din cască și se întoarce în microfon. Așa că, fără grijă, ecoul ei, ajungând pe canalul tău de microfon, e atribuit ție. Reparația e un set de praguri conștiente de ecou: când sunetul de sistem redă peste un mic prag, ștacheta pe care microfonul tău trebuie s-o treacă pentru a conta drept „tu vorbind" sare mult în sus — de la un 0.015 normal la 0.1. Cu alte cuvinte: cât timp cealaltă persoană vorbește, microfonul tău trebuie să fie clar, fără echivoc, mai tare decât ecoul ei înainte să creditam ție cuvintele. Cadrele ambigue merg implicit la „sistem", pe presupunerea că sunt ecou. E o părtinire deliberată care nimerește atribuirea corect exact în situația care înainte o greșea.

Acel proof-of-concept — mixare mono plus detecție de voce bazată pe energie, conștientă de ecou — e strămoșul sistemului de atribuire pe care GeekBye îl rulează astăzi. Schela a fost smulsă în aceeași lansare odată ce s-a dovedit, iar matematica energiei s-a întărit mai târziu într-un modul testat cum se cuvine. Dar v1.6.12 e locul unde s-a născut ideea, sub presiune, de un utilizator cu o pereche de căști intraauriculare.

Trei lucruri pe care ne-a învățat bug-ul căștilor

  1. Un mâner sănătos poate căra date moarte. Toată investigația a stat pe încrederea în contorul nostru de energie față de flag-urile de status ale platformei — live, enabled, unmuted, toate mințind în timp ce eșantioanele erau zerouri. Monitorizează payload-ul, nu auto-raportul API-ului. Dacă un flux pretinde că e în regulă, măsoară dacă chiar e.
  2. Când platforma îți combate arhitectura, schimbă arhitectura — nu platforma. N-am putut învinge în dispută protecția de feedback a lui Chromium, iar fiecare încercare de a o dezactiva pe la margini a eșuat sau s-a întors împotriva noastră. Eliminarea condiției observabile — două fluxuri devenind unul — a făcut toată problema să dispară. E aproape întotdeauna mai ieftin să elimini declanșatorul decât să câștigi o luptă cu browserul.
  3. Când colapsezi canalele pentru robustețe, trebuie să reconstruiești ce ai distrus. Mixarea la mono a omorât separarea de canale care căra „cine a vorbit". Informația aceea a trebuit re-derivată din energia dinainte de mixare și praguri conștiente de ecou. Logica compensatoare nu e o completare de după la reparație — ea e jumătate din reparație.

Acesta e al patrulea capitol al poveștii de fiabilitate care devine GeekBye v2. Pentru capitolul anterior, vezi o conexiune pierdută n-ar trebui să-ți prăbușească toată aplicația (v1.6.8); pentru rescrierea conductei pe care s-a construit această muncă audio, am șters 5.000 de linii de cod audio (v1.6.0); iar pentru întregul arc, anatomia livrării software-ului la perfecțiune.