(八)深入了解AVFoundation-采集:拍照功能的实现

引言

在上一篇文章中,我们初步完成了使用 AVFoundation 采集视频数据的流程,掌握了 AVCaptureSession 的搭建与视频流的预览显示。

本篇将继续深入 AVFoundation,聚焦于静态图片采集的实现。通过 AVCapturePhotoOutput,我们可以快速搭建起拍照功能,并获取高质量的照片数据。

本文将详细介绍拍照的基本流程,包括如何创建输出、配置拍照参数、处理拍照回调,以及如何将拍摄到的照片保存到系统相册。结合实际示例,带你完成一套完整、稳定的拍照功能搭建。

在早期的 iOS 开发中,我们常常使用 AVCaptureStillImageOutput 来实现拍照功能。但从 iOS 10 开始,Apple 推荐使用更强大、功能更全面的 AVCapturePhotoOutput 进行静态图片采集。AVCapturePhotoOutput 不仅统一了拍照接口,还支持 HEIF 格式、Live Photo、深度数据、RAW 拍摄等高级特性,能够更好地满足高质量拍摄的需求。

接下来,我们将基于 AVCapturePhotoOutput,搭建一个基础的拍照流程。

搭建拍照功能

为了更好地管理拍照流程,我们将功能封装到一个自定义的PHCaptureController类中,负责完成会话的搭建、控制与拍照等操作。

整个拍照流程主要包括以下几个步骤:

  1. 配置 AVCaptureSession。
  2. 添加摄像头输入。
  3. 添加照片输出。
  4. 启动与停止会话。
  5. 处理拍照请求与回调。

下面我们来逐一实现。

1. 类的基本结构

我们先来创建一个PHCaptureController类,大致结构如下:

//
//  PHCaptureController.swift
//  PHCaptureExample
//
//  Created by Louis on 2025/4/23.
// 负责会话控制及拍照操作import UIKit
import AVFoundationclass PHCaptureController:NSObject {/// 会话private let session = AVCaptureSession()/// 输出private let photoOutput = AVCapturePhotoOutput()/// 输入private var captureDeviceInput: AVCaptureDeviceInput?/// 队列private let sessionQueue = DispatchQueue(label: "com.example.captureSession")/// 代理weak var delegate: PHCaptureProtocol?init() {}/// 配置会话func setupConfigureSession() { }/// 启动会话func startSession() { }/// 停止会话func stopSession() { }/// 拍照func takePhoto() { }}

可以看到我们在类的内部维护了:

  1. session:采集会话。
  2. photoOutput:用于拍照的会话输出。
  3. captureDeviceInput:当前使用的会话输入。
  4. sessionQueue:串行队列,保证采集相关操作的线程安全。
  5. delegate:代理,负责错误和结果的回调。

PHCaptureProtocol 实现如下:

//
//  PHCaptureProtocol.swift
//  PHCaptureExample
//
//  Created by Louis on 2025/4/23.
//import Foundation
import UIKitprotocol PHCaptureProtocol:NSObjectProtocol {/// 发生错误func captureError(_ error: Error)/// 拍照回调func capturePhoto(_ image: UIImage)}

包含了:

  1. captureError:发生错误的回调方法。
  2. capturePhoto:拍照结果的回调方法。

2. 配置采集会话

我们在 setupConfigureSession 方法中需要完成三个操作:

  1. 设置会话预设。
  2. 添加摄像头输入。
  3. 添加照片输出。

并且保证这些操作需要在会话 beginConfiguration 与 commitConfiguration 方法之间执行。

    /// 配置会话func setupConfigureSession() {session.beginConfiguration()// 1.设置会话预设setupSessionPreset()// 2.设置会话输入if !setupSessionInput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))return}// 3.设置会话输出if !setupSessionOutput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))return}session.commitConfiguration()}

2.1 设置会话预设

就是设置分辨率,比如高质量照片。

    /// 设置会话话预设private func setupSessionPreset() {session.sessionPreset = .photo}

2.2 设置会话输入

添加摄像头设备,并包装一层 AVCaptureDeviceInput 添加到会话中。

    /// 设置会话输入private func setupSessionInput() -> Bool {guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video,position: .back) else { return false }do {captureDeviceInput = try AVCaptureDeviceInput(device: device)if session.canAddInput(captureDeviceInput!) {session.addInput(captureDeviceInput!)return true} else {return false}} catch {delegate?.captureError(error)return false}}
  1. 首先要确保获取到了摄像头设备。
  2. 检测输入是否可以被添加到会话。
  3. 如果出现错误则直接回调。

2.3 设置会话输出

将图片输出添加到会话。

    /// 设置会话输出private func setupSessionOutput() -> Bool {if session.canAddOutput(photoOutput) {session.addOutput(photoOutput)return true} else {return false}}

添加输出时也要检测是否可以被添加到会话。

3. 会话的启动与停止

我们使用单独的串行队列来管理会话的启动与停止,避免出现线程问题。

    /// 启动会话func startSession() {sessionQueue.async {if !self.session.isRunning {self.session.startRunning()}}}/// 停止会话func stopSession() {sessionQueue.async {if self.session.isRunning {self.session.stopRunning()}}}

4. 拍照

在拍照的方法里设置拍照参数和代理,并实现 AVCapturePhotoCaptureDelegate 的代理方法。

    /// 拍照func takePhoto() {let settings = AVCapturePhotoSettings()settings.flashMode = .autosettings.isHighResolutionPhotoEnabled = truesessionQueue.async {self.photoOutput.capturePhoto(with: settings, delegate: self)}}//MARK: - AVCapturePhotoCaptureDelegatefunc photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: (any Error)?) {if let error = error {delegate?.captureError(error)return}guard let imageData = photo.fileDataRepresentation() else {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1003, userInfo: [NSLocalizedDescriptionKey: "Failed to get image data"]))return}guard let image = UIImage(data: imageData) else {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1004, userInfo: [NSLocalizedDescriptionKey: "Failed to create image"]))return}delegate?.capturePhoto(image)}

整个拍照的核心功能就已经搭建完成了,接下来我们只需要添加画面预览,就可以调用这个类来实现完整的拍照功能了。

5. 画面预览

为了能够实时显示摄像头画面,我们需要一个专门的预览视图。

在 AVFoundation 中,通常通过 AVCaptureVideoPreviewLayer 来实现摄像头画面渲染,因此我们可以自定义一个简单的 PHPreviewView。

import UIKit
import AVFoundationclass PHPreviewView: UIView {override class var layerClass: AnyClass {return AVCaptureVideoPreviewLayer.self}private var previewLayer: AVCaptureVideoPreviewLayer {return layer as! AVCaptureVideoPreviewLayer}func setSession(_ session: AVCaptureSession) {previewLayer.session = sessionpreviewLayer.videoGravity = .resizeAspectFill}
}

使用拍照功能

接下来我们只需要在视图控制器中,非常简单的接入一下拍照的控制器和预览视图就可以使用拍照功能咯。

import UIKit
import AVFoundationclass ViewController: UIViewController,PHCaptureProtocol {/// 拍照控制器let captureController = PHCaptureController()/// 预览视图let previewView = PHPreviewView()override func viewDidLoad() {super.viewDidLoad()captureController.setupConfigureSession()captureController.delegate = selfcaptureController.startSession()previewView.setSession(captureController.session)view.addSubview(previewView)previewView.frame = view.bounds}//MARK: - PHCaptureProtocolfunc captureError(_ error: any Error) {}func capturePhoto(_ image: UIImage) {}}

结语

在本篇中,我们基于 AVFoundation 框架,搭建了一个基本的拍照功能实现流程:

包括配置 AVCaptureSession、添加 AVCaptureDeviceInput 和 AVCapturePhotoOutput、设置预览视图 PHPreviewView,并通过 AVCapturePhotoCaptureDelegate 拿到照片数据,为后续保存、展示、处理照片打下了基础。

需要特别注意的是:在正式使用相机功能之前,务必进行权限申请和检测。

如果未申请或未获得相机访问权限,直接启动 AVCaptureSession 会导致应用崩溃或黑屏。

通常,我们会在 App 启动或功能入口时,通过 AVCaptureDevice.requestAccess(for: .video) 请求权限,并根据权限结果决定是否继续初始化采集相关流程。

到这里,我们已经完成了拍照功能的基本搭建,感谢大家的阅读。

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/77370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git tag使用场景和实践

背景 每次上线一个迭代,为了区分本次代码的分支是哪个迭代的commit,可以给分支打上tag,这样利于追踪分支所属迭代,如果devops没有自动给分支打tag,需要自己来打 操作 1.查看当前tag git tag2.给分支打tag git tag…

从零开始掌握Linux数据流:管道与重定向完全指南

全文目录 1 知识背景与核心概念1.1 操作系统的输入输出模型1.2 Shell 的中间人角色 2 重定向技术深度解析2.1 输出重定向2.1.1 覆盖写2.1.2 追加写2.1.3 错误重定向2.1.4 同时重定向 stdout 和 stderr 2.2 输入重定向2.2.1 文件作为输入源2.2.2 Here Document(多行输…

aws(学习笔记第三十九课) iot-core

文章目录 aws(学习笔记第三十九课) iotcore(Internet Of Thing)学习内容:1. 整体架构1.1 代码链接1.2 整体架构(概要)1.3 整体架构(详细 )2. 代码解析2.1 创建`IOT thing`2.2 创建`AWS IOT certificate`证书2.2.1 创建`lambda`需要的`role`2.2.2 创建`lambda`2.2.3 `lambd…

国家新政鼓励游戏出海,全球化安全威胁如何解

本文作者:腾讯宙斯盾DDoS防护团队 01 政策红利释放:游戏出海升级为“国家战略工程” 01 4月21日,国务院新闻办公室发布《加快推进服务业扩大开放综合试点工作方案》,释放了一个信号:首次将“游戏出海”列为战略级工程&…

MobX 在 React 中的使用:状态管理的新选择

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…

Idea 配置 Git

1、下载Git 下载地址: Git - Downloading Package 2、win 打开 git bash ,配置邮箱和用户名 //配置邮箱 git config --global user.email "710419844qq.com" //配置全局用户名 git config --global user.name "smelodys" 3、ide…

Vue3 + OpenLayers 开发教程 (四) 样式配置与性能优化

1. 地图样式基础概念 1.1 什么是地图样式? 地图样式是决定地图要素(点、线、面)如何显示的重要配置。在 OpenLayers 中,样式主要包含以下几个核心组件: Fill(填充):控制面状要素的…

【Nacos-安全与限流机制健全06 】

文章目录 Nacos安全机制介绍Nacos代码实现Nacos限流机制Nacos限流的代码实现 Nacos安全机制介绍 一、Nacos安全控制机制 Nacos 提供了多种安全控制机制,以保证服务和配置的访问安全: 身份验证 (Authentication) Nacos 支持用户身份验证来防止未授权的访…

自建开源远程协助服务RustDesk —— 筑梦之路

开源项目 # 服务端https://github.com/rustdesk/rustdesk-server.git# 客户端https://github.com/rustdesk/rustdesk.git 搭建服务端 需要使用的端口、协议 hbbs - RustDesk ID 注册服务器 hbbr - RustDesk 中继服务器默认情况下,hbbs 监听 21115(tcp) , 21…

Jmeter中同步定时器使用注意点

1.设置数量不可大于总线程数量,不然会一直等待 2.设置数量必须与总线程数量成整数倍数,不然还是要一直等。 3.当配置的数量小于线程数时,最好把循环打开,避免最后一次未准备好的线程数量达不到并发数。

作为高速通道光纤传输模式怎么理解以及到底有哪些?

光纤的传输模式主要取决于光纤的结构(如纤芯直径和折射率分布),不同模式对应光波在光纤中传播的不同路径和电磁场分布。以下是光纤传输模式的主要分类及特点: 1. 单模光纤(Single-Mode Fiber, SMF) 核心特点: 纤芯直径极小(通常为 8-10微米),仅允许光以单一模式(…

小程序Npm package entry file not found?

修改依赖包的入口文件 看是不是cjs,小程序不支持cjs

Android HAL HIDL

1 Android HAL HIDL 1.1 Android中查看有哪些HIDL HAL HIDL是Treble Interface的一部分。 adb root adb shell # lshal 1.2 Android打印C调用栈 #include <utils/CallStack.h> 在需要打印的地方加如下的定义。 android::CallStack stack("oem"); logcat | g…

【AI 加持下的 Python 编程实战 2_11】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(下)

&#xff08;接 上篇&#xff09; 5 复盘与 Copilot 的交互过程 前面两篇文章分别涵盖了扫雷游戏的问题分解和代码实现过程&#xff0c;不知道各位是否会有代码一气呵成的错觉&#xff1f;实际上&#xff0c;为了达到最终效果&#xff08;如下所示&#xff09;&#xff0c;我…

游戏状态管理:用Pygame实现场景切换与暂停功能

游戏状态管理:用Pygame实现场景切换与暂停功能 在开发游戏时,管理游戏的不同状态(如主菜单、游戏进行中、暂停等)是非常重要的。这不仅有助于提升玩家的游戏体验,还能使代码结构更加清晰。本文将通过一个简单的示例,展示如何使用Pygame库来实现游戏中的场景切换和暂停功…

Java后端开发day36--源码解析:HashMap

&#xff08;以下内容均来自上述课程&#xff09; 1. HashMap&#xff08;一&#xff09; 底层&#xff1a;数组链表红黑树 1.1 前提准备 查看源码&#xff1a;选中HashMap–ctrlB 小细节&#xff1a;快捷键ctrlf12–跳出目录结构 蓝色圆圈&#xff1a;class 证明是类名粉…

RT-Thread学习笔记(四)

RT-Thread学习笔记 线程间同步信号量信号量的使用和管理动态创建信号量静态创建信号量获取信号量信号量同步实列互斥量互斥量的使用和管理互斥量动态创建互斥量静态创建互斥量获取和释放互斥量实例事件集事件集的使用和管理动态创建事件集静态初始化事件集发送和接收事件事件集…

element ui el-col的高度不一致导致换行

问题&#xff1a;ell-col的高度不一致导致换行&#xff0c;刷新后审查el-col的高度一致 我这边是el-col写的span超过了24&#xff0c;自行换行&#xff0c;测试发现初次进入里面的高度渲染的不一致&#xff0c;有的是51px有的是51.5px 问题原因分析 Flex布局换行机制 Elemen…

现代化Android开发:Compose提示信息的最佳封装方案

在 Android 开发中&#xff0c;良好的用户反馈机制至关重要。Jetpack Compose 提供了现代化的 UI 构建方式&#xff0c;但提示信息(Toast/Snackbar)的管理往往显得分散。本文将介绍如何优雅地封装提示信息&#xff0c;提升代码可维护性。 一、基础封装方案 1. 简单 Snackbar …

【C++语法】类和对象(2)

4.类和对象&#xff08;2&#xff09; 文章目录 4.类和对象&#xff08;2&#xff09;类的六个默认成员函数(1)构造函数&#xff1a;构造函数特点含有缺省参数的构造函数构造函数特点&#xff08;续&#xff09;注意事项构造函数补充 前面总结了有关对象概念&#xff0c;对比 C…