电话锁定时,iOS音频在通话期间无法正常工作。 WebRTC用于呼叫

时间:2017-07-28 06:25:42

标签: ios audio webrtc voip callkit

我遇到音频问题使用Callkit和WebRTC进行VOIP通话时,从锁定屏幕接听电话。

一般功能:

我的应用在启动时激活audioSession。对于来电,SDP优惠&生成并交换答案。对等连接已设置。无论是音频呼叫还是视频呼叫,都会生成音频和视频流。然后使用以下代码将call报告给callkit:

    callProvider.reportNewIncomingCall(with: currentCallUUID!, update: update) { error in }

如果应用程序在前台,它可以正常工作。

但是,当手机被锁定,并且用户从锁定屏幕应答呼叫时,Streams会被交换,但在用户自己进入应用程序之前,两端都没有音频。

当用户进入App时,音频在两端都变为活动状态。

正确设置了所有后台设置和功能。

我还提到了Apple员工提供的以下工作。但即使它不起作用。

https://forums.developer.apple.com/thread/64544

正如我所提到的,我正在使用WebRTC进行呼叫。如果我在用户应答呼叫之后交换媒体流(仍然在锁定屏幕上)并且此时设置了对等连接。它工作正常(但它增加了建立呼叫连接的延迟)。

但是如果在显示呼叫之前进行了对等连接(比如说在报告callkit之前),则音频停止工作。

3 个答案:

答案 0 :(得分:3)

我可以解决此问题。

我遵循的步骤 -

  • 我查看了与WebRTC here

  • 相关的代码
  • 我添加了RTCAudioSession头文件,它实际上是Webrtc的私有类。因此,每当我收到来自信令的呼叫事件时,我启用RTCAudiosession并在通话结束时禁用它。

  • 我必须将传入的流呈现为虚拟视图(虽然在呼叫进行时未显示,但应用程序尚未打开,但需要使音频正常工作)。

如果有人面临同样的问题,我希望这会有所帮助。

答案 1 :(得分:1)

@abhimanyu你还在面对这个问题还是让它成功了。我与CallKit面临同样的问题。

根据我在WebRTC M60版本中的理解,他们修复了与CallKit相关的问题,我认为这会产生副作用并导致此问题。

他们修复的问题与System AudioSession有关,当CallKit呈现来电呼叫UI并播放振铃音时,CallKit会控制AudioSession,在用户操作(接受/拒绝)后,它会释放控制权。在WebRTC M60版本中,现在他们已为此控制交换添加了观察者。这就是为什么它在应用程序处于前台时工作的原因,但是如果手机被锁定并且接受了任何来电(我假设您正在使用CallKit UI进行呼叫而不是将用户重定向到应用程序,从锁定屏幕接受)由于Native UI当呼叫通过CallKit屏幕时,WebRTC无法激活自己的AudioSession实例。

已在WebRTC M60上修复的错误链接:https://bugs.chromium.org/p/webrtc/issues/detail?id=7446

如果您发现此问题的任何解决方法,请告知我们。

答案 2 :(得分:0)

请注意,我分享了我的代码及其关于我的需求,并分享以供参考。您需要根据需要更改它。

当您收到 voip 通知时,创建您的 webrtc 处理类的新事件,以及 将这两行添加到代码块中,因为从 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 处理程序类中配置媒体发送器和音频会话

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 { $0.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

查看示例