Уявіть: користувач говорить у мікрофон, а текст з'являється на екрані із затримкою менше півсекунди. Це нетривіальне інженерне завдання, але з Deepgram Nova-2 воно вирішується. Модель дає медіанну затримку близько 300 мс і WER близько 5% на російській мові. Такі показники недоступні класичним batch-рішенням. У цій статті розберемо, як налаштувати WebSocket-транскрибацію на iOS та Android, які параметри критичні та як уникнути типових помилок.
Типові труднощі — нестабільне з'єднання, дублювання interim-результатів, неправильна конфігурація аудіокодека. Наша команда з 10+ річним досвідом впровадження ASR (5+ років на ринку, понад 50 проектів) гарантує стабільне з'єднання та низьку затримку. На реальному проекті клієнт скаржився на дублювання interim: кожне нове слово додавалося до попереднього. Після впровадження патерну заміни буфера проблема зникла повністю.
Порівняння: Deepgram Nova-2 кращий за Whisper у 6-10 разів за швидкістю стримінгу.
Які параметри впливають на якість транскрибації?
Deepgram Nova-2 забезпечує низьку затримку на стримінгу: медіана близько 300 мс від кінця фрази до тексту. Whisper такого не вміє в принципі — він синхронний. Якщо задача «користувач говорить — текст з'являється на екрані» із затримкою менше секунди, це Deepgram.
| Характеристика | Deepgram Nova-2 | Whisper (синхронний) |
|---|---|---|
| Затримка фіналу | 300–500 мс | 2–5 секунд |
| Стримінг | Асинхронний, потоковий | Синхронний, batch |
| Interim результати | Так | Ні |
| Підтримка російської | Відмінно | Добре |
| Ціна (за годину) | $0.01/хв | Безкоштовно (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%, а вартість транскрибації становить $0.01 за хвилину аудіо. Отримайте консультацію з інтеграції Deepgram для вашого мобільного проекту. Ми проаналізуємо архітектуру, підберемо оптимальну конфігурацію та реалізуємо low-latency транскрибацію під ключ. Замовте демо-версію з низькою затримкою вже сьогодні. Зв'яжіться з нами для обговорення вашого проекту.
Інтеграція Deepgram дозволила нам скоротити затримку транскрибації з 5 секунд до 300 мс — відгук клієнта з фінансового сектору.







