Представьте: пользователь говорит в микрофон, а текст появляется на экране с задержкой менее полусекунды. Это нетривиальная инженерная задача, но с 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 мс — отзыв клиента из финансового сектора.







