iOS学习笔记27-摄像头

一、摄像头

在iOS中,手机摄像头的使用有以下两种方法:

  1. UIImagePickerController拍照和视频录制
  • 优点:使用方便,功能强大
  • 缺点:高度封装性,无法实现一些自定义工作
  1. AVFoundation框架实现
  • 优点:灵活性强,提供了很多现成的输入设备和输出设备,还有很多底层的内容可以供开发者使用
  • 缺点:需要和底层打交道,学习难度大,使用复杂

我们平常使用UIImagePickerController就基本可以满足了,功能确实强大,但它也有不好的一点,那就是由于它的高度封装性,如果要进行某些自定义工作就比较复杂,例如如果要做出一款类似于美颜相机的拍照界面就比较难以实现,这个时候就要考虑AVFoundation框架实现。

二、UIImagePickerController

UIImagePickerController继承于UINavigationController,属于UIKit框架,可以实现图片选取、拍照、录制视频等功能,使用起来十分方便。

1. 常用属性:
@property (nonatomic) UIImagePickerControllerSourceType sourceType;/* 拾取源类型枚举 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerSourceType) {
    UIImagePickerControllerSourceTypePhotoLibrary,//照片库
    UIImagePickerControllerSourceTypeCamera,//摄像头
    UIImagePickerControllerSourceTypeSavedPhotosAlbum//相簿
};
/* 
  媒体类型,默认情况下此数组包含kUTTypeImage,表示拍照
  如果要录像,必须设置为kUTTypeVideo(视频不带声音)或kUTTypeMovie(视频带声音)
*/
@property (nonatomic,copy) NSArray<NSString *> *mediaTypes;
@property (nonatomic) NSTimeInterval videoMaximumDuration;//视频最大录制时长,默认10s
@property (nonatomic) UIImagePickerControllerQualityType videoQuality;//视频质量
typedef NS_ENUM(NSInteger, UIImagePickerControllerQualityType) {
    UIImagePickerControllerQualityTypeHigh = 0,  //高清
    UIImagePickerControllerQualityTypeMedium,    //中等,适合WiFi传输
    UIImagePickerControllerQualityTypeLow,      //低质量,适合蜂窝网传输
    UIImagePickerControllerQualityType640x480, //640*480
    UIImagePickerControllerQualityTypeIFrame1280x720, //1280*720
    UIImagePickerControllerQualityTypeIFrame960x540, //960*540
};

@property (nonatomic)  BOOL showsCameraControls;/* 是否显示摄像头控制面板,默认为YES */
@property (nonatomic,strong) UIView *cameraOverlayView;/* 摄像头上覆盖的视图 */
@property (nonatomic) CGAffineTransform cameraViewTransform;/* 摄像头形变 */

@property (nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode;/* 摄像头捕捉模式 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraCaptureMode) {
    UIImagePickerControllerCameraCaptureModePhoto,//拍照模式
    UIImagePickerControllerCameraCaptureModeVideo//视频录制模式
};
@property (nonatomic) UIImagePickerControllerCameraDevice cameraDevice;/* 摄像头设备 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraDevice) {
    UIImagePickerControllerCameraDeviceRear,//前置摄像头
    UIImagePickerControllerCameraDeviceFront//后置摄像头
};
@property (nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode;/* 闪光灯模式 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraFlashMode) {
    UIImagePickerControllerCameraFlashModeOff  = -1,//关闭闪光灯
    UIImagePickerControllerCameraFlashModeAuto = 0,//闪光灯自动,默认
    UIImagePickerControllerCameraFlashModeOn   = 1//打开闪光灯
};
2. 常用对象方法:
- (void)takePicture; //拍照                     
- (BOOL)startVideoCapture;//开始录制视频
- (void)stopVideoCapture;//停止录制视频
3. 代理方法:
/* 媒体获取完成会调用 */
- (void)imagePickerController:(UIImagePickerController *)picker 
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
/* 取消获取会调用 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
4. 扩展函数,用于保存到相簿:
/* 保存图片到相簿 */
void UIImageWriteToSavedPhotosAlbum(
    UIImage *image,//保存的图片UIImage
    id completionTarget,//回调的执行者
    SEL completionSelector, //回调方法
    void *contextInfo//回调参数信息
);
//上面一般保存图片的回调方法为:
- (void)image:(UIImage *)image 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;

/* 判断是否能保存视频到相簿 */
BOOL UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath);
/* 保存视频到相簿 */
void UISaveVideoAtPathToSavedPhotosAlbum(
    NSString *videoPath, //保存的视频文件路径
    id completionTarget, //回调的执行者
    SEL completionSelector,//回调方法
    void *contextInfo//回调参数信息
);
//上面一般保存视频的回调方法为:
- (void)video:(NSString *)videoPath 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;
5. 使用摄像头的步骤:
  1. 创建UIImagePickerController对象
  1. 指定拾取源,拍照和录像都需要使用摄像头
  2. 指定摄像头设备,是前置的还是后置的
  3. 设置媒体类型,媒体类型定义在MobileCoreServices.framework
  4. 指定摄像头捕捉模式,录像必须先设置媒体类型再设置捕捉模式。
  5. 展示UIImagePickerController,通常以模态弹出形式打开
  6. 拍照或录像结束后,在代理方法中展示或者保存照片或视频
6. 下面是具体实例代码:
#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>

@interface ViewController () <UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (strong, nonatomic) UIImagePickerController *pickerController;//拾取控制器
@property (strong, nonatomic) IBOutlet UIImageView *showImageView;//显示图片
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化拾取控制器
    [self initPickerController];
}
/* 初始化拾取控制器 */
- (void)initPickerController{
    //创建拾取控制器
    UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
    //设置拾取源为摄像头
    pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    //设置摄像头为后置
    pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear;
    pickerController.editing = YES;//设置运行编辑,即可以点击一些拾取控制器的控件
    pickerController.delegate = self;//设置代理
    self.pickerController = pickerController;
}
#pragma mark - UI点击
/* 点击拍照 */
- (IBAction)imagePicker:(id)sender {
    //设定拍照的媒体类型
    self.pickerController.mediaTypes = @[(NSString *)kUTTypeImage];
    //设置摄像头捕捉模式为捕捉图片
    self.pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
    //模式弹出拾取控制器
    [self presentViewController:self.pickerController animated:YES completion:nil];
}
/* 点击录像 */
- (IBAction)videoPicker:(id)sender {
    //设定录像的媒体类型
    self.pickerController.mediaTypes = @[(NSString *)kUTTypeMovie];
    //设置摄像头捕捉模式为捕捉视频
    self.pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
    //设置视频质量为高清
    self.pickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
    //模式弹出拾取控制器
    [self presentViewController:self.pickerController animated:YES completion:nil];
}

#pragma mark - 代理方法
/* 拍照或录像成功,都会调用 */
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    //从info取出此时摄像头的媒体类型
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];

    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {//如果是拍照
        //获取拍照的图像
        UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
        //保存图像到相簿
        UIImageWriteToSavedPhotosAlbum(image, self, 
                  @selector(image:didFinishSavingWithError:contextInfo:), nil);
        
    } else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {//如果是录像
        //获取录像文件路径URL
        NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL];
        NSString *path = url.path;
        //判断能不能保存到相簿
        if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
            //保存视频到相簿
            UISaveVideoAtPathToSavedPhotosAlbum(path, self, 
                   @selector(video:didFinishSavingWithError:contextInfo:), nil);
        }
        
    }
    //拾取控制器弹回
    [self dismissViewControllerAnimated:YES completion:nil];
}
/* 取消拍照或录像会调用 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    NSLog(@"取消");
    //拾取控制器弹回
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - 保存图片或视频完成的回调
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
                                            contextInfo:(void *)contextInfo {
    NSLog(@"保存图片完成");
    self.showImageView.image = image;
    self.showImageView.contentMode = UIViewContentModeScaleToFill;
}

- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error
                                                 contextInfo:(void *)contextInfo {
    NSLog(@"保存视频完成");
}
@end


功能十分强大,基本满足一般的需求,使用起来也很简单。

三、AVFoundation的拍照录像

首先了解下AVFoundation做拍照和录像的相关类:
  1. AVCaptureSession
    媒体捕捉会话,负责把捕获到的音视频数据输出到输出设备上,一个会话可以有多个输入输出。
  1. AVCaptureVideoPervieewLayer
    相机拍摄预览图层,是CALayer的子类,实时查看拍照或录像效果。
  2. AVCaptureDevice
    输入设备,包括麦克风、摄像头等,可以设置一些物理设备的属性
  3. AVCaptureDeviceInput
    设备输入数据管理对象,管理输入数据
  4. AVCaptureOutput
    设备输出数据管理对象,管理输出数据,通常使用它的子类:
AVCaptureAudioDataOutput//输出音频管理对象,输出数据为NSData
AVCaptureStillImageDataOutput//输出图片管理对象,输出数据为NSData
AVCaptureVideoDataOutput//输出视频管理对象,输出数据为NSData
/* 输出文件管理对象,输出数据以文件形式输出 */
AVCaptureFileOutput  
{//子类    
       AVCaptureAudioFileOutput   //输出是音频文件
       AVCaptureMovieFileOutput   //输出是视频文件
}
拍照或录像的一般步骤为:
  1. 创建AVCaptureSession对象
  1. 使用AVCaptureDevice的类方法获得要使用的设备
  2. 利用输入设备AVCaptureDevice创建并初始化AVCaptureDeviceInput对象
  3. 初始化输出数据管理对象,看具体输出什么数据决定使用哪个AVCaptureOutput子类
  4. AVCaptureDeviceInputAVCaptureOutput添加到媒体会话管理对象AVCaptureSession
  5. 创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中
  6. 调用媒体会话AVCaptureSessionstartRunning方法开始捕获,stopRunning方法停止捕捉
  7. 将 捕获的音频或视频数据输出到指定文件

拍照使用实例如下:

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

@interface ViewController ()
@property (strong, nonatomic) AVCaptureSession *session;//媒体管理会话
@property (strong, nonatomic) AVCaptureDeviceInput *captureInput;//输入数据对象
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;//输出数据对象
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureLayer;//视频预览图层
@property (strong, nonatomic) IBOutlet UIButton *captureBtn;//拍照按钮
@property (strong, nonatomic) IBOutlet UIButton *openCaptureBtn;//打开摄像头按钮

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initCapture];
    self.openCaptureBtn.hidden = NO;
    self.captureBtn.hidden = YES;
}
/* 初始化摄像头 */
- (void)initCapture{
    //1. 创建媒体管理会话
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    self.session = session;
    //判断分辨率是否支持1280*720,支持就设置为1280*720
    if( [session canSetSessionPreset:AVCaptureSessionPreset1280x720] ) {
        session.sessionPreset = AVCaptureSessionPreset1280x720;
    }
    //2. 获取后置摄像头设备对象
    AVCaptureDevice *device = nil;
    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if (camera.position == AVCaptureDevicePositionBack) {//取得后置摄像头
            device = camera;
        }
    }
    if(!device) {
        NSLog(@"取得后置摄像头错误");
        return;
    }
    //3. 创建输入数据对象
    NSError *error = nil;
    AVCaptureDeviceInput *captureInput = [[AVCaptureDeviceInput alloc] initWithDevice:device
                                                                                error:&error];
    if (error) {
        NSLog(@"创建输入数据对象错误");
        return;
    }
    self.captureInput = captureInput;
    //4. 创建输出数据对象
    AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];
    NSDictionary *setting = @{ AVVideoCodecKey:AVVideoCodecJPEG };
    [imageOutput setOutputSettings:setting];
    self.imageOutput = imageOutput;
    //5. 添加输入数据对象和输出对象到会话中
    if ([session canAddInput:captureInput]) {
        [session addInput:captureInput];
    }
    if ([session canAddOutput:imageOutput]) {
        [session addOutput:imageOutput];
    }
    //6. 创建视频预览图层
    AVCaptureVideoPreviewLayer *videoLayer = 
              [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    self.view.layer.masksToBounds = YES;
    videoLayer.frame = self.view.bounds;
    videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    //插入图层在拍照按钮的下方
    [self.view.layer insertSublayer:videoLayer below:self.captureBtn.layer];
    self.captureLayer = videoLayer;
}
#pragma mark - UI点击
/* 点击拍照按钮 */
- (IBAction)takeCapture:(id)sender {
    //根据设备输出获得连接
    AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
    //通过连接获得设备输出的数据
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
                      completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
    {
        //获取输出的JPG图片数据
        NSData *imageData = 
              [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [UIImage imageWithData:imageData];
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);//保存到相册
        self.captureLayer.hidden = YES;
        self.captureBtn.hidden = YES;
        self.openCaptureBtn.hidden = NO;
        [self.session stopRunning];//停止捕捉
    }];
}
/* 点击打开摄像头按钮 */
- (IBAction)openCapture:(id)sender {
    self.captureLayer.hidden = NO;
    self.captureBtn.hidden = NO;
    self.openCaptureBtn.hidden = YES;
    [self.session startRunning];//开始捕捉
}
@end

录像的操作差不多,下面代码是以上面代码为基础进行修改:

  1. 比拍照多了一个音频输入,改变输出数据对象的类
  1. 需要处理视频输出代理方法
  2. 录制的方法是在输出数据对象上
1. 获取音频输入数据对象以及视频输出数据对象
//获取麦克风设备对象
AVCaptureDevice *device = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio].firstObject;
if(!device) {
    NSLog(@"取得麦克风错误");
    return;
}
//创建输入数据对象
NSError *error = nil;
AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:device
                                                                          error:&error];
if (error) {
    NSLog(@"创建输入数据对象错误");
    return;
}
//创建视频文件输出对象
AVCaptureMovieFileOutput *movieOutput = [[AVCaptureMovieFileOutput alloc] init];
self.movieOutput = movieOutput;
2. 添加进媒体管理会话中
if([session canAddInput:captureInput]) {
    [session addInput:captureInput];
    [session addInput:audioInput];
    //添加防抖动功能
    AVCaptureConnection *connection = [movieOutput connectionWithMediaType:AVMediaTypeVideo];
    if ([connection isVideoStabilizationSupported]) {
        connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    }
}
if ([session canAddOutput:movieOutput]) {
    [session addOutput:movieOutput];
}
3. 点击录像按钮
if (!self.movieOutput.isRecording) {
    NSString *outputPath = [NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
    NSURL *url = [NSURL fileURLWithPath:outputPath];//记住是文件URL,不是普通URL
    //开始录制并设置代理监控录制过程,录制文件会存放到指定URL路径下
    [self.movieOutput startRecordingToOutputFileURL:url recordingDelegate:self];
} else {
    [self.movieOutput stopRecording];//结束录制
}
4. 处理录制代理AVCaptureFileOutputRecordingDelegate
/* 开始录制会调用 */
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
                           fromConnections:(NSArray *)connections
{
    NSLog(@"开始录制");
}
/* 录制完成会调用 */
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
                            fromConnections:(NSArray *)connections
                                      error:(NSError *)error
{
    NSLog(@"完成录制");
    NSString *path = outputFileURL.path;
    //保存录制视频到相簿
    if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
        UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil);
    }
}

四、iOS音频视频使用总结


以上的表格中,我没有全部都讲,我主要讲AVFoundation框架,里面还有非常多的内容可以学习,这个框架是非常强大,有时间也可以再深入去学习。
iOS对于多媒体支持相当灵活和完善,具体开发过程到底如何选择,以上的表格仅供参考。

代码Demo点这里:LearnDemo里面的CaptureDemo

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

推荐阅读更多精彩内容