Добавить на сайт кнопку микрофона кажется простой задачей: записать голос пользователя, распознать слова и вставить их в текстовое поле. На практике обеспечить надежную работу в браузерах на компьютерах и телефонах значительно сложнее, особенно если всё распознавание речи должно выполняться локально, без отправки звука с микрофона на сервер.
Сложность не сводится к выбору модели распознавания речи. Для работающей реализации нужно правильно организовать разрешения на доступ к микрофону, запись звука, ресемплирование, работу браузерных воркеров, WebAssembly, инициализацию моделей ONNX, токенизацию, кэширование и взаимодействие с текстовым полем приложения.
В этой статье разбирается практическая реализация приватного голосового ввода с помощью OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet и отдельного Web Worker. Также рассмотрены реальные ошибки интеграции, из-за которых кнопка микрофона может бесконечно показывать загрузку, хотя файлы модели уже успешно скачались.
Цель проста: посетитель нажимает кнопку микрофона, произносит текст и видит распознанные слова в существующем поле ввода. Перед отправкой он может проверить и отредактировать результат. Саму запись голоса при этом не нужно передавать за пределы браузера.
Что означает полностью локальное распознавание речи в браузере
Есть два принципиально разных способа добавить голосовой ввод на сайт.
Первый — использовать сервис распознавания речи. Браузер записывает звук, отправляет его удаленному сервису и получает готовый текст. Это может упростить разработку, но создает зависимость от сети, вопросы конфиденциальности и доступности провайдера, а также потенциальные расходы, зависящие от использования.
Второй способ — выполнять инференс непосредственно в браузере. Сайт скачивает модель распознавания речи и среду для ее выполнения. Браузер записывает звук с микрофона, преобразует его в числовые отсчеты, запускает модель локально и получает расшифровку без сервера распознавания речи.
Эта статья целиком посвящена второму способу.
Важно не путать обычное браузерное распознавание речи с гарантированно локальным. Web Speech API может использовать серверный движок распознавания. В некоторых новых реализациях есть параметр processLocally, но его поддержка различается между браузерами и операционными системами. Если нужна строго локальная обработка, нельзя автоматически переключаться на обычный SpeechRecognition при возникновении ошибки.
Вместо этого приложение самостоятельно управляет всей цепочкой инференса.
Microphone
|
v
AudioWorklet
|
v
Float32 PCM audio
|
v
Dedicated Web Worker
|
v
16 kHz resampling
|
v
Whisper + ONNX Runtime Web
|
v
Tokenizer and text decoding
|
v
React input stateСайт по-прежнему отдает JavaScript, WebAssembly, конфигурационные файлы и веса модели. Это обычные загрузки статических файлов. Принципиальное отличие в том, что звук с микрофона обрабатывается локально, а не отправляется на сервер для распознавания. Если позже пользователь отправит полученный текст через обычную форму, это уже отдельное действие приложения.
Выбор практичной модели: Whisper Tiny или Base
Whisper — семейство многоязычных моделей распознавания речи. Способность одной модели распознавать множество языков особенно полезна для многоязычных сайтов.
Для коротких голосовых сообщений две модели обеспечивают разумный баланс качества распознавания и потребления ресурсов браузера: Whisper Tiny и Whisper Base.
| Модель | Примерный размер выбранных весов ONNX Q8 | Рекомендуемое применение |
|---|---|---|
| Whisper Tiny | 41 MB | Осторожный выбор для мобильных и маломощных устройств |
| Whisper Base | 77 MB | Более качественное распознавание на проверенных, достаточно мощных устройствах |
Эти размеры относятся к выбранным файлам энкодера и декодера ONNX в исследованных сборках моделей. Они не включают все вспомогательные файлы, и их нельзя путать с пиковым потреблением памяти браузером.
Whisper Tiny привлекательна тем, что быстрее скачивается, обычно требует меньше вычислительных ресурсов и лучше подходит для мобильных устройств с неизвестными возможностями. Whisper Base в целом распознает речь точнее, особенно на некоторых языках, где модели меньшего размера чаще ошибаются.
Выбирать модель только по объему физической оперативной памяти не стоит. Браузеру может быть доступно существенно меньше памяти, чем указано в характеристиках устройства. Имеют значение память, уже занятая страницей, выделения WebAssembly, временные тензоры и нехватка памяти на уровне операционной системы.
Осторожная стратегия — начинать с Tiny на мобильных устройствах с неизвестными возможностями, использовать Base только на подходящих классах устройств и переключаться с Base на Tiny, если инициализация модели завершилась ошибкой.
Перед загрузкой Tiny неудачно инициализированный экземпляр Base необходимо освободить. Если обе модели останутся в памяти, переключение на более легкую модель потеряет смысл.
Почему WebAssembly — разумный вариант по умолчанию для продакшена
ONNX Runtime Web позволяет выполнять нейросети в браузере. Для этой задачи WebAssembly обеспечивает практичный путь инференса на CPU в основных настольных и мобильных браузерах.
WebGPU может ускорять поддерживаемые модели на подходящем оборудовании, но само его наличие еще не означает, что вся система распознавания речи будет работать. Если браузер предоставляет navigator.gpu, это не гарантирует ни поддержку платформы в ONNX Runtime, ни корректную инициализацию конкретного графа Whisper, ни отсутствие проблем с ресурсами при повторных запусках инференса.
Поэтому осторожная реализация может использовать WASM как основной движок в продакшене, а WebGPU оценивать отдельно после проверки совместимости, точности, памяти и повторных сеансов.
На первом этапе однопоточный WASM также позволяет не делать весь сайт зависимым от междоменной изоляции и SharedArrayBuffer.
env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;Выполнение инференса в отдельном воркере выносит тяжелые вычисления из основного потока JavaScript. Пока модель обрабатывает звук, поле ввода, экранная клавиатура, прокрутка и интерфейс приложения остаются отзывчивыми.
Не загружайте распознавание речи при первоначальном открытии страницы
Функция микрофона не должна создавать заметную нагрузку на сеть и память для посетителей, которые ей никогда не воспользуются.
Не импортируйте Transformers.js заранее в основной бандл приложения. Не инициализируйте ONNX Runtime при открытии страницы и не скачивайте веса модели автоматически во время простоя.
Вместо этого загружайте систему распознавания только после явного нажатия пользователем кнопки микрофона.
При первом использовании подойдет такая последовательность:
- Обработать нажатие кнопки микрофона.
- Запросить разрешение на доступ к микрофону через
getUserMedia(). - Если разрешение не получено, показать ошибку, не скачивая модель.
- Если разрешение получено, освободить временный поток микрофона на время подготовки модели.
- Динамически запустить отдельный воркер и загрузить выбранную локальную модель.
- Дождаться подтверждения от воркера, что система распознавания готова.
- Автоматически начать запись, если пользователь не отменил действие.
- Показать понятное состояние записи звука.
Запрос разрешения должен быть связан с действием пользователя. Приложение также должно корректно обрабатывать отмену и ограничения записи в отдельных браузерах, а не предполагать, что запись всегда можно незаметно перезапустить.
Во время подготовки модели нужен видимый статус, например Подготовка голосового ввода. Индикатор загрузки без понятного изменения состояния может создать впечатление, что микрофон сломан, даже если модель успешно скачалась.
После инициализации воркер можно ненадолго оставить для следующего голосового ввода. Тайм-аут простоя около минуты — один из разумных отправных вариантов. Затем воркер следует завершить и освободить модель из памяти. Файлы модели в кэше браузера могут остаться для следующих посещений.
Запись несжатого звука через AudioWorklet
Whisper нужны числовые отсчеты звука. MediaRecorder отлично подходит для записи аудиофайлов, но при локальном распознавании речи его использование может добавить ненужный этап кодирования и последующего декодирования.
Более простой путь обработки выглядит так:
getUserMedia()
|
v
AudioContext
|
v
AudioWorklet
|
v
Float32 PCM buffers
|
v
Dedicated WorkerAudioWorklet выполняет код обработки звука вне основного потока JavaScript приложения. Он может получать отсчеты PCM, сводить многоканальный звук к моно, рассчитывать простые показатели громкости и передавать накопленные буферы воркеру.
Упрощенная инициализация микрофона выглядит так:
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: { ideal: 1 },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
const context = new AudioContext();
await context.audioWorklet.addModule(
'/assets/speech/pcm-worklet.js'
);
const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
context,
'pcm-capture'
);
source.connect(processor);
// Connect the worklet through an inaudible output path
// if required to keep the audio processing graph active.Ограничения в настройках микрофона — это предпочтения, а не гарантии. Браузеры могут использовать разные алгоритмы эхоподавления, шумоподавления и автоматической регулировки усиления.
Важная деталь: частота дискретизации микрофона не обязательно равна 16 кГц. Многие аудиоконтексты работают на 44,1 или 48 кГц. Whisper ожидает входной сигнал 16 кГц, поэтому записанные отсчеты необходимо правильно ресемплировать.
Просто отбрасывать каждый второй или третий отсчет в общем случае неправильно. Подходящий ресемплер с низкочастотной фильтрацией, например оконный sinc-фильтр, снижает наложение спектров и сохраняет полезные речевые частоты при уменьшении частоты дискретизации.
Ресемплирование лучше выполнять в отдельном воркере, а не в обработчике AudioWorklet, работающем в реальном времени.
Эффективная передача звука между потоками браузера
AudioWorklet может создавать множество небольших фрагментов аудио. Отправка каждого кванта обработки звука отдельным сообщением создает лишние накладные расходы.
Практичный подход — накапливать небольшие порции PCM и передавать каждую как ArrayBuffer.
worker.postMessage(
{
type: 'audio-chunk',
buffer,
},
[buffer]
);Список передаваемых объектов важен: он передает владение ArrayBuffer вместо ненужного полного копирования.
Воркер может добавлять эти фрагменты в накопитель аудио и объединять их после остановки записи.
Также полезно отслеживать реальную частоту дискретизации при записи, количество собранных отсчетов и продолжительность записи. Эти метаданные помогают диагностировать записи без звука, не сохраняя конфиденциальные аудиоданные в журнале.
Обнаружение пауз без дополнительной нейросети
Для диктовки коротких текстовых сообщений не обязательно нужен нейросетевой детектор голосовой активности.
Адаптивный детектор RMS дает простую приблизительную оценку речевой активности. RMS измеряет амплитуду сигнала, позволяя приложению определить, когда прежде говоривший человек замолчал.
Интерфейс всегда должен позволять вручную остановить запись повторным нажатием кнопки микрофона. Автоматическое определение тишины — удобство, а не обязательное условие успешного распознавания.
Разумные начальные значения для короткой диктовки:
| Параметр | Начальное значение |
|---|---|
| Интервал анализа | 20 ms |
| Калибровка шума | 300 ms |
| Подтверждение речи | 140 ms |
| Тишина до автоматической остановки | 1,4 секунды |
| Тайм-аут при отсутствии речи | 8 секунд |
| Максимальная продолжительность записи | 30 секунд |
Это отправные значения настройки, а не универсальные оптимальные пороги. Тихая речь, фоновая музыка и обработка шума микрофона могут влиять на измерения RMS.
Лучше дать человеку возможность самостоятельно остановить запись, чем автоматически обрезать последние слова предложения.
Для обычного ввода короткого текста однократная транскрипция, как правило, проще, чем многократный запуск Whisper ради промежуточных результатов. Запишите фразу, остановите запись, один раз запустите модель и вставьте итоговый текст.
Для полноценной работы Whisper недостаточно одной модели ONNX
Это одна из самых важных деталей реализации.
Инференс Whisper — не просто вызов энкодера ONNX, за которым следует вызов декодера ONNX. Полная система автоматического распознавания речи также требует извлечения признаков звука, токенизации, настройки генерации и преобразования предсказанных токенов в читаемый текст.
Полная цепочка обработки выглядит так:
Raw microphone PCM
|
v
Resampling to 16 kHz
|
v
Whisper feature extractor
|
v
ONNX encoder
|
v
ONNX decoder and token generation
|
v
Whisper tokenizer
|
v
Recognized textУспешная инициализация сессии ONNX доказывает лишь то, что граф модели можно загрузить. Это еще не доказывает работоспособность всей цепочки распознавания.
В одной из протестированных интеграций Transformers.js сессии моделей успешно инициализировались, но транскрипция всё равно не работала: в локально созданной системе распознавания не были правильно настроены необходимые процессор и токенизатор.
В совместимой реализации эти компоненты явно инициализировались с использованием локальных метаданных модели.
Ниже сокращенный пример такого подхода, зависящего от версии; это не полный самостоятельный воркер:
import {
WhisperTokenizer,
WhisperFeatureExtractor,
WhisperProcessor,
} from '@huggingface/transformers';
// These values come from local model configuration files.
const tokenizer = new WhisperTokenizer(
tokenizerJson,
tokenizerConfig
);
const featureExtractor = new WhisperFeatureExtractor(
preprocessorConfig
);
const processor = new WhisperProcessor(
{},
{
tokenizer,
feature_extractor: featureExtractor,
},
''
);
// In the affected version-specific integration:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;Точные сигнатуры конструкторов и способ интеграции нужно сверять с установленной версией Transformers.js. Некоторые версии и сборки моделей умеют создавать эти компоненты автоматически; ручную инициализацию нельзя считать универсальным требованием.
Когда вся цепочка готова, транскрипцию короткой записи можно запустить, явно указав язык и задачу:
const result = await recognizer(audio16k, {
language: 'en',
task: 'transcribe',
return_timestamps: false,
});Если приложение уже знает язык пользователя, его явное указание позволяет избежать лишнего определения языка и может сделать результаты коротких записей более предсказуемыми.
Фиксируйте совместимые версии Transformers.js и ONNX Runtime
Вторая важная причина сбоев — взаимодействие Transformers.js и ONNX Runtime Web.
Transformers.js предоставляет высокоуровневую систему распознавания, а ONNX Runtime Web исполняет графы нейросетей. Эти пакеты должны быть совместимы друг с другом и с конкретными используемыми сборками моделей ONNX.
В исследованной реализации версия ONNX Runtime Web из ветки разработки успешно инициализировала энкодер Whisper, но не смогла создать сессию квантованного декодера. Ошибка была связана с отсутствующим масштабным коэффициентом квантования в операции DequantizeLinear.
Проблема не была связана ни с разрешением на микрофон, ни со скачиванием, ни с форматом аудио. Браузер получал файлы модели, но среда выполнения не могла инициализировать один из необходимых графов.
Прямое тестирование и энкодера, и декодера на стабильной, зафиксированной версии ONNX Runtime Web устранило именно эту несовместимость.
Проверка совместимости оказалась особенно полезной, поскольку позволила разделить два разных утверждения:
- Файл модели успешно скачался.
- Среда выполнения успешно создала пригодную для инференса сессию на основе файла модели.
Это не одно и то же.
Для протестированных сборок моделей совместимой оказалась пара Transformers.js 4.2.0 и ONNX Runtime Web 1.24.3. Более новая версия среды выполнения из ветки разработки завершалась ошибкой на графе квантованного декодера. Это наблюдение относится к конкретным версиям и не доказывает, что более старые версии всегда лучше.
Если переопределяете транзитивную зависимость среды выполнения, зафиксируйте точную версию в lock-файле пакетов и задокументируйте причину переопределения. Обновление среды выполнения должно требовать полного регрессионного теста инференса в браузере, а не только успешной установки пакета.
Перед отладкой Whisper проверьте формат сборки воркера
JavaScript Web Worker можно создавать с использованием разных форматов модулей.
Одна из ошибок интеграции возникла из-за того, что приложение создавало воркер как модуль ES, а сгенерированная сборка воркера использовала механизм классических воркеров importScripts().
Эти модели исполнения не взаимозаменяемы. Модульный воркер не может просто выполнить код, рассчитанный только на классический воркер.
Практический вывод: настройка конструктора Worker должна соответствовать фактическому формату сборки, а не примеру из произвольного руководства.
Например, следующие варианты не обязательно равнозначны:
// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });
// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);Правильный вариант зависит от сборщика и сгенерированного JavaScript.
При диагностике ошибки инициализации воркера проверьте консоль браузера, сгенерированный файл воркера и панель Network. Выясните, не происходит ли сбой еще до начала инициализации ONNX Runtime.
Некоторые браузерные библиотеки после сборки также вычисляют URL относительно расположения собственного модуля. При упаковке этих библиотек в воркеры проверяйте получившиеся пути к файлам среды выполнения, а не исходите из того, что первоначальные относительные пути пакета по-прежнему работают.
Отдавайте WASM и модули JavaScript с правильными MIME-типами
Даже полностью совместимые файлы JavaScript и модели ONNX могут не работать, если веб-сервер возвращает неправильные заголовки ответа.
В одном конкретном случае модуль JavaScript с расширением .mjs отдавался с типом application/octet-stream. Браузер отклонил модуль, поскольку тип ответа не подходил для выполнения модуля JavaScript.
Обычно ожидаются такие типы ответа:
| Расширение файла | Content-Type |
|---|---|
| .mjs | application/javascript |
| .wasm | application/wasm |
Проверяйте фактический ответ продакшен-сервера, а не только конфигурацию веб-сервера.
curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasmПриведенные URL служат примером. Используйте реальные имена версионированных файлов выбранной сборки среды выполнения.
Одного правильного HTTP-статуса недостаточно. Браузер всё равно может отклонить файл из-за MIME-типа, политики безопасности содержимого (Content Security Policy), режима выполнения модулей или неправильного URL ресурса.
Помните: один и тот же запрос может успешно открываться непосредственно во вкладке браузера, но завершаться ошибкой при загрузке файла как исполняемого модуля или зависимости WebAssembly.
Используйте неизменяемые URL ресурсов, но не оставляйте пользователей с нерабочими файлами среды выполнения
Файлы моделей Whisper достаточно велики, поэтому браузерное кэширование имеет большое значение.
Версионированные неизменяемые пути к ресурсам позволяют браузеру повторно использовать неизменившиеся файлы модели и среды выполнения вместо повторного скачивания при каждой диктовке.
Типичная политика кэширования:
Cache-Control: public, max-age=31536000, immutableОднако отсюда следует требование к развертыванию: если URL объявлен неизменяемым, нельзя незаметно заменять его содержимое.
Если двоичный файл среды выполнения нужно изменить, публикуйте его по новому версионированному URL.
/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/Изменение пути к среде выполнения — не косметика. Если после изменения содержимого повторно использовать неизменяемый URL, браузер может продолжить работать с ранее закэшированным несовместимым бинарным файлом WebAssembly.
Также полезно использовать JavaScript-обвязку и файлы WebAssembly из одной и той же сборки среды выполнения. Смешивание файлов разных версий способно вызвать ошибки инициализации, которые трудно диагностировать.
Манифест сборки со списком ожидаемых файлов, их размеров и криптографических контрольных сумм помогает обнаружить отсутствующие или несовместимые ресурсы еще до развертывания.
Настройте библиотеку распознавания на использование собственных ресурсов сайта и отключите удаленную загрузку моделей. Тогда отсутствие локальной модели вызовет явную ошибку, а не незапланированное скачивание с внешнего хостинга моделей.
Почему микрофон показывает загрузку, хотя ничего не работает
Типичный симптом при разработке: кнопка микрофона запрашивает разрешение, показывает анимацию загрузки, а затем возвращается в состояние ошибки, так и не вставив текст.
Браузер при этом может даже показывать, что микрофон используется.
Но это не доказывает, что распознавание речи работает. Это доказывает лишь то, что доступ к микрофону в какой-то момент был разрешен или запись была активна.
Несколько независимых сбоев могут выглядеть почти одинаково:
- Неправильный MIME-тип модуля JavaScript. Браузер скачивает модуль среды выполнения, но отказывается его исполнять.
- Несовпадение формата выполнения воркера. Скомпилированный воркер использует API классического воркера, но запускается как модульный, или наоборот.
- Несовместимость декодера ONNX. Среда выполнения создает сессию энкодера, но не может работать с графом квантованного декодера.
- Неполная цепочка распознавания. Сессии ONNX существуют, но токенизатор или обработчик аудио инициализированы неправильно.
Эти проблемы нужно диагностировать независимо. После исправления одной может обнаружиться следующая.
Главный принцип отладки: HTTP 200 не означает успешной инициализации модели, а успешная инициализация модели не означает успешной транскрипции.
Даже успешная транскрипция не доказывает, что результат попал в настоящее текстовое поле приложения.
Для каждого этапа нужен осмысленный диагностический сигнал.
Наблюдаемость без сбора приватных голосовых данных
Для полезных диагностических журналов не нужно записывать то, что произносит пользователь.
Приложение может отслеживать такие технические состояния:
permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-insertedПри ошибке записывайте этап, стабильный технический код ошибки и очищенные от чувствительных данных метаданные.
Сбой инициализации модели должен отличаться от отказа в доступе к микрофону или пустой записи. Сбой воркера должен быть виден при разработке, а не теряться внутри общего обработчика ошибок.
Не записывайте в журналы отсчеты микрофона, распознанные предложения, последовательности токенов и другое содержимое речи. Для телеметрии производительности подойдут интервалы длительности, выбранный класс модели, время инициализации, время инференса и категории ошибок.
Так неисправный микрофон гораздо легче диагностировать без ослабления требований к конфиденциальности.
Вставка распознанного текста через состояние React
Распознавание речи полезно лишь тогда, когда итоговый текст попадает в существующее поле формы.
В приложении на React это поле часто управляется состоянием компонента. Прямое присваивание input.value может конфликтовать с механизмом отрисовки фреймворка.
Вместо этого распознанный текст должен проходить через тот же путь обновления состояния, который используется при обычном вводе с клавиатуры.
Интеграция также должна учитывать асинхронность. Распознавание может закончиться уже после того, как пользователь допечатал символы или переместил курсор.
Если использовать устаревшее значение поля, сохраненное в начале записи, можно случайно перезаписать более поздние правки.
Упрощенный вариант интеграции:
const insertion = insertTranscript({
currentValue: latestValue,
transcript: recognizedText,
selection: currentSelection,
maxLength: inputLimit,
});
setMessage(insertion.value);
restoreCaret(insertion.caret);Функция вставки должна сохранять текущий текст, при необходимости заменять выделенный диапазон, правильно расставлять пробелы и учитывать максимальную длину поля.
Нужно помнить об Unicode: длина строки в JavaScript измеряется кодовыми единицами UTF-16. Разрезание строки в произвольной позиции может разделить суррогатную пару.
И главное: распознавание речи ни в коем случае не должно автоматически отправлять форму. Пользователю необходимо дать возможность проверить и исправить текст перед отправкой.
Обрабатывайте ошибки, не нарушая обычный ввод текста
Подсистема распознавания речи должна оставаться необязательной.
Если доступ к микрофону запрещен, браузер не может выделить память под модель, воркер аварийно завершился или запись оказалась без речи, обычное текстовое поле должно продолжать работать.
Жизненный цикл микрофона следует представить явными состояниями, например:
idle
preparing
listening
recognizing
errorИспользуйте доступные для вспомогательных технологий подписи и видимые сообщения о состоянии. Микрофон, который после загрузки просто становится красным или розовым, почти ничего не объясняет пользователю.
При отмене операции нужно очистить аудиодорожки, аудиоконтекст, ожидающие обработки буферы и активный сеанс записи. Интерфейс также должен не допускать, чтобы запоздалый результат отмененной записи перезаписал более новое состояние поля.
Если большая модель не запускается из-за нехватки памяти или ограничений инициализации, полностью освободите ее перед попыткой загрузить меньшую. Ограничьте число повторов, чтобы браузер не скачивал и не инициализировал снова и снова модели, которые не могут работать.
Не переключайтесь незаметно на облачный сервис распознавания при сбое локального распознавания. Если строго локальная обработка — обязательное условие, ошибка локального распознавателя должна оставаться локальной.
Проверяйте всю цепочку работы в браузере
Успешная проверка TypeScript или продакшен-сборка не доказывает работоспособность голосового ввода.
Аналогично, возможность скачать файлы ONNX в браузере не доказывает, что обе сессии модели успешно инициализируются.
Самый полезный регрессионный тест должен задействовать те же воркер, файлы среды выполнения, модель, процессор и токенизатор, которые используются в продакшене.
Передайте известную короткую запись PCM в реальную браузерную цепочку инференса и проверьте, что она возвращает непустую расшифровку.
Затем проверьте весь сценарий работы с микрофоном в настоящем браузере.
- Первоначальная загрузка страницы: убедитесь, что модели и файлы среды выполнения распознавания не скачиваются до использования микрофона.
- Холодная инициализация: проверьте разрешение на микрофон, запросы файлов моделей, MIME-типы JavaScript и WASM и реальное сообщение от воркера о готовности.
- Инициализация ONNX: убедитесь, что можно создать сессии энкодера и декодера.
- Запись звука: подтвердите, что AudioWorklet создает непустые буферы PCM.
- Ресемплирование: подтвердите, что воркер получает реальную частоту дискретизации и создает корректное аудио с частотой 16 кГц.
- Ручная остановка: проверьте, что повторное нажатие микрофона надежно останавливает запись.
- Автоматическая остановка: убедитесь, что естественные паузы завершают запись, не обрезая тихую речь.
- Транскрипция: убедитесь, что полная цепочка из процессора, модели и токенизатора возвращает распознаваемый текст.
- Интеграция с полем ввода: убедитесь, что распознанный текст появляется в настоящем управляемом поле, не перезаписывая правки пользователя.
- Восстановление после ошибок: проверьте отказ в разрешении, отсутствие файлов, ошибки воркера, сбой инициализации модели, отмену и переключение на меньшую модель.
- Конфиденциальность: проверьте сетевые запросы и убедитесь, что звук с микрофона не отправляется ни на сервер, ни в сервис аналитики.
- Совместимость с мобильными устройствами: повторите тесты на реальных телефонах, включая холодные и теплые запуски и повторные сеансы диктовки.
Тесты с записанным PCM отлично проверяют систему инференса, но не доказывают работоспособность записи с микрофона. Финальный приемочный тест обязательно должен включать настоящий микрофон и реальный пользовательский интерфейс.
Практичная архитектура надежного голосового ввода в браузере
Для сайта, которому нужен короткий многоязычный и приватный голосовой ввод, самая практичная архитектура намеренно остается простой:
React microphone button
|
v
Permission request
|
v
Lazy model initialization
|
v
AudioWorklet PCM capture
|
v
Adaptive RMS / manual stop
|
v
Dedicated Worker
|
v
16 kHz audio resampling
|
v
Whisper Tiny or Base Q8
|
v
Compatible ONNX Runtime WASM
|
v
Whisper processor + tokenizer
|
v
Final transcript
|
v
Controlled React inputПриложение должно само размещать версионированные файлы модели и среды выполнения, выполнять инференс вне основного потока, не скачивать модели без необходимости, освобождать ресурсы после использования и никогда не отправлять аудио с микрофона на сервер распознавания.
Более сложные варианты — ускорение с WebGPU, нейросетевое определение голосовой активности, непрерывная промежуточная транскрипция или дополнительные классы моделей — можно рассмотреть позже, когда измерения оправдают усложнение.
Самый важный вывод связан не с конкретной версией библиотеки или размером модели, а с необходимостью проверять каждую границу между этапами браузерной системы распознавания речи.
Работающий запрос разрешения на микрофон еще не означает, что звук распознается. Скачанная модель не означает, что ее декодер инициализирован. Успешная сессия ONNX не означает, что токенизатор доступен. И даже успешная транскрипция не означает, что приложение правильно вставило текст.
Функция готова только тогда, когда посетитель может нажать кнопку микрофона, произнести фразу, остановить запись и увидеть распознанные слова в редактируемом поле — без отправки аудиозаписи на сервер распознавания речи.