如何为AVCaptureSessionPresetPhoto启用preferredVideoStabilizationMode
解决iOS 4:3比例照片防抖问题
问题根源
你混淆了两种不同的防抖机制:
preferredVideoStabilizationMode控制的是数字视频防抖,仅在视频捕获管线中生效,.photo预设优先高分辨率照片捕获,不会激活视频防抖管线,因此该设置无效。- iOS原生相机的4:3照片防抖使用的是光学图像防抖(OIS),这是硬件级特性,与视频防抖是独立的功能。
解决方案1:启用光学防抖(匹配原生相机行为)
这是获取高分辨率4:3防抖照片的正确方式,直接利用镜头硬件的光学防抖功能。
修改后的代码
func startCamera(){ self.capturePhotoOutput = AVCapturePhotoOutput.init() guard let captureDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType.video, position: .back) else { print("无法获取相机设备") return } self.captureDevice = captureDevice do { // 启用光学图像防抖(OIS) try captureDevice.lockForConfiguration() if captureDevice.isOpticalImageStabilizationSupported { captureDevice.isOpticalImageStabilizationEnabled = true print("OIS已启用") } else { print("当前设备不支持光学防抖") } captureDevice.unlockForConfiguration() let input = try AVCaptureDeviceInput(device: captureDevice) self.captureSession = AVCaptureSession() self.captureSession?.sessionPreset = .photo // 保持4:3高分辨率 self.captureSession?.addInput(input) videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput?.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) self.captureSession?.addOutput(self.capturePhotoOutput!) if captureSession?.canAddOutput(videoDataOutput!) == true { captureSession?.addOutput(videoDataOutput!) } self.capturePhotoOutput?.isHighResolutionCaptureEnabled = true self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession!) self.videoPreviewLayer?.videoGravity = .resizeAspect self.videoPreviewLayer?.connection?.videoOrientation = .portrait self.videoPreviewLayer?.frame = self.previewView.layer.frame self.previewView.layer.insertSublayer(self.videoPreviewLayer!, at: 0) DispatchQueue.global(qos: .background).async { self.captureSession?.startRunning() } } catch { print(error) } }
解决方案2:启用4:3数字视频防抖(预览防抖)
如果需要实时预览画面具备数字防抖效果(类似视频模式),需切换到支持4:3比例的视频格式,代价是照片分辨率会低于.photo预设。
修改后的代码
func startCamera(){ self.capturePhotoOutput = AVCapturePhotoOutput.init() guard let captureDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType.video, position: .back) else { print("无法获取相机设备") return } self.captureDevice = captureDevice do { try captureDevice.lockForConfiguration() // 查找支持视频防抖的最高分辨率4:3格式 var bestFormat: AVCaptureDeviceFormat? let targetAspectRatio = CGSize(width: 4, height: 3) for format in captureDevice.formats { guard format.mediaType == .video else { continue } guard format.isVideoStabilizationSupported else { continue } let dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription) let formatAspectRatio = CGSize(width: dimensions.width, height: dimensions.height) // 允许微小比例误差 let ratioMatch = abs((formatAspectRatio.width / formatAspectRatio.height) - (targetAspectRatio.width / targetAspectRatio.height)) < 0.01 if ratioMatch { if let currentBest = bestFormat { let currentDimensions = CMVideoFormatDescriptionGetDimensions(currentBest.formatDescription) let currentPixels = currentDimensions.width * currentDimensions.height let formatPixels = dimensions.width * dimensions.height if formatPixels > currentPixels { bestFormat = format } } else { bestFormat = format } } } guard let selectedFormat = bestFormat else { captureDevice.unlockForConfiguration() print("未找到支持视频防抖的4:3格式") return } captureDevice.activeFormat = selectedFormat captureDevice.unlockForConfiguration() let input = try AVCaptureDeviceInput(device: captureDevice) self.captureSession = AVCaptureSession() self.captureSession?.sessionPreset = .inputPriority // 使用设备的激活格式 self.captureSession?.addInput(input) videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput?.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) self.captureSession?.addOutput(self.capturePhotoOutput!) if captureSession?.canAddOutput(videoDataOutput!) == true { captureSession?.addOutput(videoDataOutput!) } self.capturePhotoOutput?.isHighResolutionCaptureEnabled = true self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession!) self.videoPreviewLayer?.videoGravity = .resizeAspect // 为预览层和视频输出启用视频防抖 if let connection = self.videoPreviewLayer?.connection { connection.videoOrientation = .portrait if connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .cinematicExtended } } if let connection = videoDataOutput?.connection(with: .video) { if connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .cinematicExtended } } self.videoPreviewLayer?.frame = self.previewView.layer.frame self.previewView.layer.insertSublayer(self.videoPreviewLayer!, at: 0) DispatchQueue.global(qos: .background).async { self.captureSession?.startRunning() } } catch { print(error) } }
更新防抖模式切换函数
@objc func changeVideoStabilizationMode(){ print("\(vSModesInt)") guard let mode = AVCaptureVideoStabilizationMode(rawValue: vSModesInt) else { return } switch mode { case .off: videoStabilizationModeLabel.text = "off" case .standard: videoStabilizationModeLabel.text = "standard" case .cinematic: videoStabilizationModeLabel.text = "cinematic" case .cinematicExtended: videoStabilizationModeLabel.text = "cinematic extended" case .auto: videoStabilizationModeLabel.text = "auto" @unknown default: videoStabilizationModeLabel.text = "unknown" } vSModesInt += 1 if vSModesInt > 3 { vSModesInt = -1 } // 更新两个连接的防抖模式 if let connection = videoDataOutput?.connection(with: .video) { if connection.isVideoStabilizationSupported && connection.isVideoStabilizationModeSupported(mode) { connection.preferredVideoStabilizationMode = mode } } if let connection = videoPreviewLayer?.connection { if connection.isVideoStabilizationSupported && connection.isVideoStabilizationModeSupported(mode) { connection.preferredVideoStabilizationMode = mode } } }
关键说明
- 光学防抖(OIS)是硬件特性,部分旧设备或前置摄像头不支持。
- 数字视频防抖仅作用于预览画面,
AVCapturePhotoOutput捕获的照片仍来自原始传感器(若需防抖照片需从视频流中截取帧,但分辨率较低)。 - 原生相机的4:3照片防抖使用的是光学防抖,因此解决方案1是最接近原生体验的实现。
内容的提问来源于stack exchange,提问作者Vileriu
相关产品推荐
相关产品推荐

