GoogleWebRTC 在 swift 本机应用程序 (OpenVidu) 中挂起(冻结)主线程

Posted

技术标签:

【中文标题】GoogleWebRTC 在 swift 本机应用程序 (OpenVidu) 中挂起(冻结)主线程【英文标题】:GoogleWebRTC hangs (freezes) the main thread in swift native app (OpenVidu) 【发布时间】:2021-08-10 12:23:10 【问题描述】:

我们的 ios (swift) 本机应用与 OpenVidu 实现(在后台使用 GoogleWebRTC)存在挂起问题(应用因主线程锁定而冻结) .所需的具体条件:需要加入现有房间,至少有 8 名参与者已经在直播。有 6 名参与者时,这种情况发生的频率较低,而且几乎从来没有少于 6 人。如果参与者一一加入,它不会挂起,只有当您加入房间时所有其他参与者都已经流式传输。这表明问题的并发性质。

GoogleWebRTC 在setRemoteDescription 呼叫上挂起:

func setRemoteDescription(sdpAnswer: String) 
    let sessionDescription: RTCSessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: sdpAnswer)
    self.peerConnection!.setRemoteDescription(sessionDescription, completionHandler: (error) in
        print("Local Peer Remote Description set: " + error.debugDescription)
    )

正如您在上面的屏幕截图中看到的,主线程挂在__psynch_cvwait。似乎没有任何其他线程被锁定。 锁永远不会释放使应用程序完全冻结。

为了解决这个问题,我尝试了以下方法:

    我将 OpenVidu 信号服务器处理(RPC 协议)从主线程移到单独的线程中。这仅导致锁定现在发生在我创建的单独线程之一中。它现在不会阻塞 UI,但会阻塞 OV 信号。问题依然存在。

    我添加了锁来处理每个信令事件(参与者加入事件、发布视频等)同步(一个接一个)。这也无济于事(实际上使情况变得更糟)。

    我没有使用来自 Cocoapods 的 GoogleWebRTC v. 1.1.31999,而是下载了最新的 GoogleWebRTC 源代码,并在发布配置和included into my project 中构建了它们。这无助于解决问题。

任何建议/cmets 将不胜感激。 谢谢!

编辑 1:

signaling_threadworker_thread 都在等待同一种锁中的东西。在锁定的那一刻,它们都没有执行我的任何代码。

我还尝试在GoogleWebRTC 的 DEBUG 版本中运行,在这种情况下不会发生任何锁定,但一切都会慢得多(这对于调试来说是可以的,但我们不能在生产环境中使用它)。

编辑 2:

我尝试为offersetLocalDescription 回调添加额外的DispatchQueue,但这并没有改变。这个问题仍然可以很好地重现(几乎 100% 的时间,如果我有 8 个参与者有流):

    self.peerConnection!.offer(for: constrains)  (sdp, error) in
        DispatchQueue.global(qos: .background).async 

            guard let sdp = sdp else 
                return
            

            self.peerConnection!.setLocalDescription(sdp, completionHandler:  (error) in
                DispatchQueue.global(qos: .background).async 
                    completion(sdp)
                
            )
        
    

【问题讨论】:

【参考方案1】:

在 OpenVidu 团队发表评论后,通过在添加已经在房间内的参与者之间添加 100 毫秒的延迟解决了问题。我认为这更像是一种 hack,而不是真正的解决方案,但我可以确认它在测试和生产环境中都有效:

DispatchQueue.global(qos: .background).async 
    for info in dict.values 
        let remoteParticipant = self.newRemoteParticipant(info: info)
        if let streamId = info.streamId 
            remoteParticipant.createOffer(completion: (sdp) in
                self.receiveVideoFrom(sdp: sdp, remoteParticipant: remoteParticipant, streamId: streamId)
            )
         else 
            print("No streamId")
        
        Thread.sleep(forTimeInterval: 0.1)
    

【讨论】:

【参考方案2】:

可以从任何线程调用 WebRTC Obj-C API,但大多数方法调用都传递到 WebRTC 的内部线程 signalling thread

此外,SetLocalDescriptionObserverInterfaceRTCSetSessionDescriptionCompletionHandler 等回调/观察者在 signaling thread 上从 WebRTC 调用。

看截图,好像是信令线程目前被阻塞,不能再调用WebRTC API调用了。

因此,为避免死锁,最好创建自己的线程/dispatch_queue 并处理回调。

看 https://webrtc.googlesource.com/src/+/0a52ede821ba12ee6fff6260d69cddcca5b86a4e/api/g3doc/index.md 和 https://webrtc.googlesource.com/src/+/0a52ede821ba12ee6fff6260d69cddcca5b86a4e/api/g3doc/threading_design.md 了解详情。

【讨论】:

在我的问题中查看我的新编辑。 cv_wait 中的信号和工作线程。他们都没有在锁定时执行我的任何代码。我还尝试按照您的建议为peerConnection.setLocalDescription 回调调度队列,但这没有改变。我没有看到任何其他回调可以做任何重要的事情或可能锁定在我身边。 对于peerConnection对象,唯一使用的回调是:setRemoteDescription(但除了print之外什么都不做),offer(它立即调用peerConnection.setLocalDescription)和@987654336 @,向信令服务器发送数据。我尝试在单独的线程中执行的最后一个,但它不能解决问题。 如果是这样,我认为您应该在 bugs.chromium.org/p/webrtc/issues/list 提交错误。如果您的可重现代码最少且不使用 OpenVidu 等第三方库,它将帮助您解决问题。

以上是关于GoogleWebRTC 在 swift 本机应用程序 (OpenVidu) 中挂起(冻结)主线程的主要内容,如果未能解决你的问题,请参考以下文章