iOS:自定义相机
Page content
iOS中的自定义相机
前言
虽然系统相机功能多且强大,但很多项目中,系统相机的功能不切合需求,不可避免的用到自定义相机,所以我们来对相机做一个自定义。
自定义相机,使用的是Apple提供的AVFoundation框架;
AVFoundation是一个功能非常强大且全面的框架,包括但不限于音频,视频,录制,播放,编码解码等等,它提供了相对底层的API,允许我们自定义这些功能。
界面
以下截图,为自定义相机的界面(UI)部分,其中手势添加于containerView上:
实现
设计
我们将设备相关分拆为设备类PhotoTakeDevice,控制器类PhotoTakeViewController则负责处理逻辑和功能。
这里我们需要:
- 获取输入设备AVCaptureDevice,根据device创建设备输入AVCaptureDeviceInput;
- 使用AVCaptureStillImageOutput作为照片输出;
- 创建AVCaptureSession并关联设备输入和输出,它负责输入和输出设置之间的数据传递;
- 创建AVCaptureVideoPreviewLayer作为预览层,使用户实时获取摄像头摄入的信息;
- 至于设置闪光灯,聚焦,切换摄像头,拍摄的调用及回调,在代码中有详细的注释。
设备类PhotoTakeDevice
接口:
~~~objective-c
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface PhotoTakeDevice : NSObject
/// AVCaptureSession 负责输入和输出设置之间的数据传递
@property (nonatomic, strong) AVCaptureSession * session;
/// AVCaptureDeviceInput 负责从AVCaptureDevice获得输入数据
@property (nonatomic, strong) AVCaptureDeviceInput * deviceInput;
/// AVCaptureStillImageOutput 照片输出流
@property (nonatomic, strong) AVCaptureStillImageOutput * stillImageOutPut;
/// AVCaptureVideoPreviewLayer 拍摄预览图层
@property (nonatomic, strong) AVCaptureVideoPreviewLayer * videoPreviewLayer;
/**
* 配置预览层,将视频预览层添加到界面中
*/
- (void)configPreviewLayerInView:(UIView *)view belowView:(UIView *)belowView;
/**
* 开启会话
*/
- (void)deviceStart;
/**
* 停止会话
*/
- (void)deviceStop;
/**
* 设置闪光灯模式
*
* @param flashMode 闪光灯模式
*/
- (void)setFlashMode:(AVCaptureFlashMode )flashMode;
/**
* 获取闪光灯状态
*/
- (void)getFlashMode:(void(^)(BOOL flashAvailable, AVCaptureFlashMode flashMode))flashModeCallback;
/**
* 点按手势,点按时聚焦
*/
- (void)tapScreenPoint:(CGPoint)point;
/**
* 切换摄像头
*/
- (void)cameraToggle;
/**
* 拍照
*/
- (void)cameraTakePhotoIsSave:(BOOL)isSaveAlbum Callback:(void(^)(UIImage * image, NSData * imgData, NSError * error))photoTokenCallback;
@end
~~~
属性懒加载:
~~~objective-c
#pragma mark - Lazy Loading
- (AVCaptureSession *)session {
if (!_session) {
/// 初始化会话
_session = [[AVCaptureSession alloc] init];
/// 设置分辨率
if ([_session canSetSessionPreset:AVCaptureSessionPresetPhoto]) {
_session.sessionPreset = AVCaptureSessionPresetPhoto;
}
}
return _session;
}
- (AVCaptureDeviceInput *)deviceInput {
if (!_deviceInput) {
/// 获得输入设备 取得后置摄像头
AVCaptureDevice * captureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
if (!captureDevice) { NSLog(@"取得后置摄像头出错"); }
/// 根据输入设备初始化设备输入对象,用于获得输入数据
NSError *error=nil;
_deviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
if (error) { NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription); }
}
return _deviceInput;
}
- (AVCaptureStillImageOutput *)stillImageOutPut {
if (!_stillImageOutPut) {
/// 初始化设备输出对象,用于获得输出数据
_stillImageOutPut=[[AVCaptureStillImageOutput alloc]init];
NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG}; //输出设置
[_stillImageOutPut setOutputSettings:outputSettings];
}
return _stillImageOutPut;
}
- (AVCaptureVideoPreviewLayer *)videoPreviewLayer {
if (!_videoPreviewLayer) {
/// 创建视频预览层,用于实时展示摄像头状态
_videoPreviewLayer =[[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
/// 填充模式
_videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
}
return _videoPreviewLayer;
}
~~~
通知:
~~~objective-c
#pragma mark - 通知
/**
* 给输入设备添加通知
*/
- (void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice {
//注意添加区域改变捕获通知必须首先设置设备允许捕获
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
captureDevice.subjectAreaChangeMonitoringEnabled=YES;
}];
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//捕获区域发生改变
[notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
- (void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice {
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
* 移除所有通知
*/
- (void)removeNotification {
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
- (void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession {
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//会话出错
[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}
/**
* 设备连接成功
*
* @param notification 通知对象
*/
- (void)deviceConnected:(NSNotification *)notification {
NSLog(@"设备已连接...");
}
/**
* 设备连接断开
*
* @param notification 通知对象
*/
- (void)deviceDisconnected:(NSNotification *)notification {
NSLog(@"设备已断开.");
}
/**
* 捕获区域改变
*
* @param notification 通知对象
*/
- (void)areaChange:(NSNotification *)notification {
NSLog(@"捕获区域改变...");
}
/**
* 会话出错
*
* @param notification 通知对象
*/
- (void)sessionRuntimeError:(NSNotification *)notification {
NSLog(@"会话发生错误.");
}
~~~
初始化:
~~~objective-c
- (instancetype)init {
if (self = [super init]) {
[self configSession];
}
return self;
}
- (void)dealloc {
[self removeNotification];
}
- (void)configSession {
// 将设备输入对象添加到会话中
if ([self.session canAddInput:self.deviceInput]) {
[self.session addInput:self.deviceInput];
}
// 将设备输出对象添加到会话中
if ([self.session canAddOutput:self.stillImageOutPut]) {
[self.session addOutput:self.stillImageOutPut];
}
AVCaptureDevice * captureDevice = [self.deviceInput device];
[self addNotificationToCaptureDevice:captureDevice];
}
~~~
PublicMethods:
~~~objective-c
#pragma mark - Public
/**
* 配置预览层,将视频预览层添加到界面中
*/
- (void)configPreviewLayerInView:(UIView *)view belowView:(UIView *)belowView {
CALayer *layer = view.layer;
layer.masksToBounds=YES;
self.videoPreviewLayer.frame=layer.bounds;
[layer insertSublayer:self.videoPreviewLayer below:belowView.layer];
}
/**
* 开启会话
*/
- (void)deviceStart {
[self.session startRunning];
}
/**
* 停止会话
*/
- (void)deviceStop {
[self.session stopRunning];
}
/**
* 设置闪光灯模式
*
* @param flashMode 闪光灯模式
*/
- (void)setFlashMode:(AVCaptureFlashMode )flashMode {
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFlashModeSupported:flashMode]) {
[captureDevice setFlashMode:flashMode];
}
}];
}
/**
* 设置闪光灯按钮状态
*/
- (void)getFlashMode:(void(^)(BOOL flashAvailable, AVCaptureFlashMode flashMode))flashModeCallback {
AVCaptureDevice *captureDevice = [self.deviceInput device];
AVCaptureFlashMode flashMode = captureDevice.flashMode;
flashModeCallback([captureDevice isFlashAvailable], flashMode);
}
/**
* 点按手势,点按时聚焦
*/
- (void)tapScreenPoint:(CGPoint)point {
//将UI坐标转化为摄像头坐标
CGPoint cameraPoint = [self.videoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
/**
* 切换摄像头
*/
- (void)cameraToggle {
AVCaptureDevice *currentDevice=[self.deviceInput device];
AVCaptureDevicePosition currentPosition=[currentDevice position];
[self removeNotificationFromCaptureDevice:currentDevice];
AVCaptureDevice *toChangeDevice;
AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
toChangePosition=AVCaptureDevicePositionBack;
}
toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
[self addNotificationToCaptureDevice:toChangeDevice];
//获得要调整的设备输入对象
AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
//改变会话的配置前一定要先开启配置,配置完成后提交配置改变
[self.session beginConfiguration];
//移除原有输入对象
[self.session removeInput:self.deviceInput];
//添加新的输入对象
if ([self.session canAddInput:toChangeDeviceInput]) {
[self.session addInput:toChangeDeviceInput];
self.deviceInput=toChangeDeviceInput;
}
//提交会话配置
[self.session commitConfiguration];
}
/**
* 拍照
*/
- (void)cameraTakePhotoIsSave:(BOOL)isSaveAlbum Callback:(void(^)(UIImage * image, NSData * imgData, NSError * error))photoTokenCallback {
//根据设备输出获得连接
AVCaptureConnection *captureConnection=[self.stillImageOutPut connectionWithMediaType:AVMediaTypeVideo];
//根据连接取得设备输出的数据
__weak typeof(self) weakSelf = self;
[self.stillImageOutPut captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
__strong typeof(self) strongSelf = weakSelf;
if (imageDataSampleBuffer) {
NSData * imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage * image=[UIImage imageWithData:imageData];
if (isSaveAlbum) {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
[strongSelf.session stopRunning];
photoTokenCallback(image, imageData, error);
} else {
photoTokenCallback(nil, nil, error);
}
}];
}
~~~
PrivateMethods:
~~~objective-c
#pragma mark - 私有方法
/**
* 取得指定位置的摄像头
*
* @param position 摄像头位置
*
* @return 摄像头设备
*/
- (AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position {
NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *camera in cameras) {
if ([camera position]==position) {
return camera;
}
}
return nil;
}
/**
* 改变设备属性的统一操作方法
*
* @param propertyChange 属性改变操作
*/
- (void)changeDeviceProperty:(void(^)(AVCaptureDevice *captureDevice))propertyChange {
AVCaptureDevice *captureDevice= [self.deviceInput device];
NSError *error;
//注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
if ([captureDevice lockForConfiguration:&error]) {
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
} else {
NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
}
}
/**
* 设置聚焦模式
*
* @param focusMode 聚焦模式
*/
- (void)setFocusMode:(AVCaptureFocusMode )focusMode {
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:focusMode];
}
}];
}
/**
* 设置曝光模式
*
* @param exposureMode 曝光模式
*/
- (void)setExposureMode:(AVCaptureExposureMode)exposureMode {
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:exposureMode];
}
}];
}
/**
* 设置聚焦点
*
* @param point 聚焦点
*/
- (void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point {
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
~~~
控制器PhotoTakeViewController
接口:
~~~objective-c
#import <UIKit/UIKit.h>
@protocol PhotoTakeDelegate <NSObject>
- (void)photoTokenImage:(UIImage *)image ImgData:(NSData *)imgData ErrorMsg:(NSString *)errorMsg;
@end
@interface PhotoTakeViewController : UIViewController
@property (nonatomic, weak) id<PhotoTakeDelegate> delegate;
@property (nonatomic, assign) BOOL isSaveAlbum;
@end
~~~
匿名类别:
~~~objective-c
#import "PhotoTakeViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "PhotoTakeDevice.h"
@interface PhotoTakeViewController ()
{
/// 闪光相关灯按钮
__weak IBOutlet UIButton * _flashLightAutoButton;
__weak IBOutlet UIButton * _flashLightOnButton;
__weak IBOutlet UIButton * _flashLightOffButton;
/// 拍照按钮
__weak IBOutlet UIButton * _takePicButton;
/// 取消按钮
__weak IBOutlet UIButton * _cancelButton;
/// 使用照片按钮
__weak IBOutlet UIButton * _usePicButton;
/// 图像预览层
__weak IBOutlet UIView * _containerView;
/// 聚焦光标
__weak IBOutlet UIImageView * _focusCursor;
/// 拍摄拾取的照片
NSData * _imageData;
UIImage * _image;
}
/// 设备类
@property (nonatomic, strong) PhotoTakeDevice * photoTakeDevice;
@end
~~~
LifeCycle:
~~~objective-c
- (void)viewDidLoad {
[super viewDidLoad];
_photoTakeDevice = [[PhotoTakeDevice alloc] init];
[_photoTakeDevice configPreviewLayerInView:_containerView belowView:_focusCursor];
[self setFlashModeButtonStatus];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_photoTakeDevice deviceStart];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_photoTakeDevice deviceStop];
}
/**
* 设置闪光灯按钮状态
*/
- (void)setFlashModeButtonStatus
{
[_photoTakeDevice getFlashMode:^(BOOL flashAvailable, AVCaptureFlashMode flashMode) {
if(!flashAvailable) {
_flashLightAutoButton.hidden=YES;
_flashLightOnButton.hidden=YES;
_flashLightOffButton.hidden=YES;
return ;
}
_flashLightAutoButton.hidden=NO;
_flashLightOnButton.hidden=NO;
_flashLightOffButton.hidden=NO;
_flashLightAutoButton.enabled=YES;
_flashLightOnButton.enabled=YES;
_flashLightOffButton.enabled=YES;
switch (flashMode) {
case AVCaptureFlashModeAuto:
_flashLightAutoButton.enabled=NO;
break;
case AVCaptureFlashModeOn:
_flashLightOnButton.enabled=NO;
break;
case AVCaptureFlashModeOff:
_flashLightOffButton.enabled=NO;
break;
default:
break;
}
}];
}
/**
* 设置聚焦光标位置
*
* @param point 光标位置
*/
- (void)setFocusCursorWithPoint:(CGPoint)point {
_focusCursor.center=point;
_focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
_focusCursor.alpha=1.0;
[UIView animateWithDuration:1.0 animations:^{
_focusCursor.transform=CGAffineTransformIdentity;
} completion:^(BOOL finished) {
_focusCursor.alpha=0;
}];
}
~~~
响应:
~~~objective-c
/**
* 退出
*/
- (IBAction)exitTakeCamera {
[self dismissViewControllerAnimated:true completion:nil];
}
/**
* 闪光灯切换
*/
- (IBAction)flashLightChanged:(UIButton *)sender {
switch (sender.tag) {
case 0: //关闭闪光灯
{
[_photoTakeDevice setFlashMode:AVCaptureFlashModeOff];
[self setFlashModeButtonStatus];
break;
}
case 1: //打开闪光灯
{
[_photoTakeDevice setFlashMode:AVCaptureFlashModeOn];
[self setFlashModeButtonStatus];
break;
}
case 2: //自动闪光灯开启
{
[_photoTakeDevice setFlashMode:AVCaptureFlashModeAuto];
[self setFlashModeButtonStatus];
break;
}
}
}
/**
* 摄像头切换
*/
- (IBAction)toggleCamera:(UIButton *)sender {
[_photoTakeDevice cameraToggle];
[self setFlashModeButtonStatus];
}
/**
* 拍照
*/
- (IBAction)takePicture {
[_photoTakeDevice cameraTakePhotoIsSave:false Callback:^(UIImage *image, NSData *imgData, NSError *error) {
if (error) {
BOOL existDelegate = [self.delegate respondsToSelector:@selector(photoTokenImage:ImgData:ErrorMsg:)];
if (!existDelegate)
{
return ;
}
[self.delegate photoTokenImage:image ImgData:imgData ErrorMsg:error.localizedDescription];
[self exitTakeCamera];
} else {
_image = image;
_imageData = imgData;
_takePicButton.hidden = true;
_cancelButton.hidden = false;
_usePicButton.hidden = false;
}
}];
}
/**
* 拍照后使用照片
*/
- (IBAction)usePicture {
if ([self.delegate respondsToSelector:@selector(photoTokenImage:ImgData:ErrorMsg:)]) {
[self.delegate photoTokenImage:_image ImgData:_imageData ErrorMsg:nil];
[self exitTakeCamera];
}
}
/**
* 拍照后取消
*/
- (IBAction)cancelPicture {
_takePicButton.hidden = false;
_cancelButton.hidden = true;
_usePicButton.hidden = true;
[_photoTakeDevice deviceStart];
}
/**
* 点按手势,点按时聚焦
*/
- (IBAction)tapScreen:(UITapGestureRecognizer *)tapGesture {
CGPoint point= [tapGesture locationInView:_containerView];
[self setFocusCursorWithPoint:point];
[_photoTakeDevice tapScreenPoint:point];
}
~~~
使用这个封装好的相机控制器进行摄像
~~~objective-c
#import "ViewController.h"
#import "PhotoTakeViewController.h"
@interface ViewController ()
<PhotoTakeDelegate>
{
__weak IBOutlet UIImageView *_imgView;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)toLyraPhotoTakeVC:(id)sender {
PhotoTakeViewController * cameraVC = [[PhotoTakeViewController alloc] init];
cameraVC.delegate = self;
[self presentViewController:cameraVC animated:true completion:nil];
}
- (void)photoTokenImage:(UIImage *)image ImgData:(NSData *)imgData ErrorMsg:(NSString *)errorMsg {
if (errorMsg) {
NSLog(@"%@", errorMsg);
} else {
_imgView.image = image;
}
}
@end
~~~