Почему мы Услуги Портфолио Блог Технологии Процесс разработки Начать проект →
UA EN RU
← Все статьи
Инсайт · 20.05.2019 · 7 мин чтения

Голосовое управление в интернет-магазине: кроссбраузерное распознавание речи

Покупатель нажимает кнопку, диктует запрос — и видит его текстом в строке поиска. Разбираем, как устроено кроссбраузерное распознавание речи: от микрофона в браузере до ответа сервиса и обратно.
Голосовое управление в интернет-магазине: кроссбраузерное распознавание речи

Задача звучала просто: дать покупателю возможность из любого браузера и с любого устройства продиктовать поисковый запрос словами. Голос превращается в текст в строке поиска, а отдельные слова работают как команды — «искать», «отменить» — и запускают соответствующие действия на сайте. Ниже разбираем, как такая система устроена изнутри: от микрофона в браузере до ответа службы распознавания и обратно.

Задача

Нужно разработать функцию распознавания речи на веб-странице интернет-магазина. Покупатель нажимает кнопку записи, диктует запрос — и видит его текстом в поисковой строке. Часть сказанного обрабатывается как команды: «искать» запускает поиск, «отменить» сбрасывает введённое.

Ключевое требование — полная совместимость с самыми распространёнными браузерами: Chrome, Firefox, Safari, Opera и Edge. То есть решение не может опираться на возможности, которые есть только в одном из них.

Что должна уметь система

Чтобы это заработало, нужны три вещи:

  • получить аудиопоток из браузера;
  • передать аудиоданные потоком в службу распознавания речи;
  • получать результаты в режиме реального времени — пока пользователь ещё говорит.
5
браузеров, в которых должно работать: Chrome, Firefox, Safari, Opera, Edge
1
аудиоканал из стерео — больше службе распознавания не нужно
65 с
лимит длины аудио в одном запросе распознавания

Шаг 1. Как получить аудиопоток из браузера

Здесь нам поможет API getUserMedia / Stream, который поддерживает большинство популярных браузеров. Через этот браузерный интерфейс мы получаем аудиопоток с микрофона и отправляем его в нашу службу распознавания речи.

Поддержка API getUserMedia / Stream в популярных браузерах
Поддержка getUserMedia / Stream API браузерами

Дальше инициализируем аудиоконтекст и вызываем getUserMedia. Хотя такая конфигурация совместима с основными браузерами, параметры, которые передают в этот API, могут отличаться в зависимости от потребностей — детали стоит сверять с официальной документацией.

Настройка аудиоконтекста

Конфигурация аудиоконтекста и процессора сценариев тоже зависит от задачи. Сложность в том, что некоторые комбинации параметров сейчас не работают в Safari — это стоит проверять отдельно.

Отдельное внимание нужно уделить подписке на событие «audioprocess»: для потока с микрофона оно фактически выполняет роль события «ondata».

Звук регистрируется в стерео, поэтому с микрофона идут два аудиоканала. На самом деле мы забираем только один из них, потому что службе распознавания нужно аудио с одним каналом. Но в зависимости от ваших потребностей со звуком можно работать как угодно.

Формат имеет значение. Данные, поступающие из аудиопотока, — это типизированный массив Float32Array. То есть перед отправкой в службу распознавания их придётся конвертировать.

Что делать при остановке записи

  1. Выключить микрофон. Прекратить использование микрофона браузера и сбросить переменные, если они есть.
  2. Отключить слушателя события аудиопроцесса. Без этого поток и дальше будет вызывать событие, даже когда микрофон уже деактивирован.

Шаг 2. Куда передавать аудио

Самый простой путь — воспользоваться встроенным в браузер API распознавания речи. С ним веб-приложение распознаёт речь потоком прямо в браузере, без каких-либо сторонних сервисов.

Но, как и в каждом интересном нововведении, здесь есть мелкие проблемы совместимости. И именно они делают этот подход совершенно непригодным для продакшн-приложений.

Совместимость браузерного API распознавания речи с разными браузерами
Совместимость встроенного API распознавания речи с браузерами

Для хорошего пользовательского опыта нам нужна система распознавания в реальном времени, которая возвращает результаты, пока пользователь говорит, а не записывает звук полностью и только потом его транскрибирует. Значит, нужен сторонний сервис со следующими свойствами:

Потоковое распознавание

Результат должен приходить непрерывно, по мере того как человек говорит, а не одним куском после завершения записи.

Совместимость с каждым браузером

Сервис не должен зависеть от того, что умеет или чего не умеет конкретный браузер пользователя.

Почему мы выбрали Google Speech API

В интернете есть много сервисов, которые дают приложениям возможность распознавать речь через API. Мы остановились на Google Speech API: он даёт качественный сервис потокового распознавания, действительно эффективный, а ответ в режиме реального времени для нас критически важен.

SDK-библиотека предоставляет и службу асинхронного распознавания (через gRPC и REST API), и службу распознавания в реальном времени — последнюю только через gRPC API.

Наша цель — распознавание в режиме реального времени, поэтому мы выбрали именно потоковое распознавание. Google фактически предоставляет этот API только через вызов gRPC, используя потоковую функцию протокола gRPC.

Архитектура: браузер, сервер, служба распознавания

Именно поэтому передавать аудио напрямую из браузера через gRPC невозможно. Браузер действительно умеет делать gRPC-вызовы, но потоково — никак.

Поэтому мы выбрали другой путь: передавать данные потоком на сервер, а уже оттуда — в потоковое API распознавания Google, и обратно, чтобы забрать результат распознавания.

Вариантов потоковой передачи аудиоданных из браузера на сервер немного. Фактически единственные системы, способные отдавать данные из браузера потоком, — это протокол webRTC и WebSocket. Для простоты мы выбрали WebSocket. Серверную часть имеет смысл разрабатывать на nodeJS: там простая обработка потоков и очень несложная реализация веб-сокетов.

Схема передачи аудио из браузера через сервер в службу распознавания речи
Итоговая инфраструктура решения
  1. Подключение. После нажатия кнопки записи подключаемся к WebSocket — мы хотим избежать длительного соединения, которое может замедлить работу сервера, — и добавляем слушателя для данных аудиопотока.
  2. Прослушивание. Слушаем события на микрофоне браузера.
  3. Отправка. Отправляем буфер данных на бэкенд через WebSocket.
  4. Передача дальше. Передаём данные с бэкенда в службу распознавания речи Google.
  5. Получение. Забираем результаты распознавания.
  6. Возврат во фронтенд. Отправляем результаты во фронтенд через веб-сокет, чтобы визуализировать их по мере поступления.

Реализация веб-сокета в браузере

Здесь важно помнить, что поток аудиоданных — это массив Float32Array, и его нужно преобразовать в буфер, а потом снова развернуть на сервере.

Массив Float32Array конвертируем в массив Int16Array и уже тогда отправляем на сервер. Без этого преобразования сервер не получит данные в нужном формате. Переменная ConversionFactor нужна исключительно для целей конвертации.

Реализация веб-сокета на сервере

Реализация на сервере стандартная, болезненных точек всего две:

  • когда именно запускать распознавание через Google;
  • как справиться с разницей между форматом данных потока, который приходит из веб-сокета, и форматом аудиопотока для потока распознавания речи.

Сначала нужно создать поток распознавания речи — детали есть в документации сервиса.

Лимит 65 секунд. Как только речевой поток создан, запрос на распознавание уже запущен, а Google ограничивает длину аудио для распознавания 65 секундами. Поэтому распознавание нужно создавать именно в тот момент, когда пользователь на веб-странице нажимает кнопку записи, а сервер получает от браузера запрос на подключение к веб-сокету.

Дальше можно транслировать аудиоблоки, поступающие из браузера, в речевое API. И вот здесь всплывает вторая болезненная точка — форматы данных.

ЭтапФормат данныхЧто делаем
Микрофон в браузереFloat32Array, стереоБерём только один канал
Перед отправкой в веб-сокетInt16ArrayКонвертируем из Float32Array
На сервере из веб-сокетаСтандартный буфер, целые по 8 битПринимаем как есть
Перед записью в поток распознаванияInt16Array, кодировка «LINEAR16»Конвертируем буфер

Буфер, который приходит из веб-сокета, — это стандартный буфер, массив целых чисел по 8 бит каждое. А в настройках распознавания речи мы указали, что аудиокодировка — «LINEAR16», то есть массив целых чисел по 16 бит каждое. Поэтому стандартный буфер нужно преобразовать в Int16Array, прежде чем записывать его в поток распознавания.

Поток распознавания, который возвращает библиотека распознавания речи Google, — это обычный стандартный поток, и обрабатывать его можно привычным способом.

Не забудьте закрыть поток. Очень важно закрывать данные потока распознавания, когда закрывается веб-сокет. Так мы избегаем ошибки распознавания речи Google из-за слишком долгой потоковой передачи звука.

Результат

В итоге мы получаем кроссбраузерную систему распознавания речи, которая упрощает взаимодействие пользователя с сайтом. Покупателю больше не нужно набирать название товара руками — достаточно сказать его вслух.

Если планируете что-то подобное в своём проекте, начинайте с двух проверок: поддержка getUserMedia / Stream API в нужных вам браузерах и возможности выбранного сервиса speech-to-text. Именно эти две вещи определяют, насколько сложной получится остальная работа.