Swift中WebRTC直播音频异常:AURemoteIO启动失败问题求助
问题:WebRTC直播中Push to Talk功能偶现音频失效问题
在集成Push to Talk功能的Swift WebRTC直播类中,偶尔出现音频无法正常工作(无声音)的情况,同时收到错误提示:AURemoteIO.cpp:1679 AUIOClient_StartIO failed。但当启动直播无声音后,将应用切换至后台再切回前台,音频即可恢复正常。已在plist文件配置相机、麦克风权限,相关代码如下:
class WebRTCManager: NSObject { private let peerConnectionFactory: RTCPeerConnectionFactory = { RTCInitializeSSL() let videoEncoderFactory = RTCVideoEncoderFactoryH264() let videoDecoderFactory = RTCVideoDecoderFactoryH264() return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory) }() private var peerConnection: RTCPeerConnection? private var audioSource: RTCAudioSource? private var audioTrack: RTCAudioTrack? private var audioSender: RTCRtpSender? private var localStream: RTCMediaStream? private var remoteStream: RTCMediaStream? private var dataChannel: RTCDataChannel? private var remoteDataChannel: RTCDataChannel? private let audioQueue = DispatchQueue(label: "audio") private let rtcAudioSession = RTCAudioSession.sharedInstance() var socket: WebSocket! public private(set) var isConnected: Bool = false // enableMicrophone override init( ) { super.init() let audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) self.audioSource = self.peerConnectionFactory.audioSource(with: audioConstraints) self.audioTrack = self.peerConnectionFactory.audioTrack(with: self.audioSource!, trackId: "audio0") self.localStream = self.peerConnectionFactory.mediaStream(withStreamId: "stream") localStream!.addAudioTrack(self.audioTrack!) } deinit { self.peerConnection = nil self.socket = nil } func reset(){ self.peerConnection = nil self.socket = nil } func initLive(socketURL:String) { self.socket = WebSocket(url: URL(string: socketURL)!) self.socket.delegate = self self.socket.connect() } // MARK: Connect func connect(onSuccess: @escaping (RTCSessionDescription) -> Void){ setupPeerConnection() } func createAndConnectSocket(socketURL:String){ self.socket = WebSocket(url: URL(string: socketURL)!) self.socket.delegate = self self.socket.connect() } // MARK: - setupPeerConnection private func setupPeerConnection() { let rtcConf = RTCConfiguration() rtcConf.sdpSemantics = .unifiedPlan rtcConf.iceServers = [ RTCIceServer(urlStrings: ["turn:" + self.coturnHost + ":" + String(self.coturnPort)] ,username: self.userName ,credential: self.password )] let mediaConstraints = RTCMediaConstraints.init(mandatoryConstraints: nil, optionalConstraints: nil) self.peerConnection = self.peerConnectionFactory.peerConnection(with: rtcConf, constraints: mediaConstraints, delegate: nil) self.peerConnection?.delegate = self for track in localStream!.audioTracks { audioSender = peerConnection?.add(audioTrack!, streamIds: [localStream!.streamId]) } } // MARK: Fonctionnalité Push to Talk func pushToTalk(isPressed: Bool) { self.audioTrack?.isEnabled = isPressed } // MARK: the speaker function func setSpeaker(isOn: Bool) { audioQueue.async { [weak self] in guard let self = self else { return } self.rtcAudioSession.lockForConfiguration() do { try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue) if isOn { try self.rtcAudioSession.overrideOutputAudioPort(.speaker) try self.rtcAudioSession.setActive(true) } else { try self.rtcAudioSession.overrideOutputAudioPort(.none) } } catch let error { debugPrint("Error setting AVAudioSession category: \(error)") } self.rtcAudioSession.unlockForConfiguration() } } } // MARK: - PeerConnection Delegates extension WebRTCManager : RTCPeerConnectionDelegate { func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { print("liveWRTC--did add stream") self.remoteStream = stream if let track = stream.videoTracks.first { print("liveWRTC--video track faund") track.add(remoteRenderView!) } if let audioTrack = stream.audioTracks.first{ print("WebRTCManager: liveWRTC--audio track faund") } } func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { } func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { self.remoteDataChannel = dataChannel } func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { //peerConnection } func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection){ //peerConnectionShouldNegotiate } func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { //peerConnection } } }
==> 直播连接成功时,会执行以下代码:
if (liveStreamingStatus == .connected){ WebRTCManager.setSpeaker(isOn: true) WebRTCManager.pushToTalk(isPressed: false) }
修复方案
这个问题根源是音频会话状态冲突或WebRTC音频组件初始化时机错误,切换后台恢复是因为系统强制重置了音频会话。以下是针对性修复:
1. 修正音频会话配置逻辑
统一激活会话的时机,添加会话模式和选项,避免状态不一致:
func setSpeaker(isOn: Bool) { audioQueue.async { [weak self] in guard let self = self else { return } self.rtcAudioSession.lockForConfiguration() do { // 先设置会话类别、模式,再激活 try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, mode: .voiceChat, options: [.mixWithOthers, .allowBluetooth]) try self.rtcAudioSession.setActive(true, options: .notifyOthersOnDeactivation) if isOn { try self.rtcAudioSession.overrideOutputAudioPort(.speaker) } else { try self.rtcAudioSession.overrideOutputAudioPort(.none) } } catch let error { debugPrint("Error setting AVAudioSession: \(error)") } self.rtcAudioSession.unlockForConfiguration() } }
2. 确保权限获取后再初始化音频组件
初始化音频轨道前先确认麦克风权限,避免组件在无权限状态下创建:
override init() { super.init() // 先请求麦克风权限 AVCaptureDevice.requestAccess(for: .audio) { [weak self] granted in guard let self = self, granted else { return } DispatchQueue.main.async { let audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) self.audioSource = self.peerConnectionFactory.audioSource(with: audioConstraints) self.audioTrack = self.peerConnectionFactory.audioTrack(with: self.audioSource!, trackId: "audio0") self.localStream = self.peerConnectionFactory.mediaStream(withStreamId: "stream") self.localStream!.addAudioTrack(self.audioTrack!) } } // 添加音频中断监听 NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionInterruption(_:)), name: AVAudioSession.interruptionNotification, object: nil) }
3. 同步控制音频源与轨道状态
仅设置audioTrack.isEnabled可能无法触发音频流重启,需要同步控制音频源:
func pushToTalk(isPressed: Bool) { self.audioTrack?.isEnabled = isPressed self.audioSource?.setIsEnabled(isPressed) }
4. 监听音频中断并恢复会话
当中断结束后重新激活音频会话,避免异常状态残留:
@objc private func handleAudioSessionInterruption(_ notification: Notification) { guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return } if type == .ended { audioQueue.async { [weak self] in guard let self = self else { return } self.rtcAudioSession.lockForConfiguration() do { try self.rtcAudioSession.setActive(true) } catch { debugPrint("Failed to reactivate audio session: \(error)") } self.rtcAudioSession.unlockForConfiguration() } } } deinit { NotificationCenter.default.removeObserver(self) self.peerConnection = nil self.socket = nil }
5. 延迟创建PeerConnection确保音频组件就绪
避免在音频轨道未初始化完成时创建PeerConnection:
func connect(onSuccess: @escaping (RTCSessionDescription) -> Void){ guard self.audioTrack != nil else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.connect(onSuccess: onSuccess) } return } setupPeerConnection() // 后续创建offer等逻辑补充 }
内容的提问来源于stack exchange,提问作者Marymnar
相关产品推荐
相关产品推荐

