Steven
Steven6 хв читання

Баг з навушниками-вкладишами: коли співрозмовник зникав з нашої транскрипції

Двоканальна транскрипція працювала бездоганно на динаміках ноутбука та на повнорозмірних навушниках. Потім хтось вставив навушники-вкладиші — і людина, з якою він говорив, просто зникала з транскрипції, бо Chromium мовчки приглушував наше аудіо тієї миті, коли співрозмовник починав говорити. Одинадцять виправлень провалилися. Дванадцяте змінило всю архітектуру.

Інженерія
Аудіо
Транскрипція
Релізи GeekBye
Баг з навушниками-вкладишами: коли співрозмовник зникав з нашої транскрипції

GeekBye транскрибує дві сторони розмови: ваш мікрофон на одному каналі, звук співрозмовника (те, що програє ваш комп'ютер) на іншому. Це працювало бездоганно на динаміках ноутбука. Працювало на повнорозмірних навушниках. Потім користувач вставив навушники-вкладиші — і людина, з якою він говорив, зникала з транскрипції. Не спотворена. Не приписана не тому. Зникла.

Це історія GeekBye v1.6.12, бага, що потребував одинадцяти провалених виправлень, і моменту, коли ми зрозуміли, що боремося з браузером замість того, щоб працювати разом з ним.

Доріжка, яка була живою і порожньою

Симптом був моторошний. Ви починали зустріч, усе виглядало добре, ви вимовляли одне слово — і з цієї миті аудіоканал співрозмовника йшов у чисту цифрову тишу. Ми це виміряли: сигнал системного звуку сидів на нормальному рівні енергії (RMS близько 0.03–0.06), і тієї миті, коли мікрофон уловлював ваш голос, він падав до 0.0000 і залишався там до кінця сесії.

Ось частина, яка коштувала нам днів. Кожен API браузера наполягав, що аудіодоріжка цілком справна. readyState: live. enabled: true. muted: false. За кожним прапорцем статусу, який виставляла платформа, потік був у порядку. Мертвими були семпли — жива труба, що несла самі нулі. Ми довго не вірили власному вимірювачу енергії, бо офіційна версія стверджувала, що доріжка працює.

Тригер був специфічний для навушників-вкладишів, і причина фізична: навушник-вкладиш — це один пристрій, який водночас є вашим мікрофонним входом і аудіовиходом. Динаміки ноутбука та повнорозмірні навушники це розділяють — вихід іде в одне місце, мікрофон в іншому. Але коли вхід і вихід — той самий пристрій, у вас за визначенням є шлях зворотного зв'язку. Наш твердий висновок (це живе в нутрощах браузера, тож ми не можемо процитувати главу і вірш) полягає в тому, що Chromium виявляє цю форму мікрофон-плюс-динамік-на-одному-пристрої, вирішує, що це петля зворотного зв'язку, готова заверещати, і захисно приглушує захоплення — мовчки, не повідомляючи застосунку жодним прапорцем, який застосунок може прочитати.

Одинадцять виправлень, кілька з них гірші

Спершу ми атакували це очевидними способами і записали кожну спробу. Вимкнути придушення відлуння, придушення шуму та автопідсилення на обох потоках. Запускати обидва потоки послідовно із затримкою замість одночасного старту. Дати кожному потоку власну частоту дискретизації. Додати тихий вузол підсилення. Прогнати все через один спільний аудіоконтекст. Перейти на AudioWorklet. Спуститися аж до сирого процесора доріжки WebCodecs.

Одинадцять підходів. Вони не просто провалилися — кілька зробили гірше. Один вніс 30-секундну затримку. Інший призвів до того, що транскрипція вашого мікрофона з'являлася позначеною як співрозмовник. Ми загрузли в хащах, лікували кожен симптом, а баг усе зміщувався.

Проривом стало переформулювання: кожне виправлення досі намагалося втримати два окремі потоки здоровими. Але два окремі потоки і були саме тим, що евристика зворотного зв'язку Chromium могла побачити і приглушити. А що, як не буде чого порівнювати?

Виправлення: дай браузеру один потік, а не два

v1.6.12 припинив боротися і змінив архітектуру. Замість того щоб надсилати мікрофон і системний звук як два незалежні потоки, він мікшує їх в один моно-потік на клієнті, перш ніж будь-що покине застосунок. Коли обидва джерела течуть через один конвеєр, доріжки не можуть померти незалежно — більше немає двопристроєвої форми зворотного зв'язку, яку браузер міг би виявити і приглушити. Умова-тригер просто перестає існувати.

Був і приємний бонус. Два потоки означали два WebSocket клієнт-до-бекенду і два з'єднання бекенд-до-провайдера; один змікшований потік згорнув це до одного й одного. Виправлення бага з навушниками-вкладишами вдвічі врізало нашу вартість транскрипції як побічний ефект.

Але в моно-мікшування є очевидна проблема: якщо ти зливаєш обох людей в один канал, як усе ще зрозуміти, хто що сказав? Ти викинув рівно те розділення, яке про це повідомляло.

Перезбирання «хто говорив» з енергії

Відповіддю було виміряти гучність кожного джерела до їх мікшування і додати до кожного шматка аудіо підказку про те, яке джерело домінувало — мікрофон, система чи тиша. Хто гучніший, той і перемагає. Досить просто на динаміках.

Навушникам-вкладишам знадобився ще один шар, і тут відлуння повертається в історію — спрямоване протилежно до того, що ви б припустили. На навушниках-вкладишах голос співрозмовника витікає з вкладиша назад у мікрофон. Тож без обережності його відлуння, що приходить на ваш мікрофонний канал, приписується вам. Виправлення — це набір порогів з урахуванням відлуння: коли системний звук грає вище невеликого порога, планка, яку ваш мікрофон має взяти, щоб рахуватися як «ви говорите», підскакує високо вгору — з нормального 0.015 до 0.1. Інакше кажучи: поки співрозмовник говорить, ваш мікрофон має бути явно, однозначно гучнішим за його відлуння, перш ніж ми припишемо слова вам. Неоднозначні кадри за замовчуванням ідуть у «систему» з припущення, що це відлуння. Це навмисний перекіс, який робить атрибуцію правильною рівно в тій ситуації, де вона раніше помилялася.

Цей proof-of-concept — моно-мікшування плюс засноване на енергії розпізнавання голосу з урахуванням відлуння — предок системи атрибуції, на якій GeekBye працює сьогодні. Риштування видерли в тому ж релізі, щойно воно себе виправдало, а математика енергії пізніше затверділа в як слід протестований модуль. Але v1.6.12 — це місце, де ідея народилася, під тиском, завдяки користувачеві з парою навушників-вкладишів.

Три речі, яких навчив баг з навушниками-вкладишами

  1. Справний дескриптор може нести мертві дані. Усе розслідування трималося на довірі нашому вимірювачу енергії понад прапорцями статусу платформи — live, enabled, unmuted, усі вони брехали, поки семпли були нулями. Стеж за корисним навантаженням, а не за самозвітом API. Якщо потік заявляє, що він у порядку, виміряй, чи так це насправді.
  2. Коли платформа воює з твоєю архітектурою, змінюй архітектуру — не платформу. Ми не могли переспорити захист від зворотного зв'язку Chromium, і кожна спроба вимкнути його по краях провалювалася або била рикошетом. Усунення спостережуваної умови — два потоки, що стають одним — змусило всю проблему зникнути. Майже завжди дешевше прибрати тригер, ніж виграти бій із браузером.
  3. Коли згортаєш канали заради надійності, ти зобов'язаний перезібрати те, що знищив. Мікшування в моно вбило розділення каналів, що несло «хто говорив». Цю інформацію довелося вивести наново з енергії до міксу і порогів з урахуванням відлуння. Компенсуюча логіка — не додаток до виправлення; вона і є половина виправлення.

Це четвертий розділ історії про надійність, яка стає GeekBye v2. За попереднім розділом дивись обірване з'єднання не має ронити весь твій застосунок (v1.6.8); за переписуванням конвеєра, на якому будувалася ця робота зі звуком, ми видалили 5000 рядків аудіокоду (v1.6.0); а за всією дугою, анатомію доведення софту до досконалості.

Схожі статті

Ми видалили 5 000 рядків аудіокоду — і транскрипти почали з'являтися двічі
Steven
Steven5 хв читання

Ми видалили 5 000 рядків аудіокоду — і транскрипти почали з'являтися двічі

GeekBye v1.6 вирвав два вбудовані Swift-транскрайбери й замінив їх одним уніфікованим конвеєром — чиста різниця понад 5 000 видалених рядків, зроблено просто тоді, коли люди були посеред зустрічі в застосунку. А потім транскрипти почали з'являтися двічі, бо баг переїхав на той єдиний шар, який досі вважав, що двигунів два.

Інженерія
Транскрипція
Архітектура
Транскрипція в реальному часі, коли файрвол блокує WebSocket
Steven
Steven4 хв читання

Транскрипція в реальному часі, коли файрвол блокує WebSocket

Корпоративні мережі обожнюють пропускати HTTPS і тихцем убивати upgrade до WebSocket. Це непомітно ламає транскрипцію в реальному часі. GeekBye v2.0.8 автоматично переходить на чисто-HTTPS транспорт — а його випуск викрив баг, який зробив би всю функцію непотрібною.

Транскрипція
Мережі
Інженерія
Чому ШІ-транскрипція перекручує технічні терміни (і як ми це виправили)
Steven
Steven6 хв читання

Чому ШІ-транскрипція перекручує технічні терміни (і як ми це виправили)

Жива сесія почула «what is the pointer in C++» як «what is the point in life». Ось слід розслідування від того транскрипту до GeekBye v2.0.11 — keyterm biasing, гонка, що обривала з'єднання, і день, коли наш власний фікс обернувся проти нас.

Транскрипція
Надійність
Інженерія