美文网首页iOS精华iOS DeveloperiOS点点滴滴
AVFoundation学习Demo--拍摄照片

AVFoundation学习Demo--拍摄照片

作者: Worthy | 来源:发表于2015-12-27 17:16 被阅读1208次

    一.核心类AVCaptureSession

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

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

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

    图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/WZMediaDemo

    相关文章

      网友评论

        本文标题:AVFoundation学习Demo--拍摄照片

        本文链接:https://www.haomeiwen.com/subject/iaebhttx.html