Voice Gateway Setup
The voice gateway (cdma-voice-gw) is a separate process that bridges CDMA voice calls to SIP/PSTN networks. It handles SIP signaling, RTP media, and EVRC↔G.711 transcoding.
Architecture
CDMA Handset
↕ air interface (EVRC)
BTS / BSC / MSC media + policy
↕ A1 call control + A2p RTP bearer on the cellular side
cdma-voice-gw
↕ SIP/RTP (G.711)
SIP Trunk / PSTN
The cellular side separates call control from bearer media: MSC owns call policy, media orchestration, gateway control, and preemption over A1/A2p, while BSC executes the radio leg. The gateway remains the external SIP/RTP bridge for non-local calls.
MSC Configuration
Enable the gateway in config/msc.json:
{
"voice": {
"gateway": {
"enabled": true,
"endpoint": "http://127.0.0.1:17015",
"fallback_to_wav": true
}
}
}
| Field | Description |
|---|---|
enabled | Enable MSC-owned gateway routing for non-local calls |
endpoint | Gateway gRPC address |
fallback_to_wav | Fall back to WAV playback if gateway unreachable |
Gateway Configuration
The gateway uses its own JSON config file. A complete example is at config/voice-gw.example.json.
gRPC
{
"grpc": {
"listen_addr": "127.0.0.1:17015"
}
}
SIP
{
"sip": {
"listen_addr": "192.168.1.50:5060",
"transport": "udp",
"request_uri_template": "sip:{called}@trunk.example.com:5060",
"from_domain": "bts.local",
"caller_id_override": null,
"user_agent": "1XBTS-VoiceGW/0.1",
"keepalive_interval_secs": 0,
"auth": {
"username": "mytrunk",
"password_env": "SIP_PASSWORD"
},
"registration": {
"enabled": false,
"registrar_uri": "sip:trunk.example.com",
"expires_secs": 300
}
}
}
| Field | Description |
|---|---|
listen_addr | Concrete local IP:port (not 0.0.0.0 — libre requirement) |
transport | udp, tcp, or tls |
request_uri_template | SIP URI template — {called} replaced with dialed digits |
from_domain | Domain in SIP From header |
caller_id_override | Fixed DID for caller ID (null = use mobile’s MDN) |
auth.username | SIP digest auth username |
auth.password_env | Environment variable containing password |
registration.enabled | Send REGISTER to trunk (some providers require this) |
RTP
{
"rtp": {
"listen_addr": "0.0.0.0",
"port_range": [17100, 17200],
"advertise_addr": null,
"preferred_codecs": ["PCMU", "PCMA"]
}
}
| Field | Description |
|---|---|
listen_addr | IP for RTP bind |
port_range | UDP port range for RTP sessions |
advertise_addr | Public IP for SDP (if behind NAT) |
preferred_codecs | G.711 variants — PCMU (μ-law) or PCMA (A-law) |
NAT
{
"nat": {
"mode": "disabled",
"stun_server": "stun.l.google.com:19302"
}
}
| Mode | Description |
|---|---|
disabled | No NAT traversal (default — use when gateway has public IP) |
stun_latch | STUN for external IP discovery + RTP latching |
Calls
{
"calls": {
"max_concurrent_calls": 32,
"setup_timeout_ms": 30000,
"ringing_timeout_ms": 120000,
"media_idle_timeout_ms": 30000
}
}
Jitter Buffer
{
"jitter_buffer_ms": 60
}
Adaptive buffer (40-80 ms) for inbound RTP reordering. Inserts EVRC silence frames when packets are late.
Running the Gateway
# Set SIP password
export SIP_PASSWORD="mysecret"
# Start the gateway
cargo run --release -p cdma-voice-gw -- --config config/voice-gw.example.json
The gateway listens on the configured gRPC port. The MSC runtime connects automatically when gateway.enabled is true.
SIP Trunk Providers
The gateway works with any standard SIP trunk. Tested configurations:
| Provider | Transport | Auth | Register | Notes |
|---|---|---|---|---|
| FreeSWITCH | UDP | Digest | Optional | Local testing |
| Flowroute | UDP | Digest | Yes | Production trunks |
| Twilio | UDP/TLS | Digest | Yes | Elastic SIP |
FreeSWITCH Example
{
"sip": {
"request_uri_template": "sip:{called}@192.168.1.200:5060",
"from_domain": "192.168.1.50",
"auth": null,
"registration": { "enabled": false }
}
}
Flowroute Example
{
"sip": {
"request_uri_template": "sip:{called}@us-west-wa.sip.flowroute.com",
"from_domain": "sip.flowroute.com",
"auth": {
"username": "your-tech-prefix",
"password_env": "FLOWROUTE_PASSWORD"
},
"registration": {
"enabled": true,
"registrar_uri": "sip:sip.flowroute.com",
"expires_secs": 300
}
}
}
Transcoding
The gateway transcodes between EVRC (air interface) and G.711 (SIP/RTP):
| Direction | Pipeline |
|---|---|
| Handset → PSTN | EVRC primary bits → EVRC decode → PCM → G.711 encode → RTP |
| PSTN → Handset | RTP → G.711 decode → PCM → EVRC encode → EVRC primary bits |
Media frames on the cellular bearer are exchanged as raw EVRC codec payloads (without MuxPDU headers). The BSC adds or strips the MuxPDU MM=0 header for the Abis and air-interface side, and the MSC forwards non-local call media between A2p and the gateway. A2p uses RTP/UDP per circuit and the EVRC RTP payload format from RFC 3558. Erasure frames with rate_bps=0 are skipped in the gateway media path.
Troubleshooting
| Symptom | Check |
|---|---|
| Gateway not connecting | MSC voice.gateway.endpoint matches gateway grpc.listen_addr |
| SIP INVITE rejected | Auth credentials, from_domain, trunk provider settings |
| No audio | RTP listen address reachable from SIP peer, firewall rules on RTP port range |
| One-way audio | NAT config — try stun_latch mode, or set advertise_addr |
| Choppy audio | Jitter buffer too small, network latency to SIP peer |
| Calls fall back to WAV | Gateway process not running, or fallback_to_wav is true |
| DNS resolution failure | Check request_uri_template domain resolves, try IP directly |