在项目开发中,从媒体库中选择图片或使用相机拍摄图片是一个极为普遍的需求。通常,我们使用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的强大功能,我们能够打造出更具创意和个性化的拍照应用,满足不同用户的期望和需求。