Reference
Audio requirements
Encoding, sample rates, and chunk sizing for realtime audio streaming.
The realtime WebSocket accepts exactly one audio format: raw 16-bit PCM. Getting the format right is the difference between accurate transcripts and garbage, so this page spells out every requirement.
Encoding
| Property | Requirement |
|---|---|
| Codec | Raw PCM, 16-bit signed integers, little endian (pcm_s16le). |
| Channels | 1 (mono). |
| Container | None. Send raw sample bytes; do not send WAV, MP3, Opus, or WebM. |
| Framing | Binary WebSocket frames; no JSON wrapper, no base64. |
Sample rates
- Supported range: 8000 to 48000 Hz.
- Default and recommended: 16000 Hz. Speech models gain little above 16 kHz, and lower rates cut bandwidth.
- Declare the rate twice with the same value:
sample_rateon the token request andsample_rateon the WebSocket query string. - The declared rate must match the audio you actually send. A mismatch does not fail the session; it silently degrades transcription accuracy.
Chunk sizing and pacing
Send one binary frame per captured chunk, roughly 50 ms to 200 ms of audio each. The RealtimeVoiceKIT web client captures 4096-sample buffers and ships about 85 ms per frame at 16 kHz.
| Chunk length | Bytes at 16 kHz | Notes |
|---|---|---|
| 50 ms | 1600 | Lowest added latency; more frames per second. |
| 100 ms | 3200 | Good default for most integrations. |
| 200 ms | 6400 | Fewer frames; adds up to 200 ms of buffering latency. |
Bytes per chunk = sample_rate x 2 x milliseconds / 1000. When streaming a recording, pace frames at real time; sending a file as fast as the socket allows produces worse turn detection than live-paced audio.
Capturing in the browser
Browsers capture microphone audio as 32-bit float samples at the hardware rate, usually 44.1 or 48 kHz. Downsample to your declared rate and convert to 16-bit integers before sending.
// Downsample the capture rate to your declared rate, then convert
// float samples to 16-bit integers. Send the result as a binary frame.
function downsample(input, fromRate, toRate) {
if (fromRate === toRate) return input;
const ratio = fromRate / toRate;
const length = Math.floor(input.length / ratio);
const output = new Float32Array(length);
for (let i = 0; i < length; i++) {
output[i] = input[Math.floor(i * ratio)];
}
return output;
}
function floatToPcm16(float32) {
const pcm = new Int16Array(float32.length);
for (let i = 0; i < float32.length; i++) {
const s = Math.max(-1, Math.min(1, float32[i]));
pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
}
return pcm.buffer;
}Buffer chunks locally while the socket is still connecting and flush them once the ready event arrives, so the first words of a session are not lost.