要管理来自设备(如照相机或麦克风)的捕获,您可以组合对象来表示输入和输出,并使用AVCaptureSession实例来协调它们之间的数据流。您至少需要:
- AVCaptureDevice的一个实例,用于表示输入设备,如照相机或麦克风
- AVCaptureInput的一个具体子类的实例,用于配置输入设备的端口
- AVCaptureOutput的一个具体子类的实例,用于管理电影文件或静态图像的输出
- AVCaptureSession的一个实例,用于协调从输入到输出的数据流
要向用户显示相机正在录制的内容的预览,可以使用AVCaptureVideoPreviewLayer(CALayer的子类)的实例。
您可以配置由单个会话协调的多个输入和输出,如图4-1所示
对于许多应用程序,这是您需要的详细信息。然而,对于一些操作,(如果你想要监视音频信道中的功率电平),你需要考虑如何表示输入设备的各种端口,以及这些端口如何连接到输出。
捕获会话中捕获输入和捕获输出之间的连接由AVCaptureConnection对象表示。捕获输入(AVCaptureInput实例)有一个或多个输入端口(AVCaptureInputPort实例)。捕获输出(AVCaptureOutput的实例)可以接受来自一个或多个源的数据(例如,AVCaptureMovieFileOutput对象同时接受视频和音频数据)。
当您向会话添加输入或输出时,会话在所有兼容捕获输入端口和捕获输出之间形成连接,如图4-2所示。捕获输入和捕获输出之间的连接由AVCaptureConnection对象表示。
您可以使用捕获连接来启用或禁用从给定输入或到给定输出的数据流。您还可以使用连接来监视音频通道中的平均和峰值功率级别。
注意:媒体捕获不支持在iOS设备上同时捕获正面和背面摄像头。
使用捕获会话来协调数据流
AVCaptureSession对象是用于管理数据捕获的中心协调对象。您可以使用实例来协调从AV输入设备到输出的数据流。将捕获设备和输出添加到会话中,然后通过向会话发送startRunning消息来启动数据流,并通过发送stopRunning消息来停止数据流。
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
配置会话
您可以在会话中使用预设来指定所需的图像质量和分辨率。预设是一个常数,用于标识多个可能配置中的一个;在某些情况下,实际配置是特定于设备的:
Symbol | Resolution | Comments |
---|---|---|
AVCaptureSessionPresetHigh | High | Highest recording quality.This varies per device. |
AVCaptureSessionPresetMedium | Medium | Suitable for Wi-Fi sharing.The actual values may change. |
AVCaptureSessionPresetLow | Low | Suitable for 3G sharing.The actual values may change. |
AVCaptureSessionPreset640X480 | 640x480 | VGA. |
AVCaptureSessionPreset1280x720 | 1280x720 | 720p HD. |
AVCaptureSessionPressetPhoto | Photo | Full photo resolution.This is not supported for video output. |
如果要设置特定于媒体帧大小的配置,应在设置前检查是否支持该配置,如下所示:
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
如果您需要在比预设更精细的级别上调整会话参数,或者希望对正在运行的会话进行更改,则可以使用beginConfiguration和commitConfiguration方法对更改进行调整。beginConfiguration和commitConfiguration方法可确保设备作为一个组进行更改,从而将状态的可见性或不一致性降至最低。调用beginConfiguration后,您可以添加或删除输出、更改sessionPreset属性或配置单个捕获输入或输出属性。在调用commitConfiguration之前,实际上不会进行任何更改,此时它们将一起应用。
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
监视捕获会话状态
捕获会话发布您可以观察到的通知,以便在其启动或停止运行或中断时收到通知。如果发生运行时错误,您可以注册以接收AVCaptureSessionRuntimeErrorNotification。您还可以询问会话的running属性以确定它是否正在运行,也可以询问其interrupted属性以确定它是否被中断。此外,running和interrupted属性都是键值兼容的,通知发布在主线程上。
AVCaptureDevice对象表示输入设备
AVCaptureDevice对象抽象为向AVCaptureSession对象提供输入数据(如音频或视频)的物理捕获设备。每个输入设备有一个对象,例如,两个视频输入,一个用于前向摄像头,一个用于后向摄像头,一个用于麦克风的音频输入。
您可以使用AVCaptureDevice类方法devices和devicesWithMediaType:查找当前可用的捕获设备。如果需要,您还可以了解iPhone、iPad或iPod提供了哪些功能(请参阅设备捕获设置)。但是,可用设备的列表可能会更改。当前的输入设备可能变得不可用(如果它们被另一个应用程序使用),而新的输入设备可能变得可用(如果它们被另一个应用程序放弃)。您应该注册以接收AVCaptureDeviceWasConnectedNotification和AVCaptureDeviceWasDisconnectedNotification通知,以便在可用设备列表更改时发出警报。
使用捕获输入将输入设备添加到捕获会话(请参阅使用捕获输入将捕获设备添加到会话)。
器件特性
您可以询问设备的不同特性。您还可以分别使用hasMediaType:和SupportsCaptureSessionPreset:测试它是否提供特定的媒体类型或支持给定的捕获会话预设。要向用户提供信息,您可以找到捕获设备的位置(无论是在被测试单元的正面还是背面)及其本地化名称。如果您希望显示捕获设备的列表,以允许用户选择一个,这可能很有用。
图4-3显示了后向(AVCaptureDevicePositionBack)和前向(AVCaptureDevicePositionFront)摄像头的位置。
注意:媒体捕获不支持在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属性。
您可以以类似的方式使用各种不同的功能。存在用于指定特定模式的常量,您可以询问设备是否支持特定模式。在某些情况下,您可以观察特性,以便在特性更改时收到通知。在所有情况下,您都应该在更改特定功能的模式之前锁定设备,如配置设备中所述。
注:关注焦点和曝光点是相互排斥的,焦点模式和曝光模式也是如此。
聚焦模式
有三种聚焦模式:
- AVCaptureFocusModelLocked:焦点位置是固定的。
当您希望允许用户合成场景,然后锁定焦点时,这非常有用。 - AVCaptureFocusModeAutoFocus:相机进行单次扫描聚焦,然后恢复为锁定。
这适用于以下情况:您希望选择要聚焦的特定项目,然后即使该项目不是场景的中心,也要将焦点保持在该项目上。 - AVCaptureFocusModeContinuousAutoFocus:相机可根据需要连续自动对焦。
使用isFocusModeSupported:方法确定设备是否支持给定的聚焦模式,然后使用focusMode属性设置模式。
此外,设备可以支持关注焦点。您可以使用focusPointOfInterestSupported测试支持。如果支持,可以使用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];
}
曝光模式
有两种曝光模式:
- AVCaptureExposureModeContinuousAutoExposure:设备会根据需要自动调整曝光级别。
- AVCaptureExposureModelLocked:曝光级别固定在其当前级别。
使用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];
}
闪光模式
有三种闪光模式:
- AVCaptureFlashModeOff:闪光灯永远不会启动。
- AVCaptureFlashModeOn:闪光灯将始终启动。
- AVCaptureFlashModeAuto:闪光灯将根据环境光照条件启动。
您可以使用hasFlash来确定设备是否有闪存。如果该方法返回YES,则使用isFlashModeSupported:方法,传递所需模式以确定设备是否支持给定的闪存模式,然后使用flashMode属性设置模式。
火炬模式
在火炬模式下,闪光灯在低功率下持续启用,以照亮视频捕获。有三种火炬模式:
AVCaptureTorchModeOff:火炬始终处于关闭状态。
AVCaptureTorchModeOn:火炬一直亮着。
AVCaptureTorchModeAuto:焊炬根据需要自动打开和关闭。
您可以使用hasTorch来确定设备是否有闪光灯。使用isTorchModeSupported:方法确定设备是否支持给定的闪存模式,然后使用torchMode属性设置模式。
对于带有火炬的设备,火炬仅在设备与正在运行的捕获会话关联时打开。
视频稳定
根据特定的设备硬件,电影视频稳定可用于在视频上运行的连接。即便如此,并非所有源格式和视频分辨率都受支持。
启用电影视频稳定也可能会在视频捕获管道中引入额外的延迟。要检测何时使用视频稳定,请使用videoStabilizationEnabled属性。enablesVideoStabilizationWhenAvailable属性允许应用程序在摄像机支持的情况下自动启用视频稳定。默认情况下,由于上述限制,自动稳定功能被禁用。
白平衡
有两种白平衡模式:
- AVCaptureWhiteBalanceModelLocked:白平衡模式是固定的。
- AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance:相机会根据需要不断调整白平衡。
使用isWhiteBalanceModeSupported:方法确定设备是否支持给定的白平衡模式,然后使用whiteBalanceMode属性设置模式。
您可以使用adjustingWhiteBalance属性确定设备当前是否正在更改其白平衡设置。当设备启动和停止更改其白平衡设置时,可以使用键值观察来观察属性。
设置设备方向
您可以在AVCaptureConnection上设置所需的方向,以指定在连接的AVCaptureOutput(AVCaptureMovieFileOutput、AVCaptureStillImageOutput和AVCaptureVideoDataOutput)中图像的方向。
使用AVCaptureConnectionsupportsVideoOrientation属性确定设备是否支持更改视频的方向,使用videoOrientation属性指定图像在输出端口中的方向。清单4-1显示了如何将AVCaptureConnection的方向设置为AvCaptureVideoOrientionAndscapeLeft:
清单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.
仅当需要保持可设置设备属性不变时,才应保持设备锁定。不必要地保持设备锁定可能会降低共享设备的其他应用程序中的捕获质量。
在设备之间切换
有时您可能希望允许用户在输入设备之间切换,例如,从使用前向摄像头切换到后向摄像头。为避免暂停或口吃,您可以在会话运行时重新配置会话,但应使用beginConfiguration和commitConfiguration来支持您的配置更改:
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.
}
有关如何重新配置正在运行的会话的详细信息,请参阅配置会话。
AVCaptureInput提供一个或多个媒体数据流。例如,输入设备可以同时提供音频和视频数据。由输入提供的每个媒体流都由AVCaptureInputPort对象表示。捕获会话使用AVCaptureConnection对象定义一组AVCaptureInputPort对象和一个AVCaptureOutput对象之间的映射。
使用捕获输出从会话获取输出
要从捕获会话获取输出,请添加一个或多个输出。输出是AVCaptureOutput的一个具体子类的实例。您使用:
- AVCaptureMovieFileOutput以输出到电影文件
- AVCAPTUREVIDEODATOUTPUT如果您想处理所捕获视频的帧,例如,创建自己的自定义视图层
- AVCaptureAudioDataOutput,如果要处理正在捕获的音频数据
- AVCaptureStillImageOutput,如果要捕获带有元数据的静态图像
您可以使用addOutput:将输出添加到捕获会话。您可以使用CanadOutput:检查捕获输出是否与现有会话兼容。会话运行时,可以根据需要添加和删除输出。
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#>;
输出的分辨率和比特率取决于捕获会话的会话预设。视频编码通常为H.264,音频编码通常为AAC。实际值因设备而异。
开始录音
您可以使用startRecordingToOutputFileURL:recordingDelegate:开始录制QuickTime电影。您需要提供一个基于文件的URL和一个委托。URL不能标识现有文件,因为电影文件输出不会覆盖现有资源。您还必须具有写入指定位置的权限。委托必须符合AVCaptureFileOutputRecordingDelegate协议,并且必须实现captureOutput:DidFinishRedingToOutputFileAttribute: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:DidFinishRedingToOutputFileATURL:fromConnections:error:的实现中,代理可能会将生成的电影写入相机滚动相册。它还应该检查可能发生的任何错误。
确保文件已成功写入
要确定文件是否已成功保存,请在captureOutput:DidFinishRecordingToOutputFileAttribute: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...
您应该检查错误的用户信息字典中AVErrorRecordingSuccessfullyFinishedKeykey的值,因为即使出现错误,文件也可能已成功保存。该错误可能表示已达到某个录制限制,例如AverRorMaximumDurationReach或AVErrorMaximumFileSizeReached。录制可能停止的其他原因包括:
- 磁盘已满。磁盘已满
- 记录设备已断开,但设备已断开
- 会话被中断(例如,接到电话)-AVErrorSessionWasInterrupted
向文件添加元数据
您可以随时为电影文件设置元数据,即使在录制时也是如此。这对于记录开始时信息不可用的情况非常有用,例如位置信息。文件输出的元数据由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:队列设置委托。除了设置委托外,还可以指定一个串行队列,在该队列上调用委托方法。必须使用串行队列以确保帧以正确的顺序传递给代理。您可以使用队列修改为传送和处理视频帧指定的优先级。有关示例实现,请参见SquareCam。
帧在委托方法captureOutput:didOutputSampleBuffer:fromConnection:中显示为CMSampleBufferRef不透明类型的实例(请参见媒体表示)。默认情况下,缓冲区以相机最有效的格式发射。可以使用videoSettings属性指定自定义输出格式。视频设置属性是一个字典;目前,唯一受支持的密钥是kCVPixelBufferPixelFormatTypeKey。建议的像素格式由availableVideoCVPixelFormatTypes属性返回,AvailableVideoCodeTypes属性返回支持的值。核心图形和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];
处理视频的性能注意事项
您应该将会话输出设置为应用程序的最低实际分辨率。将输出设置为比必要的分辨率更高的分辨率会浪费处理周期,并且不必要地消耗电能。
您必须确保captureOutput:didOutputSampleBuffer:fromConnection:的实现能够在分配给帧的时间内处理样本缓冲区。如果它占用时间太长,而你保存在视频帧上,AV基金会停止发送帧,不仅对你的委托,而且对其他输出,如预览层。
您可以使用capture video data output的minFrameDuration属性来确保有足够的时间来处理帧,但代价是帧速率低于其他情况。您还可以确保alwaysDiscardsLateVideoFrames属性设置为YES(默认值)。这样可以确保删除任何延迟的视频帧,而不是将其交给您进行处理。或者,如果您正在录制,并且无论输出fame是否有点晚,您都希望获得所有fame,您可以将属性值设置为“否”。这并不意味着不会删除帧(也就是说,仍然可以删除帧),但可能不会尽早或高效地删除帧。
捕捉静止图像
如果要捕获包含元数据的静态图像,请使用AVCaptureStillImageOutput。图像的分辨率取决于会话的预设以及设备。
像素和编码格式
不同的设备支持不同的图像格式。您可以分别使用availableImageDataCVPixelFormatTypes和AvailableImageDataCodeTypes了解设备支持哪些像素和编解码器类型。每个方法都返回特定设备支持的值的数组。设置outputSettings字典以指定所需的图像格式,例如:
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
如果要捕获JPEG图像,通常不应指定自己的压缩格式。相反,您应该让静态图像输出为您进行压缩,因为它的压缩是硬件加速的。如果需要图像的数据表示,可以使用jpegStillImageNSDataRepresentation:获取NSData对象,而无需重新压缩数据,即使修改了图像的元数据也是如此。
捕获图像
如果要捕获图像,请向输出发送captureStillImageAsynchronouslyFromConnection:completionHandler:message。第一个参数是要用于捕获的连接。您需要查找其输入端口正在采集视频的连接:
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不透明类型和一个错误。示例缓冲区本身可能包含元数据,例如EXIF字典,作为附件。如果需要,可以修改附件,但请注意在像素和编码格式中讨论的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对象为用户提供录制内容的预览。AVCaptureVideoPreviewLayer是Calayer的一个子类(请参阅《核心动画编程指南》。您不需要任何输出来显示预览。
使用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对象(请参见《核心动画编程指南》)。可以像缩放任何层一样缩放图像并执行变换、旋转等。一个不同之处是,您可能需要设置层的“方向”特性,以指定它应该如何旋转来自摄影机的图像。此外,您可以通过查询supportsVideoMirroring属性来测试设备对视频镜像的支持。您可以根据需要设置videoMirrored属性,但当automaticallyAdjustsVideoMirroring属性设置为YES(默认值)时,会根据会话的配置自动设置镜像值。
视频重力模式
预览层支持使用videoGravity设置的三种重力模式:
- AVLayerVideoGravityResizeAspect:这将保留纵横比,在视频未填满可用屏幕区域的地方留下黑色条。
- AVLayerVideoGravityResizeAspectFill:这会保留纵横比,但会填充可用的屏幕区域,并在必要时裁剪视频。
- AVLayerVideoGravityResize:这只是拉伸视频以填充可用的屏幕区域,即使这样做会扭曲图像。
使用带有预览的“点击聚焦”
在结合预览层实现“点击聚焦”时,需要小心。必须考虑图层的预览方向和重力,以及预览可能被镜像的可能性。有关此功能的实现,请参阅示例代码项目AVCam iOS:使用AVFoundation捕获图像和电影。
显示音频级别
要监视捕获连接中音频通道的平均和峰值功率级别,请使用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对象。它向您展示了如何:
- 创建AVCaptureSession对象,以协调从AV输入设备到输出设备的数据流
- 查找所需输入类型的AVCaptureDevice对象
- 为设备创建AVCaptureDeviceInput对象
- 创建AVCaptureVideoDataOutput对象以生成视频帧
- 为AVCaptureVideoDataOutput对象实现一个委托以处理视频帧
- 实现一个函数,将委托接收的CMSampleBuffer转换为UIImage对象
注意:为了关注最相关的代码,本示例省略了完整应用程序的几个方面,包括内存管理。使用AV基金会,你希望有足够的经验,可可能够推断出丢失的碎片。
创建和配置捕获会话
使用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);
您可以使用队列修改为传送和处理视频帧指定的优先级。
实现示例缓冲区委托方法
在委托类中,实现在写入示例缓冲区时调用的方法(captureOutput:didOutputSampleBuffer:fromConnection:)。视频数据输出对象以CMSampleBuffer不透明类型传递帧,因此需要将CMSampleBuffer不透明类型转换为UIImage对象。此操作的函数如将CMSampleBuffer转换为UIImage对象所示。
- (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:使用AVFoundation捕获图像和电影。
[session startRunning];
要停止录制,请向会话发送stopRunning消息。
高帧速率视频捕获
iOS 7.0在选定硬件上引入了高帧速率视频捕获支持(也称为“SloMo”视频)。完整的AVFoundation框架支持高帧速率内容。
您可以使用AVCaptureDeviceFormat类确定设备的捕获能力。此类具有返回支持的媒体类型、帧速率、视野、最大缩放因子、是否支持视频稳定等的方法。
- Capture支持每秒60帧(fps)的全720p(1280 x
720像素)分辨率,包括视频稳定和可拖放的P帧(H264编码电影的一项功能,允许电影在较慢和较旧的硬件上平稳播放) - 播放增强了对慢速和快速播放的音频支持,允许以较慢或较快的速度保留音频的时间音高。
- 编辑完全支持可变组合中的缩放编辑。
- 导出在支持每秒60帧的电影时提供两个选项。可变帧速率(慢动作或快动作)可以保留,也可以将电影转换为任意较慢的帧速率,例如每秒30帧。
SloPoke示例代码演示了AVFoundation对快速视频捕获的支持,确定硬件是否支持高帧速率视频捕获、使用各种速率和时距算法进行播放以及编辑(包括为部分合成设置时间刻度)。
回放
AVPlayer的一个实例通过设置setRate:method值自动管理大部分播放速度。该值用作播放速度的倍增。值为1.0会导致正常播放,0.5以半速播放,5.0以比正常快五倍的速度播放,依此类推。
AVPlayerItem对象支持audioTimePitchAlgorithm属性。此属性允许您使用“时间基音算法”设置常量指定以不同帧速率播放电影时音频的播放方式。
下表显示了支持的时间基音算法、质量、该算法是否使音频捕捉到特定帧速率,以及每个算法支持的帧速率范围。
编辑
编辑时,可以使用AVMutableComposition类构建时态编辑。
- 使用composition类方法创建新的AVMutableComposition实例。
- 使用insertTimeRange:ofAsset:atTime:error:方法插入视频资源。
- 使用scaleTimeRange:toDuration设置合成部分的时间刻度:
出口
导出60 fps视频使用AVAssetExportSession类导出资源。可以使用两种技术导出内容:
- 使用AVAssetExportPresetPassthrough预设避免重新编码电影。它使用标记为60 fps区段、减速区段或加速区段的介质区段重定时介质。
- 使用恒定帧速率导出以获得最大播放兼容性。将视频合成的frameDuration属性设置为30 fps。您还可以通过设置导出会话的audioTimePitchAlgorithm属性来指定时间音高。
记录
您可以使用自动支持高帧速率录制的AVCaptureMovieFileOutput类捕获高帧速率视频。它将自动选择正确的H264基音电平和比特率。
要进行自定义录制,必须使用AVAssetWriter类,这需要一些额外的设置。
assetWriterInput.expectsMediaDataInRealTime=YES;
此设置确保捕获可以跟上传入的数据。