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

Swift集成PJ-SIP:UIView无法显示视频流问题排查

Swift+PJ-SIP UIView视频流显示问题解决

问题背景

开发基于Swift的VoIP应用,使用PJ-SIP框架实现SIP功能,尝试将视频流嵌入UIViewController的UIView中时,视图已成功加入层级(可看到带红色边框的视图),但无法渲染视频流。

现有代码配置

PJSIPManager核心代码

struct PJSIPManager {
    private init() {
        var cfg = pjsua_config()
        pjsua_config_default(&cfg)
        cfg.cb.on_call_media_state = on_call_media_state
    }

    var vid_win: UIView? = nil
    var isVideoCall = false

    private func on_call_media_state(pjsipID: pjsua_call_id) {
        // ... [media state handling code] ...
        if (media[Int(mi)].type == PJMEDIA_TYPE_VIDEO) {
            PJSIPManager.instance?.isVideoCall = true
            let wid = media[Int(mi)].stream.vid.win_in;
            var wi = pjsua_vid_win_info();
            if (pjsua_vid_win_get_info(wid, &wi) == PJ_SUCCESS.rawValue) {
                PJSIPManager.instance?.vid_win = Unmanaged<UIView>.fromOpaque(wi.hwnd.info.ios.window).takeUnretainedValue();
            }
        }
    }
}

UIViewController代码

override func viewDidLoad() {
    super.viewDidLoad()
         DispatchQueue.main.async {
            if let isVideoCall = PJSIPManager.getInstance()?.isVideoCall ,isVideoCall{
                if PJSIPManager.getInstance()?.vid_win != nil {
                    self.view.addSubview(PJSIPManager.getInstance()!.vid_win!)
                }
                PJSIPManager.getInstance()!.vid_win!.layer.borderColor = UIColor.red.cgColor
                PJSIPManager.getInstance()!.vid_win!.layer.borderWidth = 2.0
                PJSIPManager.instance?.vid_win?.isHidden = false
                PJSIPManager.getInstance()!.vid_win!.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    PJSIPManager.getInstance()!.vid_win!.centerXAnchor.constraint(equalTo:self.view.centerXAnchor),
                    PJSIPManager.getInstance()!.vid_win!.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
                    PJSIPManager.getInstance()!.vid_win!.widthAnchor.constraint(equalToConstant: 100),
                    PJSIPManager.getInstance()!.vid_win!.heightAnchor.constraint(equalToConstant: 100)
                ])
            }
        }   
    }
}

问题现象

vid_win已成功添加到视图层级(设置边框后可见),但无视频流渲染,确认isVideoCall为true且vid_win不为空。

解决方案

1. 将视频窗口的UI操作切换到主线程

PJ-SIP的回调默认在后台线程执行,直接操作UI会导致渲染异常,需将vid_win的赋值操作放到主线程:

private func on_call_media_state(pjsipID: pjsua_call_id) {
    // ... [media state handling code] ...
    if (media[Int(mi)].type == PJMEDIA_TYPE_VIDEO && media[Int(mi)].status == PJSUA_CALL_MEDIA_ACTIVE) {
        let wid = media[Int(mi)].stream.vid.win_in;
        var wi = pjsua_vid_win_info();
        if (pjsua_vid_win_get_info(wid, &wi) == PJ_SUCCESS.rawValue) {
            DispatchQueue.main.async {
                PJSIPManager.instance?.isVideoCall = true
                PJSIPManager.instance?.vid_win = Unmanaged<UIView>.fromOpaque(wi.hwnd.info.ios.window).takeUnretainedValue();
                // 发送通知告知ViewController更新UI
                NotificationCenter.default.post(name: NSNotification.Name("VideoCallReady"), object: nil)
            }
        }
    }
}

2. 监听视频状态变化而非在viewDidLoad中过早获取

viewDidLoad执行时视频会话可能未建立,需通过通知或回调触发UI更新:

class VideoCallViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(updateVideoView), name: NSNotification.Name("VideoCallReady"), object: nil)
    }

    @objc private func updateVideoView() {
        guard let vidWin = PJSIPManager.instance?.vid_win else { return }
        
        view.addSubview(vidWin)
        vidWin.layer.borderColor = UIColor.red.cgColor
        vidWin.layer.borderWidth = 2.0
        vidWin.isHidden = false
        vidWin.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            vidWin.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            vidWin.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            vidWin.widthAnchor.constraint(equalToConstant: 100),
            vidWin.heightAnchor.constraint(equalToConstant: 100)
        ])
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

3. 完善PJ-SIP视频初始化配置

确保初始化时启用视频相关功能:

private init() {
    var cfg = pjsua_config()
    pjsua_config_default(&cfg)
    cfg.cb.on_call_media_state = on_call_media_state
    
    // 配置视频参数
    var vid_cfg = pjsua_vid_config()
    pjsua_vid_config_default(&vid_cfg)
    vid_cfg.auto_show = PJ_TRUE // 自动显示视频窗口
    vid_cfg.auto_transmit = PJ_TRUE // 自动传输本地视频
    
    let log_cfg = pjsua_log_config()
    pjsua_log_config_default(&log_cfg)
    
    let status = pjsua_init(&cfg, &log_cfg, &vid_cfg)
    if status != PJ_SUCCESS.rawValue {
        // 处理初始化失败
    }
}

PJ-SIP嵌入视频到UIView的注意事项

  • 强制主线程操作UI:所有涉及UIView的创建、赋值、添加层级的操作必须在主线程执行,PJ-SIP回调默认在后台线程,直接操作会导致渲染异常。
  • 依赖媒体状态回调更新UI:视频窗口只有在通话媒体激活后才会创建完成,不能在ViewController初始化阶段直接获取,需通过回调或通知触发更新。
  • 正确配置视频初始化参数:必须启用auto_show和auto_transmit,否则即使获取到UIView也不会有视频流传输。
  • 权限检查:确保应用已获取相机和麦克风权限,无权限则无法采集和传输视频流。
  • 正确使用Unmanaged:使用Unmanaged<UIView>.takeUnretainedValue()时避免意外创建强引用,防止内存泄漏或视图无法更新。

内容的提问来源于stack exchange,提问作者David Feldman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 04:15:56