AVFoundation编程指南06-静态图像和视频媒体捕获

写在前面

喜欢AVFoundation资料的同学可以关注我的专题:《AVFoundation》专辑
也可以关注我的简书账号

正文

要管理来自设备(如摄像头或麦克风)的捕获,你需要组装对象以表示输入和输出,并使用AVCaptureSession实例来协调它们之间的数据流。你最不需要:

  • AVCaptureDevice的一个实例,用于表示输入设备,例如摄像头或麦克风

  • AVCaptureInput的具体子类的实例,用于配置输入设备的端口

  • AVCaptureOutput的具体子类的实例,用于管理视频文件或静态图像的输出

  • AVCaptureSession的一个实例,用于协调从输入到输出的数据流

要向用户显示摄像机正在录制的内容,可以使用AVCaptureVideoPreviewLayerCALayer的子类)的实例。

你可以配置多个输入和输出,由单个会话协调,如图4-1所示

图4-1单个会话可以配置多个输入和输出

对于许多应用程序,这是你需要的详细信息。但是,对于某些操作(例如,如果要监视音频通道中的功率级别),则需要考虑如何表示输入设备的各种端口以及这些端口如何连接到输出。

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

图4-2 AVCaptureConnection表示输入和输出之间的连接

你可以使用捕获连接来启用或禁用给定输入或给定输出的数据流。你还可以使用连接来监控音频通道中的平均功率和峰值功率。

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

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

AVCaptureSession对象是用于管理数据捕获的中央协调对象。你可以使用实例来协调从AV input devicesoutputs的数据流。你将所需的捕获设备和输出添加到会话中,然后通过向会话发送startRunning消息来启动数据流,并通过发送stopRunning消息来停止数据流。

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

配置Session

你可以在会话中使用预设来指定所需的图像质量和分辨率。预设是一个常数,用于标识多种可能配置中的一种;在某些情况下,实际配置是特定于设备的:

Symbol Resolution Comments
AVCaptureSessionPresetHigh High 最高的录音质量。
这个因设备不同而不同。
AVCaptureSessionPresetMedium Medium 适合Wi-Fi共享。
实际值可能会改变。
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后,你可以添加或删除输出,更改sessionPreset属性或配置单个捕获输入或输出属性。在调用commitConfiguration之前,实际上不会进行任何更改,此时它们将一起应用。

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

监控捕获会话状态

捕获会话会发布你可以观察到的通知,例如,启动或停止运行或中断的通知。如果发生运行时错误,你可以注册接收AVCaptureSessionRuntimeErrorNotification。你还可以查询会话的running属性以查明它是否正在运行,以及它的interrupted属性以查明它是否被中断。此外,runninginterrupted属性都符合键值观察,并且通知发布在主线程上。

AVCaptureDevice对象表示输入设备

AVCaptureDevice对象抽象物理捕获设备,该设备向AVCaptureSession对象提供输入数据(例如音频或视频)。每个输入设备有一个对象,例如,两个视频输入 - 一个用于前置摄像头,一个用于后置摄像头 - 和一个用于麦克风的音频输入。

你可以使用AVCaptureDevice类方法devicesdevicesWithMediaType:。找出当前可用的捕获设备。并且,如有必要,你可以了解iPhoneiPadiPod提供的功能(请参阅Device Capture Settings)。但是,可用设备列表可能会发生变化。当前的输入设备可能变得不可用(如果它们被另一个应用程序使用),并且新的输入设备可能变得可用(如果它们被另一个应用程序弃用)。你应该注册接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification
通知,以便在可用设备列表更改时收到警报。

使用捕获输入将输入设备添加到捕获会话(请参阅Use Capture Inputs to Add a Capture Device to a Session)。

设备特征

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

图4-3显示了背面(AVCaptureDevicePositionBack)和前置(AVCaptureDevicePositionFront)相机的位置。

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

图4-3 iOS设备前后摄像头位置

以下代码示例遍历所有可用设备并记录其名称 - 以及视频设备,它们在设备上的位置。

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

此外,你还可以找到设备的型号ID及其唯一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属性设置模式。

另外,设备可以支持关注焦点。你使用focusPointOfInterestSupported测试支持。如果支持,则使用focusPointOfInterest设置焦点。你传递一个CGPoint,其中{0,0}表示图片区域的左上角,{1,1}表示横向模式的右下角,右侧的主页按钮 - 即使设备处于纵向模式,这也适用。

你可以使用adjustingFocus属性来确定设备当前是否正在聚焦。你可以使用KVO来观察属性,以便在设备启动和停止聚焦时收到通知。

如果更改焦点模式设置,则可以将其恢复为默认配置,如下所示:

  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}表示横向模式的右下角,右侧的主页按钮 - 即使设备处于纵向模式,这也适用。

你可以使用adjustingExposure属性来确定设备当前是否正在更改其曝光设置。你可以使用键值观察来观察属性,以便在设备启动时通知并停止更改其曝光设置。

如果更改曝光设置,则可以将其恢复为默认配置,如下所示:

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

闪光灯模式

有三种闪光模式:

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

Torch模式

torch模式下,闪光灯以低功率连续启用,以照亮视频拍摄。有三种手电筒模式:

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

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

视频防抖动

电影视频防抖动功能适用于视频操作,具体取决于具体的设备硬件。即便如此,并非所有源格式和视频分辨率都受支持。

启用影院视频稳定功能还可能会在视频捕获管道中引入额外的延迟。要检测何时使用视频稳定,请使用videoStabilizationEnabled属性。 enablesVideoStabilizationWhenAvailable属性允许应用程序在摄像机支持的情况下自动启用视频稳定功能。默认情况下,由于上述限制,自动稳定功能被禁用。

白平衡

有两种白平衡模式:

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

你可以使用adjustingWhiteBalance属性来确定设备当前是否正在更改其白平衡设置。你可以使用KVO来观察属性,以便在设备启动时通知并停止更改其白平衡设置。

设置设备方向

你可以在AVCaptureConnection上设置所需的方向,以指定你希望如何在AVCaptureOutput(AVCaptureMovieFileOutput,AVCaptureStillImageOutputAVCaptureVideoDataOutput)中定向图像以进行连接。

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

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

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

配置Device配置设备

要在设备上设置捕获属性,必须首先使用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];

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

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:你不仅要检查错误,还要检查错误的用户信息字典中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...

将元数据添加到文件

你可以随时设置电影文件的元数据,即使在录制时也是如此。这对于在记录开始时信息不可用的情况很有用,如位置信息的情况。文件输出的元数据由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 opaque类型的实例(请参阅Representations of Media)。默认情况下,缓冲区以相机最有效的格式发出。你可以使用videoSettings属性指定自定义输出格式。视频设置属性是字典;目前,唯一支持的密钥是kCVPixelBufferPixelFormatTypeKeyavailableVideoCVPixelFormatTypes属性返回推荐的像素格式,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停止提供帧,不仅是你的代理,还有其他输出,比如预览图层等。

你可以使用捕获视频数据输出的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是一个带有两个参数的块:包含图像数据的CMSampleBuffer opaque类型和错误。样本缓冲区本身可能包含元数据,例如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对象。它告诉你如何:

注意:为了关注最相关的代码,本示例省略了完整应用程序的几个方面,包括内存管理。要使用AV Foundation,你应该有足够的经验使用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 opaque类型传递,因此你需要将CMSampleBuffer opaque类型转换为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方法是一个阻塞调用,可能需要一些时间,因此您应该在串行队列上执行会话设置,以便不阻止主队列(这使UI保持响应)。请参阅AVCam-iOS: Using AVFoundation to Capture Images and Movies以获取规范实现示例。

[session startRunning];

要停止录制,请向会话发送stopRunning消息。

高帧率视频捕获

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

你可以使用AVCaptureDeviceFormat类确定设备的捕获功能。此类具有返回支持的媒体类型,帧速率,视野,最大缩放因子,是否支持视频稳定等的方法。

  • Capture支持完整的720p1280 x 720像素)分辨率,每秒60帧(fps),包括视频稳定和可丢弃的P帧(H264编码电影的一项功能,即使在较慢和较旧的硬件上,电影也可以流畅地播放。 )

  • 播放增强了对慢速和快速播放的音频支持,允许以更慢或更快的速度保留音频的时间间距。

  • 编辑完全支持可变组合中的缩放编辑。

  • 导出支持60 fps电影时提供两个选项。可以保留可变帧速率,慢速或快速运动,或者将电影转换为任意较慢的帧速率,例如每秒30帧。

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

Playback

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

AVPlayerItem对象支持audioTimePitchAlgorithm属性。此属性允许你指定在使用Time Pitch Algorithm Settings常量以各种帧速率播放影片时播放音频的方式。

下表显示了支持的时间间距算法,质量,算法是否使音频捕捉到特定帧速率,以及每种算法支持的帧速率范围。

时间间距算法 质量 捕捉到特定的帧速率 帧率范围
AVAudioTimePitchAlgorithmLowQualityZeroLatency 低质量,适合快进,快退或低质量语音。 YES 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates.
AVAudioTimePitchAlgorithmTimeDomain 质量适中,计算成本低,适合语音。 NO 0.5–2x rates.
AVAudioTimePitchAlgorithmSpectral 最高质量,最昂贵的计算,保留原始item的间距。 NO 1/32–32 rates.
AVAudioTimePitchAlgorithmVarispeed 高质量播放,无音高校正. NO 1/32–32 rates.

编辑

编辑时,使用AVMutableComposition类构建时间编辑。

导出

导出60 fps视频使用AVAssetExportSession类导出asset。可以使用两种技术导出内容:

  • 使用AVAssetExportPresetPassthrough预设可避免重新编码影片。它将媒体的部分标记为部分60 fps,部分减速或部分加速。

  • 使用恒定帧速率导出以获得最大的播放兼容性。将视频合成的frameDuration属性设置为30 fps。你还可以使用设置导出会话的audioTimePitchAlgorithm属性来指定时间间距。

录制

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

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

assetWriterInput.expectsMediaDataInRealTime=YES;

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

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