【前言】
此篇文章宗旨,在于忘記時(shí)越妈,方便查閱。
【原因】
為什么要自定義相機(jī)拍照钮糖?因?yàn)橄到y(tǒng)的相機(jī)拍照無法滿足項(xiàng)目的需求梅掠。
【了解】
首先了解一下使用AVFoundation做拍照和視頻錄制開發(fā)用到的相關(guān)類:
AVCaptureSession:媒體(音、視頻)捕獲會(huì)話店归,負(fù)責(zé)把捕獲的音視頻數(shù)據(jù)輸出到輸出設(shè)備中阎抒。一個(gè)AVCaptureSession可以有多個(gè)輸入輸出:如圖:
AVCaptureDevice:輸入設(shè)備,包括麥克風(fēng)消痛、攝像頭且叁,通過該對(duì)象可以設(shè)置物理設(shè)備的一些屬性(例如相機(jī)聚焦、白平衡等)肄满。
AVCaptureDeviceInput:設(shè)備輸入數(shù)據(jù)管理對(duì)象谴古,可以根據(jù)AVCaptureDevice創(chuàng)建對(duì)應(yīng)的AVCaptureDeviceInput對(duì)象,該對(duì)象將會(huì)被添加到AVCaptureSession中管理稠歉。
AVCaptureOutput:輸出數(shù)據(jù)管理對(duì)象,用于接收各類輸出數(shù)據(jù)汇陆,通常使用對(duì)應(yīng)的子類AVCaptureAudioDataOutput怒炸、AVCaptureStillImageOutput、AVCaptureVideoDataOutput毡代、AVCaptureFileOutput阅羹,該對(duì)象將會(huì)被添加到AVCaptureSession中管理勺疼。
注意:
1.前面幾個(gè)對(duì)象的輸出數(shù)據(jù)都是NSData類型,而AVCaptureFileOutput代表數(shù)據(jù)以文件形式輸出捏鱼,類似的执庐,AVCcaptureFileOutput也不會(huì)直接創(chuàng)建使用,通常會(huì)使用其子類:AVCaptureAudioFileOutput导梆、AVCaptureMovieFileOutput。
2.當(dāng)把一個(gè)輸入或者輸出添加到AVCaptureSession之后AVCaptureSession就會(huì)在所有相符的輸入、輸出設(shè)備之間建立連接(AVCaptionConnection):
- AVCaptureVideoPreviewLayer:相機(jī)拍攝預(yù)覽圖層勋功,是CALayer的子類弓候,使用該對(duì)象可以實(shí)時(shí)查看拍照或視頻錄制效果,創(chuàng)建該對(duì)象需要指定對(duì)應(yīng)的AVCaptureSession對(duì)象藏斩。
使用AVFoundation拍照和錄制視頻的一般步驟如下:
1.創(chuàng)建AVCaptureSession對(duì)象躏结。 2.使用AVCaptureDevice的靜態(tài)方法獲得需要使用的設(shè)備,例如拍照和錄像> 3.就需要獲得攝像頭設(shè)備狰域,錄音就要獲得麥克風(fēng)設(shè)備媳拴。 4.利用輸入設(shè)備AVCaptureDevice初始化AVCaptureDeviceInput對(duì)象。 5.初始化輸出數(shù)據(jù)管理對(duì)象兆览,如果要拍照就初始化> 6.AVCaptureStillImageOutput對(duì)象屈溉;如果拍攝視頻就初始化AVCaptureMovieFileOutput對(duì)象。 7.將數(shù)據(jù)輸入對(duì)象AVCaptureDeviceInput拓颓、數(shù)據(jù)輸出對(duì)象AVCaptureOutput添加到>媒體會(huì)話管理對(duì)象AVCaptureSession中语婴。 8.創(chuàng)建視頻預(yù)覽圖層AVCaptureVideoPreviewLayer并指定媒體會(huì)話,添加圖層到顯示容器中驶睦,調(diào)用AVCaptureSession的startRuning方法開始捕獲砰左。 9.將捕獲的音頻或視頻數(shù)據(jù)輸出到指定文件。
【思維圖】
相機(jī)屬于系統(tǒng)硬件场航,這就需要我們來手動(dòng)調(diào)用iPhone的相機(jī)硬件缠导,分為以下步驟:
【代碼詳細(xì)過程】
GGCamerManager.h#import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> #import <UIKit/UIKit.h> @protocol GGCamerManagerDelegate <NSObject> @optional; @end @interface GGCamerManager : NSObject /// 代理對(duì)象 @property (nonatomic,weak) id <GGCamerManagerDelegate> delegate; /* * 相機(jī)初始化 */ - (instancetype)initWithParentView:(UIView *)view; /** * 拍照 * * @param block 原圖 比例圖 裁剪圖 (原圖是你照相機(jī)攝像頭能拍出來的大小,比例圖是按照原圖的比例去縮小一倍溉痢,裁剪圖是你設(shè)置好的攝像范圍的圖片) */ - (void)takePhotoWithImageBlock:(void(^)(UIImage *originImage,UIImage *scaledImage,UIImage *croppedImage))block; /** * 切換前后鏡 * * isFrontCamera (void(^)(NSString *))callback; */ - (void)switchCamera:(BOOL)isFrontCamera didFinishChanceBlock:(void(^)(id))block; /** * 切換閃光燈模式 * (切換順序:最開始是auto僻造,然后是off,最后是on孩饼,一直循環(huán)) */ - (void)switchFlashMode:(UIButton*)sender; @end
GGCamerManager.m
#import "GGCamerManager.h" #import "UIImage+DJResize.h" @interface GGCamerManager () <CAAnimationDelegate> /// 捕獲設(shè)備髓削,通常是前置攝像頭,后置攝像頭镀娶,麥克風(fēng)(音頻輸入) @property (nonatomic, strong) AVCaptureDevice *device; /// AVCaptureDeviceInput 代表輸入設(shè)備立膛,他使用AVCaptureDevice 來初始化 @property (nonatomic, strong) AVCaptureDeviceInput *input; /// 輸出圖片 @property (nonatomic ,strong) AVCaptureStillImageOutput *imageOutput; /// session:由他把輸入輸出結(jié)合在一起,并開始啟動(dòng)捕獲設(shè)備(攝像頭) @property (nonatomic, strong) AVCaptureSession *session; /// 圖像預(yù)覽層,實(shí)時(shí)顯示捕獲的圖像 @property (nonatomic ,strong) AVCaptureVideoPreviewLayer *previewLayer; /// 切換前后鏡動(dòng)畫結(jié)束之后 @property (nonatomic, copy) void (^finishBlock)(void); @end @implementation GGCamerManager #pragma mark - 初始化 - (instancetype)init { self = [super init]; if (self) { [self setup]; } return self; } - (instancetype)initWithParentView:(UIView *)view { self = [super init]; if (self) { [self setup]; [self configureWithParentLayer:view]; } return self; } #pragma mark - 布置UI - (void)setup { self.session = [[AVCaptureSession alloc] init]; self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session]; self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; //加入輸入設(shè)備(前置或后置攝像頭) [self addVideoInputFrontCamera:NO]; //加入輸出設(shè)備 [self addStillImageOutput]; } /** * 添加輸入設(shè)備 * * @param front 前或后攝像頭 */ - (void)addVideoInputFrontCamera:(BOOL)front { NSArray *devices = [AVCaptureDevice devices]; AVCaptureDevice *frontCamera; AVCaptureDevice *backCamera; for (AVCaptureDevice *device in devices) { if ([device hasMediaType:AVMediaTypeVideo]) { if ([device position] == AVCaptureDevicePositionBack) { backCamera = device; } else { frontCamera = device; } } } NSError *error = nil; if (front) { AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error]; if (!error) { if ([_session canAddInput:frontFacingCameraDeviceInput]) { [_session addInput:frontFacingCameraDeviceInput]; self.input = frontFacingCameraDeviceInput; } else { NSLog(@"Couldn't add front facing video input"); } }else{ NSLog(@"你的設(shè)備沒有照相機(jī)"); } } else { AVCaptureDeviceInput *backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error]; if (!error) { if ([_session canAddInput:backFacingCameraDeviceInput]) { [_session addInput:backFacingCameraDeviceInput]; self.input = backFacingCameraDeviceInput; } else { NSLog(@"Couldn't add back facing video input"); } }else{ NSLog(@"你的設(shè)備沒有照相機(jī)"); } } if (error) { [XHToast showCenterWithText:@"您的設(shè)備沒有照相機(jī)"]; } } /** * 添加輸出設(shè)備 */ - (void)addStillImageOutput { AVCaptureStillImageOutput *tmpOutput = [[AVCaptureStillImageOutput alloc] init]; NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];//輸出jpeg tmpOutput.outputSettings = outputSettings; [_session addOutput:tmpOutput]; self.imageOutput = tmpOutput; } - (void)configureWithParentLayer:(UIView *)parent { parent.userInteractionEnabled = YES; if (!parent) { [XHToast showCenterWithText:@"請(qǐng)加入負(fù)載視圖"]; return; } self.previewLayer.frame = parent.bounds; [parent.layer addSublayer:self.previewLayer]; [self.session startRunning]; } #pragma mark - 切換閃光燈模式(切換順序:最開始是auto宝泵,然后是off好啰,最后是on,一直循環(huán)) - (void)switchFlashMode:(UIButton*)sender{ Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice"); if (!captureDeviceClass) { [XHToast showCenterWithText:@"您的設(shè)備沒有拍照功能"]; return; } NSString *imgStr = @""; AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; [device lockForConfiguration:nil]; if ([device hasFlash]) { if (device.flashMode == AVCaptureFlashModeOff) { device.flashMode = AVCaptureFlashModeOn; imgStr = @"flashing_on.png"; } else if (device.flashMode == AVCaptureFlashModeOn) { device.flashMode = AVCaptureFlashModeAuto; imgStr = @"flashing_auto.png"; } else if (device.flashMode == AVCaptureFlashModeAuto) { device.flashMode = AVCaptureFlashModeOff; imgStr = @"flashing_off.png"; } if (sender) { [sender setImage:[UIImage imageNamed:imgStr] forState:UIControlStateNormal]; } } else { [XHToast showCenterWithText:@"您的設(shè)備沒有閃光燈功能"]; } [device unlockForConfiguration]; } /** * 前后鏡 * * isFrontCamera */ - (void)switchCamera:(BOOL)isFrontCamera didFinishChanceBlock:(void(^)(id))block; { if (!_input) { if (block) { block(@""); } [XHToast showCenterWithText:@"您的設(shè)備沒有攝像頭"]; return; } if (block) { self.finishBlock = [block copy]; } CABasicAnimation *caAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; // caAnimation.removedOnCompletion = NO; // caAnimation.fillMode = kCAFillModeForwards; caAnimation.fromValue = @(0); caAnimation.toValue = @(M_PI); caAnimation.duration = 1.f; caAnimation.repeatCount = 1; caAnimation.delegate = self; caAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; [self.previewLayer addAnimation:caAnimation forKey:@"anim"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.session beginConfiguration]; [self.session removeInput:self.input]; [self addVideoInputFrontCamera:isFrontCamera]; [self.session commitConfiguration]; dispatch_async(dispatch_get_main_queue(), ^{ }); }); } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if (self.finishBlock) { self.finishBlock(); } } /** * 拍照 * * @param block */ - (void)takePhotoWithImageBlock:(void (^)(UIImage *, UIImage *, UIImage *))block { AVCaptureConnection *videoConnection = [self findVideoConnection]; if (!videoConnection) { NSLog(@"你的設(shè)備沒有照相機(jī)"); [XHToast showCenterWithText:@"您的設(shè)備沒有照相機(jī)"]; return; } __weak typeof(self) weak = self; [self.imageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; UIImage *originImage = [[UIImage alloc] initWithData:imageData]; NSLog(@"originImage=%@",originImage); CGFloat squareLength = weak.previewLayer.bounds.size.width; CGFloat previewLayerH = weak.previewLayer.bounds.size.height; // CGFloat headHeight = weak.previewLayer.bounds.size.height - squareLength; // NSLog(@"heeadHeight=%f",headHeight); CGSize size = CGSizeMake(squareLength*2, previewLayerH*2); UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill bounds:size interpolationQuality:kCGInterpolationHigh]; NSLog(@"scaledImage=%@",scaledImage); CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) / 2, (scaledImage.size.height - size.height) / 2, size.width, size.height); NSLog(@"cropFrame:%@", [NSValue valueWithCGRect:cropFrame]); UIImage *croppedImage = [scaledImage croppedImage:cropFrame]; NSLog(@"croppedImage=%@",croppedImage); UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; if (orientation != UIDeviceOrientationPortrait) { CGFloat degree = 0; if (orientation == UIDeviceOrientationPortraitUpsideDown) { degree = 180;// M_PI; } else if (orientation == UIDeviceOrientationLandscapeLeft) { degree = -90;// -M_PI_2; } else if (orientation == UIDeviceOrientationLandscapeRight) { degree = 90;// M_PI_2; } croppedImage = [croppedImage rotatedByDegrees:degree]; scaledImage = [scaledImage rotatedByDegrees:degree]; originImage = [originImage rotatedByDegrees:degree]; } if (block) { block(originImage,scaledImage,croppedImage); } }]; } /** * 查找攝像頭連接設(shè)備 * * */ - (AVCaptureConnection *)findVideoConnection { AVCaptureConnection *videoConnection = nil; for (AVCaptureConnection *connection in _imageOutput.connections) { for (AVCaptureInputPort *port in connection.inputPorts) { if ([[port mediaType] isEqual:AVMediaTypeVideo]) { videoConnection = connection; break; } } if (videoConnection) { break; } } return videoConnection; } @end
【參考文章】