AV Foundation ⑫ 了解捕捉媒体

概述

    AVFoundation 的照片和视频捕捉功能从框架搭建之初就是它的一个强项。其 Capture 子系统为 iOS 和 macOS 中的视频、照片和音频捕获服务提供了一个通用的高级架构。开发者可以使用 Capture 系统完成以下任务:

  • 构建自定义相机 UI,将拍摄照片或视频集成到应用的用户体验中。
  • 让用户更直接地控制照片和视频拍摄,例如对焦、曝光和稳定选项。
  • 生成与系统相机 UI 不同的结果,例如 RAW 格式照片、深度图或具有自定义定时元数据的视频。
  • 直接从捕获设备实时访问像素或音频数据流。

    当开发一个带有捕捉功能的应用程序时会用到许多类,Capture 系统中的主要部分是会话、输入和输出,可以组装对象来表示输入和输出,并使用 AVCaptureSession 的实例来协调它们之间的数据流。如下图所示:

AVCaptureSession
  • 表示输入设备的 AVCaptureDevice 实例,例如相机或麦克风
  • 配置输入设备端口的AVCaptureInput 具体子类的实例
  • 管理输出到电影文件或静止图像的AVCaptureOutput具体子类的实例
  • 协调从输入到输出的数据流的AVCaptureSession 实例
  • 要向用户显示相机正在录制的内容的预览,可以使用AVCaptureVideoPreviewLayerCALayer的子类)的实例。

用户隐私

    在 iOS 中,用户必须明确授予每个应用访问摄像头和麦克风的权限。要确保应用在捕获媒体之前获得许可,需要按照以下步骤操作:

  • 如果应用使用设备摄像头,在应用的 Info.plist 文件中配置 NSCameraUsageDescription ,并描述为什么应用需要摄像头;
  • 如果应用使用设备麦克风,在应用的 Info.plist 文件中配置 NSMicrophoneUsageDescription ,并描述为什么应用需要麦克风;

    在配置过 Info.plist 后,需要在捕获会话之前使用[AVCaptureDevice authorizationStatusForMediaType:<#(nonnull AVMediaType)#>] 获取当前用户授权麦克风和摄像头状态:

  • AVAuthorizationStatusNotDetermined

    用户尚未授予权限,在这种情况下 使用 [AVCaptureDevice requestAccessForMediaType:<#(nonnull AVMediaType)#> completionHandler:<#^(BOOL granted)handler#>] 分别请求摄像头和麦克风权限

  • AVAuthorizationStatusAuthorized

    用户已明确授予媒体捕获权限,可以开始执行媒体捕获相关代码

  • AVAuthorizationStatusRestrictedAVAuthorizationStatusDenied

不允许用户访问媒体捕获设备和用户已明确拒绝媒体捕获权限的情况下,无法执行媒体捕获相关代码

捕捉会话

    AV Foundation 捕捉系统中的核心类是 AVCaptureSession。其用于连接输入和输出的资源,管理从物理设备(比如摄像头和麦克风)得到的数据流,输出到一个或多个目的地。

基本捕获会话架构框图

    将所需输入和输出的设备添加至捕捉会话后,通过向会话发送startRunning消息来启动数据流,并通过发送消息stopRunning来停止数据流。

AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];                 
if ([captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}
//配置输入和输出
//启动数据流
[captureSession startRunning];

    捕捉会话还可以额外配置一个 AVCaptureSessionPreset 类型的会话预设值 sessionPreset,用来捕捉数据的格式、分辨率和质量。如果要设置特定媒体帧大小的配置,则应在设置前使用 canSetSessionPreset: 检查是否支持。会话预设值默认为 AVCaptureSessionPresetHigh

preset 描述
AVCaptureSessionPresetHigh 最高的质量
AVCaptureSessionPresetMedium 适合 Wifi 共享,因设备而异。
AVCaptureSessionPresetLow 适合 3G 共享,因设备而异。
AVCaptureSessionPreset640x480 VGA
AVCaptureSessionPreset1280x720 720高清
AVCaptureSessionPreset1920x1080 1080p
AVCaptureSessionPreset3840x2160 UHD 4k
AVCaptureSessionPresetiFrame960x540 960 * 540 H.264编码 QuickTime视频
AVCaptureSessionPresetiFrame960x540 1280x720 H.264编码 QuickTime视频

一个 AVCaptureDevice 对象代表一个输入设备

获取默认捕捉设备

    AVCaptureDevice 抽象了一个物理捕获设备,该设备向 AVCaptureSession 对象提供输入数据(例如音频或视频)。AVCaptureDevice 为诸如摄像头或麦克风等物理设备定义了一个接口,可以使用 AVCapture 的类方法用于访问系统的捕捉设备,最常用的一个方法是 defaultDeviceWithMediaType:,它会根据给定的媒体类型返回一个系统指定的默认设备,如下所示,请求一个默认的视频设备,在 iOS系统下会返回后置摄像头:

AVCapture *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]

选择捕获设备

    设备提供了许多用于拍摄照片和视频的选项,包括前置和后置摄像头、双摄像头和原深感摄像头。自动选择合适的相机或为相机选择提供用户界面是开发任何具有相机功能的应用程序的重要部分。要查看符合特定标准的整套设备,以便可以使用自己的逻辑来选择一个,请使用 devicesWithMediaType:。如下所示,会返回可用得视频设备根据位置获取前置还是后置摄像头:

NSArray *devicess = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devicess)
{
    if (device.position == position) {
        return device;
    }
}

配置捕捉设备

    AVCaptureDevice 针对物理硬件设备定义了大量的控制方法,尤其是可以控制摄像头的聚焦、曝光和白平衡。AVCaptureDevice还可以控制设备的 LED 作为闪光灯或手电筒使用。

每当修改摄像头时,一定要判断修改动作是否能够被设备支持。并不是所有的摄像头都能支持所有功能,比如前置摄像头不支持对焦,当验证这一个配置的修改可以支持时,就可以执行实际的设备配置了。修改捕捉设备需要先锁定设备准备配置,执行所需的修改,最后解锁设备

AVCaptureDevice *device = <#当前激活设备#>
//..判断设备是否支持
//锁定设备
  NSError *error;
if([device lockForConfiguration:&error]){
  //.. 执行修改
  //解锁设备
  [device unlockForCOnfiguration];
}

对焦模式

    AVCaptureDevice 有以下三种对焦模式,开发时使用isFocusModeSupported:方法确定设备是否支持给定的焦点模式,然后使用该focusMode 属性设置模式。此外,设备可以支持设置焦点。判断 focusPointOfInterestSupported. 如果支持,在设置 focusPointOfInterest。当然,我们可以观察 adjustingFocus属性来确定设备当前是否正在聚焦。

typedef NS_ENUM(NSInteger, AVCaptureFocusMode) {
    //设备已锁定焦点。
    AVCaptureFocusModeLocked              = 0, 
    // 设备会自动调整一次对焦,然后将对焦模式更改为锁定焦点
    AVCaptureFocusModeAutoFocus           = 1,
    //设备持续监控焦点并在必要时自动对焦。
    AVCaptureFocusModeContinuousAutoFocus = 2,
} API_AVAILABLE(macos(10.7), ios(4.0), macCatalyst(14.0)) API_UNAVAILABLE(tvos) __WATCHOS_PROHIBITED;

曝光模式

    AVCaptureDevice 有以下四种曝光模式,使用 isExposureModeSupported:方法确定设备是否支持给定的曝光模式,然后使用该exposureMode属性设置模式。此外,设备可以支持感兴趣的曝光点。使用 exposurePointOfInterestSupported 判断是否支持,在设置曝光点 exposurePointOfInterest

typedef NS_ENUM(NSInteger, AVCaptureExposureMode) {
    //锁定曝光模式
    AVCaptureExposureModeLocked                            = 0,
    //设备会自动调整一次曝光,然后将曝光模式更改为锁定曝光
    AVCaptureExposureModeAutoExpose                        = 1,
    //该设备持续监控曝光水平并在必要时自动曝光。
    AVCaptureExposureModeContinuousAutoExposure            = 2,
    //设备应仅根据用户提供的ISO和 exposureDuration 属性值调整曝光。
    AVCaptureExposureModeCustom API_AVAILABLE(macos(10.15), ios(8.0), macCatalyst(14.0)) = 3,
} API_AVAILABLE(macos(10.7), ios(4.0), macCatalyst(14.0)) API_UNAVAILABLE(tvos) __WATCHOS_PROHIBITED;

闪光灯

    AVCaptureDevice 类可以让开发者修改摄像头的闪光灯和手电筒模式。设备后面的 LED 灯当拍摄静态图片时作为闪光灯,而当拍摄视频时用作连续灯光(手电筒)。捕捉设备的 flashModetorchMode 可以被设置为以下 3 个值中的一个:

  • AVCapture(Torch|Flash)ModeOn: 总是开启。

  • AVCapture(Torch|Flash)ModeOff: 总是关闭。

  • AVCapture(Torch|Flash)ModeAuto: 系统会基于周围环境光照情况自动关闭或打开 LED。

视频缩放

    AVCapture 提供了名为 videoZoomFactor 的属性,用于控制捕捉设备的缩放等级,这意味着所有会话的输出,包括预览层都会自动响应这一设置的状态。这个属性的最小值为 1.0,即不能进行缩放的图片。最大值由捕捉设备的 activeFormat 值确定。它是 AVCaptureDeviceFormat 类的一个实例,这个类定义了活动捕捉格式功能的细节,其中就包含 videoMaxZoomFactor

    设备执行缩放效果是通过剧中裁剪由摄像头传感器捕捉到的图片实现,当设置了一个低缩放因子时,一般低于 1.5,图片等于或大于输出尺寸,这就在不用放大的情况下进行了一个适度的缩放,图片质量得以全部保存,在哪个点开始放大图像需要由 AVCaptureDeviceFormatvideoZoomFactorUpscaleThreshold 值确定。

捕捉设备的输入

    在使用捕捉设备进行处理前,首先要将它添加为捕捉会话的输入,需要将它封装在一个 AVCaptureDeviceInput 实例中来添加。这个对象在设备输出数据和捕捉会话间扮演"接线板"的作用。使用 deviceInputWithDevice:error 创建 AVCaptureDeviceInput。如下所示,获取默认的摄像头和麦克风设备,封装在抽象的输入中,检查捕获输入是否与现有会话兼容,最后添加至捕捉会话。

//...
AVCaptureDevice *videoDevice =                                          
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput =                                      
        [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
 if (videoInput) {
        if ([captureSession canAddInput:videoInput]) {                 
            [captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        }
    } else {
        return NO;
    }

// Setup default microphone
AVCaptureDevice *audioDevice =                                         
    [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];

AVCaptureDeviceInput *audioInput =                                     
    [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
    if ([self.captureSession canAddInput:audioInput]) {               
        [self.captureSession addInput:audioInput];
    }
} else {
    return NO;
}

捕捉设备的输出

    要从捕获会话中获取输出,需要添加一个或多个输出。输出是的AVCaptureOutput具体子类的实例。AVCaptureOutput 作为一个抽象基类,用于为从捕捉会话得到的数据寻找输出目的地。AV Foundation 定义了 AVCaptureOutput 的许多扩展类:

  • AVCaptureMovieFileOutput 捕捉视频输出到文件
  • AVCaptureVideoDataOutput 访问硬件捕捉到的视频数据
  • AVCaptureAudioDataOutput 访问硬件捕捉到的音频数据
  • AVCaptureStillImageOutput 捕捉静态照片

    可以使用addOutput:将输出添加到捕获会话。在此之前需要使用canAddOutput:检查捕获输出是否与现有会话兼容,如下所示:

// Setup the still image output
self.imageOutput = [[AVCaptureStillImageOutput alloc] init];            
self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};

if ([self.captureSession canAddOutput:self.imageOutput]) {
  [self.captureSession addOutput:self.imageOutput];
}

// Setup movie file output
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];             

if ([self.captureSession canAddOutput:self.movieOutput]) {
    [self.captureSession addOutput:self.movieOutput];
}

捕捉连接

    捕获会话捕获会话中捕获输入和捕获输出之间的连接由 AVCaptureConnection 对象表示。捕获输入(AVCaptureInput的实例)具有一个或多个输入端口(AVCaptureInputPort的实例)。捕获输出(AVCaptureOutput的实例)可以接受来自一个或多个源的数据(例如,一个AVCaptureMovieFileOutput对象同时接受视频和音频数据)。

    当向会话添加输入或输出时,会话会在所有兼容的捕获输入端口和捕获输出之间形成连接,如下图所示。捕获输入和捕获输出之间的连接由 AVCaptureConnection 对象表示。

AVCaptureConnection

捕捉预览

     AV Foundation 提供了 AVCaptureVideoPreviewLayer 类对捕捉视频数据进行实时预览,类似于 AVPlayerLayer,也是 CALayer 的子类。如下所示,视频预览层维护与其关联的会话的强引用。这是为了确保在图层尝试显示视频时不会释放会话:

AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
 
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];

    当使用 AV Foundation 的捕捉 API 时,一定要理解屏幕坐标系和捕捉设备坐标系的不同。捕捉设备坐标系基于摄像头传感器的本地设置,水平方向不可旋转并且左上角坐标为 (0,0),右下角坐标为(1,1)。

    为此,AVCaptureVideoPreviewLayer 提供了一个 captureDevicePointOfInterestForPoint: 方法用于将屏幕坐标系上的坐标系点转换为摄像头上的坐标系点,另一个pointForCaptureDevicePointOfInterest:方法获取摄像头坐标系的 CGPoint 数据转换为屏幕坐标系 CGPoint 数据。

你可能感兴趣的:(AV Foundation ⑫ 了解捕捉媒体)