iOS-AVCapture,自定义实现照相功能

关于iOS调用摄像机来获取照片,通常我们都会调用UIImagePickerController来调用系统提供的相机来拍照,这个控件非常好用。但是有时UIImagePickerController控件无法满足我们的需求,例如我们需要更加复杂的OverlayerView,这时候我们就要自己构造一个摄像机控件了。

0.AVCapture <AVFoundation/AVFoundation.h>
媒体采集需要的几个对象:
1、AVCaptureDevice: 代表抽象的硬件设备(如前置摄像头,后置摄像头等)。
2、AVCaptureInput: 代表输入设备(可以是它的子类),它配置抽象硬件设备的ports。
3、AVCaptureOutput: 它代表输出数据,管理着输出到一个movie或者图像。
4、AVCaptureSession: 它是input和output的桥梁。它协调着input到output的数据传输。

关系:
有很多Device的input,也有很多数据类型的Output,都通过一个Capture Session来控制进行传输。也即:CaptureDevice适配AVCaptureInput,通过Session来输入到AVCaptureOutput中。这样也就达到了从设备到文件等持久化输入目的(如从相机设备采集图像到UIImage中)。

那么存在一个问题了:视频输入(input)就对应视频的输出(output),而音频输入就对应音频的输出,因而需要建立对应的Connections,来各自连接它们。而这样的连接对象,是由AVCaptureSession来持有的,这个对象叫AVCaptureConnection。
在一个AVCaptureConnection中,这里维持着对应的数据传输输入到数据输出的过程(detail过程)。这里,AVCaptureInput或其子类对象包含着各种input port,通过各种input port,我们的AVCaptureOutput可以获取相应的数据。

一个AVCaptureConnection可以控制input到output的数据传输。

1.Session及其使用模式
You use an instance to coordinate the flow of data from AV input devices to outputs. You add the capture devices and outputs you want to the session, then start data flow by sending the session a startRunning message, and stop recording by sending a stopRunning message.

AVCaptureSession *session = [AVCaptureSession alloc] init];
[session startRunning];

这里表明了,需要create一个session,然后发running消息给它,它会自动跑起来,把输入设备的东西,提交到输出设备中。

若想在一个已经使用上的session中(已经startRunning了)做更换新的device、删除旧的device等一系列的操作,那么就需要使用如下方法:
AVCaptureSession *session;
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];

当然,如果session的时候发生了异常,那么我们可以通过notification去observe相关的事件(可以在AVCaptureSession Class Reference中的Notifications中找到相应的情况),而session如果出现相应问题的时候,它会post出来,此时我们就可以处理了。

2.AVCaptureDevice,主要用来获取iPhone一些关于相机设备的属性。
InputDevice即是对硬件的抽象,一对一的。一个AVCaptureDevice对象,对应一个实际的硬件设备。

那么显然,我们可以通过AVCaptureDevice的类方法devices或devicesWithMediaType去获取全部或局部设备列表。(当然也可以检测相应的设备是否可以使用,这里注意有设备抢占问题,当前是否可用)

相机设备可以用下面的方法判断设备是否支持相关属性(property),比如对焦方式或者对焦状态Focus modes。
[currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];

  1. 前置和后置摄像头
    enum {
    AVCaptureDevicePositionBack = 1,
    AVCaptureDevicePositionFront = 2
    };
    typedef NSInteger AVCaptureDevicePosition;

  2. 闪光灯开关
    enum {
    AVCaptureFlashModeOff = 0,
    AVCaptureFlashModeOn = 1,
    AVCaptureFlashModeAuto = 2
    };
    typedef NSInteger AVCaptureFlashMode;

  3. 手电筒开关
    enum {
    AVCaptureTorchModelOff = 0,
    AVCaptureTorchModelOn = 1,
    AVCaptureTorchModeAuto = 2
    };
    typedef NSInteger AVCaptureTorchMode;

  4. 焦距调整
    enum {
    AVCaptureFocusModelLocked = 0,
    AVCaptureFocusModeAutoFocus = 1,
    AVCaptureFocusModeContinousAutoFocus = 2
    };
    typedef NSInteger AVCaptureFocusMode;

  5. 曝光量调节
    enum {
    AVCaptureExposureModeLocked = 0,
    AVCaptureExposureModeAutoExpose = 1,
    AVCaptureExposureModeContinuousAutoExposure = 2
    };
    typedef NSInteger AVCaptureExposureMode;

  6. 白平衡
    enum {
    AVCaptureWhiteBalanceModeLocked = 0,
    AVCaptureWhiteBalanceModeAutoWhiteBalance = 1,
    AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance = 2
    };
    typedef NSInteger AVCaptureWhiteBalanceMode;

  7. CaptureInput的构建和添加到Session中的方法
    创建并配置输入设备
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error = nil;
    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

// 添加input到session的模式是(检查可否添加到session,然后根据情况添加或者不添加)
AVCaptureSession *captureSession = <#Get a capture session#>;
if ([captureSession canAddInput:input]) {
[captureSession addInput:input];
}

4.output的分类和使用
ios中,分为MovieFile、VideoData、AudioData和StillImage几种output,使用方式类似,只是范围不同。另外,它们都继承于AVCaptureOutput。

第一个是输出成movie文件,第二个适用于逐个Frame的处理,第三个适用于声音采集,第四个是still image(静态图像<拍照>)相关。

他们的添加方式都是使用session的addOutput方法。

5.AVCaptureStillImageOutput 照片输出流对象

  1. AVCaptureVideoPreviewLayer 预览图层,来显示照相机拍摄到的画面
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
{
    
}

// AVCaptureSession对象来执行输入设备和输出设备之间的数据传递
@property (nonatomic, strong)AVCaptureSession *session;

// AVCaptureDeviceInput对象是输入流
@property (nonatomic, strong)AVCaptureDeviceInput *videoInput;

// 照片输出流对象
@property (nonatomic, strong)AVCaptureStillImageOutput *stillImageOutput;

// 预览图层,来显示照相机拍摄到的画面
@property (nonatomic, strong)AVCaptureVideoPreviewLayer *previewLayer;

// 切换前后镜头的按钮
@property (nonatomic, strong)UIButton *toggleButton;

// 拍照按钮
@property (nonatomic, strong)UIButton *shutterButton;

// 放置预览图层的View
@property (nonatomic, strong)UIView *cameraShowView;

// 用来展示拍照获取的照片
@property (nonatomic, strong)UIImageView *imageShowView;

@end

@implementation ViewController

- (id)init {
    self = [super init];
    if (self) {
        [self initialSession];
        [self initCameraShowView];
        [self initImageShowView];
        [self initButton];
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    [self setUpCameraLayer];
}

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

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

- (void)initialSession {
    self.session = [[AVCaptureSession alloc] init];
    
    self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:nil];
    
    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];
    }
}

- (void)initCameraShowView {
    self.cameraShowView = [[UIView alloc] initWithFrame:self.view.frame];
    [self.view addSubview:self.cameraShowView];
}

- (void)initImageShowView {
    self.imageShowView = [[UIImageView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height -200, 200, 200)];
    self.imageShowView.contentMode = UIViewContentModeScaleToFill;
    self.imageShowView.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.imageShowView];
}

- (void)initButton {
    self.shutterButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.shutterButton.frame = CGRectMake(10, 30, 60, 30);
    self.shutterButton.backgroundColor = [UIColor cyanColor];
    [self.shutterButton setTitle:@"拍照" forState:UIControlStateNormal];
    [self.shutterButton addTarget:self action:@selector(shutterCamera)forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.shutterButton];
    
    self.toggleButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.toggleButton.frame = CGRectMake(80, 30, 60, 30);
    self.toggleButton.backgroundColor = [UIColor cyanColor];
    [self.toggleButton setTitle:@"切换摄像头" forState:UIControlStateNormal];
    [self.toggleButton addTarget:self action:@selector(toggleCamera)forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.toggleButton];
}

// 这是获取前后摄像头对象的方法
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}

- (AVCaptureDevice *)frontCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionFront];
}

- (AVCaptureDevice *)backCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionBack];
}

- (void)setUpCameraLayer {
    if (self.previewLayer == nil) {
        self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
        UIView * view = self.cameraShowView;
        CALayer * viewLayer = [view layer];
        // UIView的clipsToBounds属性和CALayer的setMasksToBounds属性表达的意思是一致的,决定子视图的显示范围。当取值为YES的时候,剪裁超出父视图范围的子视图部分,当取值为NO时,不剪裁子视图。
        [viewLayer setMasksToBounds:YES];
        
        CGRect bounds = [view bounds];
        [self.previewLayer setFrame:bounds];
        [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
        
        [viewLayer addSublayer:self.previewLayer];
    }
}

// 这是拍照按钮的方法
- (void)shutterCamera {
    AVCaptureConnection *videoConnection = [self.stillImageOutputconnectionWithMediaType:AVMediaTypeVideo];
    if (!videoConnection) {
        return;
    }
    
    [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnectioncompletionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer == NULL) {
            return;
        }
        NSData *imageData = [AVCaptureStillImageOutputjpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [UIImage imageWithData:imageData];
        NSLog(@"image size = %@", NSStringFromCGSize(image.size));
        self.imageShowView.image = image;
    }];
}

// 这是切换镜头的按钮方法
- (void)toggleCamera {
    NSUInteger cameraCount = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
    if (cameraCount > 1) {
        NSError *error;
        AVCaptureDeviceInput *newVideoInput;
        AVCaptureDevicePosition position = [[_videoInput device] position];
        
        if (position == AVCaptureDevicePositionBack) {
            newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamera] error:&error];
        } else if (position == AVCaptureDevicePositionFront) {
            newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:&error];
        } else {
            return;
        }
        
        if (newVideoInput != nil) {
            [self.session beginConfiguration];
            [self.session removeInput:self.videoInput];
            if ([self.session canAddInput:newVideoInput]) {
                [self.session addInput:newVideoInput];
                self.videoInput = newVideoInput;
            } else {
                [self.session addInput:self.videoInput];
            }
            [self.session commitConfiguration];
        } else if (error) {
            NSLog(@"toggle carema failed, error = %@", error);
        }
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容