Skip to content

Latest commit

 

History

History
638 lines (446 loc) · 41.4 KB

untitled-3.md

File metadata and controls

638 lines (446 loc) · 41.4 KB

静态图片和视频捕捉

通过输入 (inputs) 和输出 (outputs) 对象来对采集设备 (比如摄像头或麦克风) 进行管理. 使用 AVCaptureSession 对象协调 inputs 和 outputs 之间的数据.

使用 CALayer 的子类 AVCaptureVideoPreviewLayer, 可以展示摄像头采集的画面预览.

对于一个 session, 可以配置多个 inputs 和 outputs, 如图所示:

对于大部分的应用而言, 这已经足够了. 但是有些情况下, 会涉及到如何表示一个 inputs 的多个端口 (ports), 以及这些 ports 如何连接到 outputs.

Capture session 中使用 AVCaptureConnection 表示 inputs 和 outputs 之间的连接. 一个 Inputs 包含一个或多个 input ports(AVCaptureInputPort). Outputs 可以从一个或多个来源接收数据, 比如 AVCaptureMovieFileOutput 可以同时接收视频和音频数据.

如下图所示, 当在 session 中添加一个 input 或 output 时, session 会为所有可匹配的 inputs 和 outputs 之前生成 connections(AVCaptureConnection).

可以使用一个 connection 来开启或关闭一个 input 或 output 数据流. 也可以使用 connection 监控一个 audio 频道的码率平均值和峰值.

注意: 媒体捕捉不支持模拟器, 也不能同时使用 iOS 设备上的前置摄像头和后置摄像头进行捕捉

使用Capture Session协调数据流

数据采集管理的核心是 AVCaptureSession. 在 session 中添加采集设备并对 output 进行配置之后, 可以向 session 发送 startRunning 消息开始采集, 发送 stopRunning 消息停止采集.

AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];

配置 Capture Session

使用 session 的sessionPreset属性指定图片质量和分辨率:

  • AVCaptureSessionPresetHigh: 高分辨率, 最终效果根据设备不同有所差异
  • AVCaptureSessionPresetMedium: 中等分辨率, 适合 Wi-Fi 分享. 最终效果根据设备不同有所差异
  • AVCaptureSessionPresetLow: 低分辨率, 适合 3G 分享, 最终效果根据设备不同有所差异
  • AVCaptureSessionPreset640x480: 640x480, VGA
  • AVCaptureSessionPreset1280x720: 1280x720, 720p HD
  • AVCaptureSessionPresetPhoto: 全屏照片, 不能用来作为输出视频

在设置一个 preset 之前, 需要判断设备是否支持该 preset 值:

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}

如果需要设置一个更高分辨率的 preset, 或者在 session 运行时修改一些配置, 需要在 beginConfigurationcommitConfiguration 之间完成修改. beginConfigurationcommitConfiguration 方法确保所有的修改被整体应用, 减少对预览状态的影响. 在调用 beginConfiguration 之后, 可以添加或移除一个 output, 修改sessionPreset属性, 或者单独配置 input 和 output 的属性. 只有调用了 commitConfiguration 方法, 改变才会生效.

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];

监测 Capture Session 的状态

可以使用通知 (NSNotification) 监测 session 的状态, 并且所有的通知都在主线程中发送. 注册监听 AVCaptureSessionRuntimeError 通知可以捕捉运行时发生的错误. 也可以使用 session 的running属性判断当前的运行状态, interrupted属性则可以判断当前是否中断. 此外, runninginterrupted属性都可以通过 KVO 进行监听.

使用AVCaptureDevice表示输入设备

AVCaptureDevice 是对实际的物理捕捉设备的抽象, 物体捕捉设备向AVCaptureSession提供数据. 每个AVCaptureDevice对象代表一个实际的输入设备, 例如前摄像头或后摄像头, 或麦克风.

使用AVCaptureDevice类的 devicesdevicesWithMediaType: 方法可以获取当前可用的捕捉设备. 而且可以获取捕捉设备的设备特性 (参见 Device Capture Settings). 当前的可用设备的状态可能会发生改变, 当前使用的输入设备可能会变为不可用状态 (如果设备被另外一个应用使用), 也可能会有新的设备变为可用状态 (被其他应用释放). 注册接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification通知可以得知可用设备列表的变化.

设备特性

可以获取一个设备的设备特性. 也可以通过 hasMediaType: 方法判断设备是否支持特定媒体类型的捕捉, 通过 supportsAVCaptureSessionPreset: 判断设备是否支持特定的分辨率. 当要提供一个可用的捕捉设备列表给用户进行选择时, 获取展示出设备的位置以及名称 (比如前摄像头或后摄像头) 拥有更好的用户体验.

下图展示了前摄像头 (AVCaptureDevicePositionFront) 和后摄像头 (AVCaptureDevicePositionBack):

下面的代码遍历了所有的可用设备并打印其名称, 如果是视频设备, 则打印其位置:

NSArray *devices = [AVCaptureDevice devices];

for (AVCaptureDevice *device in devices) {

    NSLog(@"Device name: %@", [device localizedName]);

    if ([device hasMediaType:AVMediaTypeVideo]) {

        if ([device position] == AVCaptureDevicePositionBack) {
            NSLog(@"Device position : back");
        }
        else {
            NSLog(@"Device position : front");
        }
    }
}

此外, 还可以获取设备的 model ID 以及 unique ID.

捕捉设置

不同的设备之间存在性能差异, 比如一些设备支持特殊的对焦或闪光灯模式, 某些设备还支持兴趣点对焦.

下面的代码示例了如何找出一个支持手电筒模式和特定 preset 的设备:

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];

for (AVCaptureDevice *device in devices) {
    [if ([device hasTorch] &&
         [device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
        [torchDevices addObject:device];
    }
}

如果找到了多个符合要求的设备, 你可能需要让用户选择其中的某一个设备, 这时可以使用 localizedName 属性获取设备的描述信息.

可以用类似的方式实现各种不同的捕捉设置. 框架预定义了一些常量用来代表特定的捕捉模式, 你可以使用这些常量以便于判断设备是否支持特定的模式. 在大部分情况下, 可以通过属性监听获取设备特性的变化状态. 任何情况下, 在改变设备的捕捉设置之前, 都应该先锁定设备, 详见下节设备的配置.

兴趣点对焦模式和兴趣点曝光模式是互斥的, 正如对焦模式和曝光模式也是互斥的一样

对焦模式

有三种对焦模式:

  • AVCaptureFocusModeLocked: 固定焦点
  • AVCaptureFocusModeAutoFocus: 自动对焦然后锁定焦点
  • AVCaptureFocusModeContinuousAutoFocus: 连续自动对焦

使用 isFocusModeSupported: 方法判断设备是否支持给定的对焦模式, 然后设置属性 focusMode 改变对焦模式.

此外, 一些设备还支持兴趣点对焦模式. 通过方法 focusPointOfInterestSupported 判断是否支持该模式, 然后使用属性 focusPointOfInterest 设置焦点. 无论设备是横屏 (Home 键靠右) 或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.

属性 adjustingFocus 可以用来判断当前设备是否正在对焦中. 可以使用 KVO 监听该属性获取对焦开始与结束的通知.

设置对焦模式的示例代码如下:

if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
    CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setFocusPointOfInterest:autofocusPoint];
    [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}

曝光模式

有两种曝光模式:

  • AVCaptureExposureModeContinuousAutoExposure: 自动调整曝光等级
  • AVCaptureExposureModeLocked: 固定曝光等级

使用 isExposureModeSupported: 方法判断设备是否支持给定的曝光模式, 然后设置属性 exposureMode 改变曝光模式.
此外, 一些设备还支持兴趣点曝光模式. 通过方法 exposurePointOfInterestSupported 判断是否支持该模式, 然后使用属性 exposurePointOfInterest 设置曝光点. 无论设备是横屏 (Home 键靠右) 或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.

属性 adjustingExposure 可以用来判断当前设备是否正在改变曝光设置中. 可以使用 KVO 监听该属性获取开始设置曝光模式与结束设置曝光模式的通知.

设置曝光模式的示例代码如下:

if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
    CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setExposurePointOfInterest:exposurePoint];
    [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}

闪光模式

有三种闪光模式:

  • AVCaptureFlashModeOff: 关闭
  • AVCaptureFlashModeOn: 打开
  • AVCaptureFlashModeAuto: 根据环境亮度自动开启或关闭

使用方法 hasFlash 判断一个设备是否有闪光灯. 使用方法 isFlashModeSupported: 判断是否支持某个闪光模式, 使用属性 flashMode 设置闪光灯模式.

手电筒模式

手电筒模式下, 闪光灯会一直处于开启状态, 用于视频捕捉. 有三种手电筒模式:

  • AVCaptureTorchModeOff: 关闭
  • AVCaptureTorchModeOn: 打开
  • AVCaptureTorchModeAuto: 根据需要自动开启或关闭

使用方法 hasTorch 判断一个设备是否有闪光灯. 使用方法 isTorchModeSupported: 判断是否支持某个手电筒模式, 使用属性 torchMode 设置手电筒模式.

对于一个有手电筒的设备, 手电筒只有在设备与一个运行中的 capture session 进行了关联后才可以设置为开启.

视频稳定性

依赖于某些特殊的硬件设备, 视频会有更好设置达到电影级别的稳定性. 但并不支持所有的视频格式和分辨率.

开启电影级别的视频稳定性特性在捕捉视频时可能会增加延迟. 使用属性 videoStabilizationEnabled 可以判断当前是否使用了视频稳定性特性. 属性 enablesVideoStabilizationWhenAvailable 可以在设备支持的情况下自动开启视频稳定性特性, 该属性默认为关闭状态.

白平衡

有两种白平衡模式:

  • AVCaptureWhiteBalanceModeLocked: 固定参数的白平衡
  • AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance: 由相机自动调整白平衡参数

使用方法 isWhiteBalanceModeSupported: 判断设备是否支持给定的白平衡模式, 然后通过属性 whiteBalanceMode 设置白平衡模式.

使用属性 adjustingWhiteBalance 判断当前是否正在修改白平衡模式. 可以使用 KVO 监听该属性获取开始设置白平衡模式与结束设置白平衡模式的通知.

设置设备方向

可以在AVCaptureConnection上指定期望的设备方向, 用来设置输出时AVCaptureOutput(AVCaptureMovieFileOutput, AVCaptureStillImageOutputAVCaptureVideoDataOutput) 的设备方向.

使用属性AVCaptureConnectionsupportsVideoOrientation判断设备是否支持修改视频方向, 使用属性videoOrientation指定一个方向. 下面的代码将AVCaptureConnection的方向设置为AVCaptureVideoOrientationLandscapeLeft:

AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
    AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
    [captureConnection setVideoOrientation:orientation];
}

设备配置

要在设备上设置捕获属性,必须首先使用lockForConfiguration: 在设备上获取锁。这样可以避免进行可能与其他应用程序中的设置不兼容的更改。以下代码片段说明了如何通过首先确定是否支持该模式,然后尝试锁定设备以进行重新配置来改变设备上的对焦模式。仅在获得锁定后才更改对焦模式,然后立即释放锁定。

if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
    NSError *error = nil;
    if ([device lockForConfiguration:&error]) {
        device.focusMode = AVCaptureFocusModeLocked;
        [device unlockForConfiguration];
    }
    else {
        // Respond to the failure as appropriate.

    }

切换设备

某些场景下可能需要允许用户切换输入设备, 比如前后摄像头. 为了避免卡顿, 可以重新配置正在运行的 session, 但是嵌套使用 beginConfigurationcommitConfiguration 方法.

AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];

[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];

[session commitConfiguration];

当最后的commitConfiguration方法被调用时, 所有的设置变化会一起执行, 确保了切换的流畅性.

使用AVCaptureInput添加输入设备

要把一个 capture device 添加到 capture session 中, 需要使用 AVCaptureDeviceInput(抽象类AVCaptureInput的子类). Capture device input 管理设备的端口.

NSError *error;
AVCaptureDeviceInput *input =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}

使用 addInput: 添加输入. 使用 canAddInput: 判断该设备是否可以被添加到 session 中.

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // Handle the failure.
}

一个AVCaptureInput对象包含一个或多个数据流. 例如, 输入设备可能同时提供音频和视频数据. 每个 AVCaptureInputPort 对象代表一个媒体数据流. Capture session 使用一个AVCaptureConnection对象定义一组AVCaptureInputPort和一个AVCaptureOutput之间的映射关系.

使用AVCaptureOutput输出数据

要从 capture session 中输出数据, 可以向其添加一个或多个 outputs(AVCaptureOutput 的子类), 你可以使用:

使用方法 addOutput: 在 capture session 中添加 outputs. 使用方法 canAddOutput: 判断是否可以添加一个给定的 output. 可以根据需要在 session 运行过程中添加或移除一个 output.

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieOutput]) {
    [captureSession addOutput:movieOutput];
}
else {
    // Handle the failure.
}

输出为视频文件

使用 AVCaptureMovieFileOutput 将视频数据保存为一个本地文件. 可以对 movie file output 的参数进行配置, 比如最大的录制时长, 最大的录制文件大小, 如果设备磁盘空间不足的话, 还可以阻止用户进行视频录制.

AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;

输出的分辨率和码率依赖于 capture session 的sessionPreset 属性, 常用的视频编码格式是 H.264, 音频编码格式是 AAC. 实际的编码格式可能由于设备不同有所差异.

开始录制

使用方法 startRecordingToOutputFileURL:recordingDelegate: 开始录制一段 QuickTime 视频, 方法中需要传入一个本地文件的 URL 和一个录制的 delegate. 传入的本地 URL 不能是已经存在的文件, 因为 movie file output 不会对已存在的文件进行重写, 而且对传入的文件路径, 程序必须有写入权限. 传入的 delegate 必须遵循 AVCaptureFileOutputRecordingDelegate 协议, 且必须实现 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 方法. 在这个代理方法中, delegate 可能会向相册写入数据, 需要对错误进行检测.

AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];

确保文件写入成功

要判断文件是否写入成功, 在 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 方法中不仅需要检测 error, 还需要对 error 中的 user info 字典中的 AVErrorRecordingSuccessfullyFinishedKey 进行判断.

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
        fromConnections:(NSArray *)connections
        error:(NSError *)error {

    BOOL recordedSuccessfully = YES;
    if ([error code] != noErr) {
        // A problem occurred: Find out if the recording was successful.
        id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (value) {
            recordedSuccessfully = [value boolValue];
        }
    }
    // Continue as appropriate...

需要对AVErrorRecordingSuccessfullyFinishedKey进行判断是因为即使写入过程中抛出了一个 error, 文件也可能被成功写入了. 抛出的 error 可能是因为达到了一些设置的限制约束条件, 比如 AVErrorMaximumDurationReached, 以及 AVErrorMaximumFileSizeReached. 其他可能导致录制中断的情况如下:

在文件中添加元数据

可在任何时刻对文件的元数据 (metadata) 进行设置, 哪怕是在录制过程中. 一个 file output 的 metadata 由一个 AVMetadataItem 对象的数组来表示. 可以使用其可变子类 AVMutableMetadataItem 创建自定义的 metadata.

AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSArray *existingMetadataArray = aMovieFileOutput.metadata;
NSMutableArray *newMetadataArray = nil;
if (existingMetadataArray) {
    newMetadataArray = [existingMetadataArray mutableCopy];
}
else {
    newMetadataArray = [[NSMutableArray alloc] init];
}

AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataCommonKeyLocation;

CLLocation *location - <#The location to set#>;
item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
    location.coordinate.latitude, location.coordinate.longitude];

[newMetadataArray addObject:item];

aMovieFileOutput.metadata = newMetadataArray;

处理视频帧

AVCaptureVideoDataOutput 使用代理模式来对视频帧进行处理. 使用方法 setSampleBufferDelegate:queue: 设置代理, 此外还需要传入代理方法被调用的队列. 必须使用同步队列确保视频帧按照录制顺序被传递到代理方法中. 可以使用队列修改视频帧传递处理的优先级, 参见示例 SquareCam.

在代理方法 captureOutput:didOutputSampleBuffer:fromConnection: 中, 视频帧由 CMSampleBufferRef 类型表示. 默认情况下, buffers 被设置为当前设备相机效率最高的格式. 也可以使用属性 videoSettings 自定义输出格式. videoSettings属性是一个字典类型, 目前只支持kCVPixelBufferPixelFormatTypeKey. 系统建议的视频格式可以通过属性 availableVideoCVPixelFormatTypes 获取, 属性 availableVideoCodecTypes 返回支持的编码格式. Core Graphics 和 OpenGL 都很好的兼容了BGRA格式.

AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new];
NSDictionary *newSettings =
                @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
videoDataOutput.videoSettings = newSettings;

 // discard if the data output queue is blocked (as we process the still image
[videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];)

// create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured
// a serial dispatch queue must be used to guarantee that video frames will be delivered in order
// see the header doc for setSampleBufferDelegate:queue: for more information
videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
[videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];

AVCaptureSession *captureSession = <#The Capture Session#>;

if ( [captureSession canAddOutput:videoDataOutput] )
     [captureSession addOutput:videoDataOutput];

视频处理时的性能考虑

导出视频应当尽可能的使用低分辨率, 高分辨率会消耗额外的 CPU 和电量.

确保在代理方法captureOutput:didOutputSampleBuffer:fromConnection:中处理 sample buffer 时不要使用耗时操作, 如果处理占用时间过长, AV Foundation 会停止向代理方法中传递视频帧, 而且会停止其他的输出, 比如 preview layer 上的预览.

可以设置 capture video data 的属性 minFrameDuration 通过降低帧率来确保有足够的时间对视频帧进行处理. 将属性 alwaysDiscardsLateVideoFrames 设置为YES(默认值) 的话, 后面的视频帧将会被丢弃, 而不是排队等待处理. 如果你并不介意延迟, 而且需要处理所有的视频帧, 也可以将alwaysDiscardsLateVideoFrames设置为NO(即使如此, 也可能会出现掉帧的情况).

捕捉静态图像

使用 AVCaptureStillImageOutput 捕捉带元数据的静态图像. 图片的分辨率依赖于 session 的 preset 设置和具体的硬件设备.

像素和编码格式

不同的设备支持不同的图片格式. 可以使用 availableImageDataCVPixelFormatTypes 获取设备支持的所有像素格式, 使用 availableImageDataCodecTypes 可以获取设备支持的图像编码类型. 设置属性字典 outputSettings 可以指定需要的图片格式:()

AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];

如果需要的是 JPEG 图片, 则不要指定压缩格式. 相反, 应该让 still image output 进行压缩 (硬件加速). 可以使用 jpegStillImageNSDataRepresentation: 将图片转换为无压缩的NSData对象.

捕捉图片

使用方法 captureStillImageAsynchronouslyFromConnection:completionHandler: 捕捉图片. 第一个参数是需要捕捉的 connection, 需要判断当前的 connection 中哪个 input 正在采集视频.

AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
    for (AVCaptureInputPort *port in [connection inputPorts]) {
        if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
            videoConnection = connection;
            break;
        }
    }
    if (videoConnection) { break; }
}

方法的第二个参数是一个有两个参数的block: 一个包含图像数据的CMSampleBuffer类型, 另一个是 NSError 对象. Sample buffer 自身包含了元数据, 比如 EXIF 信息字典, 可以对这些元数据进行修改.

[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
    ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
        CFDictionaryRef exifAttachments =
            CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
        if (exifAttachments) {
            // Do something with the attachments.
        }
        // Continue as appropriate.
    }];

录制预览

可以提供给用户一个 preview, 用来展示正在通过摄像头录制的内容 (使用 preview layer), 或者正在通过麦克风记录的音频内容 (通过监听 audio channel).

视频预览

使用 AVCaptureVideoPreviewLayer 可以进行视频预览. AVCaptureVideoPreviewLayerCALayer的子类. 进行视频预览不需要设置任何的 output 对象.

使用 AVCaptureVideoDataOutput 类可以在视频展示给用户预览之前对视频进行处理.

与 capture output 不同, 一个 video preview layer 会强引用与其相关联的 session. 这是为了确保在进行视频预览时 session 不会被销毁.

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];

大体上, video preview layer 的性质与CALayer类似. 你可以对图像进行缩放, 向操作其他任何 layer 一样进行 transformations, rotations 等操作. 一个不同点在于你可能需要设置 layer 的orientation属性指定如何对摄像头捕捉的图像方向进行旋转. 此外, 通过属性 supportsVideoMirroring 可以判断设备是否支持预览镜像. 属性 automaticallyAdjustsVideoMirroring 的默认值为YES, 但是仍然可以根据需要设置属性 videoMirrored 进行修改.

视频重力模式

Preview layer 支持三种重力模式, 可以使用属性 videoGravity 进行设置:

  • AVLayerVideoGravityResizeAspect: 保持视频款高比, 当视频内容不能铺满屏幕时, 不足的部分使用黑色背景进行填充.
  • AVLayerVideoGravityResizeAspectFill: 保持视频款高比, 但是会铺满整个屏幕, 必要时会对视频内容进行裁剪.
  • AVLayerVideoGravityResize: 拉伸视频内容铺满屏幕, 可能导致图像变形.

预览时使用点击聚焦功能

在 preview layer 上实现点击聚焦功能时, 需要注意视频方向, 视频重力模式以及可能设置了视频镜像. 参见代码示例 AVCam-iOS: Using AVFoundation to Capture Images and Movies.

展示声音等级

要在 capture connection 中检测声音的均值和峰值, 可以使用 AVCaptureAudioChannel 对象. 声音等级不能使用 KVO 的方式获取, 所以需要根据界面更新的需求定时进行轮询 (比如每秒 10 次).

AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {
    // There should be only one connection to an AVCaptureAudioDataOutput.
    AVCaptureConnection *connection = [connections objectAtIndex:0];

    NSArray *audioChannels = connection.audioChannels;

    for (AVCaptureAudioChannel *channel in audioChannels) {
        float avg = channel.averagePowerLevel;
        float peak = channel.peakHoldLevel;
        // Update the level meter user interface.
    }
}

示例: 捕捉视频帧为UIImage对象

接下来的代码简单示例了如何捕捉视频, 并将捕捉到的视频帧转换为 UIImage 对象:

  • 创建AVCaptureSession对象
  • 找到合适类型的AVCaptureDevice对象进行输入
  • 为设备创建AVCaptureDeviceInput对象
  • 创建AVCaptureVideoDataOutput对象获取视频帧
  • 实现AVCaptureVideoDataOutput的代理
  • 实现一个方法将接收到的CMSampleBuffer转换为UIImage

提示: 为了展示核心代码, 这份示例省略了某些内容, 比如内存管理和通知的移除等. 使用 AV Foundation 之前, 你最好已经拥有 Cocoa 框架的使用经验.

创建和配置 Capture Session

AVCaptureSession用来协调 input 和 output 之间的数据流.

AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;

创建和配置 Device 和 Device Input

AVCaptureDevice表示捕捉设备, AVCaptureInput用来配置捕捉设备的端口

AVCaptureDevice *device =
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

NSError *error = nil;
AVCaptureDeviceInput *input =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}
[session addInput:input];

创建和配置 Video Data Output

使用AVCaptureVideoDataOutput处理未压缩的视频帧.

AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings =
                @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);

dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

实现 Sample Buffer 代理方法

- (void)captureOutput:(AVCaptureOutput *)captureOutput
         didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
         fromConnection:(AVCaptureConnection *)connection {

    UIImage *image = imageFromSampleBuffer(sampleBuffer);
    // Add your code here that uses the image.
}

将转换为 UIImage 的操作代码参见 Converting CMSampleBuffer to a UIImage Object.

开始和停止录制

配置 capture session 之后, 需要确保应用有访问相机的权限.

NSString *mediaType = AVMediaTypeVideo;

[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
    if (granted)
    {
        //Granted access to mediaType
        [self setDeviceAuthorized:YES];
    }
    else
    {
        //Not granted access to mediaType
        dispatch_async(dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:@"AVCam!"
                                    message:@"AVCam doesn't have permission to use Camera, please change privacy settings"
                                   delegate:self
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
                [self setDeviceAuthorized:NO];
        });
    }
}];

当获取到相应的访问权限之后, 可以使用startRunning方法开始录制. startRunning会阻塞线程, 所以需要异步调用, 以免阻塞主线程.

[session startRunning];

类似的调用stopRunning可以停止录制.