OC之AVCaptureDevice

输入设备AVCaptureDevice 继承自NSObject:是关于相机硬件的接口,用于配置底层硬件的属性(例如相机聚焦、白平衡、感光度ISO、曝光、帧率、闪光灯、缩放等),这些底层硬件包括前置摄像头、后置摄像头、麦克风、闪光灯等。使用AVCaptureDeviceAVCaptureSession对象提供输入数据(如音频或视频)。

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中提供使用NSCameraUsageDescriptionNSMicrophoneUsageDescription信息的解释。iOS在最初请求用户许可时显示了这个解释,然后在设置应用程序中显示。在没有使用说明的情况下启动AVCaptureSession会引发异常。

当应用在手机上,没有请求用户授权时,执行下述代码,将会显示一个alert以请求用户授权

[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
    NSLog(@"granted --- %d currentThread : %@",granted,NSThread.currentThread);
}];
NSLog(@"currentThread : %@",NSThread.currentThread);
请求授权.png

我们点击同意,观察打印结果:

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}
****************************************************/

注意:使用AVMediaTypeVideoAVMediaTypeAudio以外的任何媒体类型调用此方法都会引发异常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
不被支持的AVMediaType.png
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 内置长焦相机,比广角相机的焦距长。这种类型只是将窄角设备与配备两种类型的摄像机的硬件上的宽角设备区分开来。要确定摄像机设备的实际焦距,可以检查AVCaptureDeviceformat数组中的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、例子

我们以获取mediaTypeAVMediaTypeVideo的后置镜头为例:

- (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中,直接设置AVCaptureDeviceactiveFormat属性,会导致AVCaptureSession的预设值sessionPreset更改为AVCaptureSessionPresetInputPriority。更改AVCaptureSession时,当调用它的startRunning方法 或 更改它的结构(如添加、删除输入和输出)后调用-commitConfiguration方法时,AVCaptureSession不再自动配置捕获格式。但是在 Mac OS 中,在进行更改之后,AVCaptureSession仍然可以自动配置捕获格式activeFormat

为了防止 Mac OS 中捕捉格式activeFormat的自动更改,需要执行以下步骤:

  • 1、调用AVCaptureDevice- lockForConfiguration:方法锁定设备。
  • 2、更改AVCaptureDeviceactiveFormat属性。
  • 3、调用AVCaptureSessionstartRunning方法
  • 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;如果传递的formatformats中不存在,调用-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,这样做将关联的AVCaptureSessionsessionPreset更改为AVCaptureSessionPresetInputPriority

注意:试图将activeFormat设置为formats数组中不存在的格式,会抛出NSInvalidArgumentException

在更改此属性的值之前,必须调用-lockForConfiguration:以获得对设备配置属性的访问权,否则,设置此属性的值将引发异常;完成设备配置后,调用-unlockForConfiguration释放锁并允许其他设备配置设置。还必须在调用AVCaptureSession-startRunning方法之前调用-lockForConfiguration:,或AVCaptureSessionsessionPreset将覆盖AVCaptureDevice的活动格式activeFormat

5.4、当前深度捕获格式
@property(nonatomic, retain) AVCaptureDeviceFormat *activeDepthDataFormat;

该属性提供了AVCaptureDevice的当前活动深度数据格式。

当使用AVCaptureDepthDataOutput类捕获深度信息时,或者使用AVCapturePhotoOutput类在照片旁边启用捕获深度映射时,捕获输出自动使用与其活动照片/视频捕获格式相关联的深度数据格式。如果需要更改深度捕获格式,需要设置此属性的值。

深度数据捕获要求视频/照片数据的兼容捕获格式,如果将此属性设置为当前activeFormat对象AVCaptureDeviceFormatsupporteddepthdataformat数组中没有列出的捕获格式,将会引发异常。

不能直接设置深度数据捕获的帧速率:深度数据帧速率与设备的activeVideoMinFrameDurationactiveVideoMaxFrameDuration值是同步的,可以等于设备当前的帧速率,如果设备不能以足够快的速度生成深度数据,则可以降低。深度数据捕获可能会增加系统负载,从而降低视频帧速率以实现热可持续性。

在更改此属性的值之前,必须调用-lockForConfiguration:以获得对设备配置属性的访问权,否则,设置此属性的值将引发异常;完成设备配置后,调用-unlockForConfiguration释放锁并允许其他设备配置设置。还必须在调用AVCaptureSession-startRunning方法之前调用-lockForConfiguration:,或AVCaptureSessionsessionPreset将覆盖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属性设置为AVCaptureFocusModeAutoFocusAVCaptureFocusModeContinuousAutoFocus

在更改此属性的值之前,必须调用-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设置为指定值且属性focusModeAVCaptureFocusModeLocked时调用的块。 该块接收与已应用所有设置的第一个缓冲区匹配的时间戳;时间戳与设备时钟同步,因此必须先转换为主时钟,然后再与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续

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