Audio helpers
WAV encode / decode and MIME parsing utilities.
The adapter exports the three audio helpers it uses internally. They're stable exports — safe to reuse if you're building custom pipelines or writing tests.
import {
framesToWav,
parseWav,
pcmSampleRateFromContentType,
} from '@spekoai/adapter-livekit';framesToWav
function framesToWav(buffer: AudioBuffer): Uint8Array;Encode one or more LiveKit AudioFrames (or an array) into a PCM16 mono WAV byte stream. Used by SpekoSTT to wrap each utterance before uploading to /v1/transcribe.
- Combines frames via
combineAudioFramesfrom@livekit/rtc-node. - Writes a standard 44-byte RIFF/WAVE header:
fmtchunk (PCM, 16-bit, mono,sampleRatefrom frames) +datachunk. - Sample rate is pulled from the input frames — whatever LiveKit gives you is what's encoded.
Mono-only. A multi-channel AudioBuffer throws:
SpekoSTT: expected mono audio (1 channel), got 2. Configure your LiveKit AgentSession to pass mono audio or pre-mix upstream of the STT.parseWav
function parseWav(bytes: Uint8Array): {
pcm: Uint8Array;
sampleRate: number;
channels: number;
};Minimal PCM16 WAV parser. Used by SpekoTTS to unwrap WAV-encoded proxy responses into raw samples for AudioByteStream.
Accepted subset:
- Valid
RIFF/WAVEheader. fmtchunk present and offormat = 1(PCM).- 16-bit samples.
datachunk reachable by walking subsequent chunks (tolerates e.g.LISTchunks betweenfmtanddata).
Anything outside this subset throws a coded SpekoAdapterError: MALFORMED_AUDIO for a truncated or non-RIFF payload (retryable, so the router can fail over) and UNSUPPORTED_AUDIO_FORMAT for non-PCM or non-16-bit audio (not retryable — retrying a misconfigured provider changes nothing). channels is returned as-is; the caller decides whether stereo is acceptable, and SpekoTTS rejects it with UNSUPPORTED_CHANNELS.
pcmSampleRateFromContentType
function pcmSampleRateFromContentType(
contentType: string,
fallback: number,
): number;Parse the rate parameter out of a Cartesia-style content type:
pcmSampleRateFromContentType('audio/pcm;rate=24000', 16_000); // 24000
pcmSampleRateFromContentType('audio/pcm', 16_000); // 16000
pcmSampleRateFromContentType('audio/pcm;rate=abc', 16_000); // 16000Falls back when the rate is missing, zero, or unparseable. Case-insensitive on rate=.
createSampleRateNormalizer
function createSampleRateNormalizer(
inputRate: number,
outputRate: number,
channels?: number,
): SampleRateNormalizer;
interface SampleRateNormalizer {
readonly resampling: boolean;
push(frame: AudioFrame): AudioFrame[];
flush(): AudioFrame[];
close(): void;
}Converts frames from the rate a provider actually produced to the single rate a stage advertises. SpekoTTS uses it so every frame it emits carries the declared sampleRate, whatever the router served.
- Equal rates return a pass-through:
resamplingisfalse,pushreturns the same frame instance,flushreturns nothing, and no native handle is allocated. - Differing rates wrap
AudioResamplerfrom@livekit/rtc-node. Callflush()to drain the resampler's warm-up tail, thenclose()to release the native handle —close()is idempotent, so afinallyblock is the right home for it.
const normalizer = createSampleRateNormalizer(48_000, 24_000);
try {
for (const frame of incoming) {
for (const out of normalizer.push(frame)) emit(out);
}
for (const out of normalizer.flush()) emit(out);
} finally {
normalizer.close();
}Intended usage
You shouldn't need these helpers when consuming the adapter through createSpekoComponents — they're used internally by SpekoSTT and SpekoTTS. They're exported for:
- Unit tests — build canned WAV fixtures with
framesToWav, round-trip them throughparseWav. - Custom STT / TTS pipelines that need to reuse the same WAV framing Speko uses.
- Debugging — decode what an upstream provider returned without instantiating a full TTS.