Feed caller uplink audio to your own speech-to-text engine as PCM16 @ 16 kHz.
Run the audio bridge yourself and pipe the caller’s uplink (what they say)
straight into a streaming recognizer you control — Deepgram, a self-hosted
Whisper, or any ASR that accepts 16-bit PCM. audioBridge.attach subscribes the
uplink track and hands you every frame in onUplink; ask for pcm16 at
16000 Hz and the bridge transcodes the G.711 PSTN leg for you, so your ASR gets
exactly the audio it wants.
This is the “bring your own ASR” path — you own the recognizer and the dialog
logic. If you’d rather have TeleQuick run a managed cascade
(ASR → LLM → TTS) from one config file, see
BYO ASR / LLM / TTS instead.
attach takes the call_sid, a codec/rate, and your onUplink callback. Frames
arrive as raw little-endian PCM16 — a mono 20 ms frame at 16 kHz is 320 samples
(640 bytes). Push each one to your recognizer as it arrives.
TypeScript
Python
import { Voice } from "@telequick/sdk/voice";const v = new Voice({ baseUrl: "https://engine.telequick.dev", apiKey: process.env.TELEQUICK_API_KEY!, orgId: "org_abc",});// `asr` is your own streaming recognizer client (e.g. a WebSocket to Deepgram// or a self-hosted Whisper). It accepts linear PCM16 @ 16 kHz.async function transcribeCall(sid: string) { const bridge = await v.audioBridge.attach(sid, { codec: "pcm16", // raw 16-bit PCM, not Opus/G.711 sampleRate: 16000, // what most streaming ASRs expect channels: 1, frameMs: 20, onUplink: (frame /* Uint8Array */, tsUs /* bigint */) => { asr.send(frame); // stream each 640-byte frame straight through }, }); asr.on("transcript", (text: string, isFinal: boolean) => { if (isFinal) console.log(`[${sid}] ${text}`); }); return bridge; // keep this reference alive for the whole call}
import osfrom telequick.voice import Voicev = Voice( base_url="https://engine.telequick.dev", api_key=os.environ["TELEQUICK_API_KEY"], org_id="org_abc",)# `asr` is your own streaming recognizer (e.g. a WebSocket to Deepgram or a# self-hosted Whisper). It accepts linear PCM16 @ 16 kHz.def transcribe_call(sid: str): def on_uplink(frame: bytes, ts_us: int) -> None: asr.send(frame) # stream each 640-byte frame straight through bridge = v.audio_bridge.attach( sid, on_uplink=on_uplink, codec="pcm16", # raw 16-bit PCM, not Opus/G.711 sample_rate=16000, # what most streaming ASRs expect channels=1, frame_ms=20, ) @asr.on("transcript") def _(text: str, is_final: bool) -> None: if is_final: print(f"[{sid}] {text}") return bridge # keep this reference alive for the whole call
attach both subscribes the uplink and publishes a downlink track, so hold
the returned bridge for the call’s lifetime — letting it be garbage-collected
drops both tracks and ends the bridge. When you’re done, bridge.close() before
call.hangup() so the tracks tear down cleanly.
Bind the bridge as soon as the call answers. Below, an outbound call is
originated with no server-side agent, so your ASR is the only consumer of the
uplink; the same attach works for an inbound sid you already hold.
If your recognizer takes Opus directly (or you’re just capturing to disk), ask
for codec: "opus" and skip the transcode — the caller’s audio reaches you as
20 ms encoded Opus packets. Use pcm16 only when your ASR needs raw samples.
tsUs / ts_us is the frame’s presentation timestamp in microseconds. Feed it
to a recognizer that supports word-level timing, or use it to align the
transcript with a recording keyed on the same call_sid.