iOS - 学习自定义相机拍照

【前言】

此篇文章宗旨,在于忘记时,方便查阅。

【原因】

 为什么要自定义相机拍照?因为系统的相机拍照无法满足项目的需求。

【了解】

首先了解一下使用AVFoundation做拍照和视频录制开发用到的相关类:
  • AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出:如图:


  • AVCaptureDevice:输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。

  • AVCaptureDeviceInput:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。

  • AVCaptureOutput:输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被添加到AVCaptureSession中管理。

注意:

1.前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。

2.当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection):


  • AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。

使用AVFoundation拍照和录制视频的一般步骤如下:

1.创建AVCaptureSession对象。
2.使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像> 
3.就需要获得摄像头设备,录音就要获得麦克风设备。
4.利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
5.初始化输出数据管理对象,如果要拍照就初始化> 
6.AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
7.将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到>媒体会话管理对象AVCaptureSession中。
8.创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession的startRuning方法开始捕获。
9.将捕获的音频或视频数据输出到指定文件。

【思维图】


思维图-1

相机属于系统硬件,这就需要我们来手动调用iPhone的相机硬件,分为以下步骤:


思维图-2

【代码详细过程】
GGCamerManager.h

 #import <Foundation/Foundation.h>
 #import <AVFoundation/AVFoundation.h>
 #import <UIKit/UIKit.h>

 @protocol GGCamerManagerDelegate <NSObject>

 @optional;

 @end

 @interface GGCamerManager : NSObject
 /// 代理对象
 @property (nonatomic,weak) id <GGCamerManagerDelegate> delegate;
 /*
  * 相机初始化
  */
 - (instancetype)initWithParentView:(UIView *)view;
 /**
  *  拍照
  *
  *  @param block 原图 比例图 裁剪图 (原图是你照相机摄像头能拍出来的大小,比例图是按照原图的比例去缩小一倍,裁剪图是你设置好的摄像范围的图片)
  */
 - (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,一直循环)
  */
 - (void)switchFlashMode:(UIButton*)sender;

 @end

GGCamerManager.m

 #import "GGCamerManager.h"
 #import "UIImage+DJResize.h"

 @interface GGCamerManager () <CAAnimationDelegate>
 /// 捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
 @property (nonatomic, strong) AVCaptureDevice *device;
 /// AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
 @property (nonatomic, strong) AVCaptureDeviceInput *input;
 /// 输出图片
 @property (nonatomic ,strong) AVCaptureStillImageOutput *imageOutput;
 /// session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头)
 @property (nonatomic, strong) AVCaptureSession *session;
 /// 图像预览层,实时显示捕获的图像
 @property (nonatomic ,strong) AVCaptureVideoPreviewLayer *previewLayer;
 /// 切换前后镜动画结束之后
 @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;
     //加入输入设备(前置或后置摄像头)
     [self addVideoInputFrontCamera:NO];
     //加入输出设备
     [self addStillImageOutput];

 }

  /**
  *  添加输入设备
  *
  *  @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(@"你的设备没有照相机");
   }
     } 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(@"你的设备没有照相机");
   }
     }
     if (error) {
   [XHToast showCenterWithText:@"您的设备没有照相机"];
     }
 }

 /**
  *  添加输出设备
  */
 - (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:@"请加入负载视图"];
       return;
   }
 self.previewLayer.frame = parent.bounds;
 [parent.layer addSublayer:self.previewLayer];
 [self.session startRunning];
 }

 #pragma mark - 切换闪光灯模式(切换顺序:最开始是auto,然后是off,最后是on,一直循环)
 - (void)switchFlashMode:(UIButton*)sender{

     Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
     if (!captureDeviceClass) {
         [XHToast showCenterWithText:@"您的设备没有拍照功能"];
   
   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:@"您的设备没有闪光灯功能"];
   }
   [device unlockForConfiguration];
 }

 /**
  *  前后镜
  *
  *  isFrontCamera
  */
 - (void)switchCamera:(BOOL)isFrontCamera didFinishChanceBlock:(void(^)(id))block;
 {
     if (!_input) {
   
     if (block) {
       block(@"");
   }
   [XHToast showCenterWithText:@"您的设备没有摄像头"];
 
   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(@"你的设备没有照相机");
    [XHToast showCenterWithText:@"您的设备没有照相机"];

 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);
   }
    }];

 }

 /**
   *  查找摄像头连接设备
  *
 *
   */
 - (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

【参考文章】

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

推荐阅读更多精彩内容