Представьте: пользователь говорит в микрофон, а текст появляется на экране с задержкой менее полусекунды. Это нетривиальная инженерная задача, но с Deepgram Nova-2 она решается. Модель даёт медианную задержку около 300 мс и WER около 5% на русском. Такие показатели недоступны классическим batch-решениям. В этой статье разберём, как настроить WebSocket-транскрибацию на iOS и Android, какие параметры критичны и как избежать типичных ошибок.
Типичные трудности — нестабильное соединение, дублирование interim-результатов, неправильная конфигурация аудиокодека. Наша команда с 10+ летним опытом внедрения ASR гарантирует стабильное соединение и низкую задержку. На реальном проекте клиент жаловался на дублирование interim: каждое новое слово добавлялось к предыдущему. После внедрения паттерна замены буфера проблема исчезла полностью.
Сравнение Deepgram Nova-2 и Whisper
Deepgram Nova-2 обеспечивает низкую задержку на стриминге: медиана около 300 мс от конца фразы до текста. Whisper такого не умеет в принципе — он синхронный. Если задача «пользователь говорит — текст появляется на экране» с задержкой меньше секунды, это Deepgram.
| Характеристика | Deepgram Nova-2 | Whisper (синхронный) |
|---|---|---|
| Задержка финала | 300–500 мс | 2–5 секунд |
| Стриминг | Асинхронный, потоковый | Синхронный, batch |
| Interim результаты | Да | Нет |
| Поддержка русского | Отлично | Хорошо |
| Цена (за час) | По запросу | Бесплатно (self-hosted) |
Для мобильного сценария Deepgram выигрывает в 6–10 раз по скорости. Кроме того, Nova-2 показывает WER ≤5% на русском языке, в то время как Whisper large-v2 — около 7%.
Настройка протокола подключения
Deepgram работает через WebSocket. Endpoint:
wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true Параметры критичны: encoding=linear16 означает сырой PCM 16-bit little-endian. Любой другой формат без явного указания кодека — риск 1008 Policy Violation. interim_results=true включает частичные результаты — именно они создают ощущение реального времени.
iOS: AVAudioEngine + URLSessionWebSocketTask
class DeepgramStreamer { private var audioEngine = AVAudioEngine() private var webSocket: URLSessionWebSocketTask? func start() throws { let session = URLSession(configuration: .default) var request = URLRequest(url: URL(string: "wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true")!) request.setValue("Token \(apiKey)", forHTTPHeaderField: "Authorization") webSocket = session.webSocketTask(with: request) webSocket?.resume() receiveLoop() let inputNode = audioEngine.inputNode let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)! inputNode.installTap(onBus: 0, bufferSize: 4096, format: format) { buffer, _ in guard let channelData = buffer.int16ChannelData else { return } let frameLength = Int(buffer.frameLength) let data = Data(bytes: channelData[0], count: frameLength * 2) self.webSocket?.send(.data(data)) { _ in } } try audioEngine.start() } private func receiveLoop() { webSocket?.receive { [weak self] result in if case .success(let message) = result, case .string(let text) = message { // Decode Deepgram JSON response self?.handleTranscript(text) } self?.receiveLoop() } } } Важная деталь: AVAudioEngine.inputNode на iOS 16+ требует явного запроса микрофона через AVAudioSession.sharedInstance().requestRecordPermission. И обязательно AVAudioSession.setCategory(.record, mode: .measurement) — режим .measurement отключает AEC и AGC, которые могут исказить сигнал для транскрипции.
Android: AudioRecord + OkHttp WebSocket
class DeepgramStreamer(private val apiKey: String) { private val client = OkHttpClient() private var webSocket: WebSocket? = null private var audioRecord: AudioRecord? = null fun start(onTranscript: (String, Boolean) -> Unit) { val request = Request.Builder() .url("wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true") .header("Authorization", "Token $apiKey") .build() webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onMessage(webSocket: WebSocket, text: String) { val json = JSONObject(text) val channel = json.getJSONObject("channel") val alternatives = channel.getJSONArray("alternatives") val transcript = alternatives.getJSONObject(0).getString("transcript") val isFinal = json.getBoolean("is_final") if (transcript.isNotEmpty()) onTranscript(transcript, isFinal) } }) val bufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize) audioRecord?.startRecording() Thread { val buffer = ShortArray(bufferSize / 2) while (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { val read = audioRecord!!.read(buffer, 0, buffer.size) if (read > 0) { val byteBuffer = ByteBuffer.allocate(read * 2).order(ByteOrder.LITTLE_ENDIAN) buffer.take(read).forEach { byteBuffer.putShort(it) } webSocket?.send(byteBuffer.array().toByteString()) } } }.start() } } ByteOrder.LITTLE_ENDIAN обязателен. Deepgram ожидает LE PCM. Если отправить BE, транскрипция будет работать, но с заметно худшим качеством.
Как избежать типичных ошибок при стриминге аудио?
-
Дублирование interim: никогда не накапливайте все interim как отдельные строки. Храните текущий utterance в буфере и перезаписывайте его при каждом новом interim. При
is_final: trueфинализируйте буфер. - Потеря соединения: внедрите reconnect с экспоненциальной задержкой (1,2,4,8 сек). Deepgram не поддерживает возобновление сессии, поэтому после reconnect нужно начать новый поток.
- Неправильная частота дискретизации: используйте строго 16000 Гц. Более высокая частота увеличивает трафик без выигрыша в качестве, более низкая — ухудшает распознавание.
Обработка interim-результатов
Deepgram возвращает два типа сообщений: с is_final: false (interim) и is_final: true (финальный). Правильный паттерн UI:
- Interim отображаем серым или курсивом — пользователь видит, что идёт распознавание
- При получении
is_final: trueзаменяем все предыдущие interim этого utterance финальным текстом -
speech_final: trueозначает конец паузы — хороший момент для начала обработки фразы
Параметры Nova-2, которые меняют качество
| Параметр | Значение | Описание |
|---|---|---|
| model | nova-2 | Модель распознавания |
| encoding | linear16 | Кодирование аудио |
| sample_rate | 16000 | Частота дискретизации |
| interim_results | true | Включить частичные результаты |
| utterance_end_ms | 1000 | Финализация по паузе |
| diarize | false | Разделение дикторов |
| punctuate | true | Автоматическая пунктуация |
| smart_format | true | Форматирование чисел и дат |
-
utterance_end_ms: 1000— Deepgram сам финализирует utterance после 1 секунды тишины. Полезно для диктовки без явных команд «стоп». -
diarize: true— разделение по спикерам, добавляетspeakerв каждый word. -
punctuate: true— автопунктуация. Без неё текст идёт без точек и запятых. -
smart_format: true— форматирует числа, даты, телефоны. «двадцать пятое марта» → «25 марта».
Что входит в работу
- Настройка WebSocket-подключения с авторизацией
- Захват аудио через AVAudioEngine / AudioRecord с правильным форматом
- Обработка interim и финальных результатов без дублирования
- Reconnect при обрыве сети с экспоненциальной задержкой
- Тестирование на реальных устройствах (iOS/Android)
- Документация и обучение команды
Процесс работы
- Анализ — изучаем архитектуру приложения и требования к транскрибации
- Проектирование — выбираем модель, протокол, параметры
- Реализация — кодовая интеграция WebSocket, аудиозахват, UI
- Тестирование — нагрузочное тестирование, проверка на разных устройствах и сетях
- Деплой — публикация в App Store и Google Play
Сроки
Базовая интеграция WebSocket + AudioRecord/AVAudioEngine + вывод текста — 4–7 дней. Добавление диаризации, обработки переключения сети (reconnect), фонового режима, экспорта результата — 8–14 дней.
Экономия на разработке собственного ASR-движка может достигать 70%. Стоимость транскрибации низкая — значительно дешевле альтернатив с batch-обработкой.
Получите консультацию по интеграции Deepgram для вашего мобильного проекта. Мы проанализируем архитектуру, подберём оптимальную конфигурацию и реализуем low-latency транскрибацию под ключ. Закажите демо-версию с низкой задержкой уже сегодня. Свяжитесь с нами для обсуждения вашего проекта.
Интеграция Deepgram позволила нам сократить задержку транскрибации с 5 секунд до 300 мс — отзыв клиента из финансового сектора.







