AVFoundation学习Demo--拍摄照片

一.核心类AVCaptureSession

关于iOS多媒体这一块,苹果已经做了很好的封装。虽然一开始多少会被数量众多的对象吓到,不过稍微了解就会发现,其实内容并不难。主要有播放,拍摄,编辑,滤镜等场景,每一个场景都有固定的套路。准备在学习过程中主要关注几个固定的场景,每个场景写一个Demo,加深理解同时与大家分享。

废话不多说,今天的主题是利用AVFoundation框架拍摄照片。不管是拍摄照片还是视频,都属于媒体捕捉,用到的核心类是AVCaptureSession。顾名思义,AVCaptureSession代表一个会话,这个会话用于连接输入源AVCaptureInput,和输出AVCaptureOutput。AVCaptureInput和AVCaptureOutput都是抽象类,我们需要根据需要实例化不同的子类。

在本例中,我们使用AVCaptureDeviceInput作为输入源。既然我们指定例输入源是设备,那必然需要诸如摄像头,麦克风等物理设备。在AVFoundation中,提供了AVCaptureDevice类实现对物理设备等抽象。同时,AVCaptureOutput的常用子类有AVCaptureFileOutput(文件输出),AVCaptureVideoDataOutput(视频帧数据输出),AVCaptureStillImageOutput(图像输出)等。这里用AVCaptureStillImageOutput就可以了。此外还有一个AVCaptureVideoPreviewLayer提供了预览画面。图1.1是apple官方对Capture Session的框架介绍。好吧,看起来有点复杂,动手写下就明白了。

AVFoundation学习Demo--拍摄照片_第1张图片
图1.1


二.创建一个相机

首先,我们创建一个新的项目,storyBoard中拖入两个全屏的View,分别命名为previewView和overlayView,并连线到h文件。

@property(weak,nonatomic)IBOutletUIView*previewView;

@property(weak,nonatomic)IBOutletUIView*overlayView;

再拖入一个Button作为拍摄按钮放到overlayView的底部,并连线到m文件并取名为:

- (IBAction)captureButtonClicked:(id)sender {

    // TODO

}

打开m文件,添加五个属性:

@property(nonatomic,strong)AVCaptureSession*captureSession;

@property(nonatomic,strong)AVCaptureDevice*captureDevice;

@property(nonatomic,strong)AVCaptureDeviceInput*captureDeviceInput;

@property(nonatomic,strong)AVCaptureStillImageOutput*stillImageOutput;

@property(nonatomic,strong)AVCaptureVideoPreviewLayer*videoPreviewLayer;

有了这五个属性我们就可以创建一个完整的拍照应用了。怎么样,很厉害吧!

下面,我们在viewDidLoad中加入调用setupCaptureSession方法(专业点应该叫发送消息- -!),用于初始化:

- (void)setupCaptureSession {

// 1.创建会话

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

self.captureSession.sessionPreset = AVCaptureSessionPresetPhoto;

// 2.创建输入设备

self.captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

// 3.创建输入

NSError *error = nil;

self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:&error];

// 3.创建输出

self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];

self.stillImageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};

// 4.连接输入与会话

if ([self.captureSession canAddInput:self.captureDeviceInput]) {

[self.captureSession addInput:self.captureDeviceInput];

}

// 5.连接输出与会话

if ([self.captureSession canAddOutput:self.stillImageOutput]) {

[self.captureSession addOutput:self.stillImageOutput];

}

// 6.预览画面

self.videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];

self.videoPreviewLayer.frame = self.previewView.bounds;

[self.previewView.layer addSublayer:self.videoPreviewLayer];

}


1.中sessionPreset属性用于控制会话的数据质量,在拍摄视频时比较有用,这里我们只需要设为AVCaptureSessionPresetPhoto就可以了。类似地,3.中outputSettings属性用于设置输出图像的格式。4,5两步都在连接Session前加了判断,这是很有必要的。因为AVFoundation部分涉及到硬件,不同手机硬件并不一样且较难调试,在做一些重要操作前加判断在AVFoundation中是很常见的。6.中将预览的layer添加到了previewView上,其实更好的实现是自定义一个View并在layerClass方法中返回AVCaptureVideoPreviewLayer类型。作为演示,我们在viewWillLayoutSubviews中加一句。

self.videoPreviewLayer.frame=self.previewView.bounds;

目前为止配置都完成了,下面就让Session跑起来吧!viewDidLoad中再加一个方法startSession。

- (void)startSession {

if(![self.captureSession isRunning]) {

[self.captureSession startRunning];

}

}

有start就有stop,下面我们再写一个方法,停止Session。

- (void)stopSession {

if([self.captureSession isRunning]) {

[self.captureSession stopRunning];

}

}

还记得我们前面的代码中还有一个TODO吗?是的,按下拍摄按钮并得到照片,我们的任务就完成了。

- (IBAction)captureButtonClicked:(id)sender {

// 1.获得连接

AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];

// 2.拍摄照片

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {

NSData *imageData = [AVCaptureStillImageOutput

jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

UIImage *image = [UIImage imageWithData:imageData];

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

}];

}


第一步获得AVCaptureConnection,不知道AVCaptureConnection是什么的可以看看图1.1。每个输入员都有不同的端口(port),例如后置摄像头既可以记录图像还能记录声音。一个Connection代表一个输入到输出的连接。第二步调用系统方法拍摄照片,这里返回的格式是CMSampleBufferRef,它是对基本数据的封装,从中还有时间格式等信息。要实现帧级别的媒体操作就需要用到它,这里我们先不深入。只需要将它转为UIImage,再调用系统函数保存即可。现在到相册看一眼,应该多出一张刚才拍摄的照片了吧。

三.增强相机功能

系统的相机能实现诸如闪光灯,调整焦距等功能,这些功能的接口都已经在AVCaptureDevice中定义了。我们可以很方便的达到系统相机的效果。

下面我们需要在storyBoard加点东西。分别在左右上角增加闪光灯和摄像头Button,在overlayView上加入pinchGestureRecognizer。

闪光灯开关

点击闪光灯按钮,实现闪光灯打开或关闭:

- (IBAction)flashButtonClicked:(id)sender {

if([self.captureDevice isFlashActive]) {

[self setFlashMode:AVCaptureFlashModeOff];

}else{

[self setFlashMode:AVCaptureFlashModeOn];

}

}

我们需要实现setFlashMode:方法。注意在切换模式前,我们调用了lockForConfiguration:方法,它实现了对设备的原子操作。

- (void)setFlashMode:(AVCaptureFlashMode)mode {

if ([self.captureDevice isFlashModeSupported:mode]) {

NSError *error = nil;

if ([self.captureDevice lockForConfiguration:&error]) {

[self.captureDevice setFlashMode:mode];

[self.captureDevice unlockForConfiguration];

}

}

}


切换摄像头

切换摄像头的设置稍微复杂一点,因为他涉及到切换AVCaptureDevice,也要注意到并不是所有设备都有前置摄像头的。点击button时,调用一下代码:

- (IBAction)cameraPositionButtonClicked:(id)sender {

AVCaptureDevice*device =nil;

if(self.captureDevice.position==AVCaptureDevicePositionFront) {

device = [self deviceWithPosition:AVCaptureDevicePositionBack];

}else{

device = [self deviceWithPosition:AVCaptureDevicePositionFront];

}

if(!device) {

return;

}else{

self.captureDevice= device;

}

NSError*error =nil;

AVCaptureDeviceInput*input = [AVCaptureDeviceInput deviceInputWithDevice:deviceerror:&error];

if(!error) {

[self.captureSession beginConfiguration];

[self.captureSession removeInput:self.captureDeviceInput];

if([self.captureSession canAddInput:input]) {

[self.captureSession addInput:input];

self.captureDeviceInput= input;

[self.captureSession commitConfiguration];

}

}

}

同样需要实现自定义方法deviceWithPosition:

- (AVCaptureDevice *)deviceWithPosition:(AVCaptureDevicePosition)position {

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for (AVCaptureDevice *device in devices) {

if (device.position == position) {

return device;

}

}

return nil;

}

焦距调整

要实现焦距调整,需要监听用户的捏合手势。基本思路是保存用户在UIGestureRecognizerStateBegan时的手指距离,当UIGestureRecognizerStateChanged时,对比捏合的差距,调整焦距。

首先要增加一个属性记录手指距离:

@property (nonatomic, assign) CGFloat lastPinchDistance;

连线方法如下:

- (IBAction)pinchGestureRecognizer:(UIPinchGestureRecognizer *)sender {

if (sender.numberOfTouches != 2) {

return;

}

CGPoint point1 = [sender locationOfTouch:0 inView:self.overlayView];

CGPoint point2 = [sender locationOfTouch:1 inView:self.overlayView];

CGFloat distanceX = point2.x = point1.x;

CGFloat distanceY = point2.y - point1.y;

CGFloat distance = sqrtf(distanceX * distanceX +distanceY * distanceY);

if (sender.state == UIGestureRecognizerStateBegan) {

self.lastPinchDistance = distance;

}

CGFloat change = distance - self.lastPinchDistance;

change = change / CGRectGetWidth(self.view.bounds);

[self zoomCamera:change];

self.lastPinchDistance = distance;

}

同样,我们还需要实现自定义方法zoomCamera:

- (void)zoomCamera:(CGFloat)change {

if (self.captureDevice.position == AVCaptureDevicePositionFront) {

return;

}

if (![self.captureDevice respondsToSelector:@selector(videoZoomFactor)]){

return;

}

NSError *error = nil;

if ([self.captureDevice lockForConfiguration:&error]) {

CGFloat max = MIN(self.captureDevice.activeFormat.videoMaxZoomFactor, 3.0);

CGFloat factor = self.captureDevice.videoZoomFactor;

CGFloat scale = MIN(MAX(factor + change, 1.0), max);

self.captureDevice.videoZoomFactor = scale;

[self.captureDevice unlockForConfiguration];

}

}

现在,我们的Demo已经实现了切换摄像头,控制闪光灯,调整焦距的功能,还有更多功能都是类似的,可以查看官方文档调用相应的接口实现。


Demo地址:https://github.com/WorthyZhang/WZCameraDemo

你可能感兴趣的:(iOS学习笔记)