You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 20:42:33