背景
由于項(xiàng)目中需要拍照功能脐帝,但是系統(tǒng)原生的相機(jī)功能根本滿足不了項(xiàng)目的需要拨黔,所以就只能自定義一個(gè)相加了逸雹。蘋果再AVFoundation框架中給我們提供了各個(gè)api止潘,我們完全可以通過這些api自定義一個(gè)滿足我們需求的相機(jī)搁拙。
關(guān)鍵類
AVCaptureSession 負(fù)責(zé)輸入流和輸出流的管理秒梳。
AVCaptureDeviceInput 輸入流。連接輸入采集設(shè)備的
AVCaptureStillImageOutput 輸出流箕速。AVCaptureOutput的子類酪碘,主要是負(fù)責(zé)采集圖片數(shù)據(jù)的,不過這個(gè)類再10.0以后廢棄掉了弧满,改用AVCapturePhotoOutput這個(gè)替換婆跑,這個(gè)類能夠支持RAW格式的圖片
AVCaptureVideoPreviewLayer 集成CALayer,負(fù)責(zé)將采集的視頻展示出來的一個(gè)類庭呜,提供了一個(gè)預(yù)覽功能而已
實(shí)現(xiàn)過程
#import "TakePictureViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "UIImage+Rotate.h"
#import "UIControl+Custom.h"
#define KSCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
#define KSCREEN_HEIGHT [[UIScreen mainScreen] bounds].size.height
typedef NS_ENUM(NSInteger, AVCamSetupResult ) {
AVCamSetupResultSuccess,
AVCamSetupResultCameraNotAuthorized,
AVCamSetupResultSessionConfigurationFailed
};
@interface TakePictureViewController ()
{
BOOL lightOn;
AVCaptureDevice *device;
ActivityIndicatorTipView *activityView;
}
// AVCaptureSession對象來執(zhí)行輸入設(shè)備和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (nonatomic, strong)AVCaptureSession *session;
// AVCaptureDeviceInput對象是輸入流
@property (nonatomic, strong)AVCaptureDeviceInput *videoInput;
// 照片輸出流對象
@property (nonatomic, strong)AVCaptureStillImageOutput *stillImageOutput;
// 預(yù)覽圖層滑进,來顯示照相機(jī)拍攝到的畫面
@property (nonatomic, strong)AVCaptureVideoPreviewLayer *previewLayer;
// 切換前后鏡頭的按鈕
@property (nonatomic, strong)UIButton *toggleButton;
// 放置預(yù)覽圖層的View
@property (nonatomic, strong)UIView *cameraShowView;
// 用來展示拍照獲取的照片
@property (nonatomic, strong)UIImageView *imageShowView;
@property (nonatomic,strong) UIView *overlayView;
@property (nonatomic) dispatch_queue_t sessionQueue;
@property (nonatomic) AVCamSetupResult setupResult;
@end
@implementation TakePictureViewController
-(BOOL)prefersStatusBarHidden
{
return YES;
}
-(instancetype)init
{
self = [super init];
if (self)
{
[self initAll];
}
return self;
}
- (void)initAll{
[self initialSession];
[self initCameraShowView];
[self.view addSubview:self.overlayView];
[self initAVDevice];
[self setUpCameraLayer];
}
- (void)initialSession
{
self.session = [[AVCaptureSession alloc] init];
}
- (void)initCameraShowView
{
self.cameraShowView = [[UIView alloc] initWithFrame:self.view.frame];
[self.view addSubview:self.cameraShowView];
}
// 這是獲取前后攝像頭對象的方法
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position
{
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *captureDevice in devices)
{
if (captureDevice.position == position)
{
return captureDevice;
}
}
return nil;
}
- (AVCaptureDevice *)frontCamera
{
return [self cameraWithPosition:AVCaptureDevicePositionFront];
}
- (AVCaptureDevice *)backCamera
{
return [self cameraWithPosition:AVCaptureDevicePositionBack];
}
/**
設(shè)備手電筒
*/
-(void)initAVDevice
{
device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (![device hasTorch])
{
//無手電筒
[[[UIAlertView alloc] initWithTitle:@"tishi" message:@"無手電筒" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil] show];
}
lightOn = NO;
}
- (void)configureSession{
if ( self.setupResult != AVCamSetupResultSuccess ) {
return;
}
[self.session beginConfiguration];//保證對AVCaptureSession設(shè)置的原子性與commitConfiguration配對使用
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,@(0.1),AVVideoQualityKey,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.session commitConfiguration];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
switch ( [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] )
{
case AVAuthorizationStatusAuthorized:
{
break;
}
case AVAuthorizationStatusNotDetermined:
{
dispatch_suspend( self.sessionQueue );
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted ) {
if ( ! granted ) {
self.setupResult = AVCamSetupResultCameraNotAuthorized;
}
dispatch_resume( self.sessionQueue );
}];
break;
}
default:
{
self.setupResult = AVCamSetupResultCameraNotAuthorized;
break;
}
}
dispatch_async(self.sessionQueue, ^{
[self configureSession];
});
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
dispatch_async( self.sessionQueue, ^{
switch ( self.setupResult )
{
case AVCamSetupResultSuccess:
{
[self.session startRunning];
break;
}
case AVCamSetupResultCameraNotAuthorized:
{
dispatch_async( dispatch_get_main_queue(), ^{
NSString *message = @"請?jiān)试S拍照權(quán)限,以用來掃描二維碼";
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:cancelAction];
UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"設(shè)置" style:UIAlertActionStyleDefault handler:^( UIAlertAction *action ) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
}];
[alertController addAction:settingsAction];
[self presentViewController:alertController animated:YES completion:nil];
} );
break;
}
case AVCamSetupResultSessionConfigurationFailed:
{
dispatch_async( dispatch_get_main_queue(), ^{
NSString *message = @"沒有拍照權(quán)限募谎,所以不能掃描二維碼";
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
} );
break;
}
}
} );
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
dispatch_async( self.sessionQueue, ^{
if ( self.setupResult == AVCamSetupResultSuccess ) {
[self.session stopRunning];
}
} );
}
- (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];
}
}
/**
打開設(shè)備手電筒
*/
-(void) actionTurnOn
{
[device lockForConfiguration:nil];
[device setTorchMode:AVCaptureTorchModeOn];
[device unlockForConfiguration];
}
/**
關(guān)閉設(shè)備手電筒
*/
-(void) actionTurnOff
{
[device lockForConfiguration:nil];
[device setTorchMode: AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
// 拍照
- (void)actionStartCamera
{
dispatch_async(self.sessionQueue, ^{
AVCaptureConnection *videoConnection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
if (!videoConnection)
{
return;
}
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer == NULL) {
return;
}
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *originalImage = [UIImage imageWithData:imageData];
//1.旋轉(zhuǎn)照片
UIImage *rotateImage = [originalImage rotate:UIImageOrientationRight];
});
}
@end
注意點(diǎn)
1、AVCaptureSession的配置過程和startRunning是阻擋主線程的一個(gè)耗時(shí)操作铜异,所以我們放到另外的queue中操作哥倔,能夠避免阻擋主線程
2、由于我們拍照不需要質(zhì)量非常高的照片揍庄,所以我們通過setOutputSettings設(shè)置了圖片質(zhì)量咆蒿,這樣減少了很大一部分內(nèi)存,可以根據(jù)情況來設(shè)置圖片的質(zhì)量蚂子。
3沃测、拍完照片圖片是倒立的,所以我們做了一個(gè)旋轉(zhuǎn)操作食茎,將圖片正立了過來