30分钟搞定iOS自定义相机

作者: _南山忆 | 来源:发表于2016-04-06 09:53 被阅读20317次

    最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究了一下相机的自定义。
      相机属于系统硬件,这就需要我们来手动调用iPhone的相机硬件,分为以下步骤:


    2016-03-30 上午9.51.09.png

    1、首先声明以下对象

    #import <AVFoundation/AVFoundation.h>
    //捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
    @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;
    

    2、初始化各个对象

    - (void)cameraDistrict
    {
    //    AVCaptureDevicePositionBack  后置摄像头
    //    AVCaptureDevicePositionFront 前置摄像头
       self.device = [self cameraWithPosition:AVCaptureDevicePositionFront];
        self.input = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];
        
        self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
        
        self.session = [[AVCaptureSession alloc] init];
        //     拿到的图像的大小可以自行设定
        //    AVCaptureSessionPreset320x240
        //    AVCaptureSessionPreset352x288
        //    AVCaptureSessionPreset640x480
        //    AVCaptureSessionPreset960x540
        //    AVCaptureSessionPreset1280x720
        //    AVCaptureSessionPreset1920x1080
        //    AVCaptureSessionPreset3840x2160
        self.session.sessionPreset = AVCaptureSessionPreset640x480;
        //输入输出设备结合
        if ([self.session canAddInput:self.input]) {
            [self.session addInput:self.input];
        }
        if ([self.session canAddOutput:self.imageOutput]) {
            [self.session addOutput:self.imageOutput];
        }
        //预览层的生成
        self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
        self.previewLayer.frame = CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT-64);
        self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        [self.view.layer addSublayer:self.previewLayer];
        //设备取景开始
        [self.session startRunning];
        if ([_device lockForConfiguration:nil]) {
        //自动闪光灯,
            if ([_device isFlashModeSupported:AVCaptureFlashModeAuto]) {
                [_device setFlashMode:AVCaptureFlashModeAuto];
            }
            //自动白平衡,但是好像一直都进不去
            if ([_device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance]) {
                [_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeAutoWhiteBalance];
            }
            [_device unlockForConfiguration];
        }
        
    }
    

    根据前后置位置拿到相应的摄像头:

    - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position{
        NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
        for ( AVCaptureDevice *device in devices )
            if ( device.position == position ){
                return device;
            }
        return nil;
    }
    

    3、拍照拿到相应图片:

    - (void)photoBtnDidClick
    {
        AVCaptureConnection *conntion = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
          if (!conntion) {
              NSLog(@"拍照失败!");
              return;
              }
        [self.imageOutput captureStillImageAsynchronouslyFromConnection:conntion completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
            if (imageDataSampleBuffer == nil) {
                return ;
              }
            NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            self.image = [UIImage imageWithData:imageData];
            [self.session stopRunning];
            [self.view addSubview:self.cameraImageView];
    }
    

    4、保存照片到相册:

    #pragma - 保存至相册
    - (void)saveImageToPhotoAlbum:(UIImage*)savedImage
    {
    
        UIImageWriteToSavedPhotosAlbum(savedImage, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
    
    }
    // 指定回调方法
    
    - (void)image: (UIImage *) image didFinishSavingWithError: (NSError *) error contextInfo: (void *) contextInfo
    
    {
        NSString *msg = nil ;
        if(error != NULL){
            msg = @"保存图片失败" ;
        }else{
            msg = @"保存图片成功" ;
        }
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"保存图片结果提示"
                                                message:msg
                                                delegate:self
                                              cancelButtonTitle:@"确定"
                                              otherButtonTitles:nil];
        [alert show];
    }
    

    5、前后置摄像头的切换

    - (void)changeCamera{
        NSUInteger cameraCount = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
        if (cameraCount > 1) {
            NSError *error;
            //给摄像头的切换添加翻转动画
            CATransition *animation = [CATransition animation];
            animation.duration = .5f;
            animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
            animation.type = @"oglFlip";
    
            AVCaptureDevice *newCamera = nil;
            AVCaptureDeviceInput *newInput = nil;
      //拿到另外一个摄像头位置
            AVCaptureDevicePosition position = [[_input device] position];
            if (position == AVCaptureDevicePositionFront){
                newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
                animation.subtype = kCATransitionFromLeft;//动画翻转方向
            }
            else {
                newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
                animation.subtype = kCATransitionFromRight;//动画翻转方向
            }
            //生成新的输入
            newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
            [self.previewLayer addAnimation:animation forKey:nil];
            if (newInput != nil) {
                [self.session beginConfiguration];
                [self.session removeInput:self.input];
                if ([self.session canAddInput:newInput]) {
                    [self.session addInput:newInput];
                    self.input = newInput;
                    
                } else {
                    [self.session addInput:self.input];
                }
                [self.session commitConfiguration];
             
            } else if (error) {
                NSLog(@"toggle carema failed, error = %@", error);
            }
        }
    }
    

    6、相机的其它参数设置

    //AVCaptureFlashMode  闪光灯
    //AVCaptureFocusMode  对焦
    //AVCaptureExposureMode  曝光
    //AVCaptureWhiteBalanceMode  白平衡
    //闪光灯和白平衡可以在生成相机时候设置
    //曝光要根据对焦点的光线状况而决定,所以和对焦一块写
    //point为点击的位置
    - (void)focusAtPoint:(CGPoint)point{
        CGSize size = self.view.bounds.size;
        CGPoint focusPoint = CGPointMake( point.y /size.height ,1-point.x/size.width );
        NSError *error;
        if ([self.device lockForConfiguration:&error]) {
            //对焦模式和对焦点
            if ([self.device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
                [self.device setFocusPointOfInterest:focusPoint];
                [self.device setFocusMode:AVCaptureFocusModeAutoFocus];
            }
            //曝光模式和曝光点
            if ([self.device isExposureModeSupported:AVCaptureExposureModeAutoExpose ]) {
                [self.device setExposurePointOfInterest:focusPoint];
                [self.device setExposureMode:AVCaptureExposureModeAutoExpose];
            }
            
            [self.device unlockForConfiguration];
            //设置对焦动画
            _focusView.center = point;
            _focusView.hidden = NO;
            [UIView animateWithDuration:0.3 animations:^{
                _focusView.transform = CGAffineTransformMakeScale(1.25, 1.25);
            }completion:^(BOOL finished) {
                [UIView animateWithDuration:0.5 animations:^{
                    _focusView.transform = CGAffineTransformIdentity;
                } completion:^(BOOL finished) {
                    _focusView.hidden = YES;
                }];
            }];
        }
        
    }
    

    7、遇到的一些坑和解决办法

    1) 前后置摄像头的切换

    前后值不能切换,各种尝试找了半天没找到有原因。后来发现我在设置图片尺寸的时候设置为1080P [self.session canSetSessionPreset: AVCaptureSessionPreset1920x1080] ,前置摄像头并不支持这么大的尺寸,所以就不能切换前置摄像头。我验证了下 前置摄像头最高支持720P,720P以内可自由切换。  当然也可以在前后置摄像头切换的时候,根据前后摄像头来设置不同的尺寸,这里不在赘述。

    2)焦点位置

    CGPoint focusPoint = CGPointMake( point.y /size.height ,1-point.x/size.width );
    setExposurePointOfInterest:focusPoint 函数后面Point取值范围是取景框左上角(0,0)到取景框右下角(1,1)之间。官方是这么写的:
      The value of this property is a CGPoint that determines the receiver's focus point of interest, if it has one. A value of (0,0) indicates that the camera should focus on the top left corner of the image, while a value of (1,1) indicates that it should focus on the bottom right. The default value is (0.5,0.5).
      我也试了按这个来但位置就是不对,只能按上面的写法才可以。前面是点击位置的y/PreviewLayer的高度,后面是1-点击位置的x/PreviewLayer的宽度

    3)对焦和曝光

    我在设置对焦是 先设置了模式setFocusMode,后设置对焦位置,就会导致很奇怪的现象,对焦位置是你上次点击的位置。所以一定要先设置位置,再设置对焦模式。
      曝光同上

    8、写在最后

    附上demo常用到的基本就这么多,写的并不完善,有什么不对的,欢迎大家批评指正,共同学习。

    相关文章

      网友评论

      • Alan龙马:大神,写得好极了,1080P 前后摄像头切换 ,困扰了我一天,点赞赞赞赞👍👍👍👍
      • 0db99e947190:关于设置焦点的位置不对这个问题. 原因是因为相机输出的画面对应竖屏显示旋转了-90度, 如果是横屏显示就是正常的没有旋转的.
      • 哄哄的薇薇:很棒,尤其后边那几个坑,如果我能早点看到您的文章就好了,手动点赞!
      • 嫌疑人zx:你好你好,我想请教一个问题,如果我想像原生一样,当点下拍摄按钮的时候,我希望画面闪一下,我该如何做呐
      • 960d2a7a31bd:能问下楼主是怎么想到这些的吗?我怎么就一点都想不到呢?
        _南山忆:@2017I 看文档,查资料:smile:
      • 5ec1da87f063:谢谢.刚好用到
      • 518edc108cec:非常感激,客户零时说需要改自定义相机,时间紧直接拿你的Demo改下界面就可以用了!
        _南山忆:@青山绿水_6be4 外包的:joy:
      • a69d93030256:这些都写在主线程是不是操作次数多了 会卡?
      • 洁简:相机声音怎么设置? 还有前置摄像头拍出来的会成镜像模式
      • 梵高的老巫婆:请教个问题 在拿到拍照图片的地方 captureStillImageAsynchronouslyFromConnection这个地方报errorcode = 11803 是什么原因呢
      • MakeThatChange:加油~
      • 彩虹丶直至黑白:不错,代码很简洁,感谢分享~
      • smarp_319f:楼主感谢分享,十分受用!有个小需求能否帮忙解答一下,就是我在对焦之后自动获取当前的图片这个可以实现吗,对焦有没有类似于生命周期的方法?
      • 哟_fc88:为什么我在这下载的demo运行后点击切换,切换不了前摄像头啊
        a69d93030256:@哟_fc88 我也去掉了 直接设置 [self.session setSessionPreset:AVCaptureSessionPresetPhoto];
        哟_fc88:但是把if语句canSetSessionPreset注掉又可以le
      • Sumency:旋转屏幕以后有问题,不是全屏了
      • 知行合一认知升级:感谢分享, 写的特别详细, 让我看完就想赞一个

      • 求墨者:楼主,我爱你
      • 4c64df4bcc63:有个问题, 发现这个相机, 不想用了, 删除不了,或者是退出 大家有好的办法吗。。
      • 小粥:最近有这方面的需求,看了一下很有帮助,谢谢楼主
      • nemie:赞赞赞
      • 75724f2f1287:找了很多代码看了,一直看不明白,在这里看明白了,谢谢楼主.
        _南山忆:@bg0422 系统相机没办法定制啊,除非自己写
        bg0422:@_南山忆 怎么能把系统相机放控件的黑色区域去掉 全屏显示相机啊
        _南山忆:不客气呢。哈哈,很高兴能帮到你
      • 菊上一枝梅:感谢分享, 写的特别详细, 让我看完就想赞一个
        _南山忆:@Regret_V 哈哈,谢谢:smile:
      • 7c8dd4534215:楼主,在下请教个问题,利用这个AVCaptureSessionPreset3840x2160可以设置照片的预设像素,iPhone自带相机拍出来的照片像素是3024x4032,还有拍出来照片的宽度明显不一样,我想问造成这种差异的原因在哪里?有没有办法让自定义相机拍出来的照片跟系统自带的相机拍出来的照片尺寸和像素都是一样的?
        89848af90932:根据像素可以得到你的previewLayer的单位像素,进行换算以后再自行裁剪
      • 沐晨__:请教个问题
        AVCaptureSessionPreset320x240 这个参数你可以设置上吗?
        在我这是被注释掉了 :joy:
      • Evyn_:感谢楼主精彩细致的分享,受益匪浅。我想请问一个问题,就是我要做做一个手表试戴的功能,调用摄像头的时候,界面上出现一个手表图片,再把手腕照进相机,请问怎么合成已经拍照的照片 并保存在相册啊? 是在saveImageToPhotoAlbum这个方法里面进行相片合成吗
        _南山忆:@我的老师罗振宇 :smile:客气了
        Evyn_:@_南山忆 非常感谢,昨天问完,想着试了下就ok了!
        _南山忆:@我的老师罗振宇 可以先合成再保存至相册,合成用的方法和在二维码上加logo图是一样的,你可以参考我的二维码篇文章里面的方法,用图形上下文画到一块。但是图片位置就看你自己来计算调整了
      • _常小仙儿:可以,感谢分享
      • 做一个有爱的伸手党:没有保存吗 楼主
        _南山忆:@做一个有爱的伸手党 以添加图片保存至相册:smile:
      • if_you_like:非常的好!感谢分享!
      • 独乐乐:感谢分享
      • 时间shiwo9:谢谢分享
      • 喵小帅:为什么发现你这里贴出来的代码和demo里面的代码完全不同。。很多都找不到
        _南山忆:@喵小帅 基本上都一样啊,功能都在里面,文章里面贴的是我原来在项目里面写的,Demo是后来写的。只是有些名字不一样
      • lumic000:这个可以,比网上的demo好多了

      本文标题:30分钟搞定iOS自定义相机

      本文链接:https://www.haomeiwen.com/subject/tiwslttx.html