PJSIP程序员

PJSIP开发VoIP记录2 - 配置

2018-04-05  本文已影响517人  T92

开发工具:Xcode9.2
开发语言:swift 4.0

自从写了上一篇集成后,一直忙于工作,忙到连说话的都没有多余的精力。趁今天节日放假,继续来探究PJSIP的配置。

为了模块化管理,我新建了一个TBPJSIPManager单例,将PJSIP的相关方法放到这里统一管理。

如图:


好了,现在正式开始配置PJSIP,配置方法在单例中定义为func configPJSIP(),在调用PSJIP相关操作基本都会返回一个是否成功的状态,这个状态的类型是pj_status_t,当这个值是PJ_SUCCESS.rawValue则表示操作成功

1.创建SUA

        var status: pj_status_t = pjsua_create()
        if status != PJ_SUCCESS.rawValue{
            
            print("error create pjsua")
        }

这里将status定义为变量是因为后面的配置可以继续使用这个变量,而不再重复创建

2.配置SUA

先声明三个配置变量,分别配置pjsua、pjsua_media、pjsua_logging。

        var cfg = pjsua_config()
        var media_cfg = pjsua_media_config()
        var log_cfg = pjsua_logging_config()

2.1 回调函数配置(pjsua_config())

回调函数配置其实就是将上一步的pjsua_config()赋值给SUA并绑定相关属性

        pjsua_config_default(&cfg)
        cfg.cb.on_incoming_call = on_incoming_call
        cfg.cb.on_call_media_state = on_call_media_state
        cfg.cb.on_call_state = on_call_state
        cfg.cb.on_reg_state = on_reg_state

等号后面跟的on_incoming_callon_call_media_stateon_call_stateon_reg_state分别是来电、媒体、电话状态、登录状态的回调(闭包),为了方便,我直接将其命名为cb的对应属性名,如果认为这样的命名方式不好可以另起名字。值得一提的是,PJSIP提供的reg方法实质是登录服务器方法,而不是我们理解的注册方法,需要服务器有该用户才能reg成功。(目前我没发现移动端注册服务器的API,或者是我本身理解出错,如果有人知道,敬请指正)

2.1.1 声明相关回调

上一步中的四个回调函数我们并未声明,会报错,接下来我们声明这四个回调,实质就是swift中的闭包,将他们声明为TBPJSIPManager的常量

来电回调

let on_incoming_call: (@convention(c) (pjsua_acc_id, pjsua_call_id, UnsafeMutablePointer<pjsip_rx_data>?) -> Void) = { (acc_id, call_id, rdata) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        let remote_info = NSString(utf8String: ci.remote_info.ptr)
        guard let startIndex = remote_info?.range(of: "<").location else { return }
        guard let endIndex = remote_info?.range(of: ">").location else { return }
        var remote_address = remote_info?.substring(with: NSMakeRange(startIndex + 1, endIndex - startIndex - 1))
        remote_address = remote_address?.components(separatedBy: ":")[1]
        
        let argument = ["call_id":call_id,"remote_address":remote_address ?? "has no remote_address"] as [String : Any]
        DispatchQueue.main.async {
            
            NotificationCenter.default.post(name: n_on_incoming_call, object: nil, userInfo: argument)
        }
    }

全局常量通知名字

let n_on_reg_state = NSNotification.Name("on_reg_state")
let n_on_call_state = NSNotification.Name("on_call_state")
let n_on_incoming_call = NSNotification.Name("on_incoming_call")

通过remote_info我们可以拿到很多来电信息,其中remote_address是截取来电人信息,是拨打电话时进行的配置,一般配置为名字或者电话号码。当获取到需要的信息后,将需要的信息保存到字典,通过通知在主线程进行发送,需要这些信息的地方接收广播就可以进行相应处理。以下配置道理一模一样

媒体状态回调

let on_call_media_state: (@convention(c) (pjsua_call_id) -> Void) = { (call_id) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        if ci.media_status == PJSUA_CALL_MEDIA_ACTIVE {
            pjsua_conf_connect(ci.conf_slot, 0)
            pjsua_conf_connect(0, ci.conf_slot)
        }
        
    }

电话状态回调

let on_call_state: (@convention(c) (pjsua_call_id, UnsafeMutablePointer<pjsip_event>?) -> Void) = { (call_id, event) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        let argument = ["call_id":call_id, "state":ci.state, "pjsipConfAudioId":ci.conf_slot] as [String : Any]
        
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: n_on_call_state, object: nil, userInfo: argument)
        }
    }

提取pjsipConfAudioId是因为后面配置通话静音需要这个值

注册状态回调(暂且叫注册吧)

let on_reg_state: (@convention(c) (pjsua_acc_id) -> Void) = { (acc_id) in
        
        var status = pj_status_t()
        var info = pjsua_acc_info()
        status = pjsua_acc_get_info(acc_id, &info)
        
        if status != PJ_SUCCESS.rawValue{
            return
        }
        
        let argument = ["acc_id":acc_id, "status_text":String(utf8String: info.status_text.ptr) ?? "has no status_text", "status":info.status] as [String : Any]
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: n_on_reg_state, object: nil, userInfo: argument)
        }
    }

2.2 媒体相关配置(pjsua_media_config())

        pjsua_media_config_default(&media_cfg)
        media_cfg.clock_rate = 16000
        media_cfg.snd_clock_rate = 16000
        media_cfg.ec_tail_len = 0

这三个属性大概是处理时钟频率和声音去噪,可以看看.h文件中的具体介绍

2.3 日志相关配置(pjsua_logging_config())

日志配置分正式环境和开发环境,开发环境需要日志,但是正式环境并不需要调试日志,所以可以关掉,我定义了一个isDebugModel判断是否是正式环境

        pjsua_logging_config_default(&log_cfg)
        if isDebugModel{
            log_cfg.msg_logging = pj_bool_t(PJ_TRUE.rawValue)
            log_cfg.console_level = 4
            log_cfg.level = 5
        }else{
            log_cfg.msg_logging = pj_bool_t(PJ_FALSE.rawValue)
            log_cfg.console_level = 0
            log_cfg.level = 0
        }

3. 初始化PJSUA

完成第二步的相关配置后,我们就可以用这些配置去初始化SUA了,此处用到了之前判断执行状态的变量status

        status = pjsua_init(&cfg, &log_cfg, &media_cfg)
        if status != PJ_SUCCESS.rawValue{
            print("error init pjsua")
        }

4. 配置传输类型

传输类型可以支持TCP、UDP,这里用UDP,监听本地默认的一个UDP端口即可

        var transport_config = pjsua_transport_config()
        pjsua_transport_config_default(&transport_config)
        status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &transport_config, nil)
        if status != PJ_SUCCESS.rawValue{
            print("error add transport for pjsua")
        }

5. 启动PJSUA

基本工作完成后便可以启动SUA让其工作了

        status = pjsua_start()
        if status != PJ_SUCCESS.rawValue{
            print("error start pjsua")
        }

至此TBPJSIPManager单例算完成了,整体代码如下

import UIKit

let n_on_reg_state = NSNotification.Name("on_reg_state")
let n_on_call_state = NSNotification.Name("on_call_state")
let n_on_incoming_call = NSNotification.Name("on_incoming_call")

class TBPJSIPManager: NSObject {
    
    let isDebugModel = true
    
    public static let shared = TBPJSIPManager()
    
    fileprivate override init() { }

    func configPJSIP(){
        
        // 创建SUA
        var status: pj_status_t = pjsua_create()
        if status != PJ_SUCCESS.rawValue{
            
            print("error create pjsua")
        }
        
        // SUA相关配置
        var cfg = pjsua_config()
        var media_cfg = pjsua_media_config()
        var log_cfg = pjsua_logging_config()
        
        // 回调函数配置
        pjsua_config_default(&cfg)
        cfg.cb.on_incoming_call = on_incoming_call
        cfg.cb.on_call_media_state = on_call_media_state
        cfg.cb.on_call_state = on_call_state
        cfg.cb.on_reg_state = on_reg_state
        
        // 媒体相关配置
        pjsua_media_config_default(&media_cfg)
        media_cfg.clock_rate = 16000
        media_cfg.snd_clock_rate = 16000
        media_cfg.ec_tail_len = 0
        
        // 日志相关配置
        pjsua_logging_config_default(&log_cfg)
        if isDebugModel{
            log_cfg.msg_logging = pj_bool_t(PJ_TRUE.rawValue)
            log_cfg.console_level = 4
            log_cfg.level = 5
        }else{
            log_cfg.msg_logging = pj_bool_t(PJ_FALSE.rawValue)
            log_cfg.console_level = 0
            log_cfg.level = 0
        }
        
        // 初始化PJSUA
        status = pjsua_init(&cfg, &log_cfg, &media_cfg)
        if status != PJ_SUCCESS.rawValue{
            print("error init pjsua")
        }
        
        // 传输类型配置
        //pjsua 监听了本地的一个UDP端口,这个端口我们是可以配置死的( pjsua_transport_config ),但如果不配置, pjsua 会随机选择一个未被占用的端口,这样挺好,否则应用可能会Crash掉。
        var transport_config = pjsua_transport_config()
        pjsua_transport_config_default(&transport_config)
        status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &transport_config, nil)
        if status != PJ_SUCCESS.rawValue{
            print("error add transport for pjsua")
        }
        
        // 启动PJSUA
        status = pjsua_start()
        if status != PJ_SUCCESS.rawValue{
            print("error start pjsua")
        }
        
    }
    
    /// 来电回调
    // 把所有的回调函数都包装成通知对外发布,在这里需要注意,所有的通知都放到主线程
    let on_incoming_call: (@convention(c) (pjsua_acc_id, pjsua_call_id, UnsafeMutablePointer<pjsip_rx_data>?) -> Void) = { (acc_id, call_id, rdata) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        let remote_info = NSString(utf8String: ci.remote_info.ptr)
        guard let startIndex = remote_info?.range(of: "<").location else { return }
        guard let endIndex = remote_info?.range(of: ">").location else { return }
        var remote_address = remote_info?.substring(with: NSMakeRange(startIndex + 1, endIndex - startIndex - 1))
        remote_address = remote_address?.components(separatedBy: ":")[1]
        
        let argument = ["call_id":call_id,"remote_address":remote_address ?? "has no remote_address"] as [String : Any]
        DispatchQueue.main.async {
            ///本Demo中只有AppDelegate在监听这个通知,实际开发也这样的话没必要发通知,灵活处理
            NotificationCenter.default.post(name: n_on_incoming_call, object: nil, userInfo: argument)
        }
    }
    
    /// 媒体状态回调(通话建立后,要播放RTP流)
    let on_call_media_state: (@convention(c) (pjsua_call_id) -> Void) = { (call_id) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        if ci.media_status == PJSUA_CALL_MEDIA_ACTIVE {
            pjsua_conf_connect(ci.conf_slot, 0)
            pjsua_conf_connect(0, ci.conf_slot)
        }
        
    }
    
    /// 电话状态回调
    let on_call_state: (@convention(c) (pjsua_call_id, UnsafeMutablePointer<pjsip_event>?) -> Void) = { (call_id, event) in
        
        var ci = pjsua_call_info()
        pjsua_call_get_info(call_id, &ci)
        
        let argument = ["call_id":call_id, "state":ci.state, "pjsipConfAudioId":ci.conf_slot] as [String : Any]
        
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: n_on_call_state, object: nil, userInfo: argument)
        }
    }
    
    ///注册状态回调
    let on_reg_state: (@convention(c) (pjsua_acc_id) -> Void) = { (acc_id) in
        
        var status = pj_status_t()
        var info = pjsua_acc_info()
        status = pjsua_acc_get_info(acc_id, &info)
        
        if status != PJ_SUCCESS.rawValue{
            return
        }
        
        let argument = ["acc_id":acc_id, "status_text":String(utf8String: info.status_text.ptr) ?? "has no status_text", "status":info.status] as [String : Any]
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: n_on_reg_state, object: nil, userInfo: argument)
        }
    }
    
}

在启动应用后我们就先配置PJSIP,在application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool方法中调用TBPJSIPManager.shared.configPJSIP()即可

6. 监听来电

最后一步,当PJSUA工作后我们需要监听来电,实质是来电后会走来电回调,所以监听来电回调发送的广播即可。方法如下:


好了,今天就到这里吧,内容虽然不多,却花了近两个小时😀😁,明天有时间就继续探究通话的实现。

上一篇 下一篇

猜你喜欢

热点阅读