iOS 自定义相机 - 拍照

一:简介

截图1.png

AVCaptureDevice录制视频过程.png
 AVCaptureStillImageOutput    输出图片
 AVCapturePhotoOutput         照片输出流
 AVCaptureSession             把输入输出结合在一起,并开始启动捕获设备(摄像头)
                              媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出
 AVCaptureDevice              捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
 AVCaptureDeviceInput         代表输入设备,他使用AVCaptureDevice 来初始化
 AVCaptureOutput              输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、
                              AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被添加到
                              AVCaptureSession中管理。

注意:
1,前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutputAVCaptureMovieFileOutput
2,当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection):

AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。
AVCaptureDevicePositionBack  后置摄像头
AVCaptureDevicePositionFront 前置摄像头
AVCaptureMetadataOutput      当启动摄像头开始捕获输入
AVCaptureDevicePosition      摄像头位置

闪光灯和白平衡可以在生成相机时候设置
曝光要根据对焦点的光线状况而决定,所以和对焦一块写

 point为点击的位置
 AVCaptureFlashMode           闪光灯
 AVCaptureFocusMode           对焦
 AVCaptureExposureMode        曝光
 AVCaptureWhiteBalanceMode    白平衡

一定要先设置位置,再设置对焦模式

拿到的图像的大小可以自行设定

 AVCaptureSessionPreset320x240
 AVCaptureSessionPreset352x288
 AVCaptureSessionPreset640x480
 AVCaptureSessionPreset960x540
 AVCaptureSessionPreset1280x720
 AVCaptureSessionPreset1920x1080
 AVCaptureSessionPreset3840x2160

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

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

iOS相机的基本开发

目录
一、设置会话

1、初始化会话枢纽
2、创建视频输入
3、添加到会话中
4、创建音频输入
5、添加音频输入
6、设置静态图片输出
7、设置录像输出

二、开启会话

1、开启会话
2、摄像头处理
2.1 获取指定位置摄像头
2.2 获取当前活跃的摄像头
2.3 获取当前可用摄像头数和未使用的摄像头
2.4 切换摄像头
3、对焦处理
3.1 是否支持对焦
3.2 设置对焦点
4、曝光处理
4.1 是否支持曝光
4.2 设置曝光
4.3 重置对焦曝光
5、闪光灯 & 手电筒
5.1 判断是否有闪光灯
5.2 闪光灯模式
5.3 设置闪光灯模式
5.4 是否支持手电筒
5.5 手电筒模式
5.6 设置手电筒模式

三、拍摄

1、拍摄静态图片
2、写入媒体库
3、捕捉视频
3.1 判断是否录制中
3.2 开始录制
3.3 结束录制
3.4 录制结束回调
3.5 将视频写入媒体库
3.6 制作视频缩略图

ps: 这里可以看到录制的视频包。

image.png

四、具体demo
关联的摄像头页面

image.png

对应控制器的代码:

#import "HBViewController2.h"
#import <AVFoundation/AVFoundation.h>

#define kScreenBounds   [UIScreen mainScreen].bounds
#define kScreenWidth  kScreenBounds.size.width*1.0
#define kScreenHeight kScreenBounds.size.height*1.0

@interface HBViewController2 ()<AVCaptureMetadataOutputObjectsDelegate,UIAlertViewDelegate>
//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
@property(nonatomic)AVCaptureDevice *device;
//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
@property(nonatomic)AVCaptureDeviceInput *input;
//当启动摄像头开始捕获输入
@property(nonatomic)AVCaptureMetadataOutput *output;
@property (nonatomic)AVCaptureStillImageOutput *ImageOutPut;
//session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头)
@property(nonatomic)AVCaptureSession *session;
//图像预览层,实时显示捕获的图像
@property(nonatomic)AVCaptureVideoPreviewLayer *previewLayer;

@property (weak, nonatomic) IBOutlet UIButton *PhotoButton;
@property (weak, nonatomic) IBOutlet UIButton *flashButton;

@property (weak, nonatomic) IBOutlet UIButton *cancleButton;
@property (weak, nonatomic) IBOutlet UIButton *changeButton;

@property (nonatomic)UIImageView *imageView;
@property (nonatomic)UIView *greenView;
@property (nonatomic)BOOL isflashOn;
@property (nonatomic)UIImage *image;

@property (nonatomic)BOOL canCa;
@property (weak, nonatomic) IBOutlet UIView *xyView;

@end

@implementation HBViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    _canCa = [self canUserCamear];
    if (_canCa) {
        [self customCamera];
        
        
    }
    
}

- (void)customCamera{

    
#pragma mark --  相机 牌照相关
    //AVCaptureDevice捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入) 使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    //AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
    self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];
    
    //当启动摄像头开始捕获输入
    self.output = [[AVCaptureMetadataOutput alloc]init];
    self.ImageOutPut = [[AVCaptureStillImageOutput alloc] init];
    
    //session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头) 生成会话,用来结合输入输出
    self.session = [[AVCaptureSession alloc]init];
    
    if ([self.session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
         self.session.sessionPreset = AVCaptureSessionPreset1280x720;
    }
    
    if ([self.session canAddInput:self.input]) {
        [self.session addInput:self.input];
    }
    
    if ([self.session canAddOutput:self.ImageOutPut]) {
        [self.session addOutput:self.ImageOutPut];
    }
    
    //使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示 图像预览层,实时显示捕获的图像
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
    
    self.previewLayer.frame = CGRectMake(0, 0,  kScreenWidth-20*2,  240);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    //layer 加到这里 就是把摄像头捕捉的视频画面 给到这个view去显示   
    [self.xyView.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];
    }
    
#pragma mark -- 四个按钮  和 绿色框
    self.view.backgroundColor = [UIColor systemGroupedBackgroundColor];
    
    self.greenView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 60, 60)];
    self.greenView .layer.borderWidth = 1.0;
    self.greenView .layer.borderColor =[UIColor greenColor].CGColor;
    self.greenView .backgroundColor = [UIColor clearColor];
    [self.xyView addSubview: self.greenView ];
    self.greenView .hidden = YES;
    
    
    [self.view bringSubviewToFront: self.xyView];
    
    [self.view bringSubviewToFront:self.cancleButton];
    [self.view bringSubviewToFront:self.PhotoButton];
    [self.view bringSubviewToFront:self.changeButton];
    [self.view bringSubviewToFront:self.flashButton];
    [self.view bringSubviewToFront: self.greenView ];

    
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapGesture:)];
    [self.xyView addGestureRecognizer:tapGesture];
}


#pragma mark - 按钮点击事件
- (IBAction)click:(UIButton *)sender {
    
    switch (sender.tag) {
            
        case 0:
        {
            //拍照
            AVCaptureConnection * videoConnection = [self.ImageOutPut connectionWithMediaType:AVMediaTypeVideo];
            if (!videoConnection) {
                NSLog(@"take photo failed!");
                return;
            }
            
            [self.ImageOutPut captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
                if (imageDataSampleBuffer == NULL) {
                    return;
                }
                NSData * imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                self.image = [UIImage imageWithData:imageData];
                [self.session stopRunning];
                [self saveImageToPhotoAlbum:self.image];
                self.imageView = [[UIImageView alloc]initWithFrame:self.previewLayer.frame];
                [self.xyView insertSubview:_imageView belowSubview:_PhotoButton];
                self.imageView.layer.masksToBounds = YES;
                self.imageView.image = _image;
                NSLog(@"image size = %@",NSStringFromCGSize(self.image.size));
            }];
        }
            break;
        case 1:
        {
            //取消
            
            [self.imageView removeFromSuperview];
            [self.session stopRunning];
            
            [self.navigationController popViewControllerAnimated:YES];
        }
            break;
        case 2:
        {
            //切换
            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:_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);
                }
                
            }
        }
            break;
        case 3:
        {
            //闪光灯
            if ([_device lockForConfiguration:nil]) {
                if (_isflashOn) {
                    if ([_device isFlashModeSupported:AVCaptureFlashModeOff]) {
                        [_device setFlashMode:AVCaptureFlashModeOff];
                        _isflashOn = NO;
                        [_flashButton setTitle:@"闪光灯关" forState:UIControlStateNormal];
                    }
                }else{
                    if ([_device isFlashModeSupported:AVCaptureFlashModeOn]) {
                        [_device setFlashMode:AVCaptureFlashModeOn];
                        _isflashOn = YES;
                        [_flashButton setTitle:@"闪光灯开" forState:UIControlStateNormal];
                    }
                }
                
                [_device unlockForConfiguration];
            }
        }
            break;
            
        default:
            break;
    }
}


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

#pragma mark -- 绿框 触碰
- (void)tapGesture:(UITapGestureRecognizer*)gesture{
    CGPoint point = [gesture locationInView:gesture.view];
    [self focusAtPoint: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];
        _greenView.center = point;
        _greenView.hidden = NO;
        [UIView animateWithDuration:0.3 animations:^{
            _greenView.transform = CGAffineTransformMakeScale(1.25, 1.25);
        }completion:^(BOOL finished) {
            [UIView animateWithDuration:0.5 animations:^{
                _greenView.transform = CGAffineTransformIdentity;
            } completion:^(BOOL finished) {
                _greenView.hidden = YES;
            }];
        }];
    }
    
}

#pragma mark -  保存至相册
- (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 = @"保存图片成功" ;
    }
    
    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"保存图片结果提示" message:msg preferredStyle:UIAlertControllerStyleAlert];
    
    UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        NSLog(@"点击了按钮1,进入按钮1的事件");
        
        [self.imageView removeFromSuperview];
        [self.session startRunning];
        
        [self.navigationController popViewControllerAnimated:YES];
    }];
    
    UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        NSLog(@"点击了取消");
        
        [self.imageView removeFromSuperview];
        [self.session stopRunning];
        
        [self.navigationController popViewControllerAnimated:YES];
    }];
    
    [alertController addAction:action1];
    [alertController addAction:action2];
    
    [self presentViewController:alertController animated:YES completion:nil];
    
}

#pragma mark - 检查相机权限
- (BOOL)canUserCamear{
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (authStatus == AVAuthorizationStatusDenied) {
        
        UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"请打开相机权限" message:@"设置-隐私-相机" preferredStyle:UIAlertControllerStyleAlert];
        
        UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            
            NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
            if([[UIApplication sharedApplication] canOpenURL:url]) {
                [[UIApplication sharedApplication] openURL:url options:nil completionHandler:^(BOOL success) {
                    
                }];
            }
        }];
        
        UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            NSLog(@"点击了取消");
            
            
        }];
        
        [alertController addAction:action1];
        [alertController addAction:action2];
        
        [self presentViewController:alertController animated:YES completion:nil];
        
        return NO;
    }
    else{
        return YES;
    }
    return YES;
}


@end

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

推荐阅读更多精彩内容