You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PJSIP/PJSUA2的Python SIP通话功能调试与实现求助

修正后的SIP认证、呼出与呼入基础代码

原代码核心问题分析

  • 呼出失败:make_call中新建未注册的Account实例,而非使用已成功注册的账户对象发起呼叫
  • 呼入无法接听:未实现账户的onIncomingCall回调方法,且MyCall中的状态回调方法名错误(应为onCallState而非on_state)
  • 音频播放:使用了不存在的start_audio方法,需通过pjsua2的AudioMediaPlayer实现

完整修正代码

import pjsua2 as pj
import time

class Account(pj.Account):
    def __init__(self, ep):
        pj.Account.__init__(self)
        self.ep = ep  # 保存Endpoint实例,用于创建呼叫
        self.current_call = None  # 保存当前呼叫实例,防止被GC回收

    def onRegState(self, prm):
        print("***注册状态更新: " + prm.reason)
        if prm.code == 200:
            print("账户注册成功")
            # 注册成功后发起呼出
            self.make_call("sip:1571@sbc.dominio.cloud:5064")

    def onIncomingCall(self, prm):
        # 处理呼入请求
        print("收到呼入请求,来自: " + prm.remoteUri)
        self.current_call = MyCall(self, prm.callId)
        # 自动接听呼入
        answer_prm = pj.CallOpParam()
        self.current_call.answer(answer_prm)

    def make_call(self, dest_uri):
        # 使用已注册的账户发起呼叫
        if self.current_call is None or self.current_call.info().state == pj.PJSIP_INV_STATE_DISCONNECTED:
            call_prm = pj.CallOpParam()
            self.current_call = MyCall(self, -1)
            try:
                self.current_call.makeCall(dest_uri, call_prm)
                print("正在呼叫: " + dest_uri)
            except pj.Error as e:
                print("呼叫失败: " + str(e))

class MyCall(pj.Call):
    def __init__(self, acc, call_id):
        pj.Call.__init__(self, acc, call_id)
        self.acc = acc
        self.player = None

    def onCallState(self, prm):
        call_info = self.info()
        if call_info.state == pj.PJSIP_INV_STATE_CALLING:
            print("呼叫中: " + call_info.remoteUri)
        elif call_info.state == pj.PJSIP_INV_STATE_INCOMING:
            print("呼入中: " + call_info.remoteUri)
        elif call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
            print("呼叫已接通,开始播放音频...")
            self.play_audio("testeaudio.wav")
        elif call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
            print("呼叫已断开,原因: " + call_info.lastReason)
            # 释放播放器资源
            if self.player:
                self.player.destroy()
                self.player = None
            self.acc.current_call = None

    def play_audio(self, audio_path):
        try:
            # 获取音频媒体会话
            aud_med = self.getAudioMedia(-1)
            # 创建音频播放器
            self.player = pj.AudioMediaPlayer()
            self.player.createPlayer(audio_path)
            # 将播放器输出连接到音频媒体会话
            self.player.startTransmit(aud_med)
        except pj.Error as e:
            print("音频播放失败: " + str(e))

def pjsua2_test():
    # 初始化Endpoint
    ep_cfg = pj.EpConfig()
    ep = pj.Endpoint()
    ep.libCreate()
    ep.libInit(ep_cfg)

    # 创建UDP传输
    sipTpConfig = pj.TransportConfig()
    sipTpConfig.port = 5064
    try:
        ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, sipTpConfig)
    except pj.Error as e:
        print("传输创建失败: " + str(e))
        return

    # 启动库
    ep.libStart()

    # 配置账户信息
    acfg = pj.AccountConfig()
    acfg.idUri = "sip:1523@sbc.dominio.cloud:5064"
    acfg.regConfig.registrarUri = "sip:sbc.dominio.cloud:5064"
    # 添加认证凭据(注意:第三个参数是用户名,原代码中写的9999需确认是否正确)
    cred = pj.AuthCredInfo("digest", "*", "1523", 0, "9999#")
    acfg.sipConfig.authCreds.append(cred)

    # 创建账户
    acc = Account(ep)
    try:
        acc.create(acfg)
    except pj.Error as e:
        print("账户创建失败: " + str(e))
        ep.libDestroy()
        return

    # 保持运行,等待事件
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("正在退出...")
        # 挂断当前呼叫
        if acc.current_call:
            hangup_prm = pj.CallOpParam()
            acc.current_call.hangup(hangup_prm)
            time.sleep(2)
        # 清理资源
        ep.libDestroy()

if __name__ == "__main__":
    pjsua2_test()

关键修复与说明

  • 呼出逻辑修正:在Account类中实现make_call方法,使用已注册的账户实例发起呼叫,避免创建未认证的新账户
  • 呼入处理实现:添加onIncomingCall回调方法,创建MyCall实例并自动接听
  • 回调方法修正:将MyCall中的on_state改为pjsua2标准回调名onCallState,确保状态变更能被正确捕获
  • 音频播放实现:通过AudioMediaPlayer创建播放器并连接到通话的音频媒体通道,实现接通后自动播放音频
  • 资源管理:保存当前呼叫实例防止被垃圾回收,呼叫断开时释放播放器资源
  • 错误处理:添加异常捕获,输出具体错误信息便于调试

内容的提问来源于stack exchange,提问作者Rodrigo Augusto Martins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 21:05:16