首先导入一个头文件

    #import <AVFoundation/AVFoundation.h>

由于后面我们需要将拍摄好的照片写入系统相册中,所以我们在这里还需要导入一个相册需要的头文件

   #import <AssetsLibrary/AssetsLibrary.h>

导入头文件后我们需要创建几个相机必须的属性

    /**
* AVCaptureSession对象来执行输入设备和输出设备之间的数据传递
*/
@property (nonatomic, strong) AVCaptureSession* session;
/**
* 输入设备
*/
@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;
/**
* 照片输出流
*/
@property (nonatomic, strong) AVCaptureStillImageOutput* stillImageOutput;
/**
* 预览图层
*/
@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;

AVCaptureSession控制输入和输出设备之间的数据传递
AVCaptureDeviceInput调用所有的输入硬件。例如摄像头和麦克风
AVCaptureStillImageOutput用于输出图像
AVCaptureVideoPreviewLayer镜头捕捉到得预览图层

一个session可以管理多个输入输出设备,如下图所示

输入输出设备之间的关系(图片来自苹果官方开发文档)

接下来初始化所有对象,下面这个方法的调用我放到viewDidLoad里面调用了

- (void)initAVCaptureSession{

self.session = [[AVCaptureSession alloc] init];

NSError *error;

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//更改这个设置的时候必须先锁定设备,修改完后再解锁,否则崩溃
[device lockForConfiguration:nil];
//设置闪光灯为自动
[device setFlashMode:AVCaptureFlashModeAuto];
[device unlockForConfiguration]; self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
if (error) {
NSLog(@"%@",error);
}
self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
//输出设置。AVVideoCodecJPEG 输出jpeg格式图片
NSDictionary * outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey, nil];
[self.stillImageOutput setOutputSettings:outputSettings]; if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
if ([self.session canAddOutput:self.stillImageOutput]) {
[self.session addOutput:self.stillImageOutput];
} //初始化预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
NSLog(@"%f",kMainScreenWidth);
self.previewLayer.frame = CGRectMake(0, 0,kMainScreenWidth, kMainScreenHeight - 64);
self.backView.layer.masksToBounds = YES;
[self.backView.layer addSublayer:self.previewLayer];
}

之后在viewWillAppear,viewDidDisappear方法里开启和关闭session

- (void)viewWillAppear:(BOOL)animated{

    [super viewWillAppear:YES];

    if (self.session) {

    [self.session startRunning];
}
} - (void)viewDidDisappear:(BOOL)animated{ [super viewDidDisappear:YES]; if (self.session) { [self.session stopRunning];
}
}

到这里所有的初始化工作基本完成,运行程序可以看到镜头捕捉到得画面。接下来实现拍照按钮。

输出图像的时候需要用到AVCaptureConnection这个类,session通过AVCaptureConnection连接AVCaptureStillImageOutput进行图片输出,输入输出与connection的关系如下图

图片来自苹果官方开发文档

接下来搞一个获取设备方向的方法,再配置图片输出的时候需要使用

 -(AVCaptureVideoOrientation)avOrientationForDeviceOrientation:(UIDeviceOrientation)deviceOrientation
{
AVCaptureVideoOrientation result = (AVCaptureVideoOrientation)deviceOrientation;
if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
result = AVCaptureVideoOrientationLandscapeRight;
else if ( deviceOrientation == UIDeviceOrientationLandscapeRight )
result = AVCaptureVideoOrientationLandscapeLeft;
return result;
}

下面是拍照按钮方法

 - (IBAction)takePhotoButtonClick:(UIBarButtonItem *)sender {

    AVCaptureConnection *stillImageConnection = [self.stillImageOutput        connectionWithMediaType:AVMediaTypeVideo];
UIDeviceOrientation curDeviceOrientation = [[UIDevice currentDevice] orientation];
AVCaptureVideoOrientation avcaptureOrientation = [self avOrientationForDeviceOrientation:curDeviceOrientation];
[stillImageConnection setVideoOrientation:avcaptureOrientation];
[stillImageConnection setVideoScaleAndCropFactor:1]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:stillImageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault,
imageDataSampleBuffer,
kCMAttachmentMode_ShouldPropagate); ALAuthorizationStatus author = [ALAssetsLibrary authorizationStatus];
if (author == ALAuthorizationStatusRestricted || author == ALAuthorizationStatusDenied){
//无权限
return ;
}
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageDataToSavedPhotosAlbum:jpegData metadata:(__bridge id)attachments completionBlock:^(NSURL *assetURL, NSError *error) { }]; }];
}

至此相机的拍照功能已经完成
注:

  • [stillImageConnection setVideoScaleAndCropFactor:1];这个方法是控制焦距用的暂时先固定为1,后边写手势缩放焦距的时候会修改这里
  • 照片写入相册之前需要进行旋转(我在代码里并没有进行旋转)
  • 写入相册之前需要判断用户是否允许了程序访问相册,否则程序会崩溃,包括在开启相机的时候和拍摄按钮点击的时候都需要做安全验证,验证设别是否支持拍照,用户是否允许程序访问相机。

接下来完成闪光灯

- (IBAction)flashButtonClick:(UIBarButtonItem *)sender {

   NSLog(@"flashButtonClick");

   AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

   //修改前必须先锁定
[device lockForConfiguration:nil];
//必须判定是否有闪光灯,否则如果没有闪光灯会崩溃
if ([device hasFlash]) { if (device.flashMode == AVCaptureFlashModeOff) {
device.flashMode = AVCaptureFlashModeOn; [sender setTitle:@"flashOn"];
} else if (device.flashMode == AVCaptureFlashModeOn) {
device.flashMode = AVCaptureFlashModeAuto;
[sender setTitle:@"flashAuto"];
} else if (device.flashMode == AVCaptureFlashModeAuto) {
device.flashMode = AVCaptureFlashModeOff;
[sender setTitle:@"flashOff"];
} } else { NSLog(@"设备不支持闪光灯");
}
[device unlockForConfiguration];
}

闪光灯的设置非常简单,只需要修改device的flashMode属性即可,这里需要注意的是,修改device时候需要先锁住,修改完成后再解锁,否则会崩溃,设置闪光灯的时候也需要做安全判断,验证设备是否支持闪光灯,有些iOS设备是没有闪光灯的,如果不做判断还是会crash掉 T_T

剩下一个小功能就是切回镜头了,方法如下

- (IBAction)switchCameraSegmentedControlClick:(UISegmentedControl *)sender {

     NSLog(@"%ld",(long)sender.selectedSegmentIndex);

    AVCaptureDevicePosition desiredPosition;
if (isUsingFrontFacingCamera){
desiredPosition = AVCaptureDevicePositionBack;
}else{
desiredPosition = AVCaptureDevicePositionFront;
} for (AVCaptureDevice *d in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
if ([d position] == desiredPosition) {
[self.previewLayer.session beginConfiguration];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:d error:nil];
for (AVCaptureInput *oldInput in self.previewLayer.session.inputs) {
[[self.previewLayer session] removeInput:oldInput];
}
[self.previewLayer.session addInput:input];
[self.previewLayer.session commitConfiguration];
break;
}
} isUsingFrontFacingCamera = !isUsingFrontFacingCamera;
}

isUsingFrontFacingCamera这个属性是个BOOL值变量,前面忘记写这个属性了。用于防止重复切换统一摄像头,调用这个点击方法的控件是个segement,文章最后我会附上demo地址。

最后一步就是加入手势缩放,手动调节相机焦距。
加入两个属性,并遵守这个协议<UIGestureRecognizerDelegate>

      /**
* 记录开始的缩放比例
*/
@property(nonatomic,assign)CGFloat beginGestureScale;
/**
* 最后的缩放比例
*/
@property(nonatomic,assign)CGFloat effectiveScale;

这两个属性分别用于记录缩放的比例。相机支持的焦距是1.0~67.5,所以再控制器加载的时候分别给这两个属性附上一个初值 1.0。之后给view添加一个缩放手势,手势调用的方法如下

//缩放手势 用于调整焦距
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer{ BOOL allTouchesAreOnThePreviewLayer = YES;
NSUInteger numTouches = [recognizer numberOfTouches], i;
for ( i = 0; i < numTouches; ++i ) {
CGPoint location = [recognizer locationOfTouch:i inView:self.backView];
CGPoint convertedLocation = [self.previewLayer convertPoint:location fromLayer:self.previewLayer.superlayer];
if ( ! [self.previewLayer containsPoint:convertedLocation] ) {
allTouchesAreOnThePreviewLayer = NO;
break;
}
} if ( allTouchesAreOnThePreviewLayer ) { self.effectiveScale = self.beginGestureScale * recognizer.scale;
if (self.effectiveScale < 1.0){
self.effectiveScale = 1.0;
} NSLog(@"%f-------------->%f------------recognizerScale%f",self.effectiveScale,self.beginGestureScale,recognizer.scale); CGFloat maxScaleAndCropFactor = [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] videoMaxScaleAndCropFactor]; NSLog(@"%f",maxScaleAndCropFactor);
if (self.effectiveScale > maxScaleAndCropFactor)
self.effectiveScale = maxScaleAndCropFactor; [CATransaction begin];
[CATransaction setAnimationDuration:.025];
[self.previewLayer setAffineTransform:CGAffineTransformMakeScale(self.effectiveScale, self.effectiveScale)];
[CATransaction commit]; } }

这样之再实现一个delegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ( [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] ) {
self.beginGestureScale = self.effectiveScale;
}
return YES;
}

在每次手势开始的时候把上一次实际缩放值赋给初始缩放值,如果不这么做的话你会发现每次手势开始的时候界面都会跳来跳去的(非常性感)。一个简单功能的相机基本上完成了,最后一步就是之前我们在拍照的方法里写死了一个1.0,我们还需要修改一下它,,否则虽然你看到的界面焦距改变了,但是实际拍出来的照片是没有变化的。找到拍照方法里的

[stillImageConnection setVideoScaleAndCropFactor:1.0];

修改为

[stillImageConnection setVideoScaleAndCropFactor:self.effectiveScale];

至此大功告成。

文章演示demo下载
(https://github.com/RockyAo/RACustomCamera)

苹果官方演示demo下载
(https://github.com/RockyAo/CameraDemo)

官方的演示demo里面还有个面部识别。

最后如果你想监听相机的对焦事件的话
再viewWillApper里面添加个监听

AVCaptureDevice *camDevice =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
int flags =NSKeyValueObservingOptionNew;
[camDevice addObserver:self forKeyPath:@"adjustingFocus" options:flags context:nil];

然后实现下面方法

-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {..........//在这里做你想做的事~~~}

最后别忘了再viewDidDisapper方法里移除监听

AVCaptureDevice*camDevice =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[camDevice removeObserver:self forKeyPath:@"adjustingFocus"];

第一次写东西哈,写的不好见谅有错误请指出。~~~

iOS开发--AVFoundation自定义相机的更多相关文章

  1. iOS开发之自定义表情键盘(组件封装与自动布局)

    下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自 ...

  2. 详解iOS开发之自定义View

    iOS开发之自定义View是本文要将介绍的内容,iOS SDK中的View是UIView,我们可以很方便的自定义一个View.创建一个 Window-based Application程序,在其中添加 ...

  3. 【Swift】IOS开发中自定义转场动画

    在IOS开发中,我们model另外一个控制器的时候,一般都使用默认的转场动画. 其实我们可以自定义一些转场动画.达到不同的转场效果. 步骤如下:(photoBrowser是目标控制器) 1.在源控制器 ...

  4. iOS使用AVCaptureSession自定义相机

    关于iOS调用摄像机来获取照片,通常我们都会调用UIImagePickerController来调用系统提供的相机来拍照,这个控件非常好 用.但是有时UIImagePickerController控件 ...

  5. 用AVFoundation自定义相机拍照

    自定义拍照或者录视频的功能,就需要用到AVFoundation框架,目前我只用到了拍照,所以记录下自定义拍照用法,视频用法等用上了再补充,应该是大同小异 demo在这里:https://github. ...

  6. iOS开发-UITableView自定义Cell

    UITableView在iOS中开发的重要地位是毋庸置疑的,基本上应用中用到的比例是一半左右,而且大部分情况都是需要自定义单元格的,这样用户看到的App才能更有美感.之前写过UITableView的基 ...

  7. iOS开发小技巧--相机相册的正确打开方式

    iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...

  8. IOS开发之自定义系统弹出键盘上方的view(转载)

    这篇文章解决的一个开发中的实际问题就是:当弹出键盘时,自定义键盘上方的view.目前就我的经验来看,有两种解决方法.一个就是利用UITextField或者UITextView的inputAccesso ...

  9. iOS开发之自定义导航栏返回按钮右滑返回手势失效的解决

    我相信针对每一个iOS开发者来说~除了根视图控制器外~所有的界面通过导航栏push过去的界面都是可以通过右滑来返回上一个界面~其实~在很多应用和APP中~用户已经习惯了这个功能~然而~作为开发者的我们 ...

随机推荐

  1. CICS的几个常用命令

    下面是CICS的几个常用命令,仅仅几个我也是刚刚使用CICS,慢慢积累. 1. cicscli /l 是查看启动的客户机守护程序,如果没有启动客户机守护程序,会提示如下信息:CCL8001I CICS ...

  2. Jenkins中构建Testcomplete项目的方法介绍

    Jenkins的部署在上一篇随笔中已经和大家介绍了,下面我们介绍一下再Jenkins中构建testcomplete项目.我这里使用的是Testcomplete11,下面详细介绍一下构建步骤. 1.Je ...

  3. 多个table切换 getElementsByClassName

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta name ...

  4. CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法

    这些天重装了电脑Win10系统,安装了CAD2012和VS2012,准备进行软件开发.在调试程序的时候,CAD没有进入界面就抛出 “正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain ...

  5. Js函数的概念、作用、创建、调用!

    一.函数是用来帮助我们封装.调用代码的最方便的工具! 二.函数的创建方法有三种: 三.函数的创建方式有3种,调用方式也不是单一的,调用方式有4种!      1.作为一个函数去调用 函数名+();(函 ...

  6. 协议分析 - DHCP协议解码详解

    协议分析 - DHCP协议解码详解 [DHCP协议简介]         DHCP,全称是 Dynamic Host Configuration Protocol﹐中文名为动态主机配置协议,它的前身是 ...

  7. Change the Target Recovery Time of a Database (SQL Server) 间接-checkpoints flushcache flushcache-message

    Change the Target Recovery Time of a Database (SQL Server) 间接checkpoints   flushcache flushcache-mes ...

  8. 【直播】APP全量混淆和瘦身技术揭秘

    [直播]APP全量混淆和瘦身技术揭秘 近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,通过逆向手段造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户苦不堪 ...

  9. Python黑帽编程 2.0 第二章概述

    Python黑帽编程 2.0 第二章概述 于 20世纪80年代末,Guido van Rossum发明了Python,初衷据说是为了打发圣诞节的无趣,1991年首次发布,是ABC语言的继承,同时也是一 ...

  10. windows下 安装Kali Linux到 U盘的方法

    作者:玄魂工作室 \ 2016年10月20日 把Kali Linux安装到U盘好处很多,可以从U盘启动使用整个电脑的硬件资源, 可以随身携带,减少对自己电脑的影响. 今天要给大家讲的是如何在windo ...