Steven
Steven7 min čítania

Ticho bolo nosné

Posledné dve vydania GeekBye v1 sú o tej istej nepríjemnej pravde: prepis v reálnom čase cez skutočnú sieť nie je bezstratový a čestný ťah je prestať predstierať, že je. v1.8.20 si nechalo kópiu každého úseku zvuku na disku, než ho počas opätovného pripojenia zahodilo, a začalo nahlas označovať medzery v prepise. v1.9.0 prestalo posielať ticho, aby ušetrilo šírku pásma — a zistilo, že ticho bolo presne tým signálom, podľa ktorého prepisovač vedel, že veta skončila. Dve vydania o cene zahadzovania vecí.

Inžinierstvo
Audio
Spoľahlivosť
Vydania GeekBye
Ticho bolo nosné

Posledné dve vydania GeekBye v1 — v1.8.20 a v1.9.0, tie tesne pred míľnikom druhej verzie — spája téma, ktorá sa stane zjavnou, až keď ich prečítaš spolu. Obe sú o veciach, ktoré aplikácia potichu zahadzovala, a obe dospejú k tomu istému záveru: vec, ktorú zahodíš kvôli efektivite, robila viac práce, než si si myslel. Jedno vydanie zahadzovalo zvuk počas sieťových opätovných pripojení. Druhé zahadzovalo ticho, aby ušetrilo šírku pásma. V oboch prípadoch sa zahodená vec ukázala byť nosnou.

Prepis v reálnom čase nie je bezstratový a v1.8.20 to priznáva

Tu je fakt o prúdovom prevode reči na text cez WebSocket, od ktorého sa ľahko odvráti pohľad: keď spojenie spadne a znovu sa pripojí — alebo keď aplikácia núdzovo prepne z jedného poskytovateľa prepisu na druhého, z Deepgram na ElevenLabs — klient zámerne zahodí úseky zvuku zachytené počas tej medzery. Musí. Čerstvo pripojený poskytovateľ, ktorý zrazu dostane nahromadenú dávku zastaraného zvuku, sa zadusí alebo, čo je horšie, zahákne svoj detektor hlasovej aktivity o nesprávnu vec. Takže klient tie úseky zahodí.

Problém nie je v zahadzovaní. Problém je v tom, čo zahadzovanie kedysi znamenalo: tie slová sa jednoducho nikdy neobjavili v prepise a nič ti nepovedalo, že chýbajú. Nahrávka vyzerala úplná. Nebola.

v1.8.20 na to útočí na dvoch frontoch a ani jeden z nich nie je „sprav, aby opätovné pripojenia nikdy nestratili zvuk“ — lebo to nejde, nie v reálnom čase. Namiesto toho robí stratu obnoviteľnou a čestnou.

Obnoviteľná polovica je trvalý PCM odvod. V handleri zachytávania v hlavnom procese sa každý zachytený úsek teraz zapisuje do súboru na disku predtým, než dosiahne brány zahadzovania pri opätovnom pripojení a v období odkladu:

every captured chunk is written to <userData>/sessions/session-<ts>.wav before realtime drop gates so reconnect-lost audio is preserved for a future batch-STT reconciliation pass

Mechanizmus je príjemne jednoduchý: teeChunkToDisk otvorí WriteStream, dopredu rezervuje 44-bajtovú WAV hlavičku, pridáva surové 16-bitové mono vzorky, ako prichádzajú, a pri zastavení hlavičku opraví finálnymi veľkosťami (fs.openSync(..., 'r+'), zápis 44 bajtov na offsete 0). Výsledkom je prehrateľný WAV s úplným zvukom — vrátane častí, ktoré prepis v reálnom čase nikdy nevidel. Keď nahrávanie skončí, vyšle udalosť session-audio-saved s cestou k súboru a počtom bajtov.

Dve poznámky o čestnosti tu majú význam, lebo je ľahké to preceniť. Po prvé: odvod v týchto vydaniach medzeru nevyplní. Zachováva surový zvuk pre neskorší zosúlaďovací prechod — dávkovú retranskripciu, ktorá zosúladí súbor so živým prepisom — čo je háčik, ktorý tieto vydania kladú, nie funkcia, ktorú dodávajú. Nadväzujúci commit dokonca opatrí session-audio-saved komentárom TODO smerujúcim na úlohy zosúlaďovania, aby to nikto nepovažoval za mŕtvy kód. Po druhé: to stojí disk. Tím odhadol zhruba 86 MB na 30-minútové nahrávanie, takže to isté vydanie pridáva StartupCleanupService, ktorý zmaže uložené relácie staršie než 7 dní. Záchranná sieť, ktorá rastie bez hraníc, je únik; práve to upratovanie robí odvod schopným dodania.

Čestná polovica je ešte jednoduchšia. Pri prvom pokuse o opätovné pripojenie klient zapíše skutočný, viditeľný záznam do prepisu:

[reconnecting — audio may be missing]

Ten jeden riadok je celá filozofia vydania v miniatúre. Aplikácia nemôže zaručiť, že počula všetko, takže prestáva naznačovať, že áno. Medzeru, ktorú vidíš, dokážeš obísť; medzera, ktorú nevidíš, je hlásenie chyby čakajúce na svoju chvíľu.

v1.9.0: ticho, ktoré si zoptimalizoval, bolo signálom

v1.9.0 je posledné vydanie v1 a práve v ňom backend odovzdal klientovi súbor zodpovedností za živý prepis — zahadzovať ticho, priraďovať každý riadok prepisu mikrofónu alebo systémovému zvuku podľa lokálnej časovej osi hlasovej aktivity, vlastniť slučku opätovného pripojenia, vynucovať limity nahrávania. Väčšina z toho je základ pre architektúru druhej verzie a bola vyrozprávaná inde. Ale jedna niť v ňom je najlepší malý príbeh z oboch vydaní, lebo je to oprava, ktorá musela zvrátiť škodu z optimalizácie, ktorá bola v izolácii úplne správna.

Začína zjavne dobrou zmenou: prestaň posielať ticho. Klient začal odfiltrovávať úseky, ktorých dominantným zdrojom bolo ticho, skôr než ich pošle cez soket — menej šírky pásma, menej zbytočného zvuku na spracovanie pre backend, a rešpektovalo to výslovný klientský kontrakt backendu, ktorý hovoril „neposielaj ticho“. Každý dôvod to urobiť bol správny.

A úplne to pokazilo finalizáciu výpovedí. Commit, ktorý to opravuje, vykladá príčinnú reťaz s nezvyčajnou úprimnosťou:

After the client began filtering silence chunks … ElevenLabs Scribe's VAD-based commit strategy no longer fires — EL needs ~1s of silence in the audio stream to commit a partial, and that silence never reaches it. Result: partials accumulate forever, UI shows trailing "...", utterances never finalize.

Toto je platónska podoba deravej abstrakcie, ktorá sa vracia hrýzť. Detektor hlasovej aktivity prepisovača nemá samostatný vstup „hovoriaci skončil“ — odvodzuje koniec vety z toho, že počuje asi sekundu ticha v zvuku. Ticho nebolo šumom na linke; bolo správou. Odfiltrovaním kvôli úspore šírky pásma klient náhodou prestal posielať ten jediný signál, ktorý prepisovaču hovoril, že veta skončila. Takže každý čiastočný prepis len ďalej rástol, chvostom miznúc do ..., ktoré sa nikdy nevyriešilo.

Mohol by si to opraviť tým, že ticho vrátiš späť — ale vtedy si vrátil optimalizáciu naspäť. Skutočná oprava je lepšia: pošli význam ticha bez posielania ticha. Klient teraz sleduje lokálne, ako dlho je zvuk tichý, a keď to prekročí SILENCE_COMMIT_THRESHOLD_MS = 1000 — zámerne zladené s vlastným prahom ticha VAD backendu — vyšle výslovný signál commit mimo pásma: sendMixedChunk si všimne trvalé ticho, pošle realtime-audio:commit cez IPC, hlavný proces prepošle { type: 'commit' } po sokete a backend povie ElevenLabs, aby finalizoval čiastočný výsledok. Dva malé kúsky stavu, silenceStartedAt a silenceCommitFired, mu bránia spúšťať sa opakovane. Výhra na šírke pásma prežije; finalizácia znovu funguje; koncové ... sa vyrieši.

K tomuto vydaniu je aj malá, čestná poznámka pod čiarou: neohraničená logika opätovného pripojenia, ktorú v1.9.0 zaviedlo, bola najprv postavená v rendereri ako WsReconnectController, potom v rámci toho istého vydania zmazaná, keď skutočná implementácia pristála v handleri hlavného procesu, kam patrila. Samotný príbeh opätovného pripojenia — neohraničený backoff, striedajúci sa poskytovatelia každých päť zlyhaní — je vyrozprávaný vo vlastnej kapitole; čo v1.9.0 pridáva, je to, že práve tu sa tá architektúra zrodila a kde ju klient prvýkrát prevzal.

Tri veci, ktoré nás tieto vydania naučili

  1. Keď nemôžeš zabrániť strate, sprav ju obnoviteľnou a sprav ju viditeľnou. v1.8.20 nebráni opätovným pripojeniam zahadzovať zvuk — to v reálnom čase fyzicky nejde. Nechá si kópiu surového zvuku na neskôr a označí medzeru v prepise. Ani jedno nie je efektné; spolu premieňajú tichú stratu dát na známy, ohraničený, obnoviteľný stav.
  2. Záchranná sieť potrebuje vlastné hranice. Trvalý odvod by potichu zaplnil disk, keby sa nechal na pokoji — oprava, ktorá sa stáva chybou v spomalenom zábere. Sedemdňové upratovanie dodané v tom istom vydaní nie je dodatočný nápad; je to práve to, čo vôbec robí odvod bezpečným na dodanie. Ak pridáš mechanizmus, ktorý hromadí, pridaj tým istým dychom mechanizmus, ktorý ho oreže.
  3. Vec, ktorú zahodíš kvôli efektivite, môže byť signálom. Najostrejšia lekcia z oboch vydaní: odfiltrovať ticho bolo správne na každej osi, ktorú bežne skontroluješ — šírka pásma, záťaž, dodržanie kontraktu — a aj tak to pokazilo produkt, lebo systém nižšie v prúde vyčítaval z ticha význam. Keď odstraňuješ dáta kvôli efektivite, spýtaj sa, čo z tých dát niečo odvodzovalo. Niekedy musíš poslať význam výslovne práve preto, že si prestal posielať vec, z ktorej sa kedysi odvodzoval.

A to je koniec príbehu prvej verzie — posledné spevňovanie pred prepísaním. Pre predchádzajúcu kapitolu — tri slovesá, ktoré udržujú Web Audio nažive (v1.8.16 + v2.0.2); pre to, kam to všetko smerovalo — čo naozaj stojí verzia dva: 206 commitov (v2.0.0); a pre celý oblúk — anatómiu dodávania softvéru do dokonalosti.

Súvisiace články

Tri slovesá, ktoré udržujú Web Audio nažive
Steven
Steven8 min čítania

Tri slovesá, ktoré udržujú Web Audio nažive

Dve bodové vydania GeekBye, dva mesiace od seba a v dvoch rôznych súboroch, naučili náš audio kód tú istú lekciu z opačných koncov: prestaň zaobchádzať s prehliadačovým AudioContext ako s jednorazovým. Jedno vydanie sa naučilo resume() kontextu, ktorý macOS potichu pozastavil uprostred nahrávania; druhé sa naučilo suspend() namiesto close(), aby na seba nadväzujúce relácie prestali narážať do Chromiom nastaveného stropu zhruba šiestich kontextov. Resume, suspend, close — to je celá zápletka.

Inžinierstvo
Audio
Desktop
Prerušené spojenie by nemalo zhodiť celú aplikáciu — no tá naša ju zhodila
Steven
Steven6 min čítania

Prerušené spojenie by nemalo zhodiť celú aplikáciu — no tá naša ju zhodila

Keď náš backend uprostred stretnutia vypadol, nielenže pozastavil prepis — zhodil celú aplikáciu. Príčinou bola jediná neobslúžená udalosť a oprava mala desať riadkov. Toto je klaster vydaní, vďaka ktorému GeekBye zostáva prihlásený a pripojený aj cez veci, ktoré ho kedysi zabíjali.

Inžinierstvo
Spoľahlivosť
Electron
Bezpečnostná kontrola, ktorá spravila našu aplikáciu nezatvárateľnou
Steven
Steven5 min čítania

Bezpečnostná kontrola, ktorá spravila našu aplikáciu nezatvárateľnou

Automatická aktualizácia bola najťažšia funkcia, akú sme kedy vydali — šesť vydaní za štyri dni, aby prestala kaziť aplikáciu. Najhoršiu chybu sme spôsobili sami snahou byť opatrní: 500-milisekundová „bezpečnostná" kontrola, ktorá premenila neúspešnú aktualizáciu na proces, ktorý sa doslova nedal zatvoriť.

Inžinierstvo
Electron
Spoľahlivosť