You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用PJSIP转接已接听的呼入电话?(C#软电话场景)

在PJSIP(Sipek SDK)中实现SIP电话转接

我之前也遇到过类似的问题,用PJSIP实现转接其实核心是利用SIP的REFER方法——这是SIP标准中定义的转接机制,PJSIP底层原生支持,但像Sipek这类封装SDK可能没有直接暴露现成的转接方法,所以需要自己基于底层API封装实现。结合你的C# + Sipek SDK + Asterisk环境,我分两种常见转接场景来详细说明:

一、盲转(Blind Transfer)

盲转是直接将当前通话转接至目标分机,无需先和目标用户确认,操作简单快速。

实现步骤

  1. 获取当前处于通话状态的Call实例(Sipek SDK中应该有全局或会话级别的活跃通话对象)
  2. 构造SIP REFER请求,核心是设置Refer-To头字段为目标分机的SIP URI
  3. 发送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)

咨询转需要先和目标分机建立通话确认,再将原通话转接过去,适合需要确认目标用户是否能接听的场景。

实现步骤

  1. 保持当前活跃通话
  2. 发起新通话至目标分机,等待通话建立
  3. 构造带Replaces参数的REFER请求(指定要转接的原通话标识)
  4. 发送请求后挂断两个本地通话,完成转接

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,提问作者РСИТ _

火山引擎 最新活动