Перейти к основному содержимому

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

Потоковый режим позволяет отправлять аудио в реальном времени и получать результаты по мере распознавания — например, для live-транскрибации звонков или совещаний.

WS /api/v1/speech-to-text/live?lang=ru

Query-параметры:

ПараметрТипОписание
langstringЯзык распознавания, например ru. По умолчанию ru.

Протокол

Подключение: клиент устанавливает WebSocket-соединение с заголовком Authorization: Bearer <api_key>. Сервер подтверждает подключение (101 Switching Protocols).

Клиент → сервер (бинарные фреймы):

ПараметрЗначение
Форматfloat32 little-endian (PCM)
Частота дискретизации16 000 Гц
Каналы1 (mono)
Размер чанка4096–32768 байт (рекомендуется)
Частота отправкиКаждые 100–500 мс

Сервер → клиент (текстовые фреймы): JSON-объект с данными распознанного сегмента.

ПолеТипОписание
startfloatВремя начала сегмента (секунды)
endfloat?Время конца сегмента
textstringРаспознанный текст
speech_probfloat?Вероятность наличия речи
vad_probfloat?Вероятность VAD
volume_70float?Уровень громкости (70-й перцентиль)
wordsarray?Детализация по словам
channelstring?Идентификатор канала
speakerstring?Идентификатор спикера
emotionobject?Эмоции: {positive, angry, sad, neutral} (значения 0–1)

Завершение: клиент закрывает WebSocket (close frame). Сервер дожидается обработки оставшихся данных, отправляет финальные результаты и закрывает соединение.

Ошибки: сервер отправляет текстовый фрейм Error: описание ошибки.

JavaScript

const ws = new WebSocket("wss://cognico.ru/api/v1/speech-to-text/live", [], {
headers: { "Authorization": "Bearer YOUR_API_KEY" },
});

ws.onopen = () => {
console.log("Соединение установлено");
};

ws.onmessage = (event) => {
const segment = JSON.parse(event.data);
console.log(`[${segment.start.toFixed(1)}s] ${segment.text}`);
};

ws.onerror = (error) => {
console.error("Ошибка WebSocket:", error);
};

// Отправка аудиочанка (ArrayBuffer)
ws.send(audioChunk);

// Завершение сессии
ws.close();

Пример с захватом микрофона в браузере (AudioWorklet, float32 PCM 16 kHz):

const stream = await navigator.mediaDevices.getUserMedia({
audio: { sampleRate: 16000, channelCount: 1 },
});

const audioCtx = new AudioContext({ sampleRate: 16000 });
const source = audioCtx.createMediaStreamSource(stream);

// Регистрируем процессор для получения сырых float32 сэмплов
await audioCtx.audioWorklet.addModule(
URL.createObjectURL(new Blob([`
class Sender extends AudioWorkletProcessor {
process(inputs) {
if (inputs[0][0]?.length) this.port.postMessage(inputs[0][0]);
return true;
}
}
registerProcessor("sender", Sender);
`], { type: "application/javascript" }))
);

const node = new AudioWorkletNode(audioCtx, "sender");
source.connect(node).connect(audioCtx.destination);

const ws = new WebSocket("wss://cognico.ru/api/v1/speech-to-text/live");

node.port.onmessage = ({ data }) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(new Float32Array(data).buffer);
}
};

ws.onmessage = (event) => {
const segment = JSON.parse(event.data);
document.getElementById("transcript").textContent += segment.text + " ";
};

Python

import asyncio
import json
import struct
import wave
import websockets

def wav_to_f32_chunks(path: str, chunk_samples: int = 4096):
"""Читает WAV-файл и отдаёт чанки в формате float32 PCM."""
with wave.open(path, "rb") as wf:
assert wf.getnchannels() == 1, "Ожидается mono"
assert wf.getsampwidth() == 2, "Ожидается 16-bit"
assert wf.getframerate() == 16000, "Ожидается 16 kHz"
while data := wf.readframes(chunk_samples):
# int16 → float32
samples = struct.unpack(f"<{len(data)//2}h", data)
yield struct.pack(f"<{len(samples)}f", *(s / 32768.0 for s in samples))

async def recognize_stream(audio_file_path: str, api_key: str):
uri = "wss://cognico.ru/api/v1/speech-to-text/live"
headers = {"Authorization": f"Bearer {api_key}"}

async with websockets.connect(uri, extra_headers=headers) as ws:
async def send_audio():
for chunk in wav_to_f32_chunks(audio_file_path):
await ws.send(chunk)
await asyncio.sleep(0.1) # имитация реального времени
await ws.close()

async def receive_results():
async for message in ws:
segment = json.loads(message)
print(f"[{segment['start']:.1f}s] {segment['text']}")

await asyncio.gather(send_audio(), receive_results())

asyncio.run(recognize_stream("recording.wav", "YOUR_API_KEY"))