I have a self hosted version of livekit integrated...
# ask-ai
m
I have a self hosted version of livekit integrated with Twilio SIP and Livekit server both are self hosted, the issue i am facing is with voice quality, the voice clips a lot its not clear at all. What could be the issue? the livekitcloud integration works just fine although some crackling noise is still there. But my concern is with clipping of voice? could there be some infra issue? let me know if more details are needed
t
Thanks for reaching out, Danish. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m sorry you’re seeing clipping and unclear audio. I’m responding to your concern about voice clipping in your self‑hosted LiveKit + Twilio SIP setup and want to help narrow the cause. Please share the following so I can pinpoint the issue and give concrete fixes: 1. LiveKit server details ◦ CPU, RAM, and network bandwidth ◦ LiveKit version and deployment method (Docker/k8s/binary) 1. Twilio SIP trunk settings ◦ Codec(s) offered (Opus / PCMU / PCMA / others) ◦ Transport (UDP / TCP / TLS) ◦ Any SIP media gateway or SBC in between 1. Audio processing configuration ◦ Are you using
BVCTelephony
for SIP participants or another noise filter? ◦ Any additional echo/noise suppression enabled on Twilio or other intermediaries 1. Observed network metrics during affected calls ◦ Packet loss, jitter, and RTT between the LiveKit server and Twilio ◦ Any interface saturation or packet drops on the server 1. Server logs and client symptoms ◦ LiveKit server logs during an affected call (errors/warnings about dropped frames, encoding, or AV sync) ◦ Whether clipping affects all participants or only SIP callers ◦ Whether clips are constant or occur at specific times (e.g., when many users join) Quick checks you can run now • Confirm Twilio and LiveKit are using a common codec (Opus recommended). Mismatched codecs that force transcoding can cause artifacts. • Verify SIP participants use
BVCTelephony
noise cancellation (the generic filter can introduce artifacts for telephony). • Monitor CPU/network on the LiveKit host during a call to see if spikes correlate with clipping. • Check for packet loss/jitter between LiveKit and Twilio — even moderate loss causes WebRTC to drop audio frames. If you send the details above and a short excerpt of relevant logs (errors/warnings timestamps), I’ll analyze them and give targeted next steps. Sources: OpenAI and LiveKit partner to turn Advanced Voice into an API | Bringing Zoom's end-to-end optimizations to WebRTC | Using Noise Cancellation for SIP and WebRTC Participants | Debugging Audio/Video Sync Issues in LiveKit Publishing | Agents telephony integration | LiveKit docs
m
1. LiveKit server details ◦ CPU, RAM, and network bandwidth - 4 core 16 GB ◦ LiveKit version and deployment method (Docker/k8s/binary) - Docker 2. Twilio SIP trunk settings ◦ Codec(s) offered (Opus / PCMU / PCMA / others) - Not sure where to find this ◦ Transport (UDP / TCP / TLS) - UDP ◦ Any SIP media gateway or SBC in between - No 3. Audio processing configuration ◦ Are you using
BVCTelephony
for SIP participants or another noise filter? - No ◦ Any additional echo/noise suppression enabled on Twilio or other intermediaries - No 4. Observed network metrics during affected calls ◦ Packet loss, jitter, and RTT between the LiveKit server and Twilio - Not available ◦ Any interface saturation or packet drops on the server - Unknown 5. Server logs and client symptoms ◦ LiveKit server logs during an affected call (errors/warnings about dropped frames, encoding, or AV sync) - Not available ◦ Whether clipping affects all participants or only SIP callers - All ◦ Whether clips are constant or occur at specific times (e.g., when many users join) - Constant Quick checks you can run now • Confirm Twilio and LiveKit are using a common codec (Opus recommended). Mismatched codecs that force transcoding can cause artifacts. - How do I ensure this? • Verify SIP participants use
BVCTelephony
noise cancellation (the generic filter can introduce artifacts for telephony). • Monitor CPU/network on the LiveKit host during a call to see if spikes correlate with clipping. - No spikes as such that could affect it • Check for packet loss/jitter between LiveKit and Twilio — even moderate loss causes WebRTC to drop audio frames.
t
Thanks — this is helpful. I’m responding to your answers and your question “How do I ensure Twilio and LiveKit use a common codec (Opus)?” — and I’ll also recap next steps that will most likely fix the constant clipping. 1) Ensure Twilio negotiates Opus • In the Twilio Console: Programmable Voice → SIP Trunks → your trunk → Voice and Fax → Audio Codec Preferences. Set Primary = Opus, Secondary = PCMU. • Verification: capture the SDP from the call (either LiveKit server logs or the browser/client SDP) and confirm the negotiated codec is
opus
in the answer. If you want, paste a redacted SDP here and I’ll check it. 2) Enable
BVCTelephony
for SIP participants • You said you’re not using it. Switch SIP participants to use
BVCTelephony
(telephony-optimized noise cancellation) instead of the standard
BVC
. This often fixes clipping/distortion for SIP callers. 3) Quick network and server checks (you can run these now) • From the LiveKit host, test latency/loss to Twilio: ◦ ping <twilio-sip-host> ◦ mtr --report --report-cycles 10 <twilio-sip-host> • Capture RTP during a call (tcpdump/wireshark) to confirm packet loss/jitter on RTP streams. • Monitor CPU/network during a call: ◦ htop ◦ iftop -i <iface> You mentioned no spikes — still check for packet drops on the NIC (dmesg) or interface counters (ethtool -S or ifconfig). 4) Codec/transcoding concerns • If Twilio does not offer Opus and falls back to PCMU/PCMA, transcoding or narrowband audio can cause artifacts. Ensuring Opus on Twilio avoids transcoding and preserves quality. 5) Logs and next diagnostic data I need from you If clipping continues after steps 1–3, please share: • A short excerpt of LiveKit server logs during an affected call (errors/warnings timestamps). • The client or server SDP offer/answer (redact IPs/keys). • A short tcpdump/Wireshark RTP snippet (pcap) if possible. • Confirmation you switched SIP to
BVCTelephony
and the result. If you want, start with: set Opus as primary on Twilio and enable
BVCTelephony
for SIP participants, then run an
mtr
during a call and paste either the mtr output or a redacted SDP here — I’ll analyze and give the next concrete steps. Sources: client-sdk-js | livekit/livekit_composite | DeepWiki | Using Noise Cancellation for SIP and WebRTC Participants | sip | livekit/livekit_composite | DeepWiki | livekit/livekit_composite | DeepWiki | Agent session | LiveKit docs
m
can you help with complete guid of setting up self hosted livekit service with twilio
t
I can walk you through a complete, step‑by‑step setup for a self‑hosted LiveKit server integrated with Twilio SIP, plus recommended configuration to avoid clipping and poor audio. I’ll cover prerequisites, deployment (Docker), Twilio SIP trunk config, LiveKit settings (codecs, noise cancellation), networking checks, testing, and troubleshooting. Tell me if you want the guide for Docker Compose or Kubernetes — I’ll assume Docker Compose unless you say otherwise. 1) Prerequisites • A server with a public IP (your current 4‑core / 16GB is OK for small deployments). • Docker & Docker Compose installed. • Domain name pointed to the server (for TLS). • Valid TLS certs (Let’s Encrypt or your cert) for secure WebRTC. • Twilio account with SIP Trunk access and a phone number or SIP URI for testing. • Ports: UDP/TCP 7880 (signaling optional), UDP 3478 and 10000–20000 (media via UDP for WebRTC) — adjust if using TURN/TCP/TLS. 2) Deploy LiveKit (Docker Compose) • Use the official LiveKit Docker Compose layout: ◦ Include services: livekit-server, redis (if required), optionally coturn (TURN server). • Configure environment variables for livekit-server: ◦ LIVEKIT_KEYS: your API key/secret ◦ LIVEKIT_DOMAIN / PUBLIC_URL: your domain ◦ RTC_ANNOUNCED_IP / RTC_INTERFACE: if behind NAT ◦ TURN servers: point clients to your coturn if you run one • Mount TLS certs or use a reverse proxy (nginx) that terminates TLS and forwards to LiveKit. • Start and verify logs: docker-compose up -d; docker-compose logs -f livekit 3) Twilio SIP Trunk configuration (ensure codec & transport) • In Twilio Console → Programmable Voice → SIP Trunks: ◦ Create a trunk (or use existing). ◦ In Origination/Destination, add your LiveKit SIP endpoint (SIP URI). ◦ Transport: set to UDP (you can also enable TCP/TLS if UDP problems persist). ◦ Audio Codec Preferences: set Primary = Opus, Secondary = PCMU (PCMA if needed). ◦ SIP Registration / Authentication: set credentials as required. • If Twilio needs a SIP URI that points at LiveKit’s SIP gateway or your SIP bridge, configure accordingly. 4) Ensure LiveKit is configured to accept SIP from Twilio • LiveKit must be able to receive SIP INVITEs from Twilio. If you use an external SIP gateway or a SIP adapter, point Twilio at that. • Confirm any SIP proxy, NAT, or firewall rules allow Twilio → LiveKit on the SIP transport and RTP ports. 5) Codec & SDP negotiation • Ensure LiveKit advertises and accepts Opus in SDP. • Enforce Opus in Twilio (step 3). If the negotiated codec is PCMU/PCMA, audio will be narrowband and may be transcoded — avoid this where possible. • To verify: capture SDP from a call (browser or server logs) and confirm
opus/48000
is negotiated. 6) Noise cancellation & processing • For SIP participants, use
BVCTelephony
noise cancellation (telephony-optimized). • For WebRTC clients, use
BVC
. • Apply noise filter per participant type at session/agent setup so telephony audio isn’t processed with the wideband filter. • Avoid double processing: disable Twilio-side noise suppression if using
BVCTelephony
. 7) TURN / NAT and network resiliency • Run a TURN server (coturn) and register it in LiveKit config to help clients behind NAT. • For Twilio → LiveKit media: if UDP proves lossy, enable TCP/TLS transport on Twilio to improve reliability. • Opening the necessary UDP RTP port range and ensuring low latency to Twilio is essential. 8) Monitoring & server tuning • Monitor CPU, memory, network: ◦ htop, iftop, dstat during test calls. ◦ Check kernel NIC counters (ifconfig / ethtool) for drops. • LiveKit logs: enable debug logging to catch dropped frames, encoder errors, or AV sync issues. • If you hit CPU limits, consider scaling to more vCPUs or additional instances. 9) Testing steps (to validate) • Local loopback test: connect two WebRTC clients to the LiveKit server and confirm audio quality. • SIP direct test: if possible, call the LiveKit SIP endpoint directly (bypass Twilio) to isolate Twilio influence. • Twilio integrated test: make a call from PSTN/SIP through Twilio to LiveKit and capture RTP. • Tools: ◦ mtr or traceroute for RTT/jitter between LiveKit server and Twilio. ◦ tcpdump/wireshark capturing RTP during a call to inspect packet loss, codec, and jitter. ◦ Inspect SDP in browser dev tools or LiveKit logs to confirm Opus. 10) Key troubleshooting for clipping (specific actions) • Confirm Opus is negotiated (if not, set primary to Opus in Twilio). • Switch SIP participants to
BVCTelephony
. • Capture RTP during a clipped call and look for packet loss or reordering. • Monitor CPU and encoder load; if frames are dropped under load, raise resources. • If RTP packet loss is present, try switching Twilio transport from UDP → TLS. • Check audio buffer sizes and synchronizer warnings in logs; increase buffer sizes if you see AV sync/queue warnings. • Ensure no double noise suppression: disable Twilio side processing if LiveKit handles it. 11) Collecting logs & data for deeper diagnosis • LiveKit server logs during an affected call (with timestamps). • SDP offer/answer from a call (redact secrets). • mtr output to Twilio. • tcpdump/pcap of RTP (a short snippet). • Server resource metrics (cpu/memory/net) during a call. If you want, I can: • Provide a sample Docker Compose snippet and recommended LiveKit env vars. • Show example AgentSession code to set
BVCTelephony
. • Walk you through gathering SDP, mtr, and pcap and interpreting them. Which would you like next: a Docker Compose example and exact env vars, AgentSession code to set
BVCTelephony
, or steps to capture SDP/pcap?
m
share livekit and sip yams and agent.py
t
Below are example files you can use as a starting point for a self‑hosted LiveKit + Twilio SIP integration. These are tuned to avoid clipping (Opus codec, telephony noise cancellation) and assume Docker Compose deployment. Edit values (API keys, domain names, passwords) to match your environment. 1) docker-compose.yml (LiveKit + Redis + coturn)
Copy code
version: "3.7"

services:
  redis:
    image: redis:6-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data

  coturn:
    image: instrumentisto/coturn:4.5.2
    restart: unless-stopped
    ports:
      - "3478:3478/udp"
      - "3478:3478/tcp"
      - "49152-65535:49152-65535/udp"
    environment:
      - LISTEN=0.0.0.0
      - EXTERNAL_IP=${PUBLIC_IP}
      - REALM=${TURN_REALM}
      - USERS=${TURN_USER}:${TURN_PASS}
    command: ["--no-stdout-log", "--lt-cred-mech", "--realm", "${TURN_REALM}", "--external-ip", "${PUBLIC_IP}", "--user", "${TURN_USER}:${TURN_PASS}", "--min-port", "49152", "--max-port", "65535"]

  livekit:
    image: livekit/livekit-server:latest
    restart: unless-stopped
    ports:
      - "7880:7880/tcp"    # public API (optional)
      - "7881:7881/tcp"    # internal API (optional)
      - "5060:5060/udp"    # SIP UDP (if using built-in SIP)
      - "3478:3478/udp"    # STUN/TURN
      - "50000-60000:50000-60000/udp" # RTP media ports
    environment:
      - LIVEKIT_KEYS=${LIVEKIT_KEYS}           # format: key:secret (or set LIVEKIT_API_KEY/SECRET)
      - LIVEKIT_DOMAIN=${LIVEKIT_DOMAIN}       # your public domain
      - LIVEKIT_ANNOUNCED_IP=${PUBLIC_IP}      # public IP if behind NAT
      - RTC_PORT=${RTC_PORT:-7882}
      - RTC_PORT_RANGE_START=50000
      - RTC_PORT_RANGE_END=60000
      - TURN_URL=turn:${TURN_USER}@${TURN_REALM}:${PUBLIC_IP}:3478?transport=udp
      - SENTRY_DSN=${SENTRY_DSN:-}
      - LOG_LEVEL=info
      - SIP_ENABLED=true
      - SIP_UDP_PORT=5060
      - SIP_CODECS=opus,pcmu,pcma
      - SIP_PASS_HEADERS=X-Twilio-Caller-Name,X-Twilio-Caller-City
    volumes:
      - ./config/livekit.yaml:/etc/livekit/livekit.yaml:ro
    depends_on:
      - redis
      - coturn

volumes:
  redis-data:
2) config/livekit.yaml (example LiveKit config)
Copy code
# config/livekit.yaml
bind:
  internal: "0.0.0.0:7880"
  external: "0.0.0.0:7881"

rtc:
  udp_port: 7882
  port_range_start: 50000
  port_range_end: 60000
  tls: true

ingress:
  api_key: "${LIVEKIT_API_KEY:-}"
  api_secret: "${LIVEKIT_API_SECRET:-}"

sip:
  enabled: true
  udp_port: 5060
  # Force Opus first; include PCMU/PCMA for fallback
  codecs:
    - name: "opus"
      clock_rate: 48000
      channels: 1
    - name: "PCMU"
      clock_rate: 8000
      channels: 1
    - name: "PCMA"
      clock_rate: 8000
      channels: 1
  pass_headers:
    - "X-Twilio-Caller-Name"
    - "X-Twilio-Caller-City"
  dtls: true
  ice_tcp: true

logging:
  level: "info"
  format: "json"
3) Twilio SIP trunk recommended settings (summary) • In Twilio Console → Programmable Voice → SIP Trunks: ◦ Origination / Termination: set destination to sip:your-domain.com:5060 ◦ Transport: UDP (can enable TCP/TLS if UDP shows loss) ◦ Audio Codec Preferences: Primary = Opus, Secondary = PCMU (PCMA) ◦ Ensure any credential lists or IP ACLs include your LiveKit server 4) agent.py (example Python agent using BVCTelephony for SIP participants)
Copy code
# agent.py
import os
import asyncio
import logging
from dotenv import load_dotenv

from livekit import agents, rtc
from livekit.agents import AgentSession
from livekit.plugins import noise_cancellation, silero

load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("sip-agent")

class SIPAgent(agents.Agent):
    def __init__(self):
        super().__init__(instructions="You are a helpful voice agent. Speak clearly and concisely.")

async def prewarm(proc):
    proc.userdata["vad"] = silero.VAD.load()

async def session_handler(ctx: agents.job_context.JobContext):
    # select noise filter based on participant type
    async def choose_filter(params: rtc.AudioInputOptions):
        if params.participant and params.participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
            <http://logger.info|logger.info>("Using BVCTelephony for SIP participant")
            return noise_cancellation.BVCTelephony()
        <http://logger.info|logger.info>("Using BVC for WebRTC participant")
        return noise_cancellation.BVC()

    session = AgentSession(
        vad=silero.VAD.load(),
        stt="deepgram/nova-3",
        llm="openai/gpt-4.1-mini",
        tts=None,
        room_input_options=rtc.AudioInputOptions(
            noise_cancellation=choose_filter
        ),
        preemptive_generation=True,
        allow_interruptions=True,
    )

    agent = SIPAgent()
    await session.start(room=ctx.room, agent=agent)
    await session.generate_reply(instructions="Hello — how can I help you today?")
    # Keep the session open until end
    await session.wait_for_end()

def main():
    server = agents.AgentServer()
    server.setup_fnc = prewarm
    server.rtc_session()(session_handler)

    try:
        agents.cli.run_app(server)
    except Exception as e:
        logger.exception("Agent server error: %s", e)

if __name__ == "__main__":
    main()
5) .env (example)
Copy code
PUBLIC_IP=203.0.113.10
LIVEKIT_API_KEY=livekitkey
LIVEKIT_API_SECRET=livekitsecret
LIVEKIT_DOMAIN=<http://your-domain.com|your-domain.com>
TURN_USER=turnuser
TURN_PASS=turnpass
TURN_REALM=<http://your-domain.com|your-domain.com>
SENTRY_DSN=
LIVEKIT_KEYS=${LIVEKIT_API_KEY}:${LIVEKIT_API_SECRET}
Notes and next steps • After deploying, verify Opus is negotiated: capture SDP from a call (browser devtools or server logs) and confirm
opus/48000
in the answer. • Enable debug logging in livekit-server if you need to inspect dropped frames or sync queue warnings. • If clipping persists: ◦ Ensure SIP participants use BVCTelephony (agent.py does this). ◦ Run mtr/ping between your server and Twilio to check packet loss/jitter. ◦ Try Twilio transport TCP/TLS if UDP packet loss is observed. ◦ Capture a short pcap of RTP during a clipped call and share the relevant info (codec, packet loss, jitter). • Adjust resources or scale if CPU/IO becomes constrained during calls. If you’d like, I can: • Provide a ready-to-run Docker Compose with values filled (you supply domain/IP and credentials). • Show exact livekit-server env var references for API keys vs LIVEKIT_KEYS. • Walk through capturing and redacting an SDP or pcap and interpreting the results. Sources: examples/bank-ivr | livekit/livekit_composite | DeepWiki
m
where is sip service?
t
Good question — thanks for asking. The SIP service is provided by the LiveKit server itself (the server includes a built‑in SIP gateway when enabled). In the example files I shared: • The livekit container runs the SIP service (enabled via
sip.enabled: true
in config/livekit.yaml). • Docker Compose maps the SIP UDP port:
5060:5060/udp
, so the LiveKit container listens on UDP port 5060 for SIP traffic. • SIP codec preferences are set in config/livekit.yaml under
sip.codecs
(Opus first), and
SIP_PASS_HEADERS
in the compose env passes Twilio headers through. So Twilio should point its trunk/termination to sip:your-domain.com:5060 (or to your NAT/public IP) and LiveKit will accept the INVITEs and handle RTP according to the configured port range (50000–60000). If you’d prefer a separate SIP proxy/gateway instead of LiveKit’s built‑in SIP, you can deploy an SBC (e.g., Kamailio/FreeSWITCH/OpenSIPS) in front of LiveKit — tell me if you want an example of that setup.
m
my current setup is below
livekit.yml
Copy code
port: 7880
log_level: debug

rtc:
  tcp_port: 7881
  port_range_start: 50000
  port_range_end: 60000
  use_external_ip: true
  enable_loopback_candidate: false

keys:
  devagent: xxxxx

redis:
  address: localhost:6379
  username: ""
  password: ""
  db: 0
  use_tls: false
sip.yml
Copy code
api_key: 'devagent'
api_secret: 'xxxx'
ws_url: '<ws://localhost:7880>'
redis:
  address: 'localhost:6379'
sip_port: 5060
rtp_port: 10000-20000
# use_external_ip Tells the server to use STUN to discover its actual public IP address and advertise that IP to clients
use_external_ip: true
logging:
  level: debug
docker-compose.yml
Copy code
livekit:
    image: livekit/livekit-server:latest
    restart: always
    network_mode: "host"
    environment:
      - LIVEKIT_API_KEY=devagent
      - LIVEKIT_API_SECRET=xxxx
      - LIVEKIT_USE_EXTERNAL_IP=true
      - LIVEKIT_BIND_ADDRESSES=0.0.0.0
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml
    command: livekit-server --config /etc/livekit.yaml

  # LiveKit SIP Server
  livekit_sip:
    image: livekit/sip:latest
    restart: always
    network_mode: "host"
    volumes:
      - ./sip.yaml:/etc/sip.yaml
    command: sip-server --config /etc/sip.yaml
    depends_on:
      - livekit

  # LiveKit Agent (Your Voice AI)
  sidecar_service:
    image: sidecar-service:latest
    build:
      context: ./sidecar_service
      dockerfile: Dockerfile
    restart: always
    network_mode: "host"
    environment:
      - LIVEKIT_URL=<wss://devdanishwani.h><http://_api.com/livekit/|_api.com/livekit/>
      - LIVEKIT_API_KEY=devagent
      - LIVEKIT_API_SECRET=xxxx
    volumes:
      - ./sidecar_service:/home/ubuntu/h_api/sidecar_service
    command: python voice_agent/agent.py start
    depends_on:
      - livekit
      - livekit_sip
agent.py
Copy code
import asyncio
import logging
import os


from dotenv import load_dotenv
# from livekit import agents, rtc
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli, Agent, AgentSession
from livekit.plugins import deepgram, openai, silero, elevenlabs


load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger(__name__)

PROMPT = """

"""

class VoiceAssistant(Agent):
    def __init__(self) -> None:
        super().__init__(
            instructions=PROMPT
        )


async def entrypoint(ctx: JobContext):
    <http://logger.info|logger.info>(f"Agent started for room: {ctx.room.name}")
    try:
    # Connect to the room with audio only
        await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

        # Create agent session
        session = AgentSession(
            vad=silero.VAD.load(),
            stt=deepgram.STT(language="en", sample_rate=8000),
            llm=openai.LLM(model="gpt-4.1-mini"),
            tts=elevenlabs.TTS(voice_id='DtsPFCrhbCbbJkwZsb3d', model='eleven_turbo_v2'),
        )

        # Start the session with your agent
        await session.start(agent=VoiceAssistant(), room=ctx.room)

        # Generate initial greeting
        <http://logger.info|logger.info>("Agent greeting caller...")
        await session.generate_reply(instructions="Greet the caller and ask how you can help them today")

        <http://logger.info|logger.info>("Agent ready for conversation")
    except asyncio.CancelledError:
        <http://logger.info|logger.info>("Agent session cancelled")
    except Exception as e:
        logger.error(f"Agent error: {e}", exc_info=True)


if __name__ == "__main__":
    livekit_url = os.getenv("LIVEKIT_URL")
    livekit_api_key = os.getenv("LIVEKIT_API_KEY")
    livekit_api_secret = os.getenv("LIVEKIT_API_SECRET")
    
    <http://logger.info|logger.info>(f"Connecting to LiveKit at {livekit_url}")
    <http://logger.info|logger.info>(f"API Key: {livekit_api_key}")
    
    cli.run_app(
        WorkerOptions(
            entrypoint_fnc=entrypoint,
            agent_name="haptik-ai-voice-agent",

        )
    )
nginx.conf
Copy code
# LiveKit WebSocket signaling - with /livekit/ prefix
location /livekit/ {
    # Proxy to LiveKit server, preserving the full path
    # Without trailing slash on proxy_pass preserves the /livekit/ path
    proxy_pass <http://127.0.0.1:7880/>;
    proxy_http_version 1.1;
    
    # CRITICAL: WebSocket upgrade headers
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    
    # Standard proxy headers
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $server_name;
    
    # Timeouts for long-lived WebSocket connections
    proxy_connect_timeout 60s;
    proxy_send_timeout 3600s;
    proxy_read_timeout 3600s;
    
    # Disable buffering for WebSocket
    proxy_buffering off;
    proxy_request_buffering off;
}

# LiveKit health check endpoint
location /livekit/healthz {
    proxy_pass <http://127.0.0.1:7880/healthz>;
    access_log off;
}

# LiveKit REST API endpoints (optional)
location /livekit/api/ {
    proxy_pass <http://127.0.0.1:7880/api/>;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
let me know what am I doing wrong here?
t
Thanks — this is very helpful. I reviewed your configs and the main issues causing clipping and what to change. I’ll be concise and actionable. What’s wrong (summary) • STT sample rate and model: you set Deepgram STT to 8000 Hz — that’s wrong and can cause audio problems. Use 16 kHz and a supported model (nova-3). • No BVCTelephony: SIP callers need `BVCTelephony()`; without it telephony audio is processed incorrectly and may clip. • SIP codec negotiation not explicit: your sip.yaml doesn’t list codecs; ensure Opus is prioritized so Twilio and LiveKit negotiate Opus (no transcoding). • Potential SDP/network NAT issues: using host networking plus
use_external_ip: true
can still mis-advertise IPs if STUN fails — verify external IP advertisement and firewall/NAT for RTP ports. • LIVEKIT_URL scheme: agent uses wss:// but local nginx/proxy may require ws:// during local testing. Immediate fixes to apply 1. Update Deepgram STT in agent.py • Remove sample_rate=8000 and set model to nova-3 (or nova-3-general). Example: stt=deepgram.STT(model="nova-3-general", language="en") 1. Apply BVCTelephony for SIP participants • When a participant is SIP, set noise filter to
noise_cancellation.BVCTelephony()
before STT. Ensure your AgentSession or audio pipeline uses that filtered audio. 1. Explicitly set codecs in sip.yaml (Opus first) • Example: codecs: • opus • pcma • pcmu 1. Verify Twilio trunk settings • In Twilio Console → SIP Trunks → Audio Codec Preferences: set Primary = Opus, Secondary = PCMU/PCMA. Confirm transport (UDP/TCP/TLS) — switch to TCP/TLS if you see UDP loss. 1. Check SDP advertisement and NAT • Ensure LiveKit advertises the correct public IP (set external IP explicitly if STUN fails). • Confirm docker host networking + nginx + LiveKit external IPs are consistent. • Open RTP port range (50000–60000) and SIP 5060 UDP on firewall; confirm no packet drops (ethtool/ifconfig counters). Diagnostic data to collect if issues persist • livekit_sip and livekit-server logs (during a clipped call) • Agent logs around STT and audio processing • A redacted SDP offer/answer from a failing call • mtr/ping output to Twilio SIP endpoint • Short pcap of RTP during a clipped call If you want, I can: • Provide an updated agent.py snippet with BVCTelephony applied and fixed Deepgram settings. • Provide an updated sip.yml and livekit.yml with exact fields to replace. • Inspect your logs/SDP if you paste redacted excerpts. Which of the above do you want next? Sources: Deepgram STT plugin guide | LiveKit docs | Using Noise Cancellation for SIP and WebRTC Participants | Bringing Zoom&amp;#39;s end-to-end optimizations to WebRTC
m
Provide the updated
agent.py
,
sip.yml
and
livekit.yml
t
Below are updated, ready-to-use versions of agent.py, sip.yml, and livekit.yml tuned to fix your issues: Opus-first codec negotiation, telephony noise cancellation (
BVCTelephony
), correct STT settings (no 8kHz), and explicit RTC settings. Replace placeholders (API keys, secrets, domain, IPs) with your real values. 1) agent.py
Copy code
import os
import asyncio
import logging
from dotenv import load_dotenv

from livekit.agents import JobContext, WorkerOptions, cli, Agent, AgentSession
from livekit import rtc
from livekit.plugins import deepgram, silero, elevenlabs, noise_cancellation, openai

load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("voice-agent")

PROMPT = "You are a helpful voice assistant. Speak clearly and concisely."

class VoiceAssistant(Agent):
    def __init__(self):
        super().__init__(instructions=PROMPT)

async def entrypoint(ctx: JobContext):
    <http://logger.info|logger.info>(f"Agent started for room: {ctx.room.name}")

    # wait for first participant (SIP caller)
    participant = await ctx.wait_for_participant()

    # choose telephony-optimized noise cancellation for SIP participants
    nc_filter = noise_cancellation.BVC()
    try:
        if participant and participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
            <http://logger.info|logger.info>("Detected SIP participant — applying BVCTelephony")
            nc_filter = noise_cancellation.BVCTelephony()
    except Exception:
        <http://logger.info|logger.info>("Could not determine participant kind; using default BVC")

    # build AgentSession with corrected STT model (no 8kHz)
    session = AgentSession(
        vad=silero.VAD.load(),
        stt=deepgram.STT(model="nova-3-general", language="en"),
        llm=openai.LLM(model="gpt-4.1-mini"),
        tts=elevenlabs.TTS(voice_id=os.getenv("ELEVEN_VOICE_ID", ""), model="eleven_turbo_v2"),
        room_input_options=rtc.AudioInputOptions(
            noise_cancellation=nc_filter
        ),
        preemptive_generation=True,
        allow_interruptions=True,
    )

    await session.start(agent=VoiceAssistant(), room=ctx.room)

    # initial greeting
    await session.generate_reply(instructions="Hello — thank you for calling. How can I help you today?")

    # keep session alive until end
    await session.wait_for_end()
    <http://logger.info|logger.info>("Session ended")

if __name__ == "__main__":
    # Ensure LIVEKIT_URL uses ws:// for local testing; wss:// in production with TLS
    <http://logger.info|logger.info>(f"Connecting to LiveKit at {os.getenv('LIVEKIT_URL')}")
    cli.run_app(
        WorkerOptions(
            entrypoint_fnc=entrypoint,
            agent_name="voice-agent",
        )
    )
2) sip.yml
Copy code
# sip.yml - LiveKit SIP server config
api_key: "devagent"           # matches LiveKit API key
api_secret: "REPLACE_WITH_SECRET"
ws_url: "<ws://localhost:7880>" # LiveKit RPC/ws URL to reach server
redis:
  address: "localhost:6379"

sip_port: 5060
rtp_port: 50000-60000         # align with LiveKit port range
use_external_ip: true         # or set a concrete external_ip field if STUN fails
# Explicit codec priorities: Opus first, then G.711 fallbacks
codecs:
  - opus
  - PCMU
  - PCMA

# Pass Twilio headers through to LiveKit
pass_headers:
  - "X-Twilio-Caller-Name"
  - "X-Twilio-Caller-City"

logging:
  level: debug
3) livekit.yml
Copy code
# livekit.yml - livekit-server config
port: 7880
log_level: debug

rtc:
  tcp_port: 7881
  port_range_start: 50000
  port_range_end: 60000
  use_external_ip: true        # set to true to use STUN, or replace with explicit external_ip
  enable_loopback_candidate: false
  # Explicit audio codec preference
  audio_codecs:
    - opus

keys:
  devagent: "REPLACE_WITH_YOUR_KEY_SECRET_PAIR"  # format key:secret if using LIVEKIT_KEYS env

redis:
  address: "localhost:6379"
  username: ""
  password: ""
  db: 0
  use_tls: false

# SIP can be configured here, but using separate sip service is fine if you run livekit/sip container
sip:
  enabled: true
  udp_port: 5060
  # enforce opus first for SIP -> LiveKit negotiation
  codecs:
    - name: "opus"
      clock_rate: 48000
      channels: 1
    - name: "PCMU"
      clock_rate: 8000
      channels: 1
    - name: "PCMA"
      clock_rate: 8000
      channels: 1

logging:
  level: debug
  format: json
Notes and quick guidance • Replace secrets, API keys, domain, and ELEVEN_VOICE_ID environment values before deploying. • Ensure firewall/NAT allow SIP 5060 and RTP range 50000–60000 UDP. Confirm no packet drops on the network interface. • In Twilio Console, set SIP Trunk Audio Codec Preferences: Primary = Opus, Secondary = PCMU (PCMA). • For local testing use ws://LIVEKIT_URL; for production with TLS use wss:// and valid certificates. • If clipping continues, collect: livekit_sip logs, livekit-server logs, a redacted SDP, and an RTP pcap so I can inspect packet loss/jitter and the negotiated codec. Would you like these as downloadable files or a combined Docker Compose example updated with these configs?
m
getting
room is not connected
error
t
Thanks — let’s fix the "room is not connected" error quickly. I’ll call out the likely causes in your setup and give focused, actionable fixes to apply. What I’m addressing: the agent failing because it attempts room actions before the room is connected. Immediate fixes (apply in your agent code) 1. Ensure you call and await connect before interacting with the room • At start of your entrypoint, add:
Copy code
await ctx.connect()
• Do this before calling wait_for_participant(), creating AgentSession, or any room API. 1. Guard against premature room use • Check connection state before room operations:
Copy code
if not ctx.room or not ctx.room.connected:
    await ctx.connect()
1. Await start/joins in the right order • Sequence: a. await ctx.connect() b. participant = await ctx.wait_for_participant() c. create AgentSession and await session.start(...) d. await session.generate_reply(...) • Don’t call session methods or ctx.room.* until step 1 completes. 1. Add connection/logging to see failures
Copy code
try:
    await ctx.connect()
    <http://logger.info|logger.info>("connected to room %s", ctx.room.name)
except Exception as e:
    logger.exception("failed to connect to room: %s", e)
    return
Config/network checks (if connect keeps failing) • LIVEKIT_URL: ensure the agent uses the correct scheme/host: ◦ Local testing: ws://localhost:7880 ◦ Production with TLS: wss://your-domain/livekit (and nginx must forward wss properly) • API key/secret: ensure env vars match livekit server keys. • Nginx proxied path: if you proxy under /livekit/, ensure LIVEKIT_URL includes that path (e.g., wss://domain/livekit/) and nginx proxy_pass preserves websocket upgrades. • Ports: ensure agent can reach LiveKit API port (7880) from container/network. Test from sidecar container: curl/ws connect. • If using host networking + nginx reverse proxy, confirm there are no conflicting ports. If you still see "room is not connected" • Paste the exact stack trace and relevant agent logs around connect() call. • Paste your LIVEKIT_URL value and confirm whether agent logs show a failed connect or never attempts connect. • If connect fails, provide livekit-server logs at the same timestamps. If you want, I can: • Provide an updated minimal agent.py snippet with the correct connect sequence and extra logging. • Inspect your agent logs/stack trace if you paste them. Which would you like next — I’ll send the updated agent snippet with connect guards and logging. Sources: SIP Lifecycle Management Agent | LiveKit docs | Agents telephony integration | LiveKit docs | Agent dispatch | LiveKit docs
m
sip.yaml
shared by you is invalid. especially on codecs section
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!
m
the sip yaml you shared above is invalid
> >