[AVFoundation]图像捕获和视频录制

原文:AVFoundation Programming Guide

写在前面

简单翻译一下AVFoundation的相关文档,本人英语水平一般,有不对的欢迎大家留言指正。

要管理从相机或麦克风等设备的捕获,您需要使用对象以表示输入和输出,并使用AVCaptureSession的实例来协调它们之间的数据流。你最少需要:

  • 一个AVCaptureDevice对象来表示输入设备,如相机或麦克风
  • 一个AVCaptureInput的具体子类的实例,用于配置输入设备端口
  • 一个AVCaptureOutput的具体子类的实例来管理是输出到一个视频文件还是静态图片
  • 一个AVCaptureSession实例来协调从输入设备到输出设备的数据流

为了给用户展示相机正在录制的内容,你可以使用一个AVCaptureVideoPreviewLayer实例。
你可以配置多个输入和输出设备,并使用一个会话(session)来协调,如图4-1。

Figure 4-1 A single session can configure multiple inputs and outputs

对于许多应用,这就是你所需要的。然而,对于某些操作(例如,你想要监视音频通道中的功率电平)则需要考虑如何表示输入设备的各种端口以及这些端口如何连接到输出。

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

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

Figure 4-2 AVCaptureConnection represents a connection between an input and output

你可以使用一个捕获连接来启用或禁用一个给定的输入或输出的数据流。您还可以使用连接来监视音频通道中的平均和峰值功率。
注意: 媒体捕获不支持在iOS设备上同时捕获前置和后置摄像头。

使用一个捕获会话来协调数据流

一个AVCaptureSession对象是用于管理数据捕获的中心协调对象。您使用它来协调从AV输入设备到输出的数据流。您将捕获设备和输出添加到会话,然后通过发送会话startRunning 消息来启动数据流,并通过发送stopRunning消息来停止数据流。

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

您可以在会话中使用预设来指定所需的图像质量和分辨率。预设是一些配置常数;在某些情况下,实际配置是由设备决定的:

标识 分辨率 注释
AVCaptureSessionPresetHigh High 最高的录制质量,每个设备有所不同
AVCaptureSessionPresetMedium Medium 适用于wifi分享,实际值可能会变
AVCaptureSessionPresetLow Low 适用于3G分享,实际值可能会变
AVCaptureSessionPreset640x480 640x480 VGA.
AVCaptureSessionPreset1280x720 1280x720 720p HD.
AVCaptureSessionPresetPhoto Photo 完整的照片分辨率,不支持视频输出。

如果你想要给媒体帧设置一个特定大小的配置,你应该在设置前检查它是否支持,如下:

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

如果您需要给会话设置一个比预设可能更精细的参数,或者您想对正在运行的会话进行更改,则可以使用beginConfigurationcommitConfiguration方法来包围您的更改。 beginConfigurationcommitConfiguration方法确保设备更改在一个组内发生。调用beginConfiguration之后,您可以添加或删除输出,更改会话的预设属性,或配置各个捕获输入或输出属性。在调用commitConfiguration之前,实际上没有进行任何更改,这些配置会在一起被应用。

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
监视捕获会话状态

捕获会话会发出通知,你可以监听它们,例如,当它启动或停止运行或中断时。您可以注册接收 AVCaptureSessionRuntimeErrorNotification来处理错误。您还可以查询会话的running属性,以确定其是否正在运行,以及interrupted属性以确定是否中断。此外,runninginterrupted的属性都符合KVC,并且通知会发布在主线程上。

An AVCaptureDevice对象表示输入设备

一个AVCaptureDevice对象是一个向AVCaptureSession对象提供输入数据(如音频或视频)的物理捕获设备的抽象。每个输入设备有一个对象,例如两个视频输入: 一个用于前置摄像头,一个用于后置摄像头,和一个用于麦克风的音频输入。您可以使用AVCaptureDevice的类方法devicesdevicesWithMediaType:找出当前可用的捕获设备。并且,如有必要,您可以找到iPhone,iPad或iPod提供的功能(请参阅Device Capture Settings)。可用设备的列表可能会改变。当前的输入设备可能变得不可用(如果它们被另一个应用程序使用),并且新的输入设备可能变得可用(如果它们被另一个应用程序放弃)。您应该注册接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification 通知,以便在可用设备列表更改时提醒。您可以使用捕获输入将输入设备添加到捕获会话(请参阅Use Capture Inputs to Add a Capture Device to a Session)。

设备特性

您可以查询设备的不同的特征。您还可以测试它是否提供特定的媒体类型或支持给定捕获会话的预设,分别使用方法hasMediaType:supportsAVCaptureSessionPreset:。要向用户提供信息,您可以找出捕获设备的位置(无论是在正在测试的设备的正面或背面)及其本地化名称。如果要提供捕获设备列表以允许用户选择一个捕获设备,这可能很有用。

图4-3显示了后置(AVCaptureDevicePositionBack) 和前置(AVCaptureDevicePositionFront)摄像机的位置。

注意:媒体捕获不支持同时捕获iOS设备上的前置和后置摄像头。

Figure 4-3 iOS device front and back facing camera positions

下面的代码展示了获取所有的可用设备并且输出它们的名字,如果是视频设备,输出它们的位置

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。

设备捕获设置

不同的设备具有不同的功能;例如,一些可能支持不同的焦点或闪光模式;有一些可能会支持在一个兴趣点上聚焦。
以下代码片段显示了如何查找具有手电筒模式并支持给定捕获会话预置的视频输入设备:

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属性。

您以类似的方式使用各种不同的功能。有一些常数来指定特定模式,您可以询问设备是否支持特定模式。在某些情况下,您可以监听属性以便在功能正在更改时获得通知。在所有情况下,您应该在更改特定功能的模式之前锁定设备,如Configuring a Device中所述。

注意:兴趣点聚焦和兴趣点曝光是相互排斥的,聚焦模式和曝光模式也是相互排斥的。

聚焦模式

有三种聚焦模式:

您使用isFocusModeSupported:方法来确定设备是否支持给定的对焦模式,然后使用focusMode属性设置模式。
另外,设备可能支持兴趣焦点。 您可以使用focusPointOfInterest来测试。 如果支持,您可以使用focusPointOfInterest设置焦点。 您传递一个CGPoint,其中{0,0}表示图片区域的左上角,而{1,1}表示右下角在横向模式(Home键在右侧),即使设备处于纵向模式时也是。

您可以使用adjustingFocus属性来确定设备当前是否正在进行对焦。 您可以使用键值观察观察属性,以在设备启动和停止对焦时获得通知。

如果更改对焦模式设置,可以按如下方式将其返回到默认配置:

if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {

    CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);

    [currentDevice setFocusPointOfInterest:autofocusPoint];

    [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
曝光模式

有两种曝光模式:

您使用isExposureModeSupported:方法来确定设备是否支持给定的曝光模式,然后使用exposureMode属性设置模式。

另外,设备可能支持兴趣点曝光。 您可以使用exposurePointOfInterestSupported测试。 如果支持,您可以使用exposurePointOfInterest设置曝光点。 您传递一个CGPoint,其中{0,0}表示图片区域的左上角,而{1,1}表示右下角在横向模式(Home键在右侧),即使设备处于纵向模式时也是。

您可以使用adjustingExposure属性来确定设备是否正在更改其曝光设置。 您可以使用键值观察监听属性,以在设备启动或停止改变曝光模式时获得通知。

如果更改曝光设置,可以按如下方式将其返回到默认配置:

if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {

    CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);

    [currentDevice setExposurePointOfInterest:exposurePoint];

    [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
闪光模式

有三种闪光模式:

您使用hasFlash来确定设备是否有闪光灯。 如果该方法返回YES,则使用isFlashModeSupported:方法,传递所需的模式以确定设备是否支持给定的闪光模式,然后使用flashMode属性设置模式。

手电筒模式

在手电筒模式下,闪光灯以低功率持续照明来进行视频拍摄。有三种手电筒模式:

您使用hasTorch来确定设备是否有闪关灯。 您使用isTorchModeSupported:方法来确定设备是否支持给定的闪关灯模式,然后使用torchMode属性设置模式。

对于带手电筒的设备,手电筒只有在设备与正在运行的捕获会话关联时才会打开。

视频稳定

视频稳定是否可用于操作视频取决于特定的设备硬件。 即使如此,并不是所有的源格式和视频分辨率都得到支持。

启用电影视频稳定也可能在视频捕获管道中加入额外的延迟。 要检测视频稳定是否正在使用,请使用videoStabilizationEnabled属性。 enablesVideoStabilizationWhenAvailable属性允许应用程序自动启用视频稳定(如果相机支持)。 默认情况下,由于上述限制,自动稳定功能被禁用。

白平衡

有两种白平衡模式:

您使用isWhiteBalanceModeSupported:方法来确定设备是否支持给定的白平衡模式,然后使用whiteBalanceMode属性设置模式。

您可以使用adjustingWhiteBalance属性来确定设备当前是否正在更改其白平衡设置。 您可以使用键值观察观察属性,以在设备启动或停止更改其白平衡设置时获得通知。

设置设备方向

您可以在AVCaptureConnection上设置所需的方向,以指定如何在AVCaptureOutput(AVCaptureMovieFileOutput,AVCaptureStillImageOutput和AVCaptureVideoDataOutput)中为图形定向。

使用AVCaptureConnectionsupportsVideoOrientation属性来确定设备是否支持更改视频的方向,以及videoOrientation属性来指定在输出端口中的图像的方向。 List 4-1显示了如何将AVCaptureConnection的方向设置为AVCaptureVideoOrientationLandscapeLeft

List 4-1设置捕获连接的方向

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.

只有当您需要可设置的设备属性保持不变时,才应该保持设备锁定。 不必要地持有设备锁会降低共享设备的其他应用程序的捕获效果。

在设备之间切换

有时您可能希望允许用户在输入设备之间切换,例如从使用前置摄像头切换到后置摄像头。 为了避免停顿或卡顿,您可以在运行时重新配置会话,但是您应该使用beginConfigurationcommitConfiguration来包含您的配置更改:

AVCaptureSession *session = <#A capture session#>;

[session beginConfiguration];

[session removeInput:frontFacingCameraDeviceInput];

[session addInput:backFacingCameraDeviceInput];

[session commitConfiguration];

当最外面的配置被调用时,所有的更改都在一起被改变。 这确保了一个平稳过渡。

使用捕获输入将捕获设备添加到会话

要将捕获设备添加到捕获会话中,可以使用AVCaptureDeviceInput(抽象类AVCaptureInput的具体子类)的实例。 捕获设备输入管理设备的端口。

NSError *error;

AVCaptureDeviceInput *input =

[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

if (!input) {

// Handle the error appropriately.

}

您可以使用addInput:向会话添加输入。 您可以使用canAddInput:检查捕获输入是否与现有会话兼容。

AVCaptureSession *captureSession = <#Get a capture session#>;

AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;

if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
} else {
    // Handle the failure.
}

有关如何重新配置正在运行的会话的详细信息,请参阅Configuring a Session
AVCaptureInput会提供一个或多个媒体数据流。 例如,输入设备可以提供音频和视频数据。 输入提供的每个媒体流由AVCaptureInputPort对象表示。 捕获会话使用AVCaptureConnection对象来定义一组AVCaptureInputPort对象与单个AVCaptureOutput之间的映射。

使用捕获输出从会话获取输出

要从捕获会话获取输出,您需要添加一个或多个输出。 输出是AVCaptureOutput的具体子类的一个实例。 你可以使用:

您可以使用addOutput:将输出添加到捕获会话。 您可以使用canAddOutput:检查捕获输出是否与现有会话兼容。 您可以在会话运行时根据需要添加和删除输出。

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对象将电影数据保存到文件。(AVCaptureMovieFileOutputAVCaptureFileOutput的具体子类,它定义了大部分的基本行为。)您可以配置影片文件输出的各个方面,例如录制的最长持续时间或文件的最大尺寸。 如果剩余的磁盘空间少于一定数量,您也可以禁止录制。

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

输出的分辨率和比特率取决于捕获会话的sessionPreset。 视频编码通常是H.264,音频编码通常是AAC。 实际值因设备而异。

开始录制

您可以使用startRecordingToOutputFileURL:recordingDelegate:来录制QuickTime影片。您需要提供一个基于文件的URL和代理。 URL不能标识现有文件,因为电影文件输出不会覆盖现有资源。 您还必须具有写入指定位置的权限。 代理必须符合AVCaptureFileOutputRecordingDelegate协议,并且必须实现captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:方法。

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:时,代理可能会将生成的电影写入Camera Roll相册。 它还应该检查可能发生的任何错误。

确保文件成功写入

要确定文件是否成功保存,在实现captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:时,您不仅要检查错误,还检查错误的用户信息字典(error’s user info dictionary)中的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...

您应该检查AVErrorRecordingSuccessfullyFinishedKeykey的值,因为该文件可能已成功保存,即使返回了一个错误。 错误可能表示您的录制达到了一个约束 - 例如,AVErrorMaximumDurationReached或者AVErrorMaximumFileSizeReached。 其他导致录制结束的原因是:

添加元数据到文件

你也可以随时设置影片文件的元数据,即使在录制时。 对于在记录开始时信息不可用的情况(如位置信息一样)下这是很有用的。 文件输出的元数据由AVMetadataItem对象的数组表示; 您可以使用其可变子类AVMutableMetadataItem的实例来创建您自己的元数据。

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的实例(请参阅 Representations of Media)。默认情况下,缓冲区以相机最有效的格式发出。您可以使用videoSettings属性来指定自定义输出格式。视频设置属性是一个字典;目前唯一支持的键是kCVPixelBufferPixelFormatTypeKey。推荐的像素格式由availableVideoCVPixelFormatTypes属性返回,由availableVideoCodecTypes属性返回支持的值。 Core GraphicsOpenGL都能与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];
处理视频的性能注意事项

您应该将会话输出设置为应用程序的最低实际分辨率。将输出设置为更高的分辨率会浪费处理周期,并且耗电。

您必须确保实现方法captureOutput:didOutputSampleBuffer:fromConnection:能够在分配给帧的时间量内处理样本缓冲区。如果时间太长,并且您持有了视频帧,AV Foundation会停止传输视频帧,不仅是你的代理,h还包括其他的输出(如预览图层)。

您可以使用捕获视频数据输出的minFrameDuration属性来确保您有足够的时间来处理帧,而不是以比其他情况更低的帧速率。您还应该确保将alwaysDiscardsLateVideoFrames属性设置为YES(默认值)。这样可以确保任何延迟的视频帧都被丢弃,而不是交给您进行处理。或者,如果不在乎录制时的延迟,并且您希望获得所有的数据,您可以将属性值设置为NO。这并不意味着帧不会被丢弃(也就是说,帧可能会被丢弃),但是它们可能不会被过早或者频繁地被丢弃。

捕捉静态图像

如果要捕获静态图像,可以使用AVCaptureStillImageOutput输出。 图像的分辨率取决于会话的预设以及设备。

像素和编码格式

不同的设备支持不同的图像格式。 您可以分别使用availableImageDataCVPixelFormatTypesavailableImageDataCodecTypes找到设备支持的像素和编码类型。 每个方法返回设备的支持的值得一个数组。 您设置outputSettings字典以指定所需的图像格式,例如:

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

如果要捕获JPEG图像,则通常不应指定您自己的压缩格式。 相反,您应该让静态图像输出为您做压缩,因为它的压缩是硬件加速的。 如果您需要图像的数据表示,您可以使用jpegStillImageNSDataRepresentation:获取NSData对象而不重新压缩数据,即使您修改了图像的元数据。

捕获图像

当您要捕获图像时,您可以给输出发送一个captureStillImageAsynchronouslyFromConnection:completionHandler:消息。 第一个参数是要用于捕获的连接。 您需要查找正在收集视频的输入端口的连接:

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

captureStillImageAsynchronouslyFromConnection:completionHandler:的第二个参数是一个包含两个参数的block:一个包含图像数据的CMSampleBuffer和一个错误。 样本缓冲区本身可以包含诸如EXIF字典的元数据作为附件。 您可以根据需要修改附件,但请注意 Pixel and Encoding Formats中讨论的JPEG图像的优化。

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

给用户展示正在录制的内容

您可以向用户展示相机(使用预览图)或麦克风(通过监听音频通道)正在录制的内容。

视频预览

您可以使用AVCaptureVideoPreviewLayer对象向用户展示正在录制的内容。 AVCaptureVideoPreviewLayerCALayer的子类(请参阅Core Animation Programming Guide),您不需要任何输出来显示预览。
使用AVCaptureVideoDataOutput类为客户端应用程序提供了在呈现给用户之前访问视频像素的能力。
与捕获输出不同,视频预览层持有了与其相关联的会话的强引用。 这是为了确保在预览层尝试显示视频时会话不会被释放。下面是初始化预览图层的方法:

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

通常,预览图层与其他CALayer对象相同(参见Core Animation Programming Guide)。 您可以像使用其他图层一样缩放图像、执行转换、旋转等操作。 一个区别是,您可能需要设置图层的orientation属性来指定如何旋转来自相机的图像。 另外,您可以通过查询supportsVideoMirroring属性来测试设备是否支持视频镜像。 您可以根据需要设置videoMirrored属性,但是当将automaticallyAdjustsVideoMirroring属性设置为YES(默认值)时,将根据会话的配置自动设置镜像值。

视频重力模式

预览图层支持三种重力模式,可以使用videoGravity设置:

在预览中使用“点击聚焦”

在预览图层中实现点击对焦时,您需要注意。 您必须考虑图层的预览方向和重力,以及预览展示的可能性。 请参阅示例项目AVCam-iOS: Using AVFoundation to Capture Images and Movies

显示音频音量

要监视拍摄连接中音频通道中的平均和峰值级别,可以使用AVCaptureAudioChannel对象。 音频音量不支持键值观察,所以您必须按您想要的频率轮询更新用户界面(例如,每秒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对象。:

注意:为了专注于最相关的代码,本示例省略了完整应用程序的几个方面,包括内存管理。 要使用AVFoundation,您需要有足够的Cocoa编程经验,以推断出丢失的部分。

创建并配置捕获会话

您可以使用AVCaptureSession对象来协调从AV输入到输出的数据流。 创建会话,并将其配置为生成中等分辨率的视频帧。

AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
创建和配置设备以及设备输入

捕获设备由AVCaptureDevice对象表示; 该类提供了检索所需输入类型的对象的方法。 一个设备具有一个或多个端口,可以使用AVCaptureInput对象进行配置。 通常,你可以使用默认的配置。
查找视频捕获设备,然后使用设备创建设备输入并将其添加到会话中。 如果找不到适当的设备,则deviceInputWithDevice:error:方法将通过引用返回错误。

AVCaptureDevice *device =
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
 
NSError *error = nil;
AVCaptureDeviceInput *input =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}
[session addInput:input];
创建和配置视频数据输出

你可以使用AVCaptureVideoDataOutput对象来处理被捕获的视频中的未压缩帧。 您通常会配置输出的几个方面。 例如,对于视频,您可以使用videoSettings属性指定像素格式,并通过设置minFrameDuration属性来限制帧率。

创建并配置视频数据的输出并将其添加到会话中; 通过把minFrameDuration属性设置为1/15秒将帧率设置为15 fps:

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

数据输出对象使用委托来处理视频帧。 代理必须实现AVCaptureVideoDataOutputSampleBufferDelegate协议。 当你设置数据输出的委托时,还必须提供一个队列,将在该队列上调用回调函数。

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

你使用队列来修改发送和处理视频帧的优先级。

实现Sample Buffer代理方法

在委托类中,实现captureOutput:didOutputSampleBuffer:fromConnection:
方法,它会在样本缓冲区被写入时调用。 视频数据输出对象将帧作为CMSampleBuffer类型传输,因此您需要将CMSampleBuffer类型转换为UIImage对象。 可以参考 Converting CMSampleBuffer to a UIImage Object

- (void)captureOutput:(AVCaptureOutput *)captureOutput
         didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
         fromConnection:(AVCaptureConnection *)connection {
 
    UIImage *image = imageFromSampleBuffer(sampleBuffer);
    // Add your code here that uses the image.
}

记住调用代理方法会在setSampleBufferDelegate:queue:中指定的队列上调用; 如果要更新用户界面,则必须在主线程上调用相关的代码。

开始以及结束录制

配置捕获会话后,您应确保有录制的权限。

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方法是一个阻塞调用,可能需要一些时间,因此您应该在串行队列上执行会话设置,以使主队列不被阻塞。 请参阅AVCam-iOS: Using AVFoundation to Capture Images and Movies

[session startRunning];

结束录制可以发送stopRunning消息。

高帧率视频采集

iOS 7.0在所选硬件上引入了对高帧率视频捕获的支持(也称为慢动作视频)。AVFoundation框架支持高帧率内容。

您可以使用AVCaptureDeviceFormat类来确定设备的捕获功能。此类具有返回支持的媒体类型、帧率、视场、最大缩放系数、是否支持视频稳定等的方法。

捕获支持720p(1280 x 720像素)分辨率以每秒60帧(fps)的帧率,包括视频稳定和可放置的P帧(H264编码视频的功能,使在较慢和较旧的硬件上也能让视频播放顺畅。 )

播放增强了对慢速和快速播放音频的支持,从而可以以更慢或更快的速度播放音频。

编辑完全支持在可变组件中的缩放编辑。

导出提供两个选项,支持60 fps视频。可用的帧率,慢速或快速运动,或者将影片转换为任意较慢的帧速率,例如每秒30帧。

SloPoke(没找到=。=)示例代码演示了AVFoundation支持快速视频捕获,确定硬件是否支持高帧速率视频捕获,使用各种速率和时间间距算法的播放以及编辑(包括为部分组件设置时间刻度)。

回放

AVPlayer的实例通过setRate:方法自动管理大部分播放速度。该值用作播放速度的乘数。值为1.0会导致正常播放,0.5以半速播放,5.0会使播放比正常快五倍,依此类推。

AVPlayerItem对象支持audioTimePitchAlgorithm属性。该属性允许您指定使用 Time Pitch Algorithm Settings常数以各种帧速率播放动画时的音频播放方式。

下表展示了支持的时间间距算法(Time pitch algorithm),质量(Quality),算法是否使音频捕捉到特定帧速率(Snaps to specific frame rate),以及每个算法支持的帧速率范围(Rate range)。

Time pitch algorithm Quality Snaps to specific frame rate Rate range
AVAudioTimePitchAlgorithmLowQualityZeroLatency 质量低,适合快进,倒带或低质量的声音 YES 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates.
AVAudioTimePitchAlgorithmTimeDomain 质量适中,计算量少,适合声音。 NO 0.5–2x rates.
AVAudioTimePitchAlgorithmSpectral 最高质量,计算量大,保留原始项目的间距。 NO 1/32–32 rates.
AVAudioTimePitchAlgorithmVarispeed 高品质播放,无间距校正。 NO 1/32–32 rates.
编辑

编辑时,您可以使用AVMutableComposition类来创建时间编辑。

导出

导出60 fps视频时使用AVAssetExportPresetPassthrough类导出资源。可以使用两种技术导出内容:

  • 使用AVAssetExportPresetPassthrough预设来避免重新编码影片。媒体将媒体部分标记为60 fps,部分放慢或部分加快。
  • 使用恒定的帧速率导出以实现最大播放兼容性。将视频组件的frameDuration属性设置为30 fps。您还可以通过使用导出会话的audioTimePitchAlgorithm属性来设置时间间隔。
录制

您可以使用AVCaptureMovieFileOutput类捕获高帧率视频,该类自动支持高帧率录制。它将自动选择正确的H264间距和比特率。

要进行自定义录制,您必须使用AVAssetWriter类,这需要一些额外的设置。

assetWriterInput.expectsMediaDataInRealTime=YES;

此设置确保捕获可以跟上输入的数据。

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

推荐阅读更多精彩内容