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 数据。

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

推荐阅读更多精彩内容