如何使用PJSIP转接已接听的呼入电话?(C#软电话场景)
在PJSIP(Sipek SDK)中实现SIP电话转接
我之前也遇到过类似的问题,用PJSIP实现转接其实核心是利用SIP的REFER方法——这是SIP标准中定义的转接机制,PJSIP底层原生支持,但像Sipek这类封装SDK可能没有直接暴露现成的转接方法,所以需要自己基于底层API封装实现。结合你的C# + Sipek SDK + Asterisk环境,我分两种常见转接场景来详细说明:
一、盲转(Blind Transfer)
盲转是直接将当前通话转接至目标分机,无需先和目标用户确认,操作简单快速。
实现步骤
- 获取当前处于通话状态的
Call实例(Sipek SDK中应该有全局或会话级别的活跃通话对象) - 构造SIP REFER请求,核心是设置
Refer-To头字段为目标分机的SIP URI - 发送REFER请求到Asterisk PBX,之后可主动挂断本地通话
C#代码示例(基于Sipek底层PJSIP调用)
using SipekSdk; using System; // 假设你已经初始化了Sipek的软电话实例 ISipekPhone phoneInstance = SipekPhoneFactory.CreatePhone(); // 获取当前活跃通话 Call activeCall = phoneInstance.ActiveCall; if (activeCall == null || activeCall.CallState != CallStateEnum.EConnected) { Console.WriteLine("没有活跃的通话可转接"); return; } try { // 目标分机的SIP URI(根据你的PBX配置调整域名/IP) string targetUri = "sip:104@your-asterisk-ip:5060"; // 1. 解析目标URI为PJSIP的原生结构 IntPtr pool = phoneInstance.Endpoint.PoolHandle; IntPtr targetSipUriPtr; pj_status_t status = PjsipInterop.pjsip_uri_parse(pool, targetUri, targetUri.Length, out targetSipUriPtr); if (status != pj_status_t.PJ_SUCCESS) { Console.WriteLine("解析目标URI失败,错误码:" + status); return; } // 2. 创建REFER请求 IntPtr tdataPtr; status = PjsipInterop.pjsip_create_request( phoneInstance.Endpoint.SipModuleHandle, PjsipInterop.pjsip_get_method("REFER"), activeCall.RemoteUriPtr, activeCall.LocalUriPtr, IntPtr.Zero, IntPtr.Zero, pool, out tdataPtr ); if (status != pj_status_t.PJ_SUCCESS) { Console.WriteLine("创建REFER请求失败,错误码:" + status); return; } // 3. 添加Refer-To头 IntPtr referToHdr = PjsipInterop.pjsip_refer_to_hdr_create(pool, targetSipUriPtr); PjsipInterop.pjsip_msg_add_hdr(tdataPtr, referToHdr); // 4. 添加Referred-By头(部分PBX要求,可选但建议添加) IntPtr referredByHdr = PjsipInterop.pjsip_referred_by_hdr_create(pool, activeCall.LocalUriPtr); PjsipInterop.pjsip_msg_add_hdr(tdataPtr, referredByHdr); // 5. 发送REFER请求 status = PjsipInterop.pjsua_call_send_request(activeCall.CallId, tdataPtr, IntPtr.Zero, IntPtr.Zero); if (status == pj_status_t.PJ_SUCCESS) { Console.WriteLine("转接请求已发送,等待PBX处理"); // 主动挂断本地通话,完成盲转 activeCall.Hangup(); } else { Console.WriteLine("发送转接请求失败,错误码:" + status); } } catch (Exception ex) { Console.WriteLine("转接过程出现异常:" + ex.Message); }
二、咨询转(Attended Transfer)
咨询转需要先和目标分机建立通话确认,再将原通话转接过去,适合需要确认目标用户是否能接听的场景。
实现步骤
- 保持当前活跃通话
- 发起新通话至目标分机,等待通话建立
- 构造带
Replaces参数的REFER请求(指定要转接的原通话标识) - 发送请求后挂断两个本地通话,完成转接
C#代码示例
using SipekSdk; using System; ISipekPhone phoneInstance = SipekPhoneFactory.CreatePhone(); Call activeCall = phoneInstance.ActiveCall; if (activeCall == null || activeCall.CallState != CallStateEnum.EConnected) { Console.WriteLine("没有活跃的通话可转接"); return; } // 1. 保持原通话 activeCall.Hold(); // 2. 发起至目标分机的新通话 Call transferCall = phoneInstance.MakeCall("104"); // 监听新通话的状态变化,确认连接后执行转接 transferCall.CallStateChanged += (sender, args) => { if (args.NewState == CallStateEnum.EConnected) { try { IntPtr pool = phoneInstance.Endpoint.PoolHandle; string targetUri = "sip:104@your-asterisk-ip:5060"; IntPtr targetSipUriPtr; pj_status_t status = PjsipInterop.pjsip_uri_parse(pool, targetUri, targetUri.Length, out targetSipUriPtr); if (status != pj_status_t.PJ_SUCCESS) return; // 构造Replaces参数(指定原通话的Call-ID、To/From标签) pjsip_replaces_param replacesParam = new pjsip_replaces_param(); PjsipInterop.pjsip_replaces_param_init(ref replacesParam); replacesParam.call_id = PjsipInterop.pj_str_dup(pool, activeCall.CallId); replacesParam.to_tag = PjsipInterop.pj_str_dup(pool, activeCall.ToTag); replacesParam.from_tag = PjsipInterop.pj_str_dup(pool, activeCall.FromTag); // 创建带Replaces参数的Refer-To头 IntPtr referToHdr = PjsipInterop.pjsip_refer_to_hdr_create(pool, targetSipUriPtr); PjsipInterop.pjsip_refer_to_hdr_set_replaces(referToHdr, ref replacesParam); // 创建REFER请求 IntPtr tdataPtr; status = PjsipInterop.pjsip_create_request( phoneInstance.Endpoint.SipModuleHandle, PjsipInterop.pjsip_get_method("REFER"), transferCall.RemoteUriPtr, transferCall.LocalUriPtr, IntPtr.Zero, IntPtr.Zero, pool, out tdataPtr ); if (status != pj_status_t.PJ_SUCCESS) return; PjsipInterop.pjsip_msg_add_hdr(tdataPtr, referToHdr); // 发送请求 status = PjsipInterop.pjsua_call_send_request(transferCall.CallId, tdataPtr, IntPtr.Zero, IntPtr.Zero); if (status == pj_status_t.PJ_SUCCESS) { Console.WriteLine("咨询转接请求已发送"); // 挂断两个本地通话 activeCall.Hangup(); transferCall.Hangup(); } } catch (Exception ex) { Console.WriteLine("咨询转接异常:" + ex.Message); // 恢复原通话(如果转接失败) activeCall.Unhold(); transferCall.Hangup(); } } };
三、Asterisk PBX配置注意事项
为了确保转接功能正常工作,需要在Asterisk中开启REFER支持:
- 如果使用传统SIP(chan_sip),在
sip.conf中添加:allowtransfer=yes - 如果使用PJSIP(chan_pjsip),在
pjsip.conf的端点配置中添加:transfer_mode=refer
另外,确保你的分机权限允许转接操作,可在Asterisk的拨号计划中添加相关权限规则。
内容的提问来源于stack exchange,提问作者РСИТ _




