iOS - 學(xué)習(xí)自定義相機(jī)拍照

【前言】

此篇文章宗旨,在于忘記時(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ù)輸出到指定文件。

【思維圖】


思維圖-1

相機(jī)屬于系統(tǒng)硬件场航,這就需要我們來手動(dòng)調(diào)用iPhone的相機(jī)硬件缠导,分為以下步驟:


思維圖-2

【代碼詳細(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

【參考文章】

  1. Video Gravity 視頻播放時(shí)的拉伸方式
  2. 30分鐘搞定iOS自定義相機(jī)
  3. iOS拍照儿奶,視頻錄制-自定義拍照框往,視頻錄制控件
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闯捎,隨后出現(xiàn)的幾起案子椰弊,更是在濱河造成了極大的恐慌,老刑警劉巖隙券,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件男应,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡娱仔,警方通過查閱死者的電腦和手機(jī)沐飘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牲迫,“玉大人耐朴,你說我怎么就攤上這事№镌鳎” “怎么了筛峭?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)陪每。 經(jīng)常有香客問我影晓,道長(zhǎng),這世上最難降的妖魔是什么檩禾? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任挂签,我火速辦了婚禮,結(jié)果婚禮上盼产,老公的妹妹穿的比我還像新娘饵婆。我一直安慰自己,他們只是感情好戏售,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布侨核。 她就那樣靜靜地躺著,像睡著了一般灌灾。 火紅的嫁衣襯著肌膚如雪搓译。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天锋喜,我揣著相機(jī)與錄音侥衬,去河邊找鬼。 笑死跑芳,一個(gè)胖子當(dāng)著我的面吹牛轴总,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播博个,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怀樟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了盆佣?” 一聲冷哼從身側(cè)響起往堡,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎共耍,沒想到半個(gè)月后虑灰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痹兜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年穆咐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片字旭。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡对湃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遗淳,到底是詐尸還是另有隱情拍柒,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布屈暗,位于F島的核電站拆讯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏养叛。R本人自食惡果不足惜种呐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望一铅。 院中可真熱鬧陕贮,春花似錦、人聲如沸潘飘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卜录。三九已至戈擒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間艰毒,已是汗流浹背筐高。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柑土。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓蜀肘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親稽屏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扮宠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容