iOS phone 锁定时接听电话时音频不工作。用于通话的WebRTC
iOS Audio not working during call answered when phone is locked. WebRTC used for calling
我在使用 Callkit 和 WebRTC 进行 VOIP 通话时遇到音频问题,同时从锁定屏幕接听电话。
一般功能:
我的应用在启动时激活 audioSession。对于来电,生成并交换 SDP Offer & Answer。建立对等连接。无论是音频通话还是视频通话,都会生成音频和视频流。然后使用以下代码将Call报告给callkit:
callProvider.reportNewIncomingCall(with: currentCallUUID!, update: update) { error in }
如果应用程序在前台,它工作正常。
但是,当 phone 被锁定,并且用户从锁定屏幕接听电话时,会交换流,但在用户自己进入应用程序之前两端都没有音频。
当用户进入应用程序时,两端的音频都会激活。
所有后台设置和功能设置正确。
我还参考了 Apple 员工提供的以下解决方法。但是还是不行。
https://forums.developer.apple.com/thread/64544
正如我提到的,我正在使用 WebRTC 进行通话。如果我在用户接听电话后交换媒体流(仍在锁定屏幕上)并且当时设置了对等连接。它工作正常(但它增加了建立呼叫连接的延迟)。
但是如果在显示呼叫之前建立对等连接(比如在向 callkit 报告呼叫之前),音频将停止工作。
@abhimanyu 你是否仍然面临这个问题,或者你已经解决了。我面临与 CallKit 相同的问题。
根据我对 WebRTC M60 版本的理解,他们已经解决了与 CallKit 相关的问题,我认为它产生了副作用并导致了这个问题。
他们修复的问题与系统 AudioSession 有关,当 CallKit 显示来电 UI 并播放铃声时,CallKit 会控制 AudioSession,并在用户操作(接受/拒绝)后释放控制。在 WebRTC M60 版本中,现在他们已经为这种控制交换添加了观察者。这就是为什么如果应用程序在前台时它可以工作,但是如果 phone 被锁定并且任何来电都被接受(我假设您正在使用 CallKit UI 进行呼叫而不是在接受时将用户重定向到应用程序来自锁定屏幕)由于本机 UI 调用,WebRTC 无法激活其自己的 AudioSession 实例,因为调用正在通过 CallKit 屏幕进行。
Link 已在 WebRTC M60 上修复的错误:https://bugs.chromium.org/p/webrtc/issues/detail?id=7446
如果您找到解决此问题的方法,请告诉我。
我能够解决这个问题。
我遵循的步骤 -
我查看了WebRTC相关的代码here
我添加了 RTCAudioSession 头文件,它实际上是 Webrtc 的私有 class。因此,每次我从信令中收到呼叫事件时,我都会启用 RTCAudiosession 并在呼叫结束时将其禁用。
我必须将传入的流呈现为虚拟视图(虽然在通话进行且应用程序尚未打开时未显示,但需要使音频正常工作)。
如果有人面临同样的问题,我希望这会有所帮助。
请注意,我分享了我的代码及其关于我的需要,我分享以供参考。你需要根据你的需要改变它。
当您收到 voip 通知时,创建新的 webrtc 处理事件 class,以及
将这两行添加到代码块,因为从 voip 通知启用音频会话失败
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
didReceive 方法;
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
let state = UIApplication.shared.applicationState
if(payload.dictionaryPayload["hangup"] == nil && state != .active
){
Globals.voipPayload = payload.dictionaryPayload as! [String:Any] // I pass parameters to Webrtc handler via Global singleton to create answer according to sdp sent by payload.
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
Globals.sipGateway = SipGateway() // my Webrtc and Janus gateway handler class
Globals.sipGateway?.configureCredentials(true) // I check janus gateway credentials stored in Shared preferences and initiate websocket connection and create peerconnection
to my janus gateway which is signaling server for my environment
initProvider() //Crating callkit provider
self.update.remoteHandle = CXHandle(type: .generic, value:String(describing: payload.dictionaryPayload["caller_id"]!))
Globals.callId = UUID()
let state = UIApplication.shared.applicationState
Globals.provider.reportNewIncomingCall(with:Globals.callId , update: self.update, completion: { error in
})
}
}
func initProvider(){
let config = CXProviderConfiguration(localizedName: "ulakBEL")
config.iconTemplateImageData = UIImage(named: "ulakbel")!.pngData()
config.ringtoneSound = "ringtone.caf"
// config.includesCallsInRecents = false;
config.supportsVideo = false
Globals.provider = CXProvider(configuration:config )
Globals.provider.setDelegate(self, queue: nil)
update = CXCallUpdate()
update.hasVideo = false
update.supportsDTMF = true
}
修改您的 didActivate 和 didDeActive 委托函数,如下所示,
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("CallManager didActivate")
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
// self.callDelegate?.callIsAnswered()
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("CallManager didDeactivate")
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
在 Webrtc 处理程序中 class 配置媒体发送器和音频会话
private func createPeerConnection(webRTCCallbacks:PluginHandleWebRTCCallbacksDelegate) {
let rtcConfig = RTCConfiguration.init()
rtcConfig.iceServers = server.iceServers
rtcConfig.bundlePolicy = RTCBundlePolicy.maxBundle
rtcConfig.rtcpMuxPolicy = RTCRtcpMuxPolicy.require
rtcConfig.continualGatheringPolicy = .gatherContinually
rtcConfig.sdpSemantics = .planB
let constraints = RTCMediaConstraints(mandatoryConstraints: nil,
optionalConstraints: ["DtlsSrtpKeyAgreement":kRTCMediaConstraintsValueTrue])
pc = sessionFactory.peerConnection(with: rtcConfig, constraints: constraints, delegate: nil)
self.createMediaSenders()
self.configureAudioSession()
if webRTCCallbacks.getJsep() != nil{
handleRemoteJsep(webrtcCallbacks: webRTCCallbacks)
}
}
mediaSenders;
private func createMediaSenders() {
let streamId = "stream"
// Audio
let audioTrack = self.createAudioTrack()
self.pc.add(audioTrack, streamIds: [streamId])
// Video
/* let videoTrack = self.createVideoTrack()
self.localVideoTrack = videoTrack
self.peerConnection.add(videoTrack, streamIds: [streamId])
self.remoteVideoTrack = self.peerConnection.transceivers.first { [=14=].mediaType == .video }?.receiver.track as? RTCVideoTrack
// Data
if let dataChannel = createDataChannel() {
dataChannel.delegate = self
self.localDataChannel = dataChannel
}*/
}
private func createAudioTrack() -> RTCAudioTrack {
let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let audioSource = sessionFactory.audioSource(with: audioConstrains)
let audioTrack = sessionFactory.audioTrack(with: audioSource, trackId: "audio0")
return audioTrack
}
音频会话;
private func configureAudioSession() {
self.rtcAudioSession.lockForConfiguration()
do {
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
} catch let error {
debugPrint("Error changeing AVAudioSession category: \(error)")
}
self.rtcAudioSession.unlockForConfiguration()
}
请考虑一下,因为我使用回调和委托代码包括委托和回调块。你可以相应地忽略它们!!
供参考您还可以查看此处的示例 link
我在使用 Callkit 和 WebRTC 进行 VOIP 通话时遇到音频问题,同时从锁定屏幕接听电话。
一般功能:
我的应用在启动时激活 audioSession。对于来电,生成并交换 SDP Offer & Answer。建立对等连接。无论是音频通话还是视频通话,都会生成音频和视频流。然后使用以下代码将Call报告给callkit:
callProvider.reportNewIncomingCall(with: currentCallUUID!, update: update) { error in }
如果应用程序在前台,它工作正常。
但是,当 phone 被锁定,并且用户从锁定屏幕接听电话时,会交换流,但在用户自己进入应用程序之前两端都没有音频。
当用户进入应用程序时,两端的音频都会激活。
所有后台设置和功能设置正确。
我还参考了 Apple 员工提供的以下解决方法。但是还是不行。
https://forums.developer.apple.com/thread/64544
正如我提到的,我正在使用 WebRTC 进行通话。如果我在用户接听电话后交换媒体流(仍在锁定屏幕上)并且当时设置了对等连接。它工作正常(但它增加了建立呼叫连接的延迟)。
但是如果在显示呼叫之前建立对等连接(比如在向 callkit 报告呼叫之前),音频将停止工作。
@abhimanyu 你是否仍然面临这个问题,或者你已经解决了。我面临与 CallKit 相同的问题。
根据我对 WebRTC M60 版本的理解,他们已经解决了与 CallKit 相关的问题,我认为它产生了副作用并导致了这个问题。
他们修复的问题与系统 AudioSession 有关,当 CallKit 显示来电 UI 并播放铃声时,CallKit 会控制 AudioSession,并在用户操作(接受/拒绝)后释放控制。在 WebRTC M60 版本中,现在他们已经为这种控制交换添加了观察者。这就是为什么如果应用程序在前台时它可以工作,但是如果 phone 被锁定并且任何来电都被接受(我假设您正在使用 CallKit UI 进行呼叫而不是在接受时将用户重定向到应用程序来自锁定屏幕)由于本机 UI 调用,WebRTC 无法激活其自己的 AudioSession 实例,因为调用正在通过 CallKit 屏幕进行。
Link 已在 WebRTC M60 上修复的错误:https://bugs.chromium.org/p/webrtc/issues/detail?id=7446
如果您找到解决此问题的方法,请告诉我。
我能够解决这个问题。
我遵循的步骤 -
我查看了WebRTC相关的代码here
我添加了 RTCAudioSession 头文件,它实际上是 Webrtc 的私有 class。因此,每次我从信令中收到呼叫事件时,我都会启用 RTCAudiosession 并在呼叫结束时将其禁用。
我必须将传入的流呈现为虚拟视图(虽然在通话进行且应用程序尚未打开时未显示,但需要使音频正常工作)。
如果有人面临同样的问题,我希望这会有所帮助。
请注意,我分享了我的代码及其关于我的需要,我分享以供参考。你需要根据你的需要改变它。
当您收到 voip 通知时,创建新的 webrtc 处理事件 class,以及 将这两行添加到代码块,因为从 voip 通知启用音频会话失败
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
didReceive 方法;
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
let state = UIApplication.shared.applicationState
if(payload.dictionaryPayload["hangup"] == nil && state != .active
){
Globals.voipPayload = payload.dictionaryPayload as! [String:Any] // I pass parameters to Webrtc handler via Global singleton to create answer according to sdp sent by payload.
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
Globals.sipGateway = SipGateway() // my Webrtc and Janus gateway handler class
Globals.sipGateway?.configureCredentials(true) // I check janus gateway credentials stored in Shared preferences and initiate websocket connection and create peerconnection
to my janus gateway which is signaling server for my environment
initProvider() //Crating callkit provider
self.update.remoteHandle = CXHandle(type: .generic, value:String(describing: payload.dictionaryPayload["caller_id"]!))
Globals.callId = UUID()
let state = UIApplication.shared.applicationState
Globals.provider.reportNewIncomingCall(with:Globals.callId , update: self.update, completion: { error in
})
}
}
func initProvider(){
let config = CXProviderConfiguration(localizedName: "ulakBEL")
config.iconTemplateImageData = UIImage(named: "ulakbel")!.pngData()
config.ringtoneSound = "ringtone.caf"
// config.includesCallsInRecents = false;
config.supportsVideo = false
Globals.provider = CXProvider(configuration:config )
Globals.provider.setDelegate(self, queue: nil)
update = CXCallUpdate()
update.hasVideo = false
update.supportsDTMF = true
}
修改您的 didActivate 和 didDeActive 委托函数,如下所示,
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("CallManager didActivate")
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
// self.callDelegate?.callIsAnswered()
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("CallManager didDeactivate")
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
在 Webrtc 处理程序中 class 配置媒体发送器和音频会话
private func createPeerConnection(webRTCCallbacks:PluginHandleWebRTCCallbacksDelegate) {
let rtcConfig = RTCConfiguration.init()
rtcConfig.iceServers = server.iceServers
rtcConfig.bundlePolicy = RTCBundlePolicy.maxBundle
rtcConfig.rtcpMuxPolicy = RTCRtcpMuxPolicy.require
rtcConfig.continualGatheringPolicy = .gatherContinually
rtcConfig.sdpSemantics = .planB
let constraints = RTCMediaConstraints(mandatoryConstraints: nil,
optionalConstraints: ["DtlsSrtpKeyAgreement":kRTCMediaConstraintsValueTrue])
pc = sessionFactory.peerConnection(with: rtcConfig, constraints: constraints, delegate: nil)
self.createMediaSenders()
self.configureAudioSession()
if webRTCCallbacks.getJsep() != nil{
handleRemoteJsep(webrtcCallbacks: webRTCCallbacks)
}
}
mediaSenders;
private func createMediaSenders() {
let streamId = "stream"
// Audio
let audioTrack = self.createAudioTrack()
self.pc.add(audioTrack, streamIds: [streamId])
// Video
/* let videoTrack = self.createVideoTrack()
self.localVideoTrack = videoTrack
self.peerConnection.add(videoTrack, streamIds: [streamId])
self.remoteVideoTrack = self.peerConnection.transceivers.first { [=14=].mediaType == .video }?.receiver.track as? RTCVideoTrack
// Data
if let dataChannel = createDataChannel() {
dataChannel.delegate = self
self.localDataChannel = dataChannel
}*/
}
private func createAudioTrack() -> RTCAudioTrack {
let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let audioSource = sessionFactory.audioSource(with: audioConstrains)
let audioTrack = sessionFactory.audioTrack(with: audioSource, trackId: "audio0")
return audioTrack
}
音频会话;
private func configureAudioSession() {
self.rtcAudioSession.lockForConfiguration()
do {
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
} catch let error {
debugPrint("Error changeing AVAudioSession category: \(error)")
}
self.rtcAudioSession.unlockForConfiguration()
}
请考虑一下,因为我使用回调和委托代码包括委托和回调块。你可以相应地忽略它们!!
供参考您还可以查看此处的示例 link