媒体捕捉-拍照
引言
在项目开发中,从媒体库中选择图片或使用相机拍摄图片是一个极为普遍的需求。通常,我们使用UIImagePickerController来实现单张图片选择或启动相机拍照。整个拍照过程由UIImagePickerController内部实现,无需我们关心细节,只需实现相应的回调以获取所需的图片。
然而,你或许好奇拍照的底层实现是什么样的,是否能够自己调用手机摄像头完成拍照功能?这正是AVFoundation发挥作用的地方。AVFoundation是一个强大的框架,提供了访问音视频的底层功能,包括相机和麦克风。通过AVFoundation,我们能够直接与设备的摄像头进行交互,实现自定义的拍照功能,为我们提供更大的灵活性和控制权。
在接下来的内容中,我们将深入探讨AVFoundation的拍照功能,了解如何通过这一框架自定义拍照过程,从而更好地满足项目的需求。
介绍

首先介绍一下主要类:
AVCaptureDevice:捕捉设备。相对手机而言,它是摄像头,麦克风等物理设备定义了一个接口。
AVCaptureDeviceInput:捕捉设备的输入。捕捉设备不能直接添加到会话中,需要封装在AVCaptureDeviceInput中再进行添加。
AVCaptureSession:捕捉会话。捕获会话是整个功能的核心,有用链接输入和输出,配置捕捉环境。
AVCaptureOutput:捕捉的输出。AVCaptureOutput是一个抽象类,用于捕捉到的数据进行输出,不能直接使用,通常我们是使用它的子类比如AVCapturePhotoOutput,AVCaptureMovieFileOutput等等。
另外还有一个比较重要的类AVCaptureVideoPreviewLayer它提供了画面的预览功能。
基本使用
这里面演示一下我们使用的最小单元,也就是一个拍照功能的最核心代码:
- 创建会话
AVCaptureSession * session = [[AVCaptureSession alloc] init];
- 创建捕捉及输入并添加到会话
AVCaptureDevice * cameraDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError * error;
AVCaptureDeviceInput * cameraInput = [AVCaptureDeviceInput deviceInputWithDevice:cameraDevice error:&error];
if ([session canAddInput:cameraInput]) {[session addInput:cameraInput];
}
- 创建输出并添加到会话
AVCapturePhotoOutput * photoOutput = [[AVCapturePhotoOutput alloc] init];
if ([session canAddOutput:photoOutput]) {[session addOutput:photoOutput];
}
上面的代码创建了一个拍摄图片最基础的框架。创建会话,将设备捕捉到的数据添加到会话,再将数据进行输出静态图片。启动会话,视频数据流就可以开始传输了。真正使用起来会比上面的示例代码复杂一点,但核心内容仍然是这几个步骤。
完整示例
这一部分内容比较多,为了更容易理解,我们将对应的功能分散到不同的类中。
PHCameraController:捕捉核心类。负责启动会话处理输入和输出。
PHPreviewView:预览图层。负责渲染预览画面。
而我们首先把注意力集中在PHCameraController上面。
捕捉核心类
配置会话
我们先来定义一个最小的功能,只声明一些拍照所需要的属性及方法。.h中对外暴漏的属性和接口如下:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN@interface PHCameraController : NSObject@property(nonatomic,strong,readonly)AVCaptureSession * captureSession;///设置会话
- (BOOL)setupSession:(NSError **)error;
///开始会话
- (void)startSession;
///停止会话
- (void)stopSession;///拍照
- (void)capturePhoto;
@end
NS_ASSUME_NONNULL_END
我们只定义了最基本的功能,设置会话,启动会话,停止会话和拍照。
接下来我们来看一下它的.m文件中的内容。
首先是扩展中的私有属性:
#import "PHCameraController.h"
@interface PHCameraController ()<AVCapturePhotoCaptureDelegate>
///会话启动队列
@property(nonatomic,strong)dispatch_queue_t videoQueue;
///会话
@property(nonatomic,strong)AVCaptureSession * captureSession;
///图片输出
@property(nonatomic,strong)AVCapturePhotoOutput * photoOutput;@end
在这里定义了一个自定义的队列,一个会话session和AVCaptureOutput的子类AVCapturePhotoOutput,专门用于输出静态图片。
再看一下它的接口实现,首先是配置会话相关的代码:
@implementation PHCameraController
- (BOOL)setupSession:(NSError *__autoreleasing _Nullable *)error{self.captureSession = [[AVCaptureSession alloc] init];self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;//获取默认摄像头AVCaptureDevice * videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];if (videoInput) {if ([self.captureSession canAddInput:videoInput]) {[self.captureSession addInput:videoInput];self.activeVideoInput = videoInput;}}else{return NO;}//设置图片输出self.photoOutput = [[AVCapturePhotoOutput alloc] init];NSDictionary * setDic = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};AVCapturePhotoSettings * settings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];[self.photoOutput capturePhotoWithSettings:settings delegate:self];if ([self.captureSession canAddOutput:self.photoOutput]) {[self.captureSession addOutput:self.photoOutput];}self.videoQueue = dispatch_queue_create("com.panghu.VideoQueue", NULL);return YES;
}
@end
这和上面提到的最核心的代码实现几乎一致,创建会话添加会话输入和输出。
启动会话
再进行捕捉之前,需要先启动会话,也就是让会话处于准备捕捉静态图片的状态。
相关代码试下如下:
- (void)startSession{if (![self.captureSession isRunning]) {dispatch_async(self.videoQueue, ^{[self.captureSession startRunning];});}
}
开始捕捉静态图片
会话启动之后,我们就可以调用捕捉图片的方法来进行图片的捕捉:
- (void)capturePhoto{NSDictionary * setDic = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};AVCapturePhotoSettings * settings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];self.photoSettings = settings;[self.photoOutput capturePhotoWithSettings:self.photoSettings delegate:self];
}
开始捕捉前,我们可以自定义捕捉静态图片的一些配置参数,比如
AVVideoCodecKey:图片类型。
AVVideoPixelAspectRatioKey:像素宽高比。
AVVideoCompressionPropertiesKey:压缩属性。
AVVideoWidthKey:宽。
AVVideoHeightKey:高。
调用拍照方法后会回调AVCapturePhotoCaptureDelegate中的代理方法:
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error{NSData * data = photo.fileDataRepresentation;UIImage * image = [UIImage imageWithData:data];
}
其中image即是我们想要的静态图片。
结束会话
使用完该功能后,退出拍照功能,需要停止会话:
- (void)stopSession{if ([self.captureSession isRunning]) {dispatch_async(self.videoQueue, ^{[self.captureSession stopRunning];});}
}
画面预览类
我们在这里定义一个专门用作画面预览的视图PHPreviewView。
可以在对应的实例中创建一个AVCaptureVideoPreviewLayer用来渲染预览画面,也可以用另外一种更优雅的方式,通过重写LayerClass方法返回一个AVCaptureVideoPreviewLayer。
.h中的代码如下:
@interface PHPreviewView : UIView
@property(nonatomic,strong)AVCaptureSession * session;
@end
只有一个捕捉会话对象。
.m中的实现如下:
#import "PHPreviewView.h"
@implementation PHPreviewView
+ (Class)layerClass{return [AVCaptureVideoPreviewLayer class];
}
- (void)setSession:(AVCaptureSession *)session{[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
- (AVCaptureSession *)session{return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
@end
通过重写session的set方法来将预览图层与捕捉会话相关联。
通过重写session的get方法来返回捕捉会话。
使用
在视图控制器ViewController中使用拍照功能。
首先声明捕捉的核心类及画面预览类:
@interface ViewController ()//画面预览view
@property(nonatomic,strong)PHPreviewView * previewView;
///相机控制
@property(nonatomic,strong)PHCameraController * controller;@end
添加画面预览视图:
- (void)viewDidLoad {[super viewDidLoad];[self setupView];
}- (void)setupView{[self addPreviewView];[self configController];
}//MARK:画面预览view
- (void)addPreviewView{self.previewView = [[PHPreviewView alloc] initWithFrame:self.view.bounds];[self.view addSubview: self.previewView];
}//MARK:配置相机控制器
- (void)configController{self.controller = [[PHCameraController alloc] init];NSError * error = nil;BOOL isSuccess = [self.controller setupSession:&error];if (isSuccess) {[self.previewView setSession:self.controller.captureSession];[self.controller startSession];}
}
接下来我们只需要在屏幕上添加一个按钮然后调用拍照方法即可完成静态图片的拍摄:
//MARK:拍照或录制
- (void)capture:(UIButton *)sender{[self.controller capturePhoto];
}
结语
在实现自定义拍照功能时,除了深入了解AVCapturePhotoSettings
等相关设置外,我们还需关注一系列前置和后续操作,以确保用户体验和功能完整性。
首先,我们必须在应用中请求摄像头和麦克风的权限,确保用户授权后才能正常使用这些设备。这是保护用户隐私的重要步骤,也是提供良好用户体验的前提。
另外,在成功捕获照片后,处理后续操作也至关重要。使用Photos
框架将照片存储到相册,以确保用户可以轻松地查看和分享他们的作品。这是一个贴近用户习惯的操作,增强了应用的实用性和友好性。
在整个拍照过程中,我们还有许多机会进行细致的自定义,例如实现自动聚焦、调整曝光、切换摄像头、开启闪光灯等功能。这些细节的处理不仅提升了用户体验,也使应用更具吸引力。
在开发过程中,不断探索和尝试这些功能,根据具体项目需求进行定制,将为用户带来更为出色的拍摄体验。通过充分利用AVFoundation的强大功能,我们能够打造出更具创意和个性化的拍照应用,满足不同用户的期望和需求。
相关文章:

媒体捕捉-拍照
引言 在项目开发中,从媒体库中选择图片或使用相机拍摄图片是一个极为普遍的需求。通常,我们使用UIImagePickerController来实现单张图片选择或启动相机拍照。整个拍照过程由UIImagePickerController内部实现,无需我们关心细节,只…...

Typora+PicGo+Gitee构建云存储图片
创建Gitee仓库 首先,打开工作台 - Gitee.com,自行注册一个账户 注册完后,新建一个仓库(记得仓库要开源) 然后创建完仓库后,鼠标移动到右上角头像位置,选择设置,并点击ÿ…...

【话题】ChatGPT等大语言模型为什么没有智能2
我们接着上一次的讨论,继续探索大模型的存在的问题。正巧CSDN最近在搞文章活动,我们来看看大模型“幻觉”。当然,本文可能有很多我自己的“幻觉”,欢迎批评指正。如果这么说的话,其实很容易得出一个小结论——大模型如…...

通过大量生物、地球、农业、气象、生态、环境科学领域中案例,一起探索如何优雅地使用大模型吧!
以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮,可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…...

slf4j+logback源码加载流程解析
slf4j绑定logback源码解析 Logger log LoggerFactory.getLogger(LogbackDemo.class);如上述代码所示,在项目中通常会这样创建一个Logger对象去打印日志。 然后点进去,会走到LoggerFactory的getILoggerFactory()方法,如下代码所示。 public …...
KVM虚拟机部署K8S重启后/etc/hosts内容丢失
前言 使用KVM开了虚拟机部署K8S,部署完成后重启,节点的pod等信息无法获取到,查看报错初步推测为域名解析失效,查看/etc/hosts后发现安装k8s时添加的内容全部消失 网上搜索一番之后发现了 如果直接修改 /etc/hosts 文件࿰…...
Redis使用场景(五)
Redis实战精讲-13小时彻底学会Redis 1.计数器 可以对 String 进行自增自减运算,从而实现计数器功能。 Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。 2.缓存 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略…...

【UnityShader入门精要学习笔记】(2)GPU流水线
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括: 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更,有始无终 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 上节复习GPU流水线顶点着色…...

CSS免费在线字体格式转换器 CSS @font-face 生成器
今天竟意外发现的一款免费的“网页字体生成器”,功能强大又好用~ 工具地址:https://transfonter.org/ 根据你设置生成后的文件预览: 支持TTF、OTF、WOFF、WOFF2 或 SVG字体格式转换生成,每个文件最大15MB。转换完成以后还会生成一…...

Codeium在IDEA里的3个坑
转载自Codeium在IDEA里的3个坑:无法log in,downloading language server和中文乱码_downloading codeium language server...-CSDN博客文章浏览阅读1.7w次,点赞26次,收藏47次。Codeium安装IDEA插件的3个常见坑_downloading codeiu…...
C-C++ 项目构建指南:如何使用 Makefile 提高开发效率
Makefile是一个常用的自动化构建工具,它可以为开发人员提供方便的项目构建方式。在C/C项目中,Makefile可以用来编译、链接和生成可执行文件。使用Makefile的好处是可以自动执行一系列命令,从而减少手动操作的复杂性和出错的可能性。此外&…...
基于SpringBoot的图书管理系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 🚀🚀🚀SpringBoot 阿博图书管理系…...
矩阵对角线遍历
Diagonal 2614. 对角线上的质数 class Solution {public int diagonalPrime(int[][] nums) {int n = nums....

【教程】Typecho Joe主题开启并修复壁纸相册不显示问题
转载请注明出处:小锋学长生活大爆炸[xfxuezhang.cn] 背景说明 Joe主题本身支持“壁纸”功能,其实就是相册。当时还在网上找了好久相册部署的开源项目,太傻了。 但是网上教程很少,一没说如何开启壁纸功能,二没说开启后为…...
MR混合现实情景实训教学系统在法律专业课堂上的应用
MR混合现实情景实训教学系统是一种将虚拟现实(VR)、增强现实(AR)相结合的先进技术。在法律教学课堂上,MR教学系统为学生模拟模拟法庭、案例分析等多种形式,让学生在实践中掌握法律知识,提高法律…...

车载 Android之 核心服务 - CarPropertyService 的VehicleHAL
前言: 本文是车载Android之核心服务-CarPropertyService的第二篇,了解一下CarPropertyService的VehicleHAL, 第一篇在车载 Android之 核心服务 - CarPropertyService 解析-CSDN博客,有兴趣的 朋友可以去看下。 本节介绍 AndroidAutomotiveOS中对于 Veh…...

年底了,准备跳槽的可以看看...
前两天跟朋友感慨,今年的铜九铁十、裁员、疫情导致好多人都没拿到offer!现在已经1月了,具体明年的金三银四只剩下两个月。 对于想跳槽的职场人来说,绝对要从现在开始做准备了。这时候,很多高薪技术岗、管理岗的缺口和市场需求也出…...
Bagging算法_随机森林Random_Forest
Bagging B a g g i n g Bagging Bagging是并行式集成学习方法最著名的代表,这个名字是由 B o o t s t r a p A G G r e g a t I N G Bootstrap AGGregatING BootstrapAGGregatING而来,顾名思义,该算法由 B o o s t s t r a p Booststrap Boos…...

物理与网络安全
物流环境安全 场地选择考虑抗震、承重、防火、防水、供电、空气调节、电磁防护、雷击及静电 场地因素: 自然灾害,社会因素(加油站、化工厂),配套条件(消防,交通,电力,…...

torch.meshgrid和np.meshgrid的区别
numpy中meshgrid: 把数组a当作一行,再根据数组b的长度扩充行。 把数组b当作一列,再根据数组a的长度扩充列。 torch中meshgrid: 把数组a当作一列,再根据数组b的长度扩充列。 把数组b当作一行,再根据数组a的…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...