Swift:录制视频时如何处理音频会话中断问题
iOS相机录制中断后无法保存视频的修复方案
核心问题分析
当前代码存在几个关键问题导致中断后相机冻结、视频无法保存:
- 通知注册时机错误:在
fileOutput(_:didStartRecordingTo:from:)中注册中断通知,每次录制都会重复注册,且中断发生后captureSession或audioDeviceInput可能被意外重置,导致回调逻辑失效。 removeAudioInput依赖audioDeviceInput的引用,但中断时该变量可能已被重置,无法正确移除音频输入,进而导致录制卡住。- 未针对不同中断类型(如来电、音频占用)执行明确的录制控制逻辑(停止保存/无音频继续录制)。
具体修复步骤
1. 提前注册中断通知
将通知注册移到CameraManager的初始化方法中,避免重复注册,确保回调始终能关联到有效的captureSession实例:
class CameraManager { var captureSession: AVCaptureSession? var audioDeviceInput: AVCaptureDeviceInput? var movieFileOutput: AVCaptureMovieFileOutput? // 确保持有录制输出的引用 init() { setupNotifications() } private func setupNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionBegin), name: .AVCaptureSessionWasInterrupted, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnd), name: .AVCaptureSessionInterruptionEnded, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(audioSessionInterrupted), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance) } deinit { NotificationCenter.default.removeObserver(self) } }
2. 修复removeAudioInput逻辑
不再依赖audioDeviceInput的引用,直接遍历captureSession的输入找到音频输入并移除,确保即使引用丢失也能生效:
func removeAudioInput() { guard let session = captureSession else { return } session.beginConfiguration() // 遍历所有输入,找到音频类型的输入移除 for input in session.inputs { if input is AVCaptureDeviceInput, input.device.hasMediaType(.audio) { session.removeInput(input) audioDeviceInput = nil break } } session.commitConfiguration() }
3. 针对不同中断类型执行录制控制
添加停止录制并保存的方法,在sessionInterruptionBegin中区分中断原因,执行不同策略:
func stopRecordingAndSave() { guard let output = movieFileOutput, output.isRecording else { return } output.stopRecording() // 后续可在fileOutput(_:didFinishRecordingTo:from:error:)中处理文件保存逻辑 } @objc func sessionInterruptionBegin(notification: Notification) { guard let reasonNumber = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? NSNumber, let reason = AVCaptureSession.InterruptionReason(rawValue: reasonNumber.intValue) else { return } switch reason { case .audioDeviceInUseByAnotherClient, .audioDeviceNotAvailable: // 音频中断,移除音频输入后继续录制无音频视频 removeAudioInput() captureSession?.startRunning() case .videoDeviceNotAvailableWithMultipleForegroundApps, .videoDeviceNotAvailableDueToSystemPressure: // 来电/系统压力等无法恢复的中断,直接停止录制并保存 stopRecordingAndSave() default: // 其他未知中断,优先停止录制避免冻结 stopRecordingAndSave() } }
4. 优化音频中断回调逻辑
确保音频中断开始时正确移除音频输入,中断结束后按需恢复:
@objc func audioSessionInterrupted(notification: Notification) { guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return } switch type { case .began: removeAudioInput() case .ended: guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) if options.contains(.shouldResume) { configureAudioSession() } @unknown default: () } }
5. 避免captureSession意外重置
检查代码中是否有其他地方会重置captureSession,确保中断期间captureSession的引用保持有效,不要随意重新初始化。
关键注意事项
- 在
fileOutput(_:didFinishRecordingTo:from:error:)回调中,即使返回错误,也要检查目标文件是否存在,尝试修复或保存已录制的部分内容。 - 测试时覆盖多种中断场景:来电、闹钟、其他APP占用音频/相机、系统资源压力等。
内容的提问来源于stack exchange,提问作者p_dip
相关产品推荐
相关产品推荐

