how to implement tenvad in python livekit agent
# ask-ai
a
how to implement tenvad in python livekit agent
t
Thanks — I can help, but I need one quick clarification so I give you the right code: by "tenvad" do you mean a specific VAD library (a package named tenvad) or are you referring generically to a TensorFlow / neural VAD model or another VAD implementation? Also, please tell me: • Which LiveKit agent flow you’re using (real-time audio agent that receives audio frames in memory, or post-recording/transcription pipeline)? • How you currently receive audio in the agent (PCM frames from the SDK, WAV files, or stream/frames from an adapter)? • Your runtime: Python version and whether you can install native extensions (pip wheels) or only pure-Python packages. If you confirm the VAD library (or provide a link), I’ll provide a compact, copy-pasteable Python example showing: 1. How to install/import tenvad (or an alternative such as webrtcvad or a TensorFlow model). 2. How to hook it into the LiveKit agent audio path (frame buffering, sample rate conversion if needed). 3. How to detect voice activity and trigger actions (start/stop recording, send STT requests, or silence-based end-of-utterance). Which VAD do you want me to target: the tenvad package specifically, or shall I use a proven alternative like webrtcvad for a working example? , with interactive elements
a
t
Thanks — I can walk you through a practical, copy-pasteable way to integrate TEN VAD (ten-vad) into a Python LiveKit agent. I’ll assume you have a LiveKit agent that receives audio frames (PCM) from participants and that you can install the ten-vad package. If any assumption is different, tell me and I’ll adapt the example. High-level approach • Install ten-vad and any dependencies. • Convert LiveKit audio frames into the format ten-vad expects (usually 16 kHz mono PCM, int16 or float32 — check ten-vad README). • Run ten-vad on frames or on short buffered windows (e.g., 20–30 ms frames or 200–500 ms windows) to detect speech segments. • Use VAD decisions to trigger actions: start/stop STT, mark turn boundaries, or forward only voiced frames to STT. • Keep the VAD processing async and non-blocking (use asyncio.create_task) so it doesn’t block the agent’s main loop. Install • Add ten-vad to your environment: ◦ pip install ten-vad • Also ensure you have numpy and soundfile if needed by ten-vad. Audio format notes • LiveKit audio frames provide raw PCM. Confirm the frame sample rate (commonly 48 kHz from the SDK) and channels. • Most VAD models (including neural VADs) expect 16 kHz mono PCM. You will likely need to: a. Downmix stereo -> mono if needed. b. Resample from 48 kHz -> 16 kHz. c. Convert byte buffer to numpy array of int16 or float32 depending on ten-vad API. • Use a lightweight resampler such as librosa.resample or torchaudio/resampy. For minimal deps, you can use soundfile + resampy or a simple linear interpolation for modest quality. Integration pattern (concise code sketch) • This example shows how to: ◦ Receive LiveKit AudioFrame-like buffers (replace with your actual frame object). ◦ Buffer into fixed windows (e.g., 200 ms). ◦ Resample/convert and feed to ten-vad. ◦ Emit VAD decisions and perform actions (start/stop).
Copy code
import asyncio
import numpy as np
from tenvad import VAD  # adapt to actual ten-vad import path
import resampy  # pip install resampy
from typing import AsyncIterable

# Configuration
SRC_SAMPLE_RATE = 48000    # incoming LiveKit frame rate (adjust if different)
TARGET_SR = 16000          # ten-vad expected rate
WINDOW_MS = 200            # analyze every 200ms
SAMPLES_PER_WINDOW = int(TARGET_SR * WINDOW_MS / 1000)

# Simple helper to convert interleaved bytes to mono int16 numpy
def bytes_to_mono_int16(frame_bytes: bytes, src_sample_rate: int, channels: int = 1) -> np.ndarray:
    # assuming 16-bit PCM little-endian
    audio = np.frombuffer(frame_bytes, dtype=np.int16)
    if channels > 1:
        audio = audio.reshape(-1, channels)
        audio = audio.mean(axis=1).astype(np.int16)  # downmix to mono
    return audio

class TenVADWrapper:
    def __init__(self, model_name: str = "default"):
        # Initialize the TEN VAD model - adjust constructor to library API
        self.vad = VAD(model_name=model_name)
        self.buffer = np.array([], dtype=np.int16)

    def process_pcm_frame(self, pcm_int16: np.ndarray, src_sr: int):
        # Resample to target if needed
        if src_sr != TARGET_SR:
            pcm_float = pcm_int16.astype(np.float32) / 32768.0
            resampled = resampy.resample(pcm_float, src_sr, TARGET_SR)
            pcm_int16 = (resampled * 32767).astype(np.int16)

        # Append to buffer
        self.buffer = np.concatenate([self.buffer, pcm_int16])

        results = []
        # Consume windows
        while len(self.buffer) >= SAMPLES_PER_WINDOW:
            window = self.buffer[:SAMPLES_PER_WINDOW]
            self.buffer = self.buffer[SAMPLES_PER_WINDOW:]
            # TEN VAD API: adapt this call to the real one
            # assume self.vad.is_speech(np_array, sample_rate) -> bool
            is_speech = self.vad.is_speech(window.astype(np.float32) / 32768.0, sampling_rate=TARGET_SR)
            results.append(is_speech)
        return results

# Example consumer that integrates with a LiveKit audio stream
async def vad_consumer(audio_frame_stream: AsyncIterable[bytes], src_sr: int, channels: int = 1):
    ten_vad = TenVADWrapper()
    in_speech = False

    async for frame_bytes in audio_frame_stream:
        pcm = bytes_to_mono_int16(frame_bytes, src_sr, channels=channels)
        vad_results = ten_vad.process_pcm_frame(pcm, src_sr)
        for speech in vad_results:
            if speech and not in_speech:
                in_speech = True
                # Voice started
                print("VAD: voice started")
                # Trigger start STT, mark turn, etc.
            elif not speech and in_speech:
                in_speech = False
                # Voice ended
                print("VAD: voice ended")
                # Trigger stop STT, finalize utterance, etc.

# Example usage:
# - replace `my_livekit_audio_stream()` with how you obtain raw PCM frames from LiveKit SDK
# asyncio.create_task(vad_consumer(my_livekit_audio_stream(), SRC_SAMPLE_RATE, channels=2))
Practical tips and choices • Window size: 200 ms is common to get stable speech decisions; you can use smaller windows (20–30 ms) but you’ll need a smoother (hangover) to avoid chopping. • Hangover logic: implement a short post-speech grace period (e.g., keep considered “in speech” for 200–400ms after last positive frame) to avoid flicker. • Batch STT sending: start STT on voice start and send buffered voiced audio; stop on voice end and finalize transcription. This reduces costly STT calls. • Performance: run VAD inference on CPU in an asyncio Task; if many participants, consider batching or a worker pool to avoid blocking. • Thread-safety: if the VAD library is not async-safe, call it inside ThreadPoolExecutor via asyncio.get_running_loop().run_in_executor. Adapting to LiveKit agent specifics • If you receive frames as livekit.rtc.AudioFrame or an AudioStream wrapper, extract the raw bytes similarly to the helper above. See the LiveKit AudioStream helper for how frames are yielded (example reference: audio_stream API). • If you want to insert this as a VAD node or pipeline node inside LiveKit Agents, create a custom node that wraps STT nodes and filters frames using the ten-vad decisions (LiveKit supports custom pipeline nodes). Edge cases and validation • Confirm ten-vad API: replace
VAD(...).is_speech(...)
with actual ten-vad function names and parameter types (ten-vad README shows exact usage). • Validate the expected sample rate and dtype for ten-vad; adjust conversion accordingly. • Test with recorded audio samples first, then live streams. • Add logging and metrics (start/stop counts, false positive rate) to tune thresholds. If you want, I can: • Produce a concrete, runnable example tailored to the actual ten-vad API (I’ll inspect the repo usage signature if you want me to pull the exact function names). • Convert the sketch into a LiveKit Agent node implementation that filters audio before STT, including hangover and batching logic. Which of those would you like next? Sources: Silero VAD plugin | LiveKit Documentation | Pipeline nodes and hooks | LiveKit Documentation | livekit-rtc/livekit/rtc/audio_stream.py | Speech-to-text (STT) models overview | LiveKit Documentation, with interactive elements
a
tenvad with livekit plugins
t
Thanks — I can build a precise, copy‑pasteable integration for TEN VAD into your Python LiveKit agent. To make the example correct and minimal-impact for your setup, I need a few details. Please answer the short questions below and I’ll return a full implementation (one file) that: • Converts LiveKit audio frames to the format TEN VAD expects (resample / mono / dtype). • Runs TEN VAD in an async-safe way with hangover logic (start/stop events). • Hooks VAD decisions into the Agent flow (start/stop STT or mark turns) as either a plugin/vad node or a wrapper around your STT adapter. • Is tuned for your expected latency and concurrency. Quick clarifying questions 1. Deployment & SDK ◦ Are you using LiveKit Cloud or a self-hosted LiveKit server? ◦ Which LiveKit Python package/version are you using for agents? 1. How audio arrives to your agent ◦ Do you receive raw PCM as livekit.rtc AudioFrame objects, raw bytes streams, or files? ◦ What is the incoming sample rate and channel layout (common: 48000 Hz stereo)? 1. TEN VAD package & API ◦ Confirm you want the pip package from https://github.com/TEN-framework/ten-vad (I’ll use the repo API, but let me know if you prefer a specific model/config). ◦ If you already know the exact function to call (e.g.,
VAD(...).is_speech()
), paste it; otherwise I’ll infer from the repo. 1. Integration style (pick one) ◦ A: Replace built-in VAD/turn_detection and register TEN VAD as the agent’s
vad
plugin (preferred if you want centralized turn detection). ◦ B: Pre-filter audio before calling your existing STT adapter (minimal change). ◦ C: As a separate async task that listens to tracks and emits start/end events to your AgentSession (non-invasive). 1. Behavior on VAD events ◦ What should happen on voice-start and voice-end? (e.g., start/stop STT, send RPC to client, buffer audio for a single STT call) ◦ Desired hangover (post-speech tail) in ms? Common values: 200–400 ms. 1. Runtime constraints ◦ Python version and whether you can install native deps (wheels)? This affects resampling choices. ◦ Approximate concurrent live audio streams (1, 10, 100) — helps sizing worker pool. If you want a default working example right away, I can provide one that assumes: • LiveKit Python agent receives 48 kHz stereo AudioFrame objects, • We resample to 16 kHz mono, • Use ten-vad via a blocking API wrapped in ThreadPoolExecutor, • Integration style B (pre-filter before STT), • Hangover = 300 ms, • Works for single or a few concurrent streams without special scaling. Tell me whether you want the default now or answer the 6 questions above and I’ll tailor the file to your environment. Sources: Turns overview | LiveKit Documentation | Speech-to-text (STT) models overview | LiveKit Documentation, with interactive elements