Skip to content

SIP Voice Backend

The SIP backend handles the full SIP call lifecycle: incoming INVITE → SDP negotiation → RTP media → BYE teardown. For PBX and SIP trunk integration.

Install with: pip install roomkit[sip]

Quick start

from roomkit.voice.backends.sip import SIPVoiceBackend

backend = SIPVoiceBackend(
    local_sip_addr=("0.0.0.0", 5060),
    local_rtp_ip="10.0.0.5",
    rtp_port_start=10000,
)
backend.on_call(handle_incoming_call)
await backend.start()

See the full SIP example for a complete runnable script.

API Reference

SIPVoiceBackend

SIPVoiceBackend(*, local_sip_addr=('0.0.0.0', 5060), local_rtp_ip='0.0.0.0', advertised_ip=None, rtp_port_start=10000, rtp_port_end=20000, supported_codecs=None, dtmf_payload_type=101, user_agent=None, server_name='-', jitter_capacity=32, jitter_prefetch=0, skip_audio_gaps=True, plc=True, cn=False, cn_payload_type=13, playout=False, playout_max_delay_ms=200, duplicate_tx=False, symmetric_rtp=False, rtp_inactivity_timeout=30.0, rtp_establishment_timeout=60.0, max_sessions=0, auth_users=None, auth_realm='roomkit', send_silence_on_answer=0.0, outbound_silence_fill=False, pacer_prebuffer_ms=80, pacer_jitter_headroom_ms=60)

Bases: SIPAuthMixin, SIPCallingMixin, SIPAudioMixin, VoiceBackend

VoiceBackend that handles incoming SIP calls with full lifecycle.

Listens for SIP INVITE requests, negotiates codecs via SDP, creates RTP sessions for audio streaming, and handles BYE/CANCEL for call teardown. Incoming calls are auto-accepted; an on_call callback lets the application route the session to a room.

Parameters:

Name Type Description Default
local_sip_addr tuple[str, int]

(host, port) to bind the SIP listener.

('0.0.0.0', 5060)
local_rtp_ip str

IP address for RTP media binding.

'0.0.0.0'
advertised_ip str | None

Public IP to advertise in SDP c=/o= lines and SIP Contact/Via headers when behind NAT. RTP sockets still bind to local_rtp_ip. Default None (use the resolved local IP for everything).

None
rtp_port_start int

First RTP port to allocate.

10000
rtp_port_end int

Last RTP port in the allocation range.

20000
supported_codecs list[int] | None

List of payload type numbers to accept (default [PT_G722, PT_PCMU, PT_PCMA]).

None
dtmf_payload_type int

RTP payload type for RFC 4733 DTMF events.

101
user_agent str | None

Value for the SIP User-Agent header in responses.

None
server_name str

SDP session name (s= line) in answers.

'-'
jitter_capacity int

Maximum number of packets the RTP jitter buffer can hold. Default 32 (~640 ms at 20 ms/packet).

32
jitter_prefetch int

Number of packets to accumulate before starting playout. Default 0 (start immediately, optimised for low latency).

0
skip_audio_gaps bool

When True (default), gaps in the RTP stream are skipped rather than stalling the jitter buffer.

True
plc bool

When True (default), packets confirmed lost are replaced with concealment PCM (native Opus PLC, or last-frame repetition fading to silence), keeping the inbound stream temporally continuous for the pipeline (recorder duration, AEC alignment). Effective only with skip_audio_gaps. The per-session concealed_frames counter is exposed in the audio stats.

True
cn bool

When True, outbound silence carries RFC 3389 comfort noise instead of dead air (via aiortp). Default False.

False
cn_payload_type int

RTP payload type for comfort noise (default 13, the static CN assignment).

13
playout bool

When True, inbound audio is delivered on a steady clock through aiortp's adaptive playout buffer — depth tracks the measured network jitter (EWMA), with deadline-based concealment. The inbound defense for jittery links (WiFi callers, congested paths). jitter_prefetch only applies when this is off. Default False.

False
playout_max_delay_ms int

Upper bound on the adaptive buffer depth — the most latency playout may add to absorb jitter (default 200).

200
duplicate_tx bool

When True, every outbound RTP datagram is sent twice — the duplicate rides the next frame's send, ~20 ms later (via aiortp). Receivers dedupe by sequence number, so no negotiation is needed; bandwidth doubles. The outbound defense for lossy links. Default False.

False
symmetric_rtp bool

When True, follow the remote RTP address from the packets that actually arrive (RFC 4961 latching, via aiortp) instead of trusting the address in the SDP for the whole call. Requires aiosipua>=0.7.1.

Two things it buys. It is the ordinary fix for a caller behind NAT, whose advertised address is not the one its packets come from. And it limits media redirection: an offer pointing at a third party stops being followed the moment the caller sends anything of its own.

What it does not do is stop a caller that stays silent. Latching only fires on an inbound packet, so an INVITE that advertises someone else's address and then sends nothing keeps the stream aimed there. rtp_establishment_timeout is what bounds that one, and authentication is what prevents it.

Default False, matching aiortp and aiosipua, so enabling it is a deliberate change to how media is addressed mid-call.

False
rtp_inactivity_timeout float

Seconds of RTP silence before forcing session disconnect (safety net for missed BYE). Set to 0 to disable. Default 30.

30.0
rtp_establishment_timeout float

Seconds an answered session may hold its RTP port without ever receiving a packet. rtp_inactivity_timeout cannot cover this — it measures time since the last packet, and there has been none — so a caller that takes the 200 OK and then stays silent would hold a port, a socket and an RTCP task for the life of the process. Set to 0 to disable. Default 60.

60.0
max_sessions int

Maximum concurrent sessions. Further INVITEs are answered 503 Service Unavailable with a Retry-After rather than allowed to drain the RTP port pool. Set to 0 for no limit. Default 0 — deployments behind a trusted PBX are already bounded by it; set this whenever the port is reachable more widely.

0
auth_users dict[str, str] | None

Optional mapping of username → password for inbound digest authentication. When set, incoming INVITEs without valid credentials are challenged with 401. For multi-tenant or large credential stores prefer :meth:set_auth_resolver instead — the resolver is consulted on every authentication attempt, so the application owns credential storage.

Authentication is off unless one of the two is configured. With neither, every INVITE that reaches the port is accepted, and the caller also chooses its own room and session via X-Room-ID / X-Session-ID. That is the intended shape behind a trusted PBX, which sets those headers itself; it is a hole anywhere else. See the Security Hardening guide.

None
auth_realm str

Realm string used in the WWW-Authenticate challenge header (default "roomkit").

'roomkit'
send_silence_on_answer float

Seconds of PCM silence to push through the outbound RTP pacer immediately after an outbound call is answered. Used to unblock PSTN trunks whose RTP receivers wait for a packet from the caller before forwarding carrier audio ("symmetric RTP learning" / NAT latching). Default 0.0 (disabled). Typical value when needed: 0.5.

0.0
outbound_silence_fill bool

When True, the outbound audio pacer emits a 20 ms PCM silence frame whenever its queue runs dry, instead of stalling the RTP stream. Keeps RTP flowing at a steady 50 pps so PSTN endpoints (which lack packet loss concealment) don't perceive glitches at sentence boundaries. Default False (pacer pauses during idle gaps).

False
pacer_prebuffer_ms float

Milliseconds of audio the outbound pacer accumulates before its first send. Default 80.

80
pacer_jitter_headroom_ms float

Milliseconds of lead the outbound pacer keeps over wall-clock so the remote jitter buffer always has a safety margin. Larger values absorb longer host-side stalls at the cost of barge-in latency. Default 60.

60

start async

start()

Start the SIP listener and prepare for incoming calls.

close async

close()

Disconnect all sessions, unregister, and stop UAS/transport.

on_dtmf_received

on_dtmf_received(callback)

Register a callback for inbound DTMF digits (RFC 4733).

on_call

on_call(callback)

Register a callback for incoming SIP calls.

Fired after the INVITE has been accepted and the RTP session is active. Accepts both sync and async callbacks. Can be used as a decorator::

@backend.on_call
async def handle_call(session):
    await kit.process_inbound(
        parse_voice_session(session, channel_id="voice")
    )

on_call_disconnected

on_call_disconnected(callback)

Register a callback for remote BYE (call hangup).

In SIP, call disconnect and client disconnect are the same event (a BYE terminates the dialog). Callbacks registered here and via :meth:on_client_disconnected share the same list and are all fired on any disconnect. Do not register the same function via both methods.

on_client_disconnected

on_client_disconnected(callback)

Register callback for client disconnection (base-class API).

In SIP, this is equivalent to :meth:on_call_disconnected — both register into the same callback list. Fired on remote BYE or RTP inactivity timeout.

Codecs

The SIP backend negotiates codecs via SDP. Supported codecs:

Codec Payload Type Audio Rate Quality
G.722 9 16 kHz (wideband) Best — recommended for voice AI
G.711 µ-law (PCMU) 0 8 kHz (narrowband) Standard
G.711 A-law (PCMA) 8 8 kHz (narrowband) Standard

By default, the backend accepts all three codecs with G.722 preferred. You can restrict codecs via supported_codecs:

from roomkit.voice.backends.sip import SIPVoiceBackend, PT_G722, PT_PCMU, PT_PCMA

# G.722 only (wideband)
backend = SIPVoiceBackend(
    local_sip_addr=("0.0.0.0", 5060),
    local_rtp_ip="10.0.0.5",
    rtp_port_start=10000,
    supported_codecs=[PT_G722],
)

# G.711 only (narrowband)
backend = SIPVoiceBackend(
    local_sip_addr=("0.0.0.0", 5060),
    local_rtp_ip="10.0.0.5",
    rtp_port_start=10000,
    supported_codecs=[PT_PCMU, PT_PCMA],
)

Capabilities

The SIP backend declares DTMF_SIGNALING (RFC 4733 out-of-band DTMF) and INTERRUPTION (cancel outbound audio mid-stream).

X-header routing

Room and session IDs are extracted from X-Room-ID and X-Session-ID SIP headers. All X-headers are available in session.metadata["x_headers"].

Callbacks

In addition to the standard VoiceBackend callbacks, the SIP backend provides:

  • on_call(callback) — fired when an incoming INVITE is accepted
  • on_call_disconnected(callback) — fired when the remote party sends BYE

Authentication

Inbound INVITEs can be challenged with RFC 2617 digest auth via either a static auth_users={"user": "pass"} dict (constructor) or a runtime set_auth_resolver(fn) callback. The resolver pattern is the right choice for multi-tenant deployments where credentials live in a database and change without a restart. See the Authentication section in the SIP Voice Backend guide for examples and the multi-tenant pattern.