GoogleWebRTC 挂起(冻结)swift 本机应用程序 (OpenVidu) 中的主线程
GoogleWebRTC hangs (freezes) the main thread in swift native app (OpenVidu)
我们的 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 中构建了它们。这对解决问题没有帮助。
任何 suggestions/comments 将不胜感激。
谢谢!
编辑 1:
signaling_thread
和worker_thread
都在等待同一种锁。在锁定的那一刻,他们都没有执行我的任何代码。
我还尝试在 GoogleWebRTC
的 DEBUG 版本中 运行,在这种情况下没有发生锁定,但一切都运行得慢得多(这对于调试来说没问题,但我们不能使用它生产中)。
编辑 2:
我尝试为 offer
和 setLocalDescription
回调包装额外的 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)
}
})
}
}
可以从任何线程调用 WebRTC Obj-C API,但大多数方法调用都传递给 WebRTC 的内部线程 signalling thread
。
此外,callbacks/observers 像 SetLocalDescriptionObserverInterface
或 RTCSetSessionDescriptionCompletionHandler
是从 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
了解详情。
经过 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)
}
}
我们的 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 中构建了它们。这对解决问题没有帮助。
任何 suggestions/comments 将不胜感激。 谢谢!
编辑 1:
signaling_thread
和worker_thread
都在等待同一种锁。在锁定的那一刻,他们都没有执行我的任何代码。
我还尝试在 GoogleWebRTC
的 DEBUG 版本中 运行,在这种情况下没有发生锁定,但一切都运行得慢得多(这对于调试来说没问题,但我们不能使用它生产中)。
编辑 2:
我尝试为 offer
和 setLocalDescription
回调包装额外的 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)
}
})
}
}
可以从任何线程调用 WebRTC Obj-C API,但大多数方法调用都传递给 WebRTC 的内部线程 signalling thread
。
此外,callbacks/observers 像 SetLocalDescriptionObserverInterface
或 RTCSetSessionDescriptionCompletionHandler
是从 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 了解详情。
经过 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)
}
}