關(guān)于iOS調(diào)用攝像機(jī)來獲取照片,通常我們都會(huì)調(diào)用UIImagePickerController來調(diào)用系統(tǒng)提供的相機(jī)來拍照,這個(gè)控件非常好用舅逸。但是有時(shí)UIImagePickerController控件無法滿足我們的需求粹淋,例如我們需要更加復(fù)雜的OverlayerView,這時(shí)候我們就要自己構(gòu)造一個(gè)攝像機(jī)控件了桶错。
0.AVCapture <AVFoundation/AVFoundation.h>
媒體采集需要的幾個(gè)對(duì)象:
1、AVCaptureDevice: 代表抽象的硬件設(shè)備(如前置攝像頭胀蛮,后置攝像頭等)院刁。
2、AVCaptureInput: 代表輸入設(shè)備(可以是它的子類)粪狼,它配置抽象硬件設(shè)備的ports退腥。
3任岸、AVCaptureOutput: 它代表輸出數(shù)據(jù),管理著輸出到一個(gè)movie或者圖像狡刘。
4享潜、AVCaptureSession: 它是input和output的橋梁。它協(xié)調(diào)著input到output的數(shù)據(jù)傳輸颓帝。
關(guān)系:
有很多Device的input米碰,也有很多數(shù)據(jù)類型的Output,都通過一個(gè)Capture Session來控制進(jìn)行傳輸购城。也即:CaptureDevice適配AVCaptureInput吕座,通過Session來輸入到AVCaptureOutput中。這樣也就達(dá)到了從設(shè)備到文件等持久化輸入目的(如從相機(jī)設(shè)備采集圖像到UIImage中)瘪板。
那么存在一個(gè)問題了:視頻輸入(input)就對(duì)應(yīng)視頻的輸出(output)吴趴,而音頻輸入就對(duì)應(yīng)音頻的輸出,因而需要建立對(duì)應(yīng)的Connections侮攀,來各自連接它們锣枝。而這樣的連接對(duì)象,是由AVCaptureSession來持有的兰英,這個(gè)對(duì)象叫AVCaptureConnection撇叁。
在一個(gè)AVCaptureConnection中,這里維持著對(duì)應(yīng)的數(shù)據(jù)傳輸輸入到數(shù)據(jù)輸出的過程(detail過程)畦贸。這里陨闹,AVCaptureInput或其子類對(duì)象包含著各種input port,通過各種input port薄坏,我們的AVCaptureOutput可以獲取相應(yīng)的數(shù)據(jù)趋厉。
一個(gè)AVCaptureConnection可以控制input到output的數(shù)據(jù)傳輸。
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一個(gè)session君账,然后發(fā)running消息給它,它會(huì)自動(dòng)跑起來沈善,把輸入設(shè)備的東西乡数,提交到輸出設(shè)備中。
若想在一個(gè)已經(jīng)使用上的session中(已經(jīng)startRunning了)做更換新的device闻牡、刪除舊的device等一系列的操作瞳脓,那么就需要使用如下方法:
AVCaptureSession *session;
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
當(dāng)然,如果session的時(shí)候發(fā)生了異常澈侠,那么我們可以通過notification去observe相關(guān)的事件(可以在AVCaptureSession Class Reference中的Notifications中找到相應(yīng)的情況),而session如果出現(xiàn)相應(yīng)問題的時(shí)候埋酬,它會(huì)post出來哨啃,此時(shí)我們就可以處理了烧栋。
2.AVCaptureDevice,主要用來獲取iPhone一些關(guān)于相機(jī)設(shè)備的屬性。
InputDevice即是對(duì)硬件的抽象拳球,一對(duì)一的审姓。一個(gè)AVCaptureDevice對(duì)象,對(duì)應(yīng)一個(gè)實(shí)際的硬件設(shè)備祝峻。
那么顯然魔吐,我們可以通過AVCaptureDevice的類方法devices或devicesWithMediaType去獲取全部或局部設(shè)備列表。(當(dāng)然也可以檢測(cè)相應(yīng)的設(shè)備是否可以使用莱找,這里注意有設(shè)備搶占問題酬姆,當(dāng)前是否可用)
相機(jī)設(shè)備可以用下面的方法判斷設(shè)備是否支持相關(guān)屬性(property),比如對(duì)焦方式或者對(duì)焦?fàn)顟B(tài)Focus modes奥溺。
[currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
前置和后置攝像頭
enum {
AVCaptureDevicePositionBack = 1,
AVCaptureDevicePositionFront = 2
};
typedef NSInteger AVCaptureDevicePosition;閃光燈開關(guān)
enum {
AVCaptureFlashModeOff = 0,
AVCaptureFlashModeOn = 1,
AVCaptureFlashModeAuto = 2
};
typedef NSInteger AVCaptureFlashMode;手電筒開關(guān)
enum {
AVCaptureTorchModelOff = 0,
AVCaptureTorchModelOn = 1,
AVCaptureTorchModeAuto = 2
};
typedef NSInteger AVCaptureTorchMode;焦距調(diào)整
enum {
AVCaptureFocusModelLocked = 0,
AVCaptureFocusModeAutoFocus = 1,
AVCaptureFocusModeContinousAutoFocus = 2
};
typedef NSInteger AVCaptureFocusMode;曝光量調(diào)節(jié)
enum {
AVCaptureExposureModeLocked = 0,
AVCaptureExposureModeAutoExpose = 1,
AVCaptureExposureModeContinuousAutoExposure = 2
};
typedef NSInteger AVCaptureExposureMode;白平衡
enum {
AVCaptureWhiteBalanceModeLocked = 0,
AVCaptureWhiteBalanceModeAutoWhiteBalance = 1,
AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance = 2
};
typedef NSInteger AVCaptureWhiteBalanceMode;CaptureInput的構(gòu)建和添加到Session中的方法
創(chuàng)建并配置輸入設(shè)備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
// 添加input到session的模式是(檢查可否添加到session辞色,然后根據(jù)情況添加或者不添加)
AVCaptureSession *captureSession = <#Get a capture session#>;
if ([captureSession canAddInput:input]) {
[captureSession addInput:input];
}
4.output的分類和使用
在ios中,分為MovieFile浮定、VideoData相满、AudioData和StillImage幾種output,使用方式類似桦卒,只是范圍不同立美。另外,它們都繼承于AVCaptureOutput方灾。
第一個(gè)是輸出成movie文件建蹄,第二個(gè)適用于逐個(gè)Frame的處理,第三個(gè)適用于聲音采集迎吵,第四個(gè)是still image(靜態(tài)圖像<拍照>)相關(guān)躲撰。
他們的添加方式都是使用session的addOutput方法。
5.AVCaptureStillImageOutput 照片輸出流對(duì)象
- AVCaptureVideoPreviewLayer 預(yù)覽圖層击费,來顯示照相機(jī)拍攝到的畫面
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
{
}
// AVCaptureSession對(duì)象來執(zhí)行輸入設(shè)備和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (nonatomic, strong)AVCaptureSession *session;
// AVCaptureDeviceInput對(duì)象是輸入流
@property (nonatomic, strong)AVCaptureDeviceInput *videoInput;
// 照片輸出流對(duì)象
@property (nonatomic, strong)AVCaptureStillImageOutput *stillImageOutput;
// 預(yù)覽圖層拢蛋,來顯示照相機(jī)拍攝到的畫面
@property (nonatomic, strong)AVCaptureVideoPreviewLayer *previewLayer;
// 切換前后鏡頭的按鈕
@property (nonatomic, strong)UIButton *toggleButton;
// 拍照按鈕
@property (nonatomic, strong)UIButton *shutterButton;
// 放置預(yù)覽圖層的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];
// 這是輸出流的設(shè)置參數(shù)AVVideoCodecJPEG參數(shù)表示以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];
}
// 這是獲取前后攝像頭對(duì)象的方法
- (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屬性表達(dá)的意思是一致的,決定子視圖的顯示范圍。當(dāng)取值為YES的時(shí)候蔫巩,剪裁超出父視圖范圍的子視圖部分谆棱,當(dāng)取值為NO時(shí),不剪裁子視圖圆仔。
[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