输入设备AVCaptureDevice 继承自NSObject
:是关于相机硬件的接口,用于配置底层硬件的属性(例如相机聚焦、白平衡、感光度ISO、曝光、帧率、闪光灯、缩放等),这些底层硬件包括前置摄像头、后置摄像头、麦克风、闪光灯等。使用AVCaptureDevice
向AVCaptureSession
对象提供输入数据(如音频或视频)。
1、验证授权
1.1、请求用户授权指定的媒体类型
为保护用户隐私,应用在使用相机或者麦克风,总是需要用户授权才能正常使用。当应用第一次为需要权限的媒体类型创建任何AVCaptureDeviceInput
对象时,系统会自动显示一个alert
以请求用户授权。
调用下述类方法,可以让应用直接获取用户授权,而不是需要等到创建AVCaptureDeviceInput
对象时,系统自动显示一个alert
以请求用户授权。
+ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler;
该方法有两个参数:
- 第一个参数:
AVMediaType mediaType
:媒体类型常量,可以是AVMediaTypeVideo
,也可以是AVMediaTypeAudio
;如果没有提供媒体类型,将抛出异常NSInvalidArgumentException
。 - 第二个参数:
(void (^)(BOOL granted))handler
:获得用户响应后将调用的块;块中参数BOOL granted
如果用户授予使用硬件的权限,则返回YES;否则返回NO。注意:块回调可能在任意线程,如果要处理UI,请回归主线程。
该方法是异步调用,被调用时允许客户端继续运行,不会堵塞当前线程。在被授予访问权限之前,任何AVMediaType
类型的AVCaptureDevice
都将关闭静默音频样本或黑色视频帧。
如果调用该方法之前,已经显示alert
以请求用户授权,不管用户同意授权或者拒绝授权,该方法的回调都会立即返回用户曾经的授权结果,而不会再次去显示一个alert
以请求用户授权。
应用程序必须在配置信息
info.plist
中提供使用NSCameraUsageDescription
或NSMicrophoneUsageDescription
信息的解释。iOS在最初请求用户许可时显示了这个解释,然后在设置应用程序中显示。在没有使用说明的情况下启动AVCaptureSession
会引发异常。
当应用在手机上,没有请求用户授权时,执行下述代码,将会显示一个alert
以请求用户授权
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
NSLog(@"granted --- %d currentThread : %@",granted,NSThread.currentThread);
}];
NSLog(@"currentThread : %@",NSThread.currentThread);
我们点击同意,观察打印结果:
14:06:49 currentThread : <NSThread: 0x17006ee80>{number = 1, name = main}
14:06:54 granted --- 1 currentThread : <NSThread: 0x174070600>{number = 3, name = (null)}
可以看到handler
中返回YES,并且handler
的响应在任意线程中
注意:
(void (^)(BOOL granted))handler
回调可能在任意线程,如果要处理UI,请确保在主线程。
1.2、获取关于指定媒体类型的授权状态
为保护用户隐私,应用在使用相机或者麦克风,总是需要用户授权才能正常使用。当应用第一次为需要权限的媒体类型创建任何
AVCaptureDeviceInput
对象时,系统会自动显示一个alert
以请求用户授权。
为获悉应用程序是否获取指定媒体类型的权限,应用可以调用下述类方法获取授权状态:
+ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType;
该方法同步调用,会立即返回授权状态;如果此方法返回AVAuthorizationStatusNotDetermined
,则可以调用+ requestAccessForMediaType:completionHandler:
以提示用户记录权限。
我们不妨执行下述代码,分析打印结果:
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
{
case AVAuthorizationStatusNotDetermined:
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
NSLog(@"granted --- %d currentThread : %@",granted,NSThread.currentThread);
}];
NSLog(@"用户尚未授予或拒绝该权限:AVAuthorizationStatusNotDetermined");
break;
case AVAuthorizationStatusRestricted:
NSLog(@"不允许用户访问媒体捕获设备:AVAuthorizationStatusRestricted");
break;
case AVAuthorizationStatusDenied:
NSLog(@"用户已经明确拒绝了应用访问捕获设备:AVAuthorizationStatusDenied");
break;
case AVAuthorizationStatusAuthorized:
NSLog(@"用户授予应用访问捕获设备的权限:AVAuthorizationStatusAuthorized");
break;
default:
break;
}
NSLog(@"currentThread : %@",NSThread.currentThread);
/****************** 打印结果 *************************
14:24:54 用户已经明确拒绝了应用访问捕获设备:AVAuthorizationStatusDenied
14:24:54 currentThread : <NSThread: 0x170264e00>{number = 1, name = main}
****************************************************/
注意:使用
AVMediaTypeVideo
或AVMediaTypeAudio
以外的任何媒体类型调用此方法都会引发异常NSInvalidArgumentException
。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[AVCaptureDevice authorizationStatusForMediaType:] The passed mediaType text is not supported'
*** First throw call stack:
(0x1836e2fe0 0x182144538 0x18b1008c0 0x1000a2b04 0x18982c838 0x18982c5a8 0x1898cc09c 0x1898cb870 0x1898cb424 0x1898cb388 0x189811cc0 0x186a02274 0x1869f6de8 0x1869f6ca8 0x18697234c 0x1869993ac 0x186999e78 0x1836909a8 0x18368e630 0x1835bedc4 0x18987efc8 0x189879c9c 0x1000a62f0 0x1825cd59c)
libc++abi.dylib: terminating with uncaught exception of type NSException
1.3、媒体类型AVMediaType
媒体类型AVMediaType是使用typedef
修饰的标识符,提供各种媒体类型:
值 | 描述 |
---|---|
AVMediaTypeVideo |
指定视频 |
AVMediaTypeAudio |
指定音频 |
AVMediaTypeText |
指定文本 |
AVMediaTypeClosedCaption |
指定闭路内容 |
AVMediaTypeSubtitle |
指定字幕 |
AVMediaTypeTimecode |
指定一个时间代码 |
AVMediaTypeMetadata |
指定元数据 |
AVMediaTypeMuxed |
指定mux媒体 |
AVMediaTypeMetadataObject |
|
AVMediaTypeDepthData |
1.4、授权状态AVAuthorizationStatus
授权状态AVAuthorizationStatus是个枚举类型,提供有关使用捕获设备AVCaptureDevice
权限信息的常量:
值 | 描述 |
---|---|
AVAuthorizationStatusNotDetermined |
用户尚未授予或拒绝该权限, |
AVAuthorizationStatusRestricted |
不允许用户访问媒体捕获设备。这个状态通常是看不到的:用于发现设备的AVCaptureDevice 类方法不会返回用户被限制访问的设备。 |
AVAuthorizationStatusDenied |
用户已经明确拒绝了应用访问捕获设备 |
AVAuthorizationStatusAuthorized |
用户授予应用访问捕获设备的权限 |
1.5、使用例子
+ (void)getAuthorizationStatus:(void(^)(void))authorizedBlock
{
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]){
case AVAuthorizationStatusNotDetermined:{
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted){
if (granted){
dispatch_async(dispatch_get_main_queue(), ^{
if (authorizedBlock) {
authorizedBlock();
}
});
}
NSLog(@"granted --- %d currentThread : %@",granted,NSThread.currentThread);
}];
NSLog(@"用户尚未授予或拒绝该权限:AVAuthorizationStatusNotDetermined");
}
break;
case AVAuthorizationStatusRestricted:
NSLog(@"不允许用户访问媒体捕获设备:AVAuthorizationStatusRestricted");
break;
case AVAuthorizationStatusDenied:
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"没有权限" message:@"该功能需要授权使用你的相机" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {}];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"授权" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSURL *url= [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if (@available(iOS 10.0, *)){
if( [[UIApplication sharedApplication]canOpenURL:url] ) {
[[UIApplication sharedApplication]openURL:url options:@{}completionHandler:^(BOOL success) {
}];
}
}else{
if( [[UIApplication sharedApplication]canOpenURL:url] ) {
[[UIApplication sharedApplication]openURL:url];
}
}
}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
[UIApplication.sharedApplication.keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
NSLog(@"用户已经明确拒绝了应用访问捕获设备:AVAuthorizationStatusDenied");
}
break;
case AVAuthorizationStatusAuthorized:
{
dispatch_async(dispatch_get_main_queue(), ^{
if (authorizedBlock) {
authorizedBlock();
}
});
NSLog(@"用户授予应用访问捕获设备的权限:AVAuthorizationStatusAuthorized");
}
break;
default:
break;
}
}
在进入相机拍摄界面之前,调用上述方法,只有授权使用相机时回调authorizedBlock
进入相机界面:
[AVCaptureTools getAuthorizationStatus:^{
if (@available(iOS 10.2, *)) {
//使用 AVCapturePhotoOutput 拍照
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[[AVCaptureViewController alloc]init]];
self.window.rootViewController = nav;
} else {
//使用 AVCaptureStillImageOutput 拍照
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[[AVCaptureLowVersionViewController alloc]init]];
self.window.rootViewController = nav;
}
}];
2、发现设备
2.1、AVCaptureDeviceDiscoverySession
在 iPhone 7plus 及以后某些 iPhone 上,采用了后置双摄像头配置:一个广角镜头和一个长焦镜头,这两个镜头可以合并为一个单一采集设备一起工作。在iOS 10
及以后的 版本中使用 AVCaptureDeviceDiscoverySession 查询可用AVCaptureDevice
。使用这个类可以找到所有匹配特定设备类型deviceTypes
(如麦克风或广角相机)、支持用于捕获的媒体类型(如音频、视频或两者)和相机位置(前或后)的可用AVCaptureDevice
。
使用下述类方法创建一个AVCaptureDeviceDiscoverySession
,用于查找具有指定标准的设备:
+ (instancetype)discoverySessionWithDeviceTypes:(NSArray<AVCaptureDeviceType> *)deviceTypes mediaType:(AVMediaType)mediaType position:(AVCaptureDevicePosition)position;
- 参数
NSArray<AVCaptureDeviceType> *deviceTypes
:要搜索的设备类型列表,例如 麦克风AVCaptureDeviceTypeBuiltInMicrophone
和相机AVCaptureDeviceTypeBuiltInWideAngleCamera
。这个数组必须至少包含一个有效的AVCaptureDeviceType
值。
创建AVCaptureDeviceDiscoverySession
后,读取其设备数组devices
以检查匹配的设备并选择一个可用的AVCaptureDevice
。
@property(nonatomic, readonly) NSArray<AVCaptureDevice *> *devices;
这个数组只包含当前可用的AVCaptureDevice
(在读取属性时),并且满足使用+ discoverySessionWithDeviceTypes:mediaType:position: initializer
创建设备发现会话时指定的条件。
在 iOS 11.0 及更高版本中,这个数组的顺序与用于创建发现会话的
deviceTypes
参数的顺序相匹配,因此可以快速选择匹配首选类型的设备(请参阅带有发现会话的排序和筛选设备)。在较老的iOS版本中,搜索整个数组以找到首选设备。
2.1.1、 AVCaptureDeviceType
设备类型AVCaptureDeviceType是使用typedef
修饰的标识符,提供各种设备类型,与+ defaultDeviceWithDeviceType:mediaType:position:
方法和AVCaptureDeviceDiscoverySession
类一起使用。
设备类型AVCaptureDeviceType
|
描述 |
---|---|
AVCaptureDeviceTypeBuiltInMicrophone |
一个内置的麦克风 |
AVCaptureDeviceTypeBuiltInWideAngleCamera |
内置广角相机,这些装置适用于一般用途。 |
AVCaptureDeviceTypeBuiltInTelephotoCamera |
内置长焦相机,比广角相机的焦距长。这种类型只是将窄角设备与配备两种类型的摄像机的硬件上的宽角设备区分开来。要确定摄像机设备的实际焦距,可以检查AVCaptureDevice 的format 数组中的AVCaptureDeviceFormat 对象。 |
AVCaptureDeviceTypeBuiltInDualCamera |
广角相机和长焦相机的组合,创建了一个拍照,录像的AVCaptureDevice 。具有和深度捕捉,增强变焦和双图像捕捉功能。 |
AVCaptureDeviceTypeBuiltInTrueDepthCamera |
相机和其他传感器的组合,创建了一个捕捉设备,能够拍照、视频和深度捕捉。 |
AVCaptureDeviceTypeBuiltInDuoCamera |
iOS 10.2 之后添加自动变焦功能,该值功能被AVCaptureDeviceTypeBuiltInDualCamera 替代 |
2.1.2、 AVCaptureDevicePosition
相机镜头位置AVCaptureDevicePosition是个枚举类型:
值 | 描述 |
---|---|
AVCaptureDevicePositionUnspecified |
AVCaptureDevice 相对于系统硬件的位置未指定。 |
AVCaptureDevicePositionBack |
后置镜头 |
AVCaptureDevicePositionFront |
前置镜头 |
2.2、获取指定设备
2.2.1、多个筛选条件获取一个默认设备
+ (AVCaptureDevice *)defaultDeviceWithDeviceType:(AVCaptureDeviceType)deviceType mediaType:(AVMediaType)mediaType position:(AVCaptureDevicePosition)position;
返回指定设备类型、媒体类型和位置的默认设备;如果当前没有可用设备满足指定条件,则为nil。使用该方法可以轻松地为指定场景选择系统默认AVCaptureDevice
,例如,要在支持的硬件上获得双摄像头,并返回到标准广角摄像头;
- (AVCaptureDevice *)defaultCamera {
AVCaptureDevice *device;
if (@available(iOS 10.0, *)) {
device = [AVCaptureDevice defaultDeviceWithDeviceType: AVCaptureDeviceTypeBuiltInDuoCamera
mediaType: AVMediaTypeVideo
position: AVCaptureDevicePositionBack];
} else {
// Fallback on earlier versions
}
if (device != nil) {
return device;
}
if (@available(iOS 10.0, *)) {
device = [AVCaptureDevice defaultDeviceWithDeviceType: AVCaptureDeviceTypeBuiltInWideAngleCamera
mediaType: AVMediaTypeVideo
position: AVCaptureDevicePositionBack];
} else {
// Fallback on earlier versions
}
if (device != nil) {
return device;
}
return nil;
}
2.2.2、给定ID的一个设备
+ (AVCaptureDevice *)deviceWithUniqueID:(NSString *)deviceUniqueID;
返回具有给定ID的设备。
2.2.3、给定媒体类型AVMediaType
的一个设备
+ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType;
返回指定媒体类型AVMediaType
的默认设备。
注意:使用该方法请求摄像机
AVMediaTypeVideo
时,返回的总是AVCaptureDeviceTypeBuiltInWideAngleCamera
设备类型。要使用其他设备类型,使用+ defaultDeviceWithDeviceType:mediaType:position:
方法。
2.2.4、返回一组设备
//返回系统上可用捕获设备的数组。已经被苹果使用AVCaptureDeviceDiscoverySession替代
+ (NSArray<AVCaptureDevice *> *)devices;
//返回给定媒体类型的设备数组。已经被苹果使用AVCaptureDeviceDiscoverySession替代
+ (NSArray<AVCaptureDevice *> *)devicesWithMediaType:(AVMediaType)mediaType;
2.3、通知
通知 | 描述 |
---|---|
AVCaptureDeviceWasConnectedNotification |
当新设备可用时发送一个通知,通知对象是AVCaptureDevice 实例,表示已可用的设备。 |
AVCaptureDeviceWasDisconnectedNotification |
当现有设备不可用时发送一个通知,通知对象是AVCaptureDevice 实例,表示不可用的设备。 |
2.4、例子
我们以获取mediaType
为AVMediaTypeVideo
的后置镜头为例:
- (void)getTheAVCaptureDevice
{
if (@available(iOS 10.2, *)) {
NSArray<AVCaptureDeviceType> *deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInDualCamera];//设备类型:广角镜头、双镜头
AVCaptureDeviceDiscoverySession *sessionDiscovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
NSArray<AVCaptureDevice *> *devices = sessionDiscovery.devices;//当前可用的AVCaptureDevice集合
__block AVCaptureDevice *newVideoDevice = nil;
//遍历所有可用的AVCaptureDevice,获取 后置双镜头
[devices enumerateObjectsUsingBlock:^(AVCaptureDevice * _Nonnull device, NSUInteger idx, BOOL * _Nonnull stop) {
if ( device.position == AVCaptureDevicePositionBack && [device.deviceType isEqualToString:AVCaptureDeviceTypeBuiltInDualCamera] ) {
newVideoDevice = device;
* stop = YES;
}
}];
if (!newVideoDevice){
//如果后置双镜头获取失败,则获取广角镜头
[devices enumerateObjectsUsingBlock:^(AVCaptureDevice * _Nonnull device, NSUInteger idx, BOOL * _Nonnull stop) {
if ( device.position == AVCaptureDevicePositionBack) {
newVideoDevice = device;
* stop = YES;
}
}];
}
} else {
//获取指定mediaType类型的AVCaptureDevice集合
NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
__block AVCaptureDevice *newVideoDevice = nil;
//遍历所有可用的AVCaptureDevice,获取后置镜头
[devices enumerateObjectsUsingBlock:^(AVCaptureDevice * _Nonnull device, NSUInteger idx, BOOL * _Nonnull stop) {
if ( device.position == AVCaptureDevicePositionBack) {
newVideoDevice = device;
* stop = YES;
}
}];
}
}
我们可以看到,针对不同版本的 iOS 系统,苹果提供了两种方法获取指定的AVCaptureDevice
:在适配 iOS 10 以后的版本中,我们可以使用AVCaptureDeviceDiscoverySession
获取后置双镜头!但是在另一种方法中,我们无法使用双镜头,只能获取一个默认的广角镜头。
3、配置设备
3.1、锁定设备
AVCaptureDevice
在改变某些参数前必须先锁定,直到改变结束才能解锁:
//在修改AVCaptureDevice相关属性之前,必须调用下述方法上锁;当这个方法成功地锁定设备进行配置时,它返回YES;如果没有获得锁,则为NO。
- (BOOL)lockForConfiguration:(NSError * _Nullable *)outError;
//在修改AVCaptureDevice相关属性完成后,还需要调用下述方法解锁,允许其他应用程序进行更改。
- (void)unlockForConfiguration;
如果要求设备属性保持不变,可以持有一个锁而不释放它。然而,不必要地持有设备锁可能会降低共享设备的其他应用程序的捕获质量。
在iOS中,直接设置AVCaptureDevice
的activeFormat
属性,会导致AVCaptureSession
的预设值sessionPreset
更改为AVCaptureSessionPresetInputPriority
。更改AVCaptureSession
时,当调用它的startRunning
方法 或 更改它的结构(如添加、删除输入和输出)后调用-commitConfiguration
方法时,AVCaptureSession
不再自动配置捕获格式。但是在 Mac OS 中,在进行更改之后,AVCaptureSession
仍然可以自动配置捕获格式activeFormat
。
为了防止 Mac OS 中捕捉格式activeFormat
的自动更改,需要执行以下步骤:
- 1、调用
AVCaptureDevice
的- lockForConfiguration:
方法锁定设备。 - 2、更改
AVCaptureDevice
的activeFormat
属性。 - 3、调用
AVCaptureSession
的startRunning
方法 - 4、调用
AVCaptureDevice
的- unlockForConfiguration
解锁设备。
为了防止 在修改 Mac OS 中的AVCaptureSession
的结构后自动更改activeFormat
:
- 1、调用
AVCaptureDevice
的- lockForConfiguration:
方法锁定设备。 - 2、调用
AVCaptureSession
的- beginConfiguration
方法,修改它的结构; - 3、调用
AVCaptureSession
的- commitConfiguration
方法,提交修改; - 4、调用
AVCaptureDevice
的- unlockForConfiguration
解锁设备。
3.2、一些设备参数
属性 | 类型 | 描述 |
---|---|---|
inUseByAnotherApplication |
BOOL |
指示设备是否被其他应用程序占用。 |
suspended |
BOOL |
指示设备是否挂起。一些设备由于参数设置不允许数据捕获;例如,当iSight的隐私虹膜关闭时,isSuspended 会返回YES,对于笔记本上的iSight摄像机,或者对于笔记本显示关闭时的内部iSight摄像机,issuspend会返回YES。 |
linkedDevices |
NSArray<AVCaptureDevice *> |
一组AVCaptureDevice 对象,表示外接设备。例如,对于外部iSight摄像机,数组包含一个AVCaptureDevice 实例,表示外部iSight麦克风。 |
transportType |
int32_t |
AVCaptureDevice 的传输类型(USB、PCI等)。 |
inputSources |
NSArray<AVCaptureDeviceInputSource *> |
AVCaptureDevice 支持的输入源的AVCaptureDeviceInputSource 对象数组。 有些设备可以从多个数据源之一(例如,同一音频设备上的不同输入插孔)捕获数据。 |
activeInputSource |
AVCaptureDeviceInputSource |
当前活跃的输入源。要设置该属性,需要先调用- lockForConfiguration: 方法锁定设备;否则将抛出异常NSGenericException ;如果传递的format 在formats 中不存在,调用-setActiveInputSource: 抛出异常NSInvalidArgumentException 。 |
3.3、输入源AVCaptureDeviceInputSource
AVCaptureDeviceInputSource是捕获设备AVCaptureDevice
上的输入源。
AVCaptureDevice
对象可以从inputSources
数组中选择一个作为输入源,表示设备的不同的互斥输入。例如,音频捕获设备可能具有ADAT光学和模拟输入源;视频捕获设备可能有HDMI输入源或组件输入源。
属性 | 类型 | 描述 |
---|---|---|
inputSourceID |
NSString |
输入源ID;在指定AVCaptureDevice 对象的输入源中,ID是唯一的。 |
localizedName |
NSString |
输入源的本地化名称;可以使用此属性在用户界面中显示AVCaptureDevice 输入源的名称。 |
4、检查设备特征
属性 | 类型 | 描述 |
---|---|---|
connected |
BOOL |
表示AVCaptureDevice 实例当前是否已连接,是否可用。但是,当这个属性的值对于给定的AVCaptureDevice 实例变成NO时,它就不会再变成YES。如果相同的物理设备再次对系统可用,它将使用AVCaptureDevice 的新实例表示。 |
position |
AVCaptureDevicePosition |
表示设备AVCaptureDevice 的物理位置。 |
modelID |
NSString |
设备的型号ID。该值是同一型号的所有设备的唯一标识符,该值是跨设备连接和断开连接以及跨不同系统的持久值。例如,内置在两个相同iPhone机型上的摄像头的型号ID将是相同的,尽管它们是不同的物理设备。 |
localizedName |
NSString |
一个本地可读的AVCaptureDevice 设备名称;可以使用该值在用户界面中显示AVCaptureDevice 设备设备的名称。 |
uniqueID |
NSString |
AVCaptureDevice 唯一ID,它可以跨设备连接和断开连接、应用程序重新启动和系统重新启动在一个系统上持续存在。可以存储该值,以便将来收回或跟踪特定设备的状态。 |
lensAperture |
float |
镜片隔膜的尺寸,表示透镜光圈的大小(f号)。是个只读属性,该值不变。 |
deviceType |
AVCaptureDeviceType |
设备类型,如内置麦克风或广角摄像机。 |
manufacturer |
NSString |
设备制造商。对于所有苹果设备来说,这一属性值都是“Apple Inc.”;来自第三方制造商的设备可能不被识别,此时该值值为空字符串。 |
//返回一个布尔值,表示AVCaptureDevice是否为指定AVMediaType提供媒体。
- (BOOL)hasMediaType:(AVMediaType)mediaType;
//返回一个布尔值,表示AVCaptureDevice是否可以使用 指定sessionPreset的会话AVCaptureSession。
- (BOOL)supportsAVCaptureSessionPreset:(AVCaptureSessionPreset)preset;
5、管理格式
5.1、捕获格式AVCaptureDeviceFormat
AVCaptureDeviceFormat 对象详细描述了特定捕获模式的视频、图像或音频参数等,提供一组可用于配置AVCaptureDevice
的媒体格式与设置,如视频分辨率和帧速率。
5.2、设备支持的捕获格式
@property(nonatomic, readonly) NSArray<AVCaptureDeviceFormat *> *formats;
该属性提供了AVCaptureDevice
支持的捕获格式,如果需要访问AVCaptureSession
预设中未包含的设置,可以将activeFormat
属性设置为该数组中的任何格式。
5.3、当前捕获格式
@property(nonatomic, retain) AVCaptureDeviceFormat *activeFormat;
该属性提供了AVCaptureDevice
的当前活动媒体数据格式;可以使用此属性获取或设置当前活动的设备格式。
在iOS中,通常在AVCaptureSession
对象上设置sessionPreset
来配置图像或视频捕获,并使用共享的AVAudioSession
对象来配置音频捕获;在使用sessionPreset
时,AVCaptureSession
会自动控制AVCaptureDevice
的活动格式activeFormat
。然而,在AVCaptureSession
的设置中,有些专门的捕获选项(比如高帧率)是不可用的;对于这些选项,需要设置AVCaptureDevice
的活动格式activeFormat
,这样做将关联的AVCaptureSession
的sessionPreset
更改为AVCaptureSessionPresetInputPriority
。
注意:试图将activeFormat
设置为formats
数组中不存在的格式,会抛出NSInvalidArgumentException
。
在更改此属性的值之前,必须调用-lockForConfiguration:
以获得对设备配置属性的访问权,否则,设置此属性的值将引发异常;完成设备配置后,调用-unlockForConfiguration
释放锁并允许其他设备配置设置。还必须在调用AVCaptureSession
的-startRunning
方法之前调用-lockForConfiguration:
,或AVCaptureSession
的sessionPreset
将覆盖AVCaptureDevice
的活动格式activeFormat
。
5.4、当前深度捕获格式
@property(nonatomic, retain) AVCaptureDeviceFormat *activeDepthDataFormat;
该属性提供了AVCaptureDevice
的当前活动深度数据格式。
当使用AVCaptureDepthDataOutput
类捕获深度信息时,或者使用AVCapturePhotoOutput
类在照片旁边启用捕获深度映射时,捕获输出自动使用与其活动照片/视频捕获格式相关联的深度数据格式。如果需要更改深度捕获格式,需要设置此属性的值。
深度数据捕获要求视频/照片数据的兼容捕获格式,如果将此属性设置为当前activeFormat
对象AVCaptureDeviceFormat
的supporteddepthdataformat
数组中没有列出的捕获格式,将会引发异常。
不能直接设置深度数据捕获的帧速率:深度数据帧速率与设备的activeVideoMinFrameDuration
和activeVideoMaxFrameDuration
值是同步的,可以等于设备当前的帧速率,如果设备不能以足够快的速度生成深度数据,则可以降低。深度数据捕获可能会增加系统负载,从而降低视频帧速率以实现热可持续性。
在更改此属性的值之前,必须调用-lockForConfiguration:
以获得对设备配置属性的访问权,否则,设置此属性的值将引发异常;完成设备配置后,调用-unlockForConfiguration
释放锁并允许其他设备配置设置。还必须在调用AVCaptureSession
的-startRunning
方法之前调用-lockForConfiguration:
,或AVCaptureSession
的sessionPreset
将覆盖AVCaptureDevice
的活动格式activeFormat
。
6、焦点设置
镜头焦点与焦距的区别:
焦点 是透镜(或曲面镜)将光线会聚后所形成的点,因光线会聚成一点可将物烧焦。
焦距 是焦点到面镜的中心点之间的距离。镜头焦距的长短决定着拍摄的成像大小,视场角大小,景深大小和画面的透视强弱。镜头的焦距是镜头的一个非常重要的指标。镜头焦距的长短决定了被摄物在成像介质(胶片或CCD等)上成像的大小,也就是相当于物和象的比例尺。当对同一距离远的同一个被摄目标拍摄时,镜头焦距长的所成的象大,镜头焦距短的所成的象小。根据用途的不同,照相机镜头的焦距相差非常大,有短到几毫米,十几毫米的,也有长达几米的。
6.1、对焦模式
属性 | 类型 | 描述 |
---|---|---|
focusMode |
AVCaptureFocusMode |
设备的对焦模式。在更改此属性的值之前,必须调用-lockForConfiguration: 以锁定AVCaptureDevice ;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration 以释放锁定并允许其他设备配置设置。 |
focusPointOfInterestSupported |
BOOL |
指示设备是否支持焦点。 |
adjustingFocus |
BOOL |
指示设备当前是否正在调整其焦点设置。 |
AVCaptureFocusMode 用于指定捕获设备的焦点模式的枚举。
枚举值 | 描述 |
---|---|
AVCaptureFocusModeLocked |
焦点被锁定。 |
AVCaptureFocusModeAutoFocus |
设备会自动调整焦距一次,然后将焦点模式更改为AVCaptureFocusModeLocked 。 |
AVCaptureFocusModeContinuousAutoFocus |
AVCaptureDevice 持续监视焦点并在必要时自动聚焦。 |
当设备支持焦点,设置对焦模式时,我们需要调用- (BOOL)isFocusModeSupported:(AVCaptureFocusMode)focusMode
方法判断设备是否支持给定的焦点模式。如果支持focusMode
则为YES,否则为NO。
6.2、焦点位置设置
@property(nonatomic) CGPoint focusPointOfInterest;
聚焦的中心点; {0,0}
是图片区域的左上角,{1,1}
是右下角。无论实际的设备方向如何,此坐标系始终相对于横向设备方向,主页按钮位于右侧。可以使用AVCaptureVideoPreviewLayer
方法在此坐标系和视图坐标之间进行转换。
设置此属性不会启动聚焦操作。要将相机聚焦在中心点,首先设置此属性的值,然后将focusMode
属性设置为AVCaptureFocusModeAutoFocus
或AVCaptureFocusModeContinuousAutoFocus
。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。
6.3、自动对焦
属性 | 类型 | 描述 |
---|---|---|
smoothAutoFocusSupported |
BOOL |
指示设备是否支持平滑自动对焦。自动对焦模式仅适用于兼容设备,如果此属性的值为NO,则将smoothAutoFocusEnabled 的值设置为YES会引发异常。 |
smoothAutoFocusEnabled |
BOOL |
用于确定是否启用了平滑自动对焦;在兼容设备上,可以启用自动对焦模式,使镜头移动速度更慢。在更改此属性的值之前,必须调用-lockForConfiguration: 以锁定AVCaptureDevice ;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration 以释放锁定并允许其他设备配置设置。 |
6.4、自动对焦范围
属性
AVCaptureAutoFocusRangeRestriction autoFocusRangeRestriction
:
控制自动对焦允许范围;默认情况下,能够进行硬件聚焦的设备会尝试聚焦在任何距离的对象上。如果希望主要关注近物体或远物体,请设置范围限制以提高速度并降低自动聚焦的功耗,并减少聚焦模糊的可能性。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。属性
BOOL autoFocusRangeRestrictionSupported
:一个布尔值,指示设备是否支持焦距范围限制。对焦范围限制仅适用于兼容设备。 如果此属性的值为NO,则设置autoFocusRangeRestriction
会引发异常。
AVCaptureAutoFocusRangeRestriction
用于指定捕获设备的自动聚焦范围的枚举:如果您希望主要关注近物体或远物体,可以使用autoFocusRangeRestriction
属性为聚焦系统提供提示。 这种方法使自动对焦更快,更节能,并且更不容易出错。 限制优先于指定范围内的距离聚焦,但如果设备在该范围内没有找到焦点,则不会阻止聚焦在别处。
枚举值 | 描述 |
---|---|
AVCaptureAutoFocusRangeRestrictionNone |
设备会尝试关注任何范围内的对象。此值是默认值,是不支持焦点范围限制的设备上允许的唯一值。 |
AVCaptureAutoFocusRangeRestrictionNear |
该设备主要尝试聚焦在相机附近的拍摄对象上。对于使用AVCaptureMetadataOutput 识别机器可读代码的应用程序,建议使用此值。 |
AVCaptureAutoFocusRangeRestrictionFar |
该设备主要尝试对焦于远离相机的拍摄对象。 |
7、镜头焦距设置
7.1、获取镜头焦距
@property(nonatomic, readonly) float lensPosition;
表示镜头的焦距;取值范围是 0.0~1.0 :0.0是镜头可以聚焦的最短距离,1.0是最远的距离;默认值是1.0。
该值只能通过-setFocusModeLockedWithLensPosition:completionHandler:
来设置。
const float AVCaptureLensPositionCurrent
表示当前镜头位置的特殊常数。将此值传递给-setFocusModeLockedWithLensPosition:completionHandler:
在不更改镜头当前位置的情况下锁定焦点(即禁用自动对焦)。
7.2、焦距是否支持修改
@property(nonatomic, readonly, getter=isLockingFocusWithCustomLensPositionSupported) BOOL lockingFocusWithCustomLensPositionSupported;
指示设备是否支持将焦点锁定到特定的镜头位置的布尔值;如果此属性的值为NO,则使用除AVCaptureLensPositionCurrent
之外的镜头位置值调用-setFocusModeLockedWithLensPosition:completionHandler:
方法会引发异常。
7.3、设置镜头焦距
- (void)setFocusModeLockedWithLensPosition:(float)lensPosition completionHandler:(void (^)(CMTime syncTime))handler;
修改镜头焦距为指定值;该方法是设置镜头焦距的唯一路径。如果lensPosition
被设为不支持的值,这个方法会抛出NSInvalidArgumentException
异常。如果调用该方法前没有使用lockForConfiguration
锁定AVCaptureDevice
,则会引发NSGenericException
异常。
- 第一个参数
lensPosition
:镜头的焦距; - 第二个参数
(void (^)(CMTime syncTime))handler
: 当lensPosition
设置为指定值且属性focusMode
为AVCaptureFocusModeLocked
时调用的块。 该块接收与已应用所有设置的第一个缓冲区匹配的时间戳;时间戳与设备时钟同步,因此必须先转换为主时钟,然后再与AVCaptureVideoDataOutput
实例传送的缓冲区的时间戳进行比较。如果不需要知道操作的完成情况,可以将参数传递nil。
8、闪光灯设置
属性 | 类型 | 描述 |
---|---|---|
hasFlash |
BOOL |
指示AVCaptureDevice 是否有闪光灯。 |
flashMode |
AVCaptureFlashMode |
当前的闪光灯模式。在更改此属性的值之前,必须调用-lockForConfiguration: 以锁定AVCaptureDevice ;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration 以释放锁定并允许其他设备配置设置。 |
flashAvailable |
BOOL |
指示闪光灯当前是否可用;例如,如果设备过热并需要冷却,闪光灯可能会变得不可用。 |
AVCaptureFlashMode 是指定AVCaptureDevice
的闪光灯模式的枚举:
枚举值 | 描述 |
---|---|
AVCaptureFlashModeOff |
捕获设备闪光灯始终处于关闭状态。 |
AVCaptureFlashModeOn |
捕获设备闪存始终打开。 |
AVCaptureFlashModeAuto |
捕获设备持续监控光照水平,并在必要时使用闪光灯。 |
我们可以调用 - (BOOL)isFlashModeSupported:(AVCaptureFlashMode)flashMode;
方法判断是否支持指定的闪光模式。
- (void)flashModelButtonClick:(UIButton *)sender
{
dispatch_async( self.sessionQueue, ^{
AVCaptureDevice *device = self.videoDeviceInput.device;
//是否有闪光灯,闪光灯当前是否可用:如果设备过热并需要冷却,闪光灯可能会变得不可用
if (device.hasFlash == NO || device.flashAvailable == NO) {
return ;
}
NSError *error = nil;
if ([device lockForConfiguration:&error] ) {
switch (device.flashMode){
case AVCaptureFlashModeAuto:{
if ([device isFlashModeSupported:AVCaptureFlashModeOn]){
[device setFlashMode:AVCaptureFlashModeOn];
dispatch_async(dispatch_get_main_queue(), ^{
[sender setImage:resourceImage(@"cameraFlash_On") forState:UIControlStateNormal];
});
}
}
break;
case AVCaptureFlashModeOff:{
if ([device isFlashModeSupported:AVCaptureFlashModeAuto]){
[device setFlashMode:AVCaptureFlashModeAuto];
dispatch_async(dispatch_get_main_queue(), ^{
[sender setImage:resourceImage(@"cameraFlash_Auto") forState:UIControlStateNormal];
});
}
}
break;
case AVCaptureFlashModeOn: {
if ([device isFlashModeSupported:AVCaptureFlashModeOff]){
[device setFlashMode:AVCaptureFlashModeOff];
dispatch_async(dispatch_get_main_queue(), ^{
[sender setImage:resourceImage(@"cameraFlash_Off") forState:UIControlStateNormal];
});
}
}
break;
default:
break;
}
[device unlockForConfiguration];
}
else {
NSLog( @"Could not lock device for configuration: %@", error );
}
} );
}
9、手电筒设置
手电筒是一种光源,例如LED闪光灯,可在设备上使用,用于照亮捕获的内容或提供一般照明。
属性 | 类型 | 描述 |
---|---|---|
hasTorch |
BOOL |
反映当前设备是否具有内置的手电筒。即使设备有手电筒,也可能无法使用。 因此,在使用之前检查torchAvailable 属性的值。 |
torchAvailable |
BOOL |
指示手电筒当前是否可供使用。例如,如果设备过热并需要冷却,则手电筒可能无法使用。 |
torchActive |
BOOL |
指示设备的手电筒当前是否打开。必须在设备上有一个手电筒,并且当前可用,然后才能激活它。 |
torchLevel |
float |
获取当前手电筒亮度级别;其值在 0.0 到 1.0 的范围内:0.0 表示手电筒已关闭,1.0 表示理论最大值,如果设备当前过热,则实际最大值可能更低。 |
torchMode |
AVCaptureTorchMode |
目前的手电筒模式;设置此属性的值还会将手电筒级别设置为其最大当前值。在更改此属性的值之前,必须调用-lockForConfiguration: 以锁定AVCaptureDevice ;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration 以释放锁定并允许其他设备配置设置。 |
9.1、手电筒模式
AVCaptureTorchMode 指定AVCaptureDevice
的手电筒模式的枚举:
枚举值 | 描述 |
---|---|
AVCaptureTorchModeOff |
设备手电筒始终关闭。 |
AVCaptureTorchModeOn |
设备手电筒始终打开。 |
AVCaptureTorchModeAuto |
设备持续监控光照水平,并在必要时使用手电筒。 |
- (BOOL)isTorchModeSupported:(AVCaptureTorchMode)torchMode;
我们必须在修改torchMode
之前调用上述方法判断设备是否支持指定的手电筒模式。
9.2、设置手电筒照明级别
- (BOOL)setTorchModeOnWithLevel:(float)torchLevel error:(NSError * _Nullable *)outError;
我们设置手电筒照明级别,需要调用上述方法。要将割炬模式级别torchLevel
设置为当前可用的最大值,需要使用常量AVCaptureMaxAvailableTorchLevel
。
设置手电筒照明级别之前需要将手电筒模式torchMode
设置为AVCaptureTorchModeOn
并将级别设置为指定值。 如果设备不支持AVCaptureTorchModeOn
手电筒模式,或者如果为torchLevel
指定的值超出了可接受的范围,则此方法会引发异常。 如果割炬值在可接受范围内但大于当前支持的最大值 - 可能是因为设备过热 - 此方法只返回NO。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。
10、缩放设置
10.1、用于控制图像的缩放值
@property(nonatomic) CGFloat videoZoomFactor;
该属性是一个用于控制AVCaptureDevice
的图像的裁剪和放大的值;它是一个乘数:例如,值 2.0 会使图像主体的大小加倍(并使视野减半)。允许的值范围从 1.0(完整视野)到活动格式AVCaptureDeviceFormat
的属性videoMaxZoomFactor
值。
AVCaptureDevice
通过围绕传感器捕获的图像的中心进行裁剪来实现缩放效果。在低缩放系数下,裁剪的图像等于或大于输出大小。在较高的缩放系数下,设备必须将裁剪后的图像缩放到输出尺寸,从而导致图像质量下降。活动格式AVCaptureDeviceFormat
的videoZoomFactorUpscaleThreshold属性指示将进行放大的因素。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。
设置此属性的值会立即跳转到新的缩放系数;要流畅过渡,需要使用-rampToVideoZoomFactor:withRate:
方法。
最小缩放值
@property(nonatomic, readonly) CGFloat minAvailableVideoZoomFactor;
当前AVCaptureDevice
配置中允许的最小缩放系数。在单摄像头设备上,此值始终为 1.0 ;在双摄像头设备上,如果设备将深度数据传送到一个或多个捕获输出,则允许的视频缩放系数范围可能会发生变化。
设置属性videoZoomFactor
或调用-rampToVideoZoomFactor:withRate:
方法使值小于 1.0 会引发异常。 将视频缩放系数设置为介于 1.0 和最小可用缩放系数之间的值会将缩放设置限制为最小值。
最大缩放值
@property(nonatomic, readonly) CGFloat maxAvailableVideoZoomFactor;
当前AVCaptureDevice
配置中允许的最大缩放系数。在单摄像头设备上,此值始终等于AVCaptureDeviceFormat
的属性videoMaxZoomFactor
;在双摄像头设备上,如果设备将深度数据传送到一个或多个捕获输出,则允许的视频缩放系数范围可能会发生变化。
设置属性videoZoomFactor
或调用-rampToVideoZoomFactor:withRate:
方法使其值大于AVCaptureDeviceFormat
的属性videoMaxZoomFactor
,会引发异常。将视频缩放系数设置为最大可用缩放系数与设备格式最大值之间的值会将缩放设置限制为最大可用值。
10.2、流畅过渡到新的缩放值
我们可以使用下述方法实现从当前缩放因子到另一个缩放因子的流畅过渡:
- (void)rampToVideoZoomFactor:(CGFloat)factor withRate:(float)rate;
在该方法中有两个参数:
-
CGFloat factor
: 新的放大系数; -
float rate
:转换到新放大系数的速率,以每秒 2 的幂表示。
缩放值factor
允许范围从1.0(完整视野)到活动捕获格式AVCaptureDeviceFormat
指定的属性videoMaxZoomFactor
值。
在变化期间,缩放系数factor
以指数速率变化,但这会产生视觉线性过渡。rate
参数控制此转换的速度,与方向无关; 例如,值 1.0 会导致缩放因子在放大时每秒加倍(即,如果指定的因子大于当前的videoZoomFactor
),或者如果缩小则每秒减半。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。
10.3、结束缩放
流畅的进行缩放需要一定的过渡时间,我们可能需要停止该缓慢的过渡,需要调用下述方法:
- (void)cancelVideoZoomRamp;
该方法能流畅地结束正在进行的缩放而不是突然停止;调用此方法相当于调用 -rampToVideoZoomFactor:withRate:
的速率rate
为零。 如果正在进行缩放转换,则转换将减慢为停止。
在更改此属性的值之前,必须调用-lockForConfiguration:
以锁定AVCaptureDevice
;否则,设置此属性的值会引发异常。完成配置设备后,请调用-unlockForConfiguration
以释放锁定并允许其他设备配置设置。
10.4、其它指示参数
属性 | 类型 | 描述 |
---|---|---|
rampingVideoZoom |
BOOL |
指示是否正在进行缩放转换的布尔值。使用KVO观察对此属性值的更改,以便在缩放过渡开始或结束时得到通知。 |
dualCameraSwitchOverVideoZoomFactor |
CGFloat |
双摄像头设备可以在摄像头之间自动切换的视频缩放系数;在该系数下,来自广角摄像机的缩放视野与远摄相机的全视野相匹配。 当videoZoomFactor 设置达到或超过此值时,设备可以根据场景条件自动选择哪个摄像机提供输出图像(或自动组合两者的图像以创建最终输出)。对于低于此值的缩放系数,设备始终使用来自广角摄像机的图像。在单摄像头设备上,此值始终为1.0。 |
10.5、缩放示例
//最小缩放值
- (CGFloat)minZoomFactor
{
CGFloat minZoomFactor = 1.0;
if (@available(iOS 11.0, *)) {
minZoomFactor = self.device.minAvailableVideoZoomFactor;
}
return minZoomFactor;
}
//最大缩放值
- (CGFloat)maxZoomFactor
{
CGFloat maxZoomFactor = self.device.activeFormat.videoMaxZoomFactor;
if (@available(iOS 11.0, *)) {
maxZoomFactor = self.device.maxAvailableVideoZoomFactor;
}
if (maxZoomFactor > 6.0) {
maxZoomFactor = 6.0;
}
return maxZoomFactor;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]){
self.slider.minimumValue = self.minZoomFactor;
self.slider.maximumValue = self.maxZoomFactor;
self.currentZoomFactor = self.device.videoZoomFactor;
}
return YES;
}
//缩放手势
- (void)zoomChangePinchGestureRecognizerClick:(UIPinchGestureRecognizer *)pinchGestureRecognizer
{
if (pinchGestureRecognizer.state == UIGestureRecognizerStateBegan ||
pinchGestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGFloat currentZoomFactor = self.currentZoomFactor * pinchGestureRecognizer.scale;
self.slider.hidden = NO;
if (currentZoomFactor < self.maxZoomFactor &&
currentZoomFactor > self.minZoomFactor){
NSError *error = nil;
if ([self.device lockForConfiguration:&error] ) {
self.device.videoZoomFactor = currentZoomFactor;
self.slider.value = self.device.videoZoomFactor;
[self.device unlockForConfiguration];
}
else {
NSLog( @"Could not lock device for configuration: %@", error );
}
}
}
else
{
self.slider.hidden = YES;
}
}
- (void)sliderValueChangeClick:(UISlider *)sender
{
self.slider.hidden = NO;
if (sender.value < self.maxZoomFactor &&
sender.value > self.minZoomFactor){
NSError *error = nil;
if ([self.device lockForConfiguration:&error] ) {
self.device.videoZoomFactor = sender.value;
[self.device unlockForConfiguration];
}
else {
NSLog( @"Could not lock device for configuration: %@", error );
}
}
}
由于篇幅限制,关于 AVCaptureDevice
的更多知识在 OC之AVCaptureDevice续