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

