杰瑞科技汇

python sip client

Of course! Building a Python SIP client is a common task for VoIP applications, softphones, or integrating with telephony systems. The most popular and robust library for this is pjsip.

Here's a comprehensive guide covering:

  1. Why pjsip? (The best choice)
  2. Installation (The tricky part)
  3. A Simple SIP Client Example (Making a call)
  4. An Advanced Example (Handling incoming calls and media)
  5. Alternative Libraries

Why pjsip?

While Python has other SIP-related libraries (like py SIP), pjsip is the industry standard for several reasons:

  • Full-featured: It implements the entire SIP RFCs, not just a subset. It handles SIP, SDP (for media negotiation), RTP (for audio/video transport), and more.
  • Stable & Mature: It's a battle-tested C library with a Python wrapper (pjsua2). It's used in production systems worldwide.
  • Media Support: It has built-in support for audio codecs (like G.711, Opus) and can interface with sound cards (PortAudio, ALSA, CoreAudio) to capture and play audio.
  • Pythonic Wrapper: The pjsua2 wrapper provides a clean, object-oriented Python interface to the underlying C library.

Installation (The Most Important Step)

This is the most challenging part. pjsip is not a simple pip install. You need to compile the C library and its Python bindings.

Prerequisites

You'll need a C/C++ compiler and build tools:

  • On Ubuntu/Debian:
    sudo apt-get update
    sudo apt-get install -y python3-dev python3-pip build-essential portaudio19-dev libssl-dev libasound2-dev
  • On macOS (using Homebrew):
    brew install portaudio pjsip

    Note: Homebrew has a pjsip package, but it might not always have the Python bindings enabled. Compiling from source (as below) is more reliable for development.

Recommended Method: Compile from Source

This gives you the most control and ensures compatibility.

  1. Install Dependencies: Use the commands from the "Prerequisites" section above for your OS.

  2. Clone and Compile pjsip:

    # Clone the repository
    git clone https://github.com/pjsip/pjproject.git
    cd pjproject
    # Configure the build. The --with-python flag is crucial.
    # The --enable-shared flag is often needed for the Python module to load correctly.
    ./configure --with-python --enable-shared
    # Compile and install
    make dep
    make
    sudo make install
  3. Install the Python Package: The compilation above creates the Python bindings. Now, you just need to pip install them. The setup.py script will find the compiled library automatically.

    pip install pjsua2

    If you have multiple Python versions, you might need pip3 install pjsua2.

You can verify the installation by running:

import pjsua2 as pj
print(pj.__version__)

A Simple SIP Client Example (Making an Outgoing Call)

This example demonstrates the most basic functionality: initializing the library, registering with a SIP server, and making a call.

Code: simple_client.py

import pjsua2 as pj
import time
import threading
# --- Configuration ---
SIP_ACCOUNT = "sip:1001@sip.example.com" # Your SIP account URI
SIP_PASSWORD = "your_password"
SIP_SERVER = "sip.example.com"         # Your SIP server's domain or IP
AUDIO_DEV = None                       # Use default audio device
# --- Global objects ---
ep = None  # Endpoint object
call = None # Call object
# --- Custom Call Class ---
class MyCall(pj.Call):
    """
    Custom Call class to handle call events.
    """
    def __init__(self, account, call_id):
        super().__init__(account, call_id)
        print("Call created")
    def onCallState(self, prm):
        print("Call state:", prm.state)
        if prm.state == pj.PJSIP_INV_STATE_DISCONNECTED:
            print("Call disconnected")
            global call
            call = None
    def onCallMediaState(self, prm):
        print("Call media state")
        # When media is established, connect the call to the sound device
        if self.getMediaStatus() == pj.PJSIP_INV_STATE_CONFIRMED:
            self.connectAudio()
# --- Custom Account Class ---
class MyAccount(pj.Account):
    """
    Custom Account class to handle account events.
    """
    def __init__(self, ep):
        super().__init__()
        self.ep = ep
        self.call = None
    def onRegState(self, prm):
        print("Registration state:", prm.code, prm.reason)
        if prm.code == 200:
            print("Successfully registered to", SIP_ACCOUNT)
    def makeCall(self, dest_uri):
        """Helper to make a call."""
        global call
        call = MyCall(self, -1)
        call.makeCall(dest_uri, pj.CallOpParam())
        print("Calling", dest_uri)
# --- Main Application Logic ---
def main():
    global ep, call
    # 1. Create and initialize the library
    ep = pj.Endpoint()
    ep.libCreate()
    ep.libInit(pj.EpConfig())
    # 2. Configure transport (SIP signaling)
    transport_config = pj.TransportConfig()
    transport_config.port = 5060
    transport_config.publicAddress = "YOUR_PUBLIC_IP_OR_HOSTNAME"
    transport = ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_config)
    # 3. Configure audio device
    aud_dev_manager = ep.getAudioDevManager()
    aud_dev_manager.setNullDev() # Start with no device
    if AUDIO_DEV is not None:
        # You can iterate and select a specific device if needed
        pass
    # 4. Start the library
    ep.libStart()
    print("PJSUA2 started")
    # 5. Create and register the account
    acc = MyAccount(ep)
    acc_cfg = pj.AccountConfig()
    acc_cfg.idUri = SIP_ACCOUNT
    acc_cfg.regConfig.registrarUri = "sip:" + SIP_SERVER
    acc_cfg.sipConfig.authCreds.append(pj.AuthCred("digest", "*", SIP_PASSWORD))
    acc.create(acc_cfg)
    # Wait for registration
    time.sleep(2)
    # 6. Make a call
    dest_uri = "sip:1002@sip.example.com" # The number/URI you want to call
    acc.makeCall(dest_uri)
    # 7. Run the main loop until the call is finished
    try:
        while call is not None:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Shutting down...")
    # 8. Clean up
    if call:
        call.hangup()
    if acc:
        acc.delete()
    ep.libDestroy()
    print("PJSUA2 destroyed")
if __name__ == "__main__":
    main()

How to Run:

  1. Fill in your SIP_ACCOUNT, SIP_PASSWORD, and SIP_SERVER.
  2. Replace YOUR_PUBLIC_IP_OR_HOSTNAME with the public IP of the machine running the script. This is crucial for NAT traversal.
  3. Run the script: python simple_client.py
  4. It will register, make the call, and then wait. Press Ctrl+C to hang up and exit.

Advanced Example (Handling Incoming Calls & Media)

This example builds on the previous one by showing how to handle incoming calls and demonstrates media handling.

Key additions:

  • A MyCall class that handles incoming call requests (onIncomingCall).
  • A custom AudioMedia class to demonstrate capturing and playing audio.

Code: advanced_client.py

import pjsua2 as pj
import time
import threading
# --- Configuration (same as before) ---
SIP_ACCOUNT = "sip:1001@sip.example.com"
SIP_PASSWORD = "your_password"
SIP_SERVER = "sip.example.com"
AUDIO_DEV = None
# --- Global objects ---
ep = None
incoming_call = None
# --- Custom Call Class ---
class MyCall(pj.Call):
    def __init__(self, account, call_id):
        super().__init__(account, call_id)
        self.tx_dev = None
        self.rx_dev = None
    def onCallState(self, prm):
        print("Call state:", prm.state)
        if prm.state == pj.PJSIP_INV_STATE_DISCONNECTED:
            print("Call disconnected")
            global incoming_call
            incoming_call = None
            if self.tx_dev:
                self.tx_dev.delete()
            if self.rx_dev:
                self.rx_dev.delete()
    def onCallMediaState(self, prm):
        print("Call media state")
        if self.getMediaStatus() == pj.PJSIP_INV_STATE_CONFIRMED:
            # Get the default audio device
            aud_dev_manager = ep.getAudioDevManager()
            cap_dev = aud_dev_manager.getCaptureDev()
            play_dev = aud_dev_manager.getPlaybackDev()
            # Create media ports and connect them
            self.tx_dev = pj.AudioMedia.createMedia(ep)
            self.rx_dev = pj.AudioMedia.createMedia(ep)
            self.tx_dev.startTransmit(self.getStream(pj.PJMEDIA_TYPE_AUDIO).getTransmitter(pj.PJMEDIA_DIR_CAPTURE))
            play_dev.startTransmit(self.rx_dev)
            self.getStream(pj.PJMEDIA_TYPE_AUDIO).getReceiver(pj.PJMEDIA_DIR_PLAYBACK).startTransmit(self.tx_dev)
            print("Audio streams connected.")
    def onIncomingCall(self, prm):
        print("Incoming call from:", prm.remoteUri)
        global incoming_call
        incoming_call = MyCall(self, prm.callId)
        incoming_call.answer(pj.CallOpParam())
# --- Main Application Logic ---
def main():
    global ep, incoming_call
    # 1. Create and initialize the library
    ep = pj.Endpoint()
    ep.libCreate()
    ep.libInit(pj.EpConfig())
    # 2. Configure transport
    transport_config = pj.TransportConfig()
    transport_config.port = 5060
    transport_config.publicAddress = "YOUR_PUBLIC_IP_OR_HOSTNAME"
    ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_config)
    # 3. Configure audio device
    ep.audDevManager().setNullDev()
    # 4. Start the library
    ep.libStart()
    print("PJSUA2 started. Waiting for calls...")
    # 5. Create and register the account
    acc = pj.Account()
    acc_cfg = pj.AccountConfig()
    acc_cfg.idUri = SIP_ACCOUNT
    acc_cfg.regConfig.registrarUri = "sip:" + SIP_SERVER
    acc_cfg.sipConfig.authCreds.append(pj.AuthCred("digest", "*", SIP_PASSWORD))
    acc.create(acc_cfg)
    time.sleep(2)
    # 6. Run the main loop
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Shutting down...")
    # 7. Clean up
    if incoming_call:
        incoming_call.hangup()
    acc.delete()
    ep.libDestroy()
    print("PJSUA2 destroyed")
if __name__ == "__main__":
    main()

Alternative Libraries

While pjsua2 is the most powerful, it's heavy. For simpler use cases, consider these:

Library Description Pros Cons
py SIP A pure Python SIP library. Easy to install (pip install pysip), lightweight, pure Python. Does not handle media (RTP). You'd need another library for audio. Less stable for complex scenarios.
sipsimple A higher-level Python framework for SIP. More abstract than pjsua2, focuses on application logic. Handles some media. Can be complex to set up. The project seems less active than pjsip.
async-pj An async/await wrapper for pjsua2. Allows pjsua2 to be used in modern async applications (e.g., FastAPI, asyncio). Requires understanding of both pjsua2 and asyncio.

Summary

Task Recommended Tool
Building a full-featured softphone pjsua2
Simple SIP signaling (no media) py SIP
Integrating SIP into an async app async-pj or pjsua2 in a separate thread

For any serious project, investing the time to learn and use pjsua2 is the best path forward. Its power and stability are unmatched in the Python ecosystem for SIP.

分享:
扫描分享到社交APP
上一篇
下一篇