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

如何在不冻结APP的情况下保存同一AVCaptureSession快速拍摄的图片?

问题分析与解决方案

核心原因

直接通过UIImage(ciImage: ciImage)创建的图片对象并未独立存储像素数据,它只是对AVCaptureSession提供的CVImageBuffer的包装。当你把这类UIImage存入数组时,会阻止底层buffer被系统回收,相机的buffer池被耗尽后就会出现冻结现象。后台切换回来时系统会回收部分闲置资源,所以能短暂恢复,但很快又会耗尽。而压缩图片时会将像素数据转为独立的JPEG/PNG数据,脱离了原buffer的依赖,因此不会触发这个问题。

修复步骤

1. 生成带独立像素数据的UIImage

捕获图片时,不要直接用UIImage(ciImage:),而是通过CIContext将CIImage渲染为CGImage,再创建UIImage,这样会生成独立的像素数据,不会占用相机的buffer:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    guard takePicture, let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
    let ciImage = CIImage(cvImageBuffer: buffer)
    
    // 用CIContext渲染出独立的CGImage
    guard let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) else {
        takePicture = false
        return
    }
    let uiImage = UIImage(cgImage: cgImage)

    DispatchQueue.main.async {
        self.takePicture = false
        self.capturedImages.append(uiImage)
        self.capturedImageImageView.image = uiImage
        self.imageCountLabel.text = String(self.capturedImages.count)
    }
}

2. 修复线程安全问题

原代码在主队列中嵌套全局队列操作capturedImages,会导致数组读写在不同线程,存在线程安全隐患。统一在主队列操作数组即可:

// 移除原代码中嵌套的全局队列,直接在主队列操作数组
DispatchQueue.main.async {
    self.takePicture = false
    self.capturedImages.append(uiImage)
    self.capturedImageImageView.image = uiImage
    self.imageCountLabel.text = String(self.capturedImages.count)
}

3. 复用CIContext提升性能

CIContext创建耗时,建议将其作为类属性初始化,避免每次捕获都重复创建:

class CameraViewController: UIViewController {
    // 添加CIContext属性
    private let ciContext = CIContext(options: [.useSoftwareRenderer: false])
    // ... 其他属性
    
    // 后续捕获方法中直接复用ciContext
}

4. 补充相机权限检查

原代码checkPermissions()为空,相机权限是功能正常运行的前提,补充权限检查:

private func checkPermissions() {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        break
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            if granted {
                DispatchQueue.main.async {
                    self.setUpAndStartCaptureSession()
                }
            } else {
                // 提示用户开启相机权限
            }
        }
    case .denied, .restricted:
        // 引导用户前往设置开启权限
        break
    @unknown default:
        break
    }
}

内容的提问来源于stack exchange,提问作者KevinF

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:37:36