基于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
相关产品推荐
相关产品推荐

