Steven
Steven7 min čtení

Ticho bylo nosné

Poslední dvě vydání GeekBye v1 jsou o téže nepříjemné pravdě: přepis v reálném čase přes skutečnou síť není bezeztrátový a poctivý krok je přestat předstírat, že je. v1.8.20 uchovávalo kopii každého audio chunku na disku, než ho během reconnectu zahodilo, a začalo mezery v přepisu označovat nahlas. v1.9.0 přestalo posílat ticho kvůli úspoře šířky pásma — a zjistilo, že ticho bylo přesně ten signál, podle kterého přepisovač poznal, že věta skončila. Dvě vydání o ceně zahazování věcí.

Inženýrství
Audio
Spolehlivost
Vydání GeekBye
Ticho bylo nosné

Poslední dvě vydání GeekBye v1 — v1.8.20 a v1.9.0, ta těsně před milníkem verze 2 — sdílejí téma, které je zřejmé teprve, když je čteš spolu. Obě jsou o věcech, které aplikace tiše zahazovala, a obě docházejí ke stejnému závěru: věc, kterou zahazuješ kvůli efektivitě, odváděla víc práce, než sis myslel. Jedno vydání zahazovalo audio během síťových reconnectů. Druhé zahazovalo ticho kvůli úspoře šířky pásma. V obou případech se zahazovaná věc ukázala být nosná.

Přepis v reálném čase není bezeztrátový a v1.8.20 to přiznává

Tady je fakt o streamovaném speech-to-textu přes WebSocket, od kterého se snadno odvrací pohled: když spojení spadne a znovu se připojí — nebo když aplikace přepne z jednoho poskytovatele přepisu na druhého, z Deepgramu na ElevenLabs — klient záměrně zahodí audio chunky zachycené během té mezery. Musí. Čerstvě připojený poskytovatel, který náhle dostane nahromaděnou dávku zastaralého audia, se zakucká, nebo hůř, zachytí svůj detektor hlasové aktivity na špatnou věc. Takže klient ty chunky zahodí.

Problém není v zahazování. Problém je v tom, co zahazování dřív znamenalo: ta slova se prostě nikdy v přepisu neobjevila a nic ti neřeklo, že chybí. Nahrávka vypadala úplná. Nebyla.

v1.8.20 to řeší na dvou frontách a ani jedna z nich není „ať reconnecty nikdy neztratí audio" — protože to nejde, ne v reálném čase. Místo toho dělá ztrátu obnovitelnou a poctivou.

Obnovitelná polovina je trvalá PCM odbočka. V capture handleru hlavního procesu se teď každý zachycený chunk zapíše do souboru na disku předtím, než dorazí k zahazovacím bránám reconnectu a ochranné lhůty:

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

Mechanismus je příjemně nízkotechnologický: teeChunkToDisk otevře WriteStream, dopředu rezervuje 44bajtovou WAV hlavičku, přidává surové 16bitové mono vzorky, jak přicházejí, a při zastavení hlavičku doplní o finální velikosti (fs.openSync(..., 'r+'), zápis 44 bajtů na offset 0). Výsledkem je přehratelný WAV kompletního audia — včetně částí, které živý přepis nikdy neviděl. Když nahrávání skončí, vyšle událost session-audio-saved s cestou k souboru a počtem bajtů.

Tady jsou důležité dvě poznámky v duchu poctivosti, protože je snadné to přehnat. Zaprvé: odbočka mezeru v těchto vydáních nevyplní. Zachovává surové audio pro pozdější rekonciliační průchod — dávkový přepis, který soubor srovná s živým přepisem — což je háček, který tato vydání pokládají, ne funkce, kterou dodávají. Následný commit dokonce session-audio-saved opatří TODO poznámkou odkazující na rekonciliační issues, aby si to nikdo nespletl s mrtvým kódem. Zadruhé: tohle stojí místo na disku. Tým odhadl zhruba 86 MB na 30minutové nahrávání, takže totéž vydání přidává StartupCleanupService, který promazává uložené sesiony starší než 7 dní. Záchranná síť, která roste bez omezení, je únik; úklid je to, co dělá odbočku dodatelnou.

Poctivá polovina je ještě jednodušší. Při prvním pokusu o reconnect klient zapíše do přepisu skutečný, viditelný záznam:

[reconnecting — audio may be missing]

Ten jediný řádek je celá filozofie vydání v miniatuře. Aplikace nemůže zaručit, že slyšela všechno, tak přestane naznačovat, že ano. Mezera, kterou vidíš, je mezera, kterou umíš obejít; mezera, kterou nevidíš, je bug report čekající, až se stane.

v1.9.0: ticho, které jsi odoptimalizoval, byl signál

v1.9.0 je poslední vydání v1 a je to místo, kde backend předal klientovi sadu zodpovědností za živý přepis — zahazování ticha, přiřazování každého řádku přepisu k mikrofonu nebo systémovému audiu z lokální časové osy hlasové aktivity, vlastnictví reconnect smyčky, vynucování limitů nahrávání. Většina toho je základ pro architekturu verze 2 a byla vyprávěna jinde. Ale jedno vlákno v tom je nejlepší malý příběh z obou vydání, protože je to oprava, která musela napravit škodu způsobenou optimalizací, jež byla sama o sobě naprosto správná.

Začíná to očividně dobrou změnou: přestaň posílat ticho. Klient začal odfiltrovávat chunky, jejichž dominantním zdrojem bylo ticho, ještě než je poslal přes socket — méně šířky pásma, méně zbytečného audia ke zpracování na backendu, a ctilo to explicitní klientský kontrakt backendu, který říkal „neposílej ticho". Každý důvod to udělat byl rozumný.

A úplně to rozbilo finalizaci výpovědí. Commit, který to opravuje, rozkládá kauzální řetězec s neobvyklou otevřeností:

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.

Tohle je platónská podoba děravé abstrakce, která se mstí. Detektor hlasové aktivity přepisovače nemá separátní vstup „mluvčí domluvil" — odvozuje konec věty z toho, že v audiu zaslechne zhruba sekundu ticha. Ticho nebylo šum na drátě; byla to zpráva. Tím, že ho klient odfiltroval kvůli úspoře šířky pásma, náhodně přestal posílat ten jediný signál, který přepisovači řekl, že věta skončila. Takže každý částečný přepis prostě dál rostl, ocas se táhl do ..., které se nikdy nevyřešilo.

Mohl bys to opravit tím, že ticho vrátíš zpět — ale pak jsi odoptimalizoval optimalizaci. Skutečná oprava je lepší: pošli význam ticha, aniž bys posílal ticho. Klient teď lokálně sleduje, jak dlouho je audio tiché, a když to překročí SILENCE_COMMIT_THRESHOLD_MS = 1000 — záměrně sladěné s vlastním prahem ticha VAD na backendu — vystřelí explicitní signál commit mimo pásmo: sendMixedChunk si všimne trvalého ticha, pošle realtime-audio:commit přes IPC, hlavní proces přepošle { type: 'commit' } po socketu a backend řekne ElevenLabs, ať částečný přepis finalizuje. Dva malé kousky stavu, silenceStartedAt a silenceCommitFired, brání opakovanému vystřelení. Výhra v šířce pásma přežívá; finalizace zase funguje; koncové ... se vyřeší.

I k tomuto vydání patří malá, poctivá poznámka pod čarou: neomezená reconnect logika, kterou v1.9.0 zavedlo, byla nejdřív postavena v rendereru jako WsReconnectController, a pak během téhož vydání smazána, jakmile skutečná implementace přistála v handleru hlavního procesu, kam patřila. Samotný příběh reconnectu — neomezený backoff, střídání poskytovatelů každých pět selhání — je vyprávěn ve vlastní kapitole; co v1.9.0 přidává, je to, že je to místo, kde se ta architektura zrodila a kde ji klient poprvé převzal do vlastnictví.

Tři věci, které nás tato vydání naučila

  1. Když nemůžeš zabránit ztrátě, udělej ji obnovitelnou a udělej ji viditelnou. v1.8.20 nebrání reconnectům v zahazování audia — to v reálném čase fyzicky nejde. Uchovává kopii surového audia na později a označí mezeru v přepisu. Ani jedno není okázalé; společně mění tichou ztrátu dat na známý, ohraničený, obnovitelný stav.
  2. Záchranná síť potřebuje vlastní meze. Trvalá odbočka by potichu zaplnila disk, kdyby ji člověk nechal být — oprava, která se stane bugem ve zpomaleném záběru. Sedmidenní úklid dodaný v témže vydání není dodatečný nápad; je to to, co vůbec dělá odbočku bezpečnou k dodání. Když přidáš mechanismus, který se hromadí, přidej jedním dechem i mechanismus, který ho promazává.
  3. Věc, kterou zahodíš kvůli efektivitě, může být signál. Ta nejostřejší lekce z obou vydání: odfiltrování ticha bylo správné na každé ose, kterou bys normálně kontroloval — šířka pásma, zátěž, dodržení kontraktu — a přesto to rozbilo produkt, protože navazující systém do ticha vkládal význam. Když odstraňuješ data kvůli efektivitě, ptej se, co si z těch dat něco odvozovalo. Někdy musíš poslat význam explicitně právě proto, že jsi přestal posílat věc, ze které se dřív odvozoval.

A to je konec příběhu verze 1 — poslední zpevňování před přepisem. Předchozí kapitolu najdeš v tři slovesa, která udržují web audio naživu (v1.8.16 + v2.0.2); kam to všechno směřovalo, v co doopravdy stojí verze 2: 206 commitů (v2.0.0); a celý oblouk v anatomii dodávání softwaru k dokonalosti.

Související články

Tři slovesa, která udržují Web Audio naživu
Steven
Steven8 min čtení

Tři slovesa, která udržují Web Audio naživu

Dvě bodová vydání GeekBye, dva měsíce od sebe a ve dvou různých souborech, naučila náš audio kód stejné lekci z opačných konců: přestaň zacházet s AudioContext prohlížeče jako s jednorázovým. Jedno vydání se naučilo volat resume() na kontextu, který macOS tiše suspendoval uprostřed nahrávání; druhé se naučilo volat suspend() místo close(), aby po sobě jdoucí sesiony přestaly narážet do stropu Chromia zhruba šesti kontextů. Resume, suspend, close — to je celá zápletka.

Inženýrství
Audio
Desktop
Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala
Steven
Steven6 min čtení

Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala

Když nám backend spadl uprostřed schůzky, nezastavil jen přepis — shodil celou aplikaci. Příčinou byla jediná neošetřená událost a oprava měla deset řádků. Tohle je shluk vydání, díky kterému GeekBye zůstává přihlášený a připojený i skrz věci, které ho dřív zabíjely.

Inženýrství
Spolehlivost
Electron
Bezpečnostní kontrola, kvůli které nešla naše aplikace ukončit
Steven
Steven5 min čtení

Bezpečnostní kontrola, kvůli které nešla naše aplikace ukončit

Automatická aktualizace byla nejtěžší funkce, jakou jsme kdy vydali — šest vydání za čtyři dny, aby přestala odrovnávat aplikaci. Nejhorší chybu jsme si způsobili sami ve snaze být opatrní: 500milisekundová „bezpečnostní" kontrola, která z neúspěšné aktualizace udělala proces, který se doslova nedal ukončit.

Inženýrství
Electron
Spolehlivost