Pure-Python SIP/RTP user-agent library (UAC + UAS) with asyncio
Project description
opensip
Saf-Python, asyncio tabanlı SIP/RTP user-agent kütüphanesi. UAC + UAS, REGISTER, HTTP Digest auth, G.711 ses kodek'i, opsiyonel mikrofon/hoparlör köprüsü — sıfır C bağımlılığı, ~33 KB wheel.
pip install opensip # sinyalleşme + RTP (numpy varsa hızlandırma)
pip install "opensip[audio]" # + mic/speaker köprüsü (sounddevice + numpy)
⚠️ Erken alfa. Gerçek bir Türk bulut-PBX provider'ı (netsantral.com) üzerinden uçtan uca iki yönlü ses ile doğrulandı. Yine de RFC 3261'in birkaç önemli parçası eksik — production'a almadan önce Bilinen sınırlamalar bölümünü okuyun.
Özellikler
- 🎯 Saf Python, sıfır C bağımlılığı (audio extra hariç)
- ⚡ asyncio — tek event loop'ta binlerce eşzamanlı dialog
- 📞 UAC + UAS — hem arama yap hem de cevap ver
- 🔐 HTTP Digest auth — MD5, MD5-sess, SHA-256, qop=auth (RFC 7616)
- 🎙️ G.711 PCMU/PCMA — numpy varsa vektörize, 20 ms frame başına ~1 µs
- 🔄 OPTIONS keepalive — provider'ın "alive?" pingini otomatik 200 OK
- 🎧 Mic/speaker köprüsü —
sounddeviceile sistem ses cihazlarına bağla - 🧱 Jitter buffer — reorder + gap-fill (silence) + RFC 3550 §A.8 jitter ölçümü
- ☎️ DTMF her iki yönde —
send_dtmf()(RFC 4733) gönderim,on_dtmfcallback ile alım - 📊 RTP istatistikleri —
RTPSession.statsile paket/byte sayaçları + jitter - 📦 Tip ipuçları her yerde —
py.typedpaketlenmiş
Sürüm notları
0.2.0 (yeni)
- Jitter buffer —
RTPSession'a varsayılan açık (jitter_ms=60). Sıra dışı paketleri yeniden sıralar, kayıp frame yerine sessizlik koyar,jitter_ms=0ile devre dışı bırakılabilir. JitterBuffer.recommended_target_ms()— RFC 3550 §A.8 EWMA ile ağ koşullarına göre hedef derinlik önerir.- DTMF alımı (RFC 4733) —
RTPSession.on_dtmf/Call.on_dtmfcallback; sürdürme + 3 redundant end-packet otomatik dedup. RTPSession.stats—packets_sent/recv,bytes_sent/recv,dtmf_recv+ iç içe jitter sub-stats.
Hızlı başlangıç
Giden arama
import asyncio
from opensip import UserAgent, Account
async def main():
ua = UserAgent(local_addr=("0.0.0.0", 5060))
await ua.start()
acc = Account(
username="alice",
domain="sip.example.com",
password="s3cret",
server=("sip.example.com", 5060),
)
await ua.register(acc)
call = await ua.invite(acc, "sip:bob@sip.example.com")
await call.wait_answered()
await asyncio.sleep(10)
await call.hangup()
await ua.stop()
asyncio.run(main())
Gelen arama
import asyncio
from opensip import UserAgent, Account
async def main():
ua = UserAgent(local_addr=("0.0.0.0", 5060))
@ua.on_incoming_call
async def handle(call):
await call.answer()
await call.wait_ended()
await ua.start()
acc = Account(username="alice", domain="sip.example.com",
password="s3cret", server=("sip.example.com", 5060))
await ua.register(acc)
await asyncio.Event().wait() # sonsuza kadar bekle
asyncio.run(main())
Mikrofon ↔ uzak taraf köprüsü (audio extras ile)
from opensip.audio import AudioBridge
# ... yukarıdaki örnekteki gibi call'u kur ...
bridge = AudioBridge(sample_rate=8000)
bridge.start()
call.on_pcm(bridge.feed_speaker) # uzak taraf → hoparlör
async def pump_mic():
while call.is_active:
pcm = await bridge.read_microphone()
call.write_pcm(pcm) # mikrofon → uzak taraf
asyncio.create_task(pump_mic())
Çalışan örnekler: examples/make_call.py, examples/receive_call.py.
API özeti
from opensip import UserAgent, Account, Call
UserAgent — top-level facade
| Yöntem | Açıklama |
|---|---|
await ua.start() / stop() |
Transport'u aç / kapat |
await ua.register(acc) |
REGISTER + otomatik yenileme |
await ua.unregister(acc) |
Expires: 0 REGISTER |
await ua.invite(acc, target) → Call |
Giden çağrı kur |
@ua.on_incoming_call |
Gelen INVITE handler decorator |
Call — bir SIP dialog'u
| Yöntem / property | Açıklama |
|---|---|
await call.wait_answered(timeout=None) |
200 OK'i bekle |
await call.wait_ended() |
BYE'ı bekle |
await call.answer() |
UAS — gelen çağrıyı cevapla |
await call.hangup() |
BYE gönder, RTP kapat |
call.write_pcm(bytes) |
Uzak tarafa 16-bit PCM gönder |
call.on_pcm(callback) |
Gelen PCM için handler |
call.is_active |
Dialog "answered" durumunda mı |
call.codec |
Anlaşılan codec (Codec nesnesi) |
AudioBridge (opensip[audio] extras)
| Yöntem | Açıklama |
|---|---|
bridge.start() / stop() |
Mikrofon + hoparlör stream'leri |
await bridge.read_microphone() |
Bir frame PCM oku |
bridge.feed_speaker(bytes) |
Hoparlöre PCM gönder |
Performans
G.711 PCMU/PCMA hot path, numpy varsa vektörize LUT lookup'a düşer. Apple M-series üzerinde, 8 kHz × 20 ms × 16-bit frame için:
| İşlem | Saf-Python | numpy | Hızlanma |
|---|---|---|---|
| PCMU encode | 13.1 µs/frame | 1.0 µs/frame | 13.6× |
| PCMU decode | 25.8 µs/frame | 1.0 µs/frame | 25.3× |
| PCMA encode | 12.4 µs/frame | 1.0 µs/frame | 12.7× |
| PCMA decode | 25.6 µs/frame | 1.0 µs/frame | 24.4× |
20 ms ptime bütçesinin %0.005'i — binlerce paralel çağrıda codec CPU yükü ihmal edilebilir. Çıktının bit-exact'liği 65,536 PCM değerinin tümü üzerinde doğrulanmıştır.
Yeniden üretmek için:
python tests/bench_codecs.py
Doğrulanmış provider'lar
| Provider | Sinyalleşme | İki yönlü ses | Notlar |
|---|---|---|---|
| netsantral.com | ✅ | ✅ | Symmetric-RTP / SBC NAT handling sağlıyor |
| sip2sip.info | ⚠️ test edilmedi | — | Açık-kayıt test provider'ı, deneyebilirsiniz |
| Twilio SIP trunking (TLS) | ❌ | — | TLS-only; opensip henüz TLS desteklemiyor |
| Yerel Asterisk / FreeSWITCH | 🟢 beklenen | 🟢 beklenen | LAN'da NAT olmadan çalışmalı |
Mimari
opensip/
├── message.py # SIP request/response parser + serializer (RFC 3261 §7-§20)
├── headers.py # URI, NameAddr, Via — compact form, IPv6, quoted params
├── auth.py # HTTP Digest (MD5/MD5-sess/SHA-256, qop=auth)
├── sdp.py # SDP offer/answer (RFC 4566), audio m=line + codec seçimi
├── transport.py # asyncio UDP transport
├── ua.py # UserAgent — UAC + UAS facade, dialog state inline
├── rtp.py # RTP packetization (RFC 3550) + 20 ms ptime sender loop
├── codecs.py # G.711 µ-law / A-law — numpy hot path + pure-Python fallback
├── audio.py # sounddevice wrapper (opsiyonel)
├── utils.py # branch/tag/Call-ID üreteçleri, IP keşfi
└── exceptions.py # Hata hiyerarşisi
Bilinen sınırlamalar
opensip v0.1 minimal-viable bir UA: gerçek bir provider ile çağrı yapar ama RFC 3261'in birkaç önemli parçası henüz yok. Telefon altyapısı kurmadan önce farkında olun.
- Transaction katmanı yok (RFC 3261 §17). UDP retransmission timer'ları (Timer A–K) yok — paket düşerse istek timeout'a düşer. LAN / kayıpsız ağda fark edilmez; internet üzerinden kaybedilen ilk INVITE'ı tekrar göndermez.
- Dialog state machine sınırlı. Re-INVITE (hold/resume), UPDATE, target refresh çalışmaz; route set INVITE/BYE/ACK'te kullanılmaz — uzun proxy zincirleri kırılır.
- NAT handling client-side yok. SDP
c=satırına LAN IP yazılır; iki yönlü RTP yalnızca provider symmetric-RTP / SBC NAT handling yapıyorsa çalışır (netsantral yapıyor, çoğu yapmaz). rport/received Contact'a yansıtılmaz, STUN/ICE yok. - Yalnızca UDP. TCP ve TLS yok; TLS-only provider'lar (bazı Twilio konfigürasyonları) için kullanılamaz.
- RTCP yok. Jitter buffer yok — gelen RTP doğrudan callback'e gönderilir, out-of-order paket / sıralama boşluğu metrikleri yok.
- DTMF yok. RFC 2833 payload type 101 alınır ama sessizce düşürülür.
- Codec sınırlı. PCMU + PCMA + telephone-event (gönderme yok). Opus / G.722 / G.729 yok.
- Authorization re-use yok. Her INVITE/BYE'da yeniden challenge — küçük gecikme katar.
Karşılaştırma
| opensip | aiosip | baresip | pjproject | |
|---|---|---|---|---|
| Dil | Pure Python | Pure Python | C | C/C++ |
| asyncio | ✅ | ✅ | ❌ | ❌ |
| Mic/speaker bridge | ✅ | ❌ | ✅ | ✅ |
| Transaction layer | ❌ (roadmap) | ❌ | ✅ | ✅ |
| NAT (ICE/STUN) | ❌ (roadmap) | ❌ | ✅ | ✅ |
| TLS / SRTP | ❌ (roadmap) | ❌ | ✅ | ✅ |
| Video | ❌ | ❌ | ✅ | ✅ |
| Wheel boyutu | ~33 KB | ~30 KB | — | — |
| Kullanım amacı | Python-native scripting, prototip, embedded automation | İlkel araştırma | Production CLI/embedded | Carrier-grade SDK |
opensip "küçük, Python-native, hack'lemesi kolay" yönünde bir niş tutuyor. Carrier-grade telefon altyapısı için pjproject veya baresip + Python binding önerilir.
Yol haritası
- Faz 1 — sağlamlaştırma: transaction katmanı (RFC 3261 §17), tam dialog state machine, rport/received NAT, TCP transport, RTCP SR/RR, jitter buffer, DTMF (RFC 2833 in/out)
- Faz 2 — özellik: TLS + SRTP, ICE-lite (RFC 8445), Opus + G.722, MESSAGE/SUBSCRIBE/NOTIFY/REFER, re-INVITE/hold, video iskeleti
- Faz 3 — test + perf: Docker'da Asterisk/Kamailio/FreeSWITCH entegrasyon testleri, parser fuzzing (hypothesis), zero-copy parsing
Detaylı önceliklendirme ve durum: issue tracker.
Geliştirme
git clone https://github.com/artan/opensip.git
cd opensip
python3.12 -m venv .venv
.venv/bin/pip install -e ".[dev,audio]"
.venv/bin/pytest -v # 17 unit + 1 loopback testi
.venv/bin/ruff check src tests
Python 3.10+ gerekiyor. macOS Homebrew kullanıyorsan: brew install python@3.12.
G.711 mikrobenchmark:
.venv/bin/python tests/bench_codecs.py
Canlı arama testi: examples/.env.example'ı kopyalayıp credentials gir, ardından:
LOG_LEVEL=DEBUG .venv/bin/python examples/make_call.py
Katkı
PR'lar memnuniyetle. Açmadan önce:
- Public API değişikliği yapıyorsanız önce bir issue açın
pytestveruff check src testsyeşil olmalı- Yeni özelliklere unit test eklenmeli — özellikle parser/codec/auth modüllerinde
- Commit mesajları konvansiyonel format (
feat:,fix:,docs:) tercih edilir ama zorunlu değil
Lisans
MIT © artan
Teşekkürler
- numpy — G.711 vektörize hot path
- sounddevice + PortAudio — mikrofon/hoparlör erişimi
- hatchling — build sistemi
- IETF RFC 3261, 3550, 4566, 7616 yazarları
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file opensip-0.2.1.tar.gz.
File metadata
- Download URL: opensip-0.2.1.tar.gz
- Upload date:
- Size: 42.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1016c2cf1461663935f3d0caf61614ab43855ed57b92afb2fc52d32de1850a01
|
|
| MD5 |
2cf8c798633944e7050412f02e98400c
|
|
| BLAKE2b-256 |
0dd77a7a1316cbfa81823b501fa6bf36e68d362b22787890501fef1cbef178cb
|
File details
Details for the file opensip-0.2.1-py3-none-any.whl.
File metadata
- Download URL: opensip-0.2.1-py3-none-any.whl
- Upload date:
- Size: 41.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5c76ce352999030a96ce9d46da8bcae8d2b7abd288a82fa16d75ede338ee794
|
|
| MD5 |
9fbe98d0c78990a0148462d31250e339
|
|
| BLAKE2b-256 |
e8ab6b2d0fe582da924c59620c97354de68b497f259fa599b8c0ef1e137d9870
|