原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、使用 UIImagePickerController 拍照和视频录制
- 1、UIImagePickerController 简介
- 2、UIImagePickerController 属性和方法
- 3、UIImagePickerController Demo演示
- 二、使用 AVFoundation 拍照和录制视频
- 1、AVFoundation 简介
- 2、摄像头录制工具类提供的属性和方法
- 3、录制工具类的Session
- 4、切换前后摄像头
- 5、使用捕捉设备进行聚焦和曝光
- 6、实现摄像头手电筒和闪关灯模式的开启关闭
- 7、静态图片的拍摄
- 8、视频录制实现
- 9、实现预览录制内容功能
- Demo
- 参考文献
一、使用 UIImagePickerController 拍照和视频录制
1、UIImagePickerController 简介
UIImagePickerController
继承于UINavigationController
平时主要使用它来选取照片,其实UIImagePickerController
的功能不仅如此,它还可以用来拍照和录制视频。当然这个过程中有很多细节可以设置,例如是否显示拍照控制面板,拍照后是否允许编辑等等。要用UIImagePickerController
来拍照或者录制视频通常可以分为如下步骤:
- 创建
UIImagePickerController
对象。 - 指定拾取源,平时选择照片时使用的拾取源是照片库或者相簿,此刻需要指定为摄像头类型。
- 指定摄像头,前置摄像头或者后置摄像头。
- 设置媒体类型
mediaType
,注意如果是录像必须设置,如果是拍照此步骤可以省略,因为mediaType
默认包含kUTTypeImage
(注意媒体类型定义在MobileCoreServices.framework
中) - 指定捕获模式,拍照或者录制视频。(视频录制时必须先设置媒体类型再设置捕获模式)
- 展示
UIImagePickerController
(通常以模态窗口形式打开)。 - 拍照和录制视频结束后在代理方法中展示/保存照片或视频。
2、UIImagePickerController 属性和方法
a、枚举类型
数据源类型,sourceType是枚举类型
@property(nonatomic) UIImagePickerControllerSourceType sourceType
UIImagePickerControllerSourceTypePhotoLibrary:照片库,默认值
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
视频质量,枚举类型
@property(nonatomic) UIImagePickerControllerQualityType videoQuality
UIImagePickerControllerQualityTypeHigh:高清质量
UIImagePickerControllerQualityTypeMedium:中等质量,适合WiFi传输
UIImagePickerControllerQualityTypeLow:低质量,适合蜂窝网传输
UIImagePickerControllerQualityType640x480:640*480
UIImagePickerControllerQualityTypeIFrame1280x720:1280*720
UIImagePickerControllerQualityTypeIFrame960x540:960*540
摄像头捕获模式,捕获模式是枚举类型
@property(nonatomic) UIImagePickerControllerCameraCaptureMode
UIImagePickerControllerCameraCaptureModePhoto:拍照模式
UIImagePickerControllerCameraCaptureModeVideo:视频录制模式
摄像头设备,cameraDevice是枚举类
@property(nonatomic) UIImagePickerControllerCameraDevice cameraDevice
UIImagePickerControllerCameraDeviceRear:前置摄像头
UIImagePickerControllerCameraDeviceFront:后置摄像头
闪光灯模式,枚举类型
@property(nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode
UIImagePickerControllerCameraFlashModeOff:关闭闪光灯
UIImagePickerControllerCameraFlashModeAuto:闪光灯自动
UIImagePickerControllerCameraFlashModeOn:打开闪光灯
b、常用属性
@property(nonatomic,copy) NSArray *mediaTypes //媒体类型,默认情况下此数组包含kUTTypeImage,所以拍照时可以不用设置;但是当要录像的时候必须设置,可以设置为kUTTypeVideo(视频,但不带声音)或者kUTTypeMovie(视频并带有声音)
@property(nonatomic) NSTimeInterval videoMaximumDuration //视频最大录制时长,默认为10 s
@property(nonatomic) BOOL showsCameraControls //是否显示摄像头控制面板,默认为YES
@property(nonatomic,retain) UIView *cameraOverlayView //摄像头上覆盖的视图,可用通过这个视频来自定义拍照或录像界面
@property(nonatomic) CGAffineTransform cameraViewTransform //摄像头形变
b、常用方法
类方法
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType //指定的源类型是否可用
+ (BOOL)isCameraDeviceAvailable:(UIImagePickerControllerCameraDevice)cameraDevice //指定的摄像头是否可用
+ (NSArray *)availableCaptureModesForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice //获得指定摄像头上的可用捕获模式
+ (NSArray *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType //指定的源设备上可用的媒体类型,一般就是图片和视频
+ (BOOL)isFlashAvailableForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice //指定摄像头的闪光灯是否可用
对象方法
- (void)takePicture //编程方式拍照
- (BOOL)startVideoCapture //编程方式录制视频
- (void)stopVideoCapture //编程方式停止录制视频
代理方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info //媒体拾取完成
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker //取消拾取
扩展方法(主要用于保存照片、视频到相簿)
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo) //保存照片到相簿
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath) //能否将视频保存到相簿
UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo) //保存视频到相簿
3、Demo演示
运行效果
拍摄视频
拍摄相片
下面就以一个示例展示如何使用UIImagePickerController
来拍照和录制视频,下面的程序中只要将_isVideo
设置为YES
就是视频录制模式,录制完后在主视图控制器中自动播放;如果将_isVideo
设置为NO
则为拍照模式,拍照完成之后在主视图控制器中显示拍摄的照片。
- (void)viewDidLoad
{
[super viewDidLoad];
// 通过这里设置当前程序是拍照还是录制视频
self.isVideo = YES;
}
a、需要使用到的头文件和属性
#import <MobileCoreServices/MobileCoreServices.h>
#import <AVFoundation/AVFoundation.h>
@interface UIImagePickerControllerDemo ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (assign,nonatomic) BOOL isVideo; //是否录制视频,如果为1表示录制视频,0代表拍照
@property (strong,nonatomic) UIImagePickerController *imagePicker;
@property (strong,nonatomic) UIImageView *photo; //照片展示视图
@property (strong,nonatomic) AVPlayer *player; //播放器,用于录制完视频后播放视频
@end
b、创建 imagePicker
通过懒加载的方式创建了 imagePicker
,并配置其属性。
- (UIImagePickerController *)imagePicker
{
if (!_imagePicker)
{
_imagePicker = [[UIImagePickerController alloc] init];
// 设置image picker的来源,这里设置为摄像头
_imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
// 设置使用哪个摄像头,这里设置为后置摄像头
_imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
if (self.isVideo)
{
_imagePicker.mediaTypes = @[(NSString *)kUTTypeMovie];
_imagePicker.videoQuality = UIImagePickerControllerQualityTypeIFrame1280x720;
// 设置摄像头模式(拍照,录制视频)
_imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
}
else
{
_imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
}
// 允许编辑
_imagePicker.allowsEditing = YES;
// 设置代理,检测操作
_imagePicker.delegate=self;
}
return _imagePicker;
}
c、UIImagePickerController 的代理方法
取消拍照或者录制视频时调用的方法
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
NSLog(@"取消");
}
拍照或者录制视频完成时候调用的方法。
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// 获取媒体类型
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
......
// 录制完成后退出拍摄界面
[self dismissViewControllerAnimated:YES completion:nil];
}
实现拍照的代码
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) //如果是拍照
{
NSLog(@"拍照");
UIImage *image;
// 如果允许编辑则获得编辑后的照片,否则获取原始照片
if (self.imagePicker.allowsEditing)
{
// 获取编辑后的照片
image = [info objectForKey:UIImagePickerControllerEditedImage];
}
else
{
// 获取原始照片
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
// 显示照片
[self.photo setImage:image];
// 保存到相簿
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
实现录制视频的代码
else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]) //如果是录制视频
{
NSLog(@"录制视频");
// 视频路径
NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL];
NSString *urlStr = [url path];
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr))
{
// 保存视频到相簿,注意也可以使用ALAssetsLibrary来保存
UISaveVideoAtPathToSavedPhotosAlbum(urlStr, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);//保存视频到相簿
}
}
d、点击拍照按钮触发拍照或者录制视频
- (void)takeClick
{
[self presentViewController:self.imagePicker animated:YES completion:nil];
}
二、使用 AVFoundation 拍照和录制视频
运行效果
1、AVFoundation 简介
a、AVFoundation 的作用
UIImagePickerController
确实强大,但由于它的高度封装性,要进行某些自定义工作就比较复杂了。例如要做出一款类似于美颜相机的拍照界面或者抖音小视频和虎牙直播就比较难以实现了,此时就可以考虑使用AVFoundation
来实现。因为AVFoundation
中包含了很多和底层输入、输出设备打交道的类,依靠这些类开发人员面对的不再是封装好的音频播放器AVAudioPlayer
、录音机(AVAudioRecorder
)、视频(包括音频)播放器AVPlayer
,而是输入设备(例如麦克风、摄像头)、输出设备(图片、视频)等。
b、使用AVFoundation做拍照和视频录制开发用到的相关类
AVCaptureSession
媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession
可以有多个输入输出。
AVCaptureDevice
输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
AVCaptureDeviceInput
设备输入数据管理对象,可以根据AVCaptureDevice
创建对应的AVCaptureDeviceInput
对象,该对象将会被添加到AVCaptureSession
中管理。
AVCaptureOutput
输出数据管理对象,用于接收各类输出数据,该对象将会被添加到AVCaptureSession
中管理。通常使用对应的子类AVCaptureAudioDataOutput
、AVCaptureStillImageOutput
、AVCaptureVideoDataOutput
、AVCaptureFileOutput
。前面几个对象的输出数据都是NSData
类型,而AVCaptureFileOutput
代表数据以文件形式输出。
类似的,AVCcaptureFileOutput
也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput
、AVCaptureMovieFileOutput
。
AVCaptureConnection
当把一个输入或者输出添加到AVCaptureSession
之后,AVCaptureSession
就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection
)。
AVCaptureVideoPreviewLayer
相机拍摄预览图层,是CALayer
的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession
对象。
c、使用 AVFoundation 拍照和录制视频的步骤
- 创建
AVCaptureSession
对象。 - 使用
AVCaptureDevice
的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。 - 利用输入设备
AVCaptureDevice
初始化AVCaptureDeviceInput
对象。 - 初始化输出数据管理对象,如果要拍照就初始化
AVCaptureStillImageOutput
对象,如果拍摄视频就初始化AVCaptureMovieFileOutput
对象。 - 将数据输入对象
AVCaptureDeviceInput
、数据输出对象AVCaptureOutput
添加到媒体会话管理对象AVCaptureSession
中。 - 创建视频预览图层
AVCaptureVideoPreviewLayer
并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession
的startRuning
方法开始捕获。 - 将捕获的音频或视频数据输出到指定文件。
d、在 info.plist 中添加权限说明
需要注意的是访问到相册、麦克风、相机就需要修改 plist
权限,否则会导致项目崩溃。
- 相机使用权限:Privacy - Camera Usage Description
- 麦克风使用权限:Privacy - Microphone Usage Description
- 相册使用权限:Privacy - Photo Library Usage Description
2、摄像头录制工具类提供的属性和方法
a、录制发生错误时用于处理的委托方法
发生错误事件时需要在对象委托上调用一些方法来处理。
@protocol THCameraControllerDelegate <NSObject>
// 设备错误
- (void)deviceConfigurationFailedWithError:(NSError *)error;
// 媒体捕捉错误
- (void)mediaCaptureFailedWithError:(NSError *)error;
// 写入时错误
- (void)assetLibraryWriteFailedWithError:(NSError *)error;
@end
b、录制工具类头文件中提供的可用方法
用于设置、配置视频捕捉会话
- (BOOL)setupSession:(NSError **)error;
- (void)startSession;
- (void)stopSession;
切换不同的摄像头
- (BOOL)switchCameras;//切换不同的摄像头
- (BOOL)canSwitchCameras;//是否能切换
@property (nonatomic, readonly) NSUInteger cameraCount;//摄像头个数
@property (nonatomic, readonly) BOOL cameraHasTorch; //手电筒
@property (nonatomic, readonly) BOOL cameraHasFlash; //闪光灯
@property (nonatomic, readonly) BOOL cameraSupportsTapToFocus; //聚焦
@property (nonatomic, readonly) BOOL cameraSupportsTapToExpose;//曝光
@property (nonatomic) AVCaptureTorchMode torchMode; //手电筒模式
@property (nonatomic) AVCaptureFlashMode flashMode; //闪光灯模式
聚焦、曝光、重设聚焦、曝光的方法
- (void)focusAtPoint:(CGPoint)point;//聚焦
- (void)exposeAtPoint:(CGPoint)point;//曝光
- (void)resetFocusAndExposureModes;//重设聚焦、曝光
实现捕捉静态图片 & 视频的功能
//捕捉静态图片
- (void)captureStillImage;
//开始录制
- (void)startRecording;
//停止录制
- (void)stopRecording;
//获取录制状态
- (BOOL)isRecording;
//录制时间
- (CMTime)recordedDuration;
c、录制工具类扩展里提供的私有属性
@interface THCameraController () <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) dispatch_queue_t videoQueue; //视频队列
@property (strong, nonatomic) AVCaptureSession *captureSession;// 捕捉会话
@property (weak, nonatomic) AVCaptureDeviceInput *activeVideoInput;//视图输入
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;//图片输出
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;//视频输出
@property (strong, nonatomic) NSURL *outputURL;//输出路径
@end
3、录制工具类的Session
a、配置Session
- (BOOL)setupSession:(NSError **)error {
}
步骤
- 初始化
- 设置分辨率
- 配置输入设备(注意转化为AVCaptureDeviceInput对象)
- 配置输入设备包括音频和视频输入
- 配置输出,包括静态图像输出和视频文件输出
- 在为Session添加输入输出设备的时候,需要判断能否添加
❶ 初始化
创建捕捉会话。AVCaptureSession
是捕捉场景的中心枢纽。
self.captureSession = [[AVCaptureSession alloc]init];
❷ 设置图像的分辨率
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
AVCaptureSessionPresetHigh
AVCaptureSessionPresetMedium
AVCaptureSessionPresetLow
AVCaptureSessionPreset640x480
AVCaptureSessionPreset1280x720
AVCaptureSessionPresetPhoto
❸ 添加视频的输入设备
拿到视频捕捉设备。iOS系统默认返回后置摄像头,但是美颜相机的开发者使用的是前置摄像头。
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
将捕捉设备封装成AVCaptureDeviceInput
。需要注意的是,如果想要为会话添加捕捉设备,那么必须将设备封装成AVCaptureDeviceInput
对象,因为设备和会话之间不能产生之间联系,需要使用AVCaptureDeviceInput
作为中介。
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
判断videoInput
是否有效,再通过canAddInput:
测试是否能被添加到会话中,倘若能则将videoInput
添加到 captureSession
中。在为Session
添加输入输出设备时候,需要注意一定要判断能否添加,原因是摄像头是公共设备,并不隶属于任何一个APP,所以当别人的APP正在使用摄像头的时候,我们的APP再强制添加的话就会崩溃,此时不能添加。
//判断videoInput是否有效
if (videoInput)
{
//canAddInput:测试是否能被添加到会话中
if ([self.captureSession canAddInput:videoInput])
{
//将videoInput 添加到 captureSession中
[self.captureSession addInput:videoInput];
//记录此时活跃的视图输入设备,用于区分是前置还是后置摄像头
self.activeVideoInput = videoInput;
}
}
else
{
return NO;
}
❹ 添加音频的输入设备
选择默认音频捕捉设备,即返回一个内置麦克风。
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
为这个设备创建一个捕捉设备输入。
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
判断规则同上。注意因为麦克风只有一个,所以不需要添加使用self.activeVideoInput
区分前置和后置摄像头的操作。
//判断audioInput是否有效
if (audioInput)
{
//canAddInput:测试是否能被添加到会话中
if ([self.captureSession canAddInput:audioInput])
{
//将audioInput 添加到 captureSession中
[self.captureSession addInput:audioInput];
}
}
else
{
return NO;
}
❺ 配置输出的图片
使用的是AVCaptureStillImageOutput
实例,用于从摄像头捕捉静态图片
self.imageOutput = [[AVCaptureStillImageOutput alloc]init];
配置字典:希望捕捉到JPEG格式的图片
self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
输出连接。判断是否可用,可用则添加到输出连接中去。
if ([self.captureSession canAddOutput:self.imageOutput])
{
[self.captureSession addOutput:self.imageOutput];
}
❻ 配置输出的视频
创建一个AVCaptureMovieFileOutput
实例,用于将Quick Time 电影录制到文件系统。
self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
输出连接。判断是否可用,可用则添加到输出连接中去。
if ([self.captureSession canAddOutput:self.movieOutput])
{
[self.captureSession addOutput:self.movieOutput];
}
创建视频队列
self.videoQueue = dispatch_queue_create("XieJiapei.VideoQueue", NULL);
b、开启或者停止 Session
❶ 开启 Session 进行捕捉
检查是否处于运行状态,未运行则开始进行捕捉。使用同步调用会损耗一定的时间,则用异步的方式处理。
- (void)startSession
{
//检查是否处于运行状态
if (![self.captureSession isRunning])
{
//使用同步调用会损耗一定的时间,则用异步的方式处理
dispatch_async(self.videoQueue, ^{
[self.captureSession startRunning];
});
}
}
❷ 停止 Session 捕捉
检查是否处于运行状态,处于运行状态则停止捕捉。使用异步方式,停止运行。
- (void)stopSession
{
//检查是否处于运行状态
if ([self.captureSession isRunning])
{
//使用异步方式,停止运行
dispatch_async(self.videoQueue, ^{
[self.captureSession stopRunning];
});
}
}
4、切换前后摄像头
a、切换前后摄像头需要使用到的辅助方法
❶ 寻找指定摄像头设备
首先获取所有可用视频设备(包括前置和后置摄像头),接着遍历可用的视频设备并返回 position
参数值。
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position
{
//获取可用视频设备
NSArray *devicess = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
//遍历可用的视频设备 并返回position 参数值
for (AVCaptureDevice *device in devicess)
{
if (device.position == position) {
return device;
}
}
return nil;
}
❷ 获取当前活跃的设备
默认活跃的设备是后置摄像头。返回当前捕捉会话对应的摄像头的device
属性。
- (AVCaptureDevice *)activeCamera
{
return self.activeVideoInput.device;
}
❸ 返回当前未激活的摄像头
通过查找当前激活摄像头的反向摄像头获得未激活的摄像头。如果设备只有1个摄像头,则返回nil
。
- (AVCaptureDevice *)inactiveCamera
{
//通过查找当前激活摄像头的反向摄像头获得未激活的摄像头
AVCaptureDevice *device = nil;
if (self.cameraCount > 1)
{
// 当前是后置摄像头
if ([self activeCamera].position == AVCaptureDevicePositionBack)
{
// 需要通过 cameraWithPosition 方法才能根据位置拿到设备
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
}
// 当前是前置摄像头
else
{
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
}
// 如果设备只有1个摄像头,则返回nil
return device;
}
❹ 能否切换摄像头
判断是否有超过1个摄像头可用,有则可以切换。
- (BOOL)canSwitchCameras
{
return self.cameraCount > 1;
}
❺ 可用视频捕捉设备的数量
- (NSUInteger)cameraCount
{
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}
b、进行切换摄像头
- (BOOL)switchCameras
{
.......
}
❶ 判断是否有多个摄像头
if (![self canSwitchCameras])
{
return NO;
}
❷ 获取当前设备的反向设备
NSError *error;
AVCaptureDevice *videoDevice = [self inactiveCamera];
❸ 将输入设备封装成 AVCaptureDeviceInput
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
❹ 判断 videoInput 是否为 nil
为nil
则表示创建 AVCaptureDeviceInput
出现错误,需要通知委托来处理该错误。
if (videoInput)
{
.......
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
return NO;
}
❺ 标注原始配置要发生改变
倘若videoInput
存在,则标注原始配置要发生改变。
[self.captureSession beginConfiguration];
❻ 再将捕捉会话中原本的捕捉输入设备移除
判断新的设备是否能加入之前,一定要进行前面的移除旧设备操作,否则无法切换摄像头。
[self.captureSession removeInput:self.activeVideoInput];
❼ 添加新的捕捉输入设备
如果新设备无法加入,那么此时旧设备没有了,新设备又用不了,则将原本的视频捕捉设备重新加入到捕捉会话中。
//判断新的设备是否能加入
if ([self.captureSession canAddInput:videoInput])
{
//能加入成功,则将videoInput 作为新的视频捕捉设备
[self.captureSession addInput:videoInput];
//将活跃设备修改为 videoInput
self.activeVideoInput = videoInput;
}
else
{
//如果新设备无法加入,则将原本的视频捕捉设备重新加入到捕捉会话中
[self.captureSession addInput:self.activeVideoInput];
}
❽ 提交配置
配置完成后,AVCaptureSession commitConfiguration
会分批的将所有变更整合在一起进行提交
[self.captureSession commitConfiguration];
5、使用捕捉设备进行聚焦和曝光
AVCapture Device
定义了很多方法,让开发者控制 ios 设备上的摄像头。可以独立调整和锁定摄像头的焦距、曝光、白平衡。对焦和曝光可以基于特定的兴趣点进行设置,使其在应用中实现点击对焦、点击曝光的功能。还可以让你控制设备的LED作为拍照的闪光灯或手电筒的使用。
每当修改摄像头设备时,一定要先测试修改动作是否能被设备支持。并不是所有的摄像头都支持所有功能,例如前置摄像头就不支持对焦操作,因为它和目标距离一般在一臂之长的距离。但大部分后置摄像头是可以支持全尺寸对焦。尝试应用一个不被支持的动作,会导致异常崩溃。
a、点击聚焦方法的实现
❶ 询问激活中的摄像头是否支持兴趣点对焦
- (BOOL)cameraSupportsTapToFocus
{
return [[self activeCamera]isFocusPointOfInterestSupported];
}
❷ 对兴趣点进行对焦
判断是否支持兴趣点对焦和自动对焦模式。
- (void)focusAtPoint:(CGPoint)point
{
AVCaptureDevice *device = [self activeCamera];
//是否支持兴趣点对焦 & 是否支持自动对焦模式
if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus])
{
.....
}
}
因为配置的时候,不能让多个对象对它进行更改,所以需要锁定设备进行配置。倘若锁定设备发生了错误,则返回给错误处理代理。
NSError *error;
if ([device lockForConfiguration:&error])
{
......
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
倘若成功锁定,则修改聚焦位置为兴趣点,聚焦模式为自动模式,再释放该锁定。
//聚焦位置
device.focusPointOfInterest = point;
//聚焦模式
device.focusMode = AVCaptureFocusModeAutoFocus;
//释放该锁定
[device unlockForConfiguration];
b、点击曝光的方法实现
❶ 询问设备是否支持对一个兴趣点进行曝光
- (BOOL)cameraSupportsTapToExpose
{
return [[self activeCamera] isExposurePointOfInterestSupported];
}
❷ 对兴趣点进行曝光
判断是否支持自动曝光模式
- (void)exposeAtPoint:(CGPoint)point
{
AVCaptureDevice *device = [self activeCamera];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode])
{
......
}
}
锁定设备准备配置。倘若锁定设备发生了错误,则返回给错误处理代理。
if ([device lockForConfiguration:&error])
{
.......
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
倘若成功锁定,则配置期望值。
device.exposurePointOfInterest = point;
判断设备是否支持锁定曝光的模式,倘若支持,则使用kvo
确定设备的adjustingExposure
属性的状态
static const NSString *THCameraAdjustingExposureContext;
if ([device isExposureModeSupported:AVCaptureExposureModeLocked])
{
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&THCameraAdjustingExposureContext];
}
释放该锁定
[device unlockForConfiguration];
c、重写observeValueForKeyPath方法,观察属性状态
判断context
(上下文)是否为THCameraAdjustingExposureContext
,倘若不是则调用父类的observeValueForKeyPath
方法,不进行重写。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
//判断 context(上下文)是否为 THCameraAdjustingExposureContext
if (context == &THCameraAdjustingExposureContext)
{
.....
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
通过object
获取device
,再判断设备是否不再调整曝光等级,确认设备的exposureMode
是否可以设置为AVCaptureExposureModeLocked
。
AVCaptureDevice *device = (AVCaptureDevice *)object;
if(!device.isAdjustingExposure && [device isExposureModeSupported:AVCaptureExposureModeLocked])
{
......
}
移除作为adjustingExposure
的self
,就不会得到后续变更的通知。
[object removeObserver:self forKeyPath:@"adjustingExposure" context:&THCameraAdjustingExposureContext];
异步方式调回主队列修改exposureMode
,再释放该锁定。
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
if ([device lockForConfiguration:&error])
{
//修改exposureMode
device.exposureMode = AVCaptureExposureModeLocked;
//释放该锁定
[device unlockForConfiguration];
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
});
d、重新设置对焦&曝光
判断对焦兴趣点和连续自动对焦模式是否被支持,再确认曝光度是否可以被重设
- (void)resetFocusAndExposureModes
{
AVCaptureDevice *device = [self activeCamera];
AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
//获取对焦兴趣点和连续自动对焦模式是否被支持
BOOL canResetFocus = [device isFocusPointOfInterestSupported]&& [device isFocusModeSupported:focusMode];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
//确认曝光度是否可以被重设
BOOL canResetExposure = [device isFocusPointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
......
}
捕捉设备空间左上角(0,0),右下角(1,1) 中心点则(0.5,0.5)。我们将中点作为默认聚焦的位置。
CGPoint centPoint = CGPointMake(0.5f, 0.5f);
锁定设备,准备配置。焦点可设,则修改。曝光度可设,则设置为期望的曝光模式。
//锁定设备,准备配置
if ([device lockForConfiguration:&error])
{
//焦点可设,则修改
if (canResetFocus)
{
device.focusMode = focusMode;
device.focusPointOfInterest = centPoint;
}
//曝光度可设,则设置为期望的曝光模式
if (canResetExposure)
{
device.exposureMode = exposureMode;
device.exposurePointOfInterest = centPoint;
}
//释放锁定
[device unlockForConfiguration];
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
6、实现摄像头手电筒和闪关灯模式的开启关闭
a、实现摄像头的闪关灯模式的开启关闭
❶ 判断是否有闪光灯
- (BOOL)cameraHasFlash
{
return [[self activeCamera] hasFlash];
}
❷ 获取闪光灯模式
- (AVCaptureFlashMode)flashMode
{
return [[self activeCamera] flashMode];
}
❸ 设置闪光灯
判断是否支持闪光灯模式。如果支持,则锁定设备,再修改闪光灯模式,最后解锁释放设备。
- (void)setFlashMode:(AVCaptureFlashMode)flashMode
{
//获取会话
AVCaptureDevice *device = [self activeCamera];
//判断是否支持闪光灯模式
if ([device isFlashModeSupported:flashMode])
{
//如果支持,则锁定设备
NSError *error;
if ([device lockForConfiguration:&error])
{
//修改闪光灯模式
device.flashMode = flashMode;
//修改完成,解锁释放设备
[device unlockForConfiguration];
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
b、实现摄像头手电筒的开启关闭
❶ 是否支持手电筒
- (BOOL)cameraHasTorch
{
return [[self activeCamera] hasTorch];
}
❷ 获取手电筒模式
- (AVCaptureTorchMode)torchMode
{
return [[self activeCamera] torchMode];
}
❸ 设置是否打开手电筒
- (void)setTorchMode:(AVCaptureTorchMode)torchMode
{
AVCaptureDevice *device = [self activeCamera];
if ([device isTorchModeSupported:torchMode])
{
NSError *error;
if ([device lockForConfiguration:&error])
{
device.torchMode = torchMode;
[device unlockForConfiguration];
}
else
{
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
7、静态图片的拍摄
AVCaptureStillImageOutput
是AVCaptureOutput
的子类,用于捕捉图片。
a、捕捉图片
- (void)captureStillImage
{
......
}
获取连接,将图片和视频连接起来,因为图片也属于视频的一种。
AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向。
//判断是否支持设置视频方向
if (connection.isVideoOrientationSupported)
{
//获取方向值
connection.videoOrientation = [self currentVideoOrientation];
}
定义一个 handler
块,会返回1个图片的NSData
数据。
id handler = ^(CMSampleBufferRef sampleBuffer,NSError *error) {
.......
};
如果缓存区中的 sampleBuffer
非空,则将缓存区中的数据转化为图片数据,既然成功捕捉到了图片,那么就需要将图片传递出去。
if (sampleBuffer != NULL)
{
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
//重点:捕捉图片成功后,将图片传递出去
[self writeImageToAssetsLibrary:image];
}
else
{
NSLog(@"NULL sampleBuffer:%@",[error localizedDescription]);
}
进行捕捉静态图片,完成之后调用handler
块。
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:handler];
b、获取方向值
通过获取 UIDevice
的 orientation
来获取捕捉设备的方向。
- (AVCaptureVideoOrientation)currentVideoOrientation
{
AVCaptureVideoOrientation orientation;
switch ([UIDevice currentDevice].orientation)
{
case UIDeviceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
default:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
}
return orientation;
}
c、将图片写入到 Library
Assets Library
框架用来让开发者通过代码方式访问相册。
- (void)writeImageToAssetsLibrary:(UIImage *)image
{
.......
}
创建 ALAssetsLibrary
实例对象。
#import <AssetsLibrary/AssetsLibrary.h>//用于将文件写入到资源库中
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
-
图片参数:参数为
CGImageRef
,所以使用image.CGImage
-
方向参数:将方向转为
NSUInteger
- 回调参数:写入成功、失败处理
[library writeImageToSavedPhotosAlbum:image.CGImage
orientation:(NSUInteger)image.imageOrientation
completionBlock:^(NSURL *assetURL, NSError *error) {
}];
成功后,发送捕捉图片成功通知,用于绘制相机左下角的缩略图。失败则打印错误信息。
if (!error)
{
[self postThumbnailNotifification:image];
}
else
{
id message = [error localizedDescription];
NSLog(@"%@",message);
}
d、发送缩略图通知
工具类只是个controller
控制类,不负责展示内容,所以只是回到主队列使用通知的方式将图片发送过去。
NSString *const THThumbnailCreatedNotification = @"THThumbnailCreated";
- (void)postThumbnailNotifification:(UIImage *)image
{
//回到主队列
dispatch_async(dispatch_get_main_queue(), ^{
//发送请求
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:THThumbnailCreatedNotification object:image];
});
}
8、视频录制实现
a、视频内容捕捉的原理
对于视频内容的捕捉,当设置捕捉会话时,会添加一个名为AVCaptureMovieFileOutput
的输出,这个输出会将QuickTime
影片捕捉到磁盘。
AVCaptureMovieFileOutput
类大多数核心功能继承于超类AVCaptureFileOutput
。这个超类定义了许多实用功能,比如录制到最长时限或录制到特定文件大小时为止。超类还支持可以配置成保留最小可用的磁盘空间,这一点在存储空间有限的移动设备上录制视频时非常重要。
通常当QuickTime
影片准备发布时,影片头的元数据处于文件的开始位置。这样可以让视频播放器快速读取头包含信息来确定文件的内容、结构和其包含的多个样本的位置。不过,当录制一个QuickTime
影片时,直到所有的样片都完成捕捉后才能创建信息头。当录制结束时,创建头数据并将它附在文件结尾处。
将创建头的过程放在所有影片样本完成捕捉之后存在一个问题,尤其是在移动设备的情况下问题更为明显。如果遇到崩溃或其他中断,比如有电话拨入,则影片头就不会被正确写入,会在磁盘生成一个不可读的影片文件。对此,AVCaptureMovieFileOutput
提供一个核心功能就是分段捕捉 QuickTime
影片。
当录制开始时,在文件最前面写入一个最小化的头信息,随着录制的进行,片段按照一定的周期写入,最终创建完整的头信息。默认状态下,每10秒写入一个片段,不过这个时间的间隔可以通过修改捕捉设备输出的movieFragentInterval
属性来改变。我们用默认的间隔来做这demo
,但是如果你可以在你的的APP修改这个值。通过写入片段的方式可以逐步创建完整的 QuickTime
影片头,这样确保了当遇到应用程序崩溃或中断时,影片仍然会以最好的一个写入片段为终点进行保存。
b、辅助视频录制的方法
❶ 判断是否录制状态
- (BOOL)isRecording
{
return self.movieOutput.isRecording;
}
❷ 录制的时间
- (CMTime)recordedDuration
{
return self.movieOutput.recordedDuration;
}
❸ 提供写入视频唯一文件系统URL
- (NSURL *)uniqueURL
{
NSFileManager *fileManager = [NSFileManager defaultManager];
//创建一个临时唯一命名的目录
//临时文件在写入过程中不会产生后缀名,只有当全部写入完成后,才会产生后缀名
NSString *dirPath = [fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];
if (dirPath)
{
//mov是视频封装容器,和视频编码格式存在区别
NSString *filePath = [dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
return [NSURL fileURLWithPath:filePath];
}
return nil;
}
❹ 停止录制
- (void)stopRecording
{
//是否正在录制
if ([self isRecording])
{
[self.movieOutput stopRecording];
}
}
c、开始录制视频
录制之前需要判断当前是否已经处于录制状态,未录制才允许开始录制视频。
- (void)startRecording
{
if (![self isRecording])
{
......
}
}
获取当前视频捕捉连接信息,用于为捕捉视频数据配置一些核心属性。
AVCaptureConnection * videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
判断是否支持设置videoOrientation
属性,倘若支持则修改当前视频的方向。
if([videoConnection isVideoOrientationSupported])
{
//支持则修改当前视频的方向
videoConnection.videoOrientation = [self currentVideoOrientation];
}
判断是否支持视频稳定。视频稳定只会在录制视频文件涉及,可以显著提高视频的质量。
if([videoConnection isVideoStabilizationSupported])
{
videoConnection.enablesVideoStabilizationWhenAvailable = YES;
}
判断当前活跃的摄像头是否可以进行平滑对焦模式操作。平滑对焦模式即减慢摄像头镜头对焦速度,这样当用户移动拍摄时摄像头会尝试快速自动对焦。
AVCaptureDevice *device = [self activeCamera];
if (device.isSmoothAutoFocusEnabled)
{
NSError *error;
if ([device lockForConfiguration:&error])
{
device.smoothAutoFocusEnabled = YES;
[device unlockForConfiguration];
}
else
{
//设备错误
[self.delegate deviceConfigurationFailedWithError:error];
}
}
查找写入捕捉视频的唯一文件系统URL
。
self.outputURL = [self uniqueURL];
摄像头的相关配置已经完成,也已经获取到路径,则开始进行录制。直播/小视频APP捕捉到视频信息后就会通过(AAC/H264)进行压缩。但是本Demo
为了简便,就将录制成的QuickTime
视频文件直接存储到相册,其实这个过程中也会涉及到编码,不过是由AVFoundation
提供的硬编码。
//在捕捉输出上调用方法
//参数1:录制保存路径 参数2:代理
[self.movieOutput startRecordingToOutputFileURL:self.outputURL recordingDelegate:self];
d、AVCaptureFileOutputRecordingDelegate
❶ 捕捉录制的输出文件
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error
{
//错误
if (error)
{
[self.delegate mediaCaptureFailedWithError:error];
}
else
{
//视频写入到相册
[self writeVideoToAssetsLibrary:[self.outputURL copy]];
}
//录制完成后将路径清空
self.outputURL = nil;
}
❷ 写入捕捉到的视频到相册
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL
{
......
}
写资源库写入前,检查视频是否可被写入 (写入前尽量养成判断的习惯)。
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL])
{
.......
}
创建block
块。视频在拍摄完成后也会在左下角有个缩略图。
ALAssetsLibraryWriteVideoCompletionBlock completionBlock;
completionBlock = ^(NSURL *assetURL,NSError *error)
{
if (error)
{
[self.delegate assetLibraryWriteFailedWithError:error];
}
else
{
//用于界面展示视频缩略图
[self generateThumbnailForVideoAtURL:videoURL];
}
};
执行实际写入资源库的动作。
[library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:completionBlock];
e、获取视频左下角缩略图
在异步队列上生成缩略图。
- (void)generateThumbnailForVideoAtURL:(NSURL *)videoURL
{
//在 videoQueue 上
dispatch_async(self.videoQueue, ^{
......
});
}
根据 videoURL
生成 AVAsset
实例对象,再根据生成的asset
去创建AVAssetImageGenerator
实例对象。
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
设置 maximumSize
宽为100,高为0。系统会根据视频的宽高比来计算图片的高度。
imageGenerator.maximumSize = CGSizeMake(100.0f, 0.0f);
捕捉视频缩略图会考虑视频的变化(如视频的方向变化)。如果不设置,缩略图的方向可能出错。
imageGenerator.appliesPreferredTrackTransform = YES;
获取第一帧的CGImageRef
图片。注意需要自己管理它的创建和释放。
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:nil];
将图片转化为UIImage
。
UIImage *image = [UIImage imageWithCGImage:imageRef];
已经拿到左下角的缩略图,则释放CGImageRef imageRef
防止内存泄漏。
CGImageRelease(imageRef);
回到主线程。因为ImageView
添加图片并不在该工具类中,所以通过通知的方式将图片传递过去。
//回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
//发送通知,传递最新的image
[self postThumbnailNotifification:image];
});
9、实现预览录制内容功能
方式一:使用新建 View 的 layer 作为预览视图
❶ 重写layerClass 类方法可以让开发者创建视图实例自定义图层
+ (Class)layerClass
{
return [AVCaptureVideoPreviewLayer class];
}
❷ 重写 session 方法,返回捕捉会话
- (AVCaptureSession*)session
{
return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
❸ 重写session属性的访问方法
在setSession:
方法中访问了视图layer
属性。使用AVCaptureVideoPreviewLayer
实例设置AVCaptureSession
将捕捉数据直接输出到图层中,确保与会话状态同步。
- (void)setSession:(AVCaptureSession *)session
{
[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
❹ 转换摄像头坐标系
将屏幕坐标系上的触控点转换为摄像头上的坐标系点。这是个私有方法,用于支持该类定义的不同触摸处理方法。
- (CGPoint)captureDevicePointForPoint:(CGPoint)point
{
AVCaptureVideoPreviewLayer *layer = (AVCaptureVideoPreviewLayer *)self.layer;
return [layer captureDevicePointOfInterestForPoint:point];
}
方式二:将预览图层添加到 viewContainer 自带的 layer
❶ 创建视频预览层,用于实时展示摄像头状态
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
CALayer *layer = self.viewContainer.layer;
layer.masksToBounds = YES;
_captureVideoPreviewLayer.frame = layer.bounds;
_captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//填充模式
❷ 将视频预览层添加到界面中
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
Demo
Demo在我的Github上,欢迎下载。
Multi-MediaDemo