A SIP softphone that lives in your terminal.
Make calls, record audio, and talk to the world — all from the command line.
Why open a GUI when your terminal is already open?
sipris a pure-Rust SIP softphone built for developers, sysadmins, and anyone who prefersCtrl+Cover clicking "End Call." No Overkill. No Electron. Just SIP over UDP, RTP audio, and your command line.
- Zero-config calls — dial a SIP URI directly, no registration needed
- Digest authentication — RFC 2617 MD5 challenge-response for 401/407, works with Ooma, Asterisk, FreeSWITCH, etc.
- Incoming call support — accept inbound INVITEs with the
listencommand - Real-time audio — G.711 mu-law & A-law codecs at 8 kHz, plus real Opus codec at 48 kHz
- Call hold/resume — re-INVITE with
a=sendonly/a=sendrecvSDP direction - Call transfer (REFER) — blind transfers with Refer-To and NOTIFY status updates
- PRACK — reliable provisional responses (100rel / RFC 3262)
- DNS SRV resolution — automatic
_sip._udp.<domain>server discovery and failover - Call recording — save incoming audio to WAV with
--record - Interactive command history — Up/Down navigation with persistent history in
~/.sipr.history - Reverse history search —
Ctrl+Rincremental search inside in-call CLI - Speed dial slots — store SIP URIs in config (
0-9) and dial quickly withsiphone call <slot> - SIP + RTP flow tracing —
sniff/flowsnow shows early media and connected RTP flow events - Cross-platform audio — CoreAudio (macOS), ALSA (Linux), WASAPI (Windows) via cpal
- Jitter buffer — handles out-of-order and delayed packets gracefully
- 220+ tests — from codec round-trips to full end-to-end SIP scenario tests
- ~10K lines of Rust — small, auditable, hackable
# Install from source
cargo install --path siphone
# Make a call — it's that simple (no --server or --user required)
siphone call sip:echo@sip.provider.comPress Ctrl+C to hang up.
- Fixed audio degradation over call time — replaced hard buffer trim with adaptive clock recovery. When the sender RTP clock drifts faster than the audio hardware clock, 1 sample per frame is gently dropped (linear blend, ~0.6% rate adjust) instead of a hard discontinuous trim that repeated every few seconds. Symmetric low-water duplicate keeps latency bounded in both directions. A hard cap (80 ms) still acts as a burst safety net but trims to the 40 ms midpoint to leave headroom.
- Fixed metallic artifacts from RTCP packets decoded as audio — RTCP packets (PT 200–204) were parsed as RTP with payload type 72–76 and fed to the G.711 decoder, producing brief metallic noise. Added RFC 5761 payload type filter (PT 64–95) to discard them before decoding.
- Fixed unbounded playback latency — added a 4-frame (80 ms) cap to the output buffer to prevent the mpsc channel from filling, which previously caused silent-frame drops heard as clicks.
- Fixed real-time audio callback blocking — changed
Mutex::lock(potentially blocking) totry_lockin the cpal output callback, with a localVecDequethat drains in one short critical section. Eliminated O(n)Vec::drainfront-drain artefacts by usingVecDeque::pop_front. - Instant DTMF keypress — pressing
0-9,*, or#during a call sends the DTMF tone immediately via RTP RFC2833, no Enter required. Letters are not intercepted so commands likedtmf,hold, etc. can still be typed normally. Usedtmf Afor tones A-D. - Added persistent in-call history (
~/.sipr.history) with Up/Down navigation. - Added
Ctrl+Rreverse search for in-call command history. - Added
max_historyconfig option (default1000) to cap stored history entries. - Added speed dial slots in config (
speed_dials) with CLI management commands. - Added
siphone call <slot>support to dial speed-dial entries directly. - Added in-call shortcut
Ctrl+0..9for quick speed-dial transfer (speed <slot>). - Added RTP flow visibility in
flows, including:- early media RTP announced on
183 - connected RTP announced on
200 OK - first active RTP
RXandTXevents
- early media RTP announced on
- Improved terminal rendering in interactive mode (fixed drift/skew under raw mode).
- Updated
hangupcommand to wait for BYE200 OK(or timeout after 3s).
git clone https://github.com/xmppjingle/sipr.git
cd sipr
cargo install --path siphoneInstall from a Homebrew tap (after a formula is published):
brew tap <owner>/sipr
brew install siphoneInstall from a GitHub Release .deb:
wget https://github.com/xmppjingle/sipr/releases/download/vX.Y.Z/siphone_X.Y.Z_amd64.deb
sudo apt install ./siphone_X.Y.Z_amd64.debBuild your own .deb package from source:
sudo apt-get update
sudo apt-get install -y libasound2-dev pkg-config
cargo install cargo-deb
cargo deb -p siphone
sudo apt install ./target/debian/siphone_*_amd64.deb- Rust 1.70+
- A working audio device (speakers + optional microphone)
- libopus (for Opus codec support — installed automatically on most systems)
The server is automatically extracted from the SIP URI — no need to specify it separately:
siphone call sip:bob@sip.example.comAuthenticate with a SIP server that requires credentials:
siphone call sip:bob@sip.example.com --user alice --password secretOr route through a specific SIP proxy:
siphone call sip:bob@example.com --server sip.proxy.com --user aliceCall a configured speed-dial slot:
siphone call 1
# or:
siphone dial 1Listen for inbound INVITEs on a specific port:
siphone listen --port 5060
siphone listen --port 5060 --record incoming.wav --timeout 120After the call connects, use interactive commands:
hold # put the remote party on hold
resume # resume from hold
transfer <uri> # blind transfer (REFER) to another SIP URI
dtmf 123# # queue/send RTP RFC2833 DTMF
dtmf-info 55 # queue/send SIP INFO DTMF
dtmf-send # flush queued DTMF immediately
dtmf-queue # show queued DTMF count
sniff # start SIP tracing
sniff verbose # SIP tracing with full message details
flows # show SIP ladder + RTP flow events (EARLY/CONNECTED/RX/TX)
speed 1 # transfer call to speed-dial slot 1 (REFER)
hangup # send BYE and wait for 200 OK (up to 3 seconds)
Incoming DTMF is announced in the CLI for both RTP RFC2833 and SIP INFO.
Use Up/Down for history and Ctrl+R to search history.
Use Ctrl+0..9 as a shortcut for speed <slot>.
Generate a template and save it:
siphone config --init > ~/.config/sipr/config.jsonExample history limit:
{
"max_history": 1000
}Configure speed dials from CLI:
# Set slot 1
siphone speed-dial set 1 sip:bob@example.com
# Update slot 1 (alias of set)
siphone speed-dial update 1 sip:bob@new.example.com
# Show one slot
siphone speed-dial show 1
# List all configured slots
siphone speed-dial list
# Remove slot
siphone speed-dial remove 1Or define directly in config JSON:
{
"speed_dials": {
"1": "sip:alice@example.com",
"2": "sip:bob@example.com"
}
}Capture incoming audio to a WAV file:
siphone call sip:echo@provider.com --user alice --record conversation.wavThe recording is saved even if you hang up with Ctrl+C.
Registration supports digest authentication automatically:
siphone register --server sip.example.com --user alice --password secret# List all audio devices
siphone devices
# Test your speakers with a tone
siphone test-audio --duration 3
# Test mic capture + playback loop (3s default)
siphone test-micsipr is organized as a three-crate Rust workspace, keeping concerns cleanly separated:
sipr/
├── sip-core/ # SIP protocol engine
│ ├── Message parsing & serialization (INVITE, ACK, BYE, REGISTER, REFER, PRACK, …)
│ ├── Digest authentication (RFC 2617) with pure-Rust MD5
│ ├── UDP transport layer
│ ├── Dialog & transaction state machines
│ └── SDP offer/answer negotiation (including hold/resume direction)
│
├── rtp-core/ # Real-time audio engine
│ ├── RTP packet parsing & construction
│ ├── G.711 mu-law (PCMU) & A-law (PCMA) codecs
│ ├── Real Opus codec (48 kHz via audiopus)
│ ├── Adaptive jitter buffer
│ ├── Audio device abstraction (cpal backend)
│ ├── Sample rate conversion & channel mapping
│ └── WAV file recording
│
└── siphone/ # CLI application
├── Call management (INVITE → media → BYE)
├── Incoming call acceptance (UAS / listen mode)
├── Hold, resume, blind transfer (REFER), PRACK
├── DNS SRV resolution (_sip._udp.domain)
├── Registration with digest auth
└── Audio device enumeration & testing
┌─────────────┐ SIP/UDP ┌──────────────┐
│ siphone │ ◄──────────────► │ SIP Server │
│ (CLI) │ │ / Endpoint │
│ │ RTP/UDP │ │
│ ┌────────┐ │ ◄──────────────► │ │
│ │ Jitter │ │ └──────────────┘
│ │ Buffer │ │
│ └───┬────┘ │
│ │ │
│ ┌───▼────┐ │
│ │ G.711 │ │
│ │ Codec │ │
│ └───┬────┘ │
│ │ │
│ ┌───▼────┐ │
│ │Speaker │ │ ──► WAV file (optional)
│ └────────┘ │
└─────────────┘
- SIP signaling sets up the call (INVITE → 200 OK → ACK)
- SDP negotiation agrees on codec and RTP port
- RTP packets carry G.711 or Opus-encoded audio
- Jitter buffer reorders packets and smooths playback
- cpal plays decoded audio through your speakers (with automatic sample rate conversion)
- Optionally, decoded audio is written to a WAV file
Contributions are welcome! Whether it's a bug fix, new codec, or improved documentation — open a PR.
# Run the test suite
cargo test --workspace
# Run with logging
RUST_LOG=debug siphone call sip:test@example.com --user testYou can publish via your own tap, and Apache-2.0 is compatible with Homebrew/core licensing requirements.
- Create a tap repository, for example
github.com/<owner>/homebrew-sipr. - Add a formula using the helper script:
# From the sipr repo:
./scripts/update-homebrew-formula.sh v0.1.0 ../homebrew-sipr/Formula/siphone.rb- Commit and push in the tap repository:
cd ../homebrew-sipr
git add Formula/siphone.rb
git commit -m "siphone v0.1.0"
git push- Users can then install:
brew tap <owner>/sipr
brew install siphoneThis repo includes .github/workflows/publish-homebrew.yml to update a tap formula automatically.
- Add a repository secret named
TAP_GITHUB_TOKENwith push access to your tap repo. - Run the workflow manually with:
tag: release tag (for examplev0.1.0)tap_repo: full tap repo name (for examplexmppjingle/homebrew-sipr)
This repo includes .github/workflows/release-deb.yml to build and publish .deb packages.
- On every
v*tag push, it:- builds
siphoneDebian package viacargo deb - uploads it as a workflow artifact
- attaches it to the GitHub Release for that tag
- builds
Release example:
git tag v0.1.0
git push origin v0.1.0