iOS(Swift)中类似WhatsApp的画中画模式摄像头访问异常问题
问题解决:iOS后台PIP模式下摄像头画面冻结且触发
videoDeviceNotAvailableWithMultipleForegroundApps错误 核心问题分析
你的代码存在几个关键问题,导致后台PIP时摄像头被中断:
- 音频会话配置错误:使用了
.playback类别和.moviePlayback模式,这两类配置不支持视频通话所需的后台音视频同时工作,系统会在后台限制摄像头访问。 - 视图层级冲突:将同一个
cameraPreview同时添加到主控制器视图和PIP内容控制器视图,导致实时画面无法正确传递到PIP窗口。 - 错误处理缺失:多处使用
try?忽略异常,无法排查权限或设备初始化失败的问题。 - 多任务摄像头权限的启用逻辑:虽然开启了
isMultitaskingCameraAccessEnabled,但音频会话的配置顺序和类型不对,无法触发多任务摄像头权限生效。
修复后的完整代码
import UIKit import AVFoundation import AVKit class ViewController: UIViewController { private var pictureInPictureController: AVPictureInPictureController! let captureSession = AVCaptureSession() let pipVideoCallViewController = AVPictureInPictureVideoCallViewController() var pipContentSource: AVPictureInPictureController.ContentSource! // 单独为PIP创建预览层,避免视图复用冲突 private var pipPreviewLayer: AVCaptureVideoPreviewLayer! override func viewDidLoad() { super.viewDidLoad() // 添加捕获会话通知监听 NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: .AVCaptureSessionWasInterrupted, object: captureSession) NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: .AVCaptureSessionInterruptionEnded, object: captureSession) NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: .AVCaptureSessionRuntimeError, object: captureSession) // 优先配置音频会话(视频通话必须用playAndRecord类别) do { let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.playAndRecord, mode: .videoChat, options: [.mixWithOthers, .allowBluetooth]) try audioSession.setActive(true) } catch { print("音频会话配置失败:\(error.localizedDescription)") return } // 检查摄像头权限 AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in guard granted, let self = self else { print("摄像头权限被拒绝") return } DispatchQueue.main.async { self.setupCaptureSession() self.setupPictureInPicture() } } } private func setupCaptureSession() { // 获取后置摄像头 guard let captureDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first else { print("无法获取后置摄像头") return } do { let deviceInput = try AVCaptureDeviceInput(device: captureDevice) if captureSession.canAddInput(deviceInput) { captureSession.addInput(deviceInput) } // 配置多任务摄像头访问 captureSession.beginConfiguration() if captureSession.isMultitaskingCameraAccessSupported { captureSession.isMultitaskingCameraAccessEnabled = true } // 视频通话用合适的分辨率,不要用photo(太高且不适合实时传输) captureSession.sessionPreset = .hd1280x720 captureSession.commitConfiguration() // 启动捕获会话 DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() } // 为主视图创建预览层 let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = view.bounds previewLayer.videoGravity = .resizeAspectFill view.layer.addSublayer(previewLayer) // 为PIP创建独立的预览层 pipPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) pipPreviewLayer.frame = pipVideoCallViewController.view.bounds pipPreviewLayer.videoGravity = .resizeAspectFill pipVideoCallViewController.view.layer.addSublayer(pipPreviewLayer) } catch { print("摄像头输入配置失败:\(error.localizedDescription)") } } private func setupPictureInPicture() { guard AVPictureInPictureController.isPictureInPictureSupported() else { print("设备不支持PIP模式") return } pipVideoCallViewController.preferredContentSize = CGSize(width: 640, height: 480) pipContentSource = AVPictureInPictureController.ContentSource( activeVideoCallSourceView: view, contentViewController: pipVideoCallViewController) pictureInPictureController = AVPictureInPictureController(contentSource: pipContentSource) pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true // 监听PIP状态变化,确保后台时会话正常运行 pictureInPictureController.delegate = self } @objc func sessionWasInterrupted(notification: NSNotification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVCaptureSessionInterruptionReasonKey] as? Int, let reason = AVCaptureSession.InterruptionReason(rawValue: reasonValue) else { return } switch reason { case .videoDeviceNotAvailableInBackground: print("后台无法访问摄像头") case .audioDeviceInUseByAnotherClient, .videoDeviceInUseByAnotherClient: print("音视频设备被其他应用占用") case .videoDeviceNotAvailableWithMultipleForegroundApps: print("多前台应用导致摄像头不可用") case .videoDeviceNotAvailableDueToSystemPressure: print("系统压力导致摄像头不可用") @unknown default: print("未知中断原因") } } @objc func sessionInterruptionEnded(_ notification: NSNotification) { print("捕获会话中断结束") // 重新启动会话(如果需要) if !captureSession.isRunning { DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() } } } @objc func sessionRuntimeError(_ notification: NSNotification) { if let error = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError { print("捕获会话运行错误:\(error.localizedDescription)") // 处理会话错误,比如重新启动 if error.code == AVError.deviceWasDisconnected.rawValue { DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() } } } } deinit { NotificationCenter.default.removeObserver(self) captureSession.stopRunning() } } // 实现PIP代理,处理状态变化 extension ViewController: AVPictureInPictureControllerDelegate { func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { print("PIP模式已启动") } func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { print("PIP模式已停止") } }
关键修复点说明
- 音频会话配置:改用
.playAndRecord类别和.videoChat模式,这是视频通话场景的标准配置,支持后台音视频同时工作,确保系统不会限制摄像头访问。 - 独立预览层:为主视图和PIP窗口分别创建
AVCaptureVideoPreviewLayer,避免同一个视图被添加到两个层级导致的画面冻结问题。 - 权限检查:显式请求摄像头权限,确保用户授权后再初始化捕获会话。
- 错误处理:替换
try?为do-catch,输出具体错误信息,便于排查问题。 - PIP代理:实现
AVPictureInPictureControllerDelegate,监听PIP状态变化,必要时恢复捕获会话。 - 会话预设调整:改用
.hd1280x720适合视频通话的分辨率,避免photo预设带来的性能和兼容性问题。
额外注意事项
- 确保在
Info.plist中添加以下权限描述:NSCameraUsageDescription:摄像头权限说明NSMicrophoneUsageDescription:麦克风权限说明UIBackgroundModes:添加audio和voip(如果是VOIP通话)
- 测试时确保没有其他前台应用占用摄像头,否则仍会触发
videoDeviceNotAvailableWithMultipleForegroundApps错误。 - 对于iOS 15+,可以使用
AVCaptureDevice.isMultitaskingAccessSupported进一步验证多任务摄像头访问支持情况。
内容的提问来源于stack exchange,提问作者VarunRaj
相关产品推荐
相关产品推荐

