内容来源
https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html#//apple_ref/doc/uid/TP40010188-CH5-SW4
要管理从设备(比如:摄像或麦克风)捕获的数据,你需要一些类来代表输入和输出。并且还要一个AVCaptureSession
的实例来协调input和ouput之间的数据流。
最少你需要以下几点:
- 一个
AVCaptureDevice
的实例来表示输入设备,比如摄像头或麦克风。 - 一个
AVCaptureInput
子类的实例,去配置输入设备的端口。(AVCaptureInput
为抽象类,不能直接实例化) - 一个
AVCaptureOutput
子类的实例,去管理输出为电影文件或是静态图片。(AVCaptureOutput
为抽象类,不能直接实例化) - 一个
AVCaptureSessioin
的实例,去协调从输入到输出的数据流。
如果要展示你的摄像头录了什么,你可以用一个AVCaptureVideoPreviewLayer
(CALayer
的子类)的实例
你可以用一个session来协调多个inputs和outputs.如下图:
一个session里的一个输入和一个输出之间的联系是AVCaptureConnection
对象。inputs(AVCaptureInput
实例)有一个或多个端口(AVCaptureInputPort
实例)。outputs(AVCaptureOutput
实例)能从一个或多个源访问数据(比如:AVCaptureMovieFileOutput
对象可以访问音频和视频数据)。
当你要增加一个输入或输出到session时,session会在所有兼容的输入端口和输出之间形成连接(connections),如下图。输入和输出之间的连接可以用AVCaptureConnection
对象表示。
你可以用connection去开启或关闭一个从input来的或到output去的数据流。你也可以用一个connection去跟踪一个音频的平均或峰值音量。
媒体捕获设备不支持同时得到前置和后置两个摄像头。
用Capture Session和协调数据流
一个AVCaptureSession
对象是你可以用来管理数据捕获的中央协调对象。你可以用一个实例去协调从AV Input devices到输出的数据流。你可以增加你想要的捕获设备和输出到session,然后发送startRunning
消息到session去开始数据流,并且你也可以发送stopRunning
消息去停止数据流。
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
配置一个session
你可以用preset
(预设)到session,来具体指明你想要的图片质量和象素。一个preset是一个常量,它是标识了多个可能配置的其中一个。某些情况下特定的设备是用来特定的配置。
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. |
AVCaptureSessionPresetPhoto | Photo | Full photo resolution.This is not supported for video output. |
如果你要设置一个特定尺寸的配置给媒体帧,你应该在设置之前检查它是否支持。如下
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
如果你需要调整session属性到比preset
更高的粒层,或你改变一个正在运行的session.你应该把你的改变包含在beginConfiguration
和commitConfiguration
方法之间。这两个方法确保设备的改变做为一个组,最小的可见性或状态的不一致性。在调用beginConfiguration
之后,你可以增加或移除输出,替换sessionPreset
属性,或配置单独的输入或输出属性。没有一个改变真正的实现,直到你调用commitConfiguration
,每一个它们都是成对出现的。
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
跟踪Capture Session状态
capture session会发送一些你可以观察到的notifications
,比如,当它开始或结束运行时,或当它被打断时都会发送notifications
。当一个运行时错误产生时,你可以注册去接收一个AVCaptureSessionRuntimeErrorNotification
.你也可以查询session的running
属性去看一下它是否在运行,它的interrupted
属性去看一下它是否中断。额外的,running
和interrupted
属性也是兼容key-value observing,它们的notifications也会在主线程发送。
一个AVCaptureDevice对象代表一个输入设备
一个AVCaptureDevice
对象抽象自一个物理捕获设备,这个设备提供了输入数据(比如:音频或视频)到AVCaptureSession
对象。每一个输入设备都有一个对象,比如,两个视频输入——一个前置摄像头,一个后置摄像头——还有一个用麦克风的声音输入。
你可以用AVCaptureDevices
的class method devices
和devicesWithMediaType:
来找出哪个捕获设备是当前有效的。还有,如果需要,你也可以找出iPhone, iPad,iPod提供了什么功能。有效的设备列表是可能会变的。当前有输入设备也有可能变成无效的(如果被其它的app使用)。并且新的输入设备有可能变成有效的(如果它们被其它app放开)。你应该注册并接收AVCaptureDeviceWasConnectedNotification
和AVCaptureDeviceWasDisconnectedNotification
,当新的有效设备列表改变时,notifications就会通知。
设备特征
你可以得到一个设备的不同特征。你也可以测试它是否提供了特定的媒体类型或它是否支持一个capture sessioin的preset,分别可以hasMediaType:
和supportsAVCaptureSessionPreset:
验证。你可找出设备的位置(前置或后置)和它的本地名字给用户。有一点非常有用,你可以提供一个捕获设备的列表给用户选择。
以下代码遍历了所有的有效设备并找印了它们的名字:
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和它的唯一ID
Device Capture Settings
不同的设备有不同的功能。比如:一些支持不同的焦点或闪光模式。一些可能支持聚集一个兴趣点。
以下代码片断展示的你怎么找出支持手电筒模式和一个给定的capture session 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
属性。
你可以用相似的方式来使用不同的功能。那里有一些常量来表明一些具体的模式,你可以询问一个设备它是否支持那个特定的模式。在一些情况下,当一个功能改变时,你可以通过观察一个属性来得到一个通知。在所有的情况下,当你要改变一个特定功能的模式时,你应该锁定设备。
焦点兴趣点和曝光点是相互排斥的,焦点模式和曝光模式也是相互排斥的(Focus mode and exposure mode)。
Focus Modes(焦点)
Exposure Modes(爆光点)
Flash Modes(闪光点)
Torch Mode(手电筒)
Video Stabilization(视频稳定)
White Balance(白平衡)
设置设备方向
你可以在AVCaptureConnection
里设置你想要的方向,去指明在输出AVCaptureOutput
(AVCaptureMovieFileOutput
, AVCaptureStillImageOutput
and AVCaptureVideoDataOutput
)上图片的朝向。
用AVCaptureConnectionsupportsVideoOrientation
属性来查明那个设备是否支持改变视频的方向的改变。还有videoOrientatioin
属性表明你要哪个图片朝向到输出端口。
以下代码是设置AVCaptureConnection
的朝向到AVCaptureVideoOrientationLandscapeLeft
AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
[captureConnection setVideoOrientation:orientation];
}
配置一个设备
要设置一个设备的属性,你必须首先使用lockForConfiguration:
来获得在这个设备上的锁。这个就避免了其它App的设置冲突。以下的代码就是展示了一种合理的方式来改变设备上的focus mode。首先查明是否支持这个模式,再去锁这个设备去再配置。只当锁定设备时focus mode才改变,然后立刻释放锁。
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
}
else {
// Respond to the failure as appropriate.
你应该只在要改变设备属性时保持设备锁,保持设备锁的目地是不被其它App改变属性。如果在不必要是还保持锁,那其它的App就不能改变属性了。
不同设备之间的切换
有时你会要允许用户去切换不同的输入设备——比如:从前置摄像头切换到后置的。为了避免暂停或磕磕绊绊,你要在运行时就重配置session,你应该把你的配置修改用beginConfiguration
和commitConfiguration
来包裹起来:
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
当最外层的commitConfiguration
被调用,所有的改变都会同时发生。这就保证了一个平滑的过渡。
通过Capture Inputs增加Capture Device到一个session里
要增加一个capture device到capture session,你要使用AVCaptureDeviceInput
(抽象类AVCaptureInput
的具体子类)实例。这个Capture device input要管理设备的端口。
NSError *error;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
一个AVCaptureInput
运送一个或多个媒体数据流。比如:input devices 可以提供包括声频和视频数据。输入设备提供的每一个媒体流都可以被AVCaptureInputPort
对象来代表。一个capture session使用一个AVCaptureConnection
对象来定义一个AVCaptureInputPort
对象的集合和单个AVCaputreOutput
对象的联系。
通过Capture Outputs从Session得到输出
要从Capture Session得到输出,你要增加一个或多个Outputs.一个输出就是AVCaptureOutput
具体子类的实例。你使用:
-
AVCaptureMovieFileOutput
输出一个电影文件。 -
AVCaptureVideoDataOuput
如果你要处理捕获视频里的每一帧,比如:你要创建你自己的view layer.(捕获人脸,活体检测可以用这个) -
AVCaptureAudioDataOutput
可以让你处理捕获的音频数据。 -
AVCaptureStillImageOutput
可以让你获取带有元数据的表态图片。
你可以使用addOutput:
去增加output到Capture Session。你可以用canAddOutput:
检查是否这个output是否兼容这个已存在的session.你也可以在session运行时,按要求增加和移除outputs.
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
对象来保存电影数据(这个类是抽象类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#>;
输出的分辨率和比特率是依赖于capture session的sessioinPreset
。
视频编码通常是H.264,音频的编码通常是AAC.实际值因设备而异。
开始录制
你可以使用startRecordingToOutputFileURL:recordingDelegate:
开始录制一段Quick Time电影。你需要一个基于文件的URL和一个delegate。这个URL不能指向一个已存在的文件,因为电影文件输出不能重写已存在的源。你必须有权限去在特定位置去写东西。这个delegate必须遵守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:
实现里,这个delegate可能会把录制的电影文件写到相册里。它也应该检查任何已经产生的错误。
确保文件已被成功的写入
为了查明你的文件是否被成功的保存,在captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
实现里你不仅要检查error,还要检查在error的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...
你应该检查error里的user info dictionary里的AVErrorRecordingSuccessfullyFinishedKey
的值。因为这个文件你可能保存成功了,但你还是得到一个error.这个error可能是指向一个你录制的限制,比如:AVErrorMaximumDurationReached
或AVErrorMaximumFileSizeReached
.其它停止录制的原因:
- 磁盘满了——
AVErrorDiskFull
- 录制设备失去连接——
AVErrorDeviceWasDisconnected
- session被中断(比如:电话进来了)——
AVErrorSessionWasInterrupted
加元数据到文件
你可以在任何时间为一个电影文件设置元数据,即使是录制中。这对有些情况非常有用,比如:当开始录制时信息无效了,可能是位置信息的情况。对于一个文件输出来说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
对象使用delegation来运送视频帧。你可以使用setSampleBufferDelegate:queue:
来设置delegate。除了设置delegate,你还可以指定一个串行队列来调用这个delegate。你必须使用串行队列去确保视频帧是用合适的顺序派发给delegate的。您可以使用队列来修改提供和处理视频帧的优先级。看一下简单实现的例子 SquareCam
视频帧是是做为一个CMSampleBufferRef
类型的实例,被delegate方法captureOutput:didOutputSampleBuffer:fromConnection:
里显示的。默认的,这些buffers是被相机以最有效的格式发出。你可以使用videoSettings
属性去指定一个自定义的输出格式。这个view settings属性是个dictionary。目前,唯一支持的key是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];
处理视频的性能考虑
您应该将session output设置为应用程序的最低实际分辨率。 将输出设置为比必要的更高分辨率浪费处理周期,并且不必要地消耗功率。
你必须确保你的captureOutput:didOutputSampleBuffer:fromConnection:
实现能够在分配给一个帧的时间量内处理sample buffered。如果你花了太长的时间并且持有视频帧,AV Foundation就会停止递送视频帧,不只是你的delegate,还有其它输入,比如:preview layer(预览)。
你可以使用capture video data ouputs的minFrameDuration
属性去确保你有足够的时间去处理一帧,以更小的帧率来代替。你也可能要确保alwaysDiscardsLateVideoFrames
属性为true
(默认)。这个属性保证任何迟来的视频帧都将被丢弃,而不是处理它。相反的,如果你正在录制,不介意输出帧有点迟并且最好是全部保留,你要设置这个属性为false
。这是并不意味着不会丢失帧(帧也是依旧会被丢弃),但是它们不会被过早或过于高效的丢弃。
获取静态图片
如果你想要一个带有metadata的静态图片,你可以使用AVCaptureStillImageOutput
输出。这张图片的分辨率依赖于session的preset,还有设备。
象素和编码格式
不同的设备支持不同的图片格式。你可以使用availableImageDataCVPixelFormatTypes
和availableImageDataCodecTypes
找出这个设备支持什么象素和编解码器类型。每个方法都会返回一个特定设备的支持类型数组。你可以设置outputSettings
dictionary,指明哪个图片格式是你需要的。如下:
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
如果你要获取JPEG图片,你通常不应该指明你自己的压缩格式。相反 ,你应该让静态图片output为你做压缩,因为它的压缩是硬件加速的。如果你需要图片的数据代表,你可以使用jpegStillImageNSDataRepresentation:
得到一个NSData
对象而不重新压缩数据,即使你修改图片的metadata。
获取图片
当你要获取一张图片时,你要发送captureStillImageAsynchronouslyFromConnection:completionHandler:
方法给output。第一个形参是你在capture里用的connection.你需要的是查看一下connection里哪个输入端口是收集视频的:
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
类型包含了image data,还有一个error
。sample buffer本身可以包含诸如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.
}];
展示给用户录制了什么
你可以为用户提供录制东西的预览,比如用preview layer预览摄像头录制的东西,或跟踪麦克风录制的声音轨道。
Video Preview
你可以通过AVCaptureVideoPreviewLayer
对象预览录制的东西。AVCaptureVideoPreviewLayer
是CALayer
的子类。你不需要任何outputs去展示预览。使用AVCaptureVideoDataOutput类为客户端应用程序提供访问视频像素的能力,然后才能呈现给用户。
不像capture output,一个视频preview layer保持了一个强引用到 相关的session。这就保证了当preview layer还在显示视频时,session不会被释放。这就是初始化preview layer就反映出来了:
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];
通常来说,preview layer在渲染树的行为就像其它的CALayer对象一样(可以查看Core Animation Programming Guide).你可像任何Layer一样缩放图像并执行转换,旋转等操作.一个不同点是你需要设置layer的orientation
属性,去表明怎么旋转从摄像头过来的图像.另外,你可以通过查询supportsVideoMirroring
属性来测试视频镜像的设备支持.尽管当automaticallyAdjustsVideoMirroring
属性被 设为true
(默认的),这个镜像值会在session配置的基础上自动设置,但你还是可以在需要时设置videoMirrored
属性.
视频重力模式
preview layer支持你用videoGravity
设置的三种重力模式:
-
AVLayerVideoGravityResizeAspect
:保留高宽比,把视频未填充的地方显示黑条. -
AVLayerVideoGravityResizeAspectFill
:保留高宽比,但填充所有的有效屏幕区域,必要时可以裁剪视频. -
AVLayerVideoGravityResize
:这简单地延伸视频以填充可用的屏幕区域,即使这样做使扭曲图像
在preview里使用"点击聚焦"
实施点对焦与预览图层时,您需要注意。 您必须考虑图层的预览方向和重力,以及可能镜像预览的可能性。 请参阅示例代码项目AVCam-iOS:Using AVFoundation to Capture Images and Movies 以实现此功能
展示声音电平
在capture connection里要跟踪声音频道的平均和峰值电平,你要使用AVCaptureAudioChannel
对象.因为声音电平不是KVO(key-value observabel),所以你必须要像更新用户界面一样轮询更新电平(比如:1秒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
对象来产生视频帧 - 实现
AVCaputreVideoDataOutput
对象的delegate
来处理视频帧 - 实现一个把从
delegate
接收到的CMSampleBuffer
转成UIImage
对象的方法.
创建和配置一个Capture Session
创建一个AVCaptureSession
对象来协调AV输入设备到输出的数据流,并配置它来产生中等分辨率的视频帧.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
创建并配置Device和Device Input
用AVCaptureDevice
对象来代表Capture devices;这个类提供了一些方法,可以用来接收你想要的输入类型的对象.一个设备可以有多个端口,可以用AVCaptureInput
对象来配置.通常,你都会用它的默认配置来配置你使用Capture Input.
找一个视频捕获Device,并创建为这个Device创建一个Device Input,再加到session里.如果找不到 合适的设备,那时deviceInputWithDevice:error:
方法就会返回一个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];
创建并配置Video data output
你使用AVCaptureVideoDataOutput
对象来处理捕获视频里非压缩的帧.你通常会为output配置多个方面.对于视频来说,比如,你会用VideoSettings
属性来指明象素格式,并且会通过设置minFrameDuration
属性来限制帧速率.
为一个视频数据创建并配置output,并把它加到session;通过设置minFrameDuration
属性为1/15秒,来限制帧速率和15fps:
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);
这个data output对象使用delegate
来发送视频帧.这个delegate
必须采用AVCaptureVideoDataOutputSampleBufferDelegate
协议.当你设置data output的delegate
时,你必须要提供一个queue来调用回调方法:
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
你可以使用queue来修改正在发送和处理中的视频帧的优先级.
实现Sample Buffer的代理方法
在delegate class里,实现captureOutput:didOutputSampleBuffer:fromConnection:
方法,这个方法只有在sample buffer被写入时才被调用.video output对象以CMSampleBuffer
类型的形式发送帧,所有你需要把CMSampleBuffer
opaque type转成UIImage
对象.
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}
开始和结束录制
在配置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];
});
}
}];
如果摄像机session被配置完成,并且用户允许去访问摄像头(必要时,可以是麦克风),发送一个startRunning
消息去开始录制.
重要:这个
startRunning
方法是一个阻塞式的调用,可能会耗费一点时间.因此,你应该在一个串行队列(serial queue)里执行session的配置,让main queue不阻塞,保持UI的及时响应.权威的实现你可以看AVCam-iOS: Using AVFoundation to Capture Images and Movies
[session startRunning];
要停止录制,你可以发送stopRunning
消息给session.
视频采集的高帧速率
整个AVFoundation框架支持高帧速率内容.
你要用AVCaptureDeviceFormat
类来查明设备的采集能力.此类具有返回支持的媒体类型,帧速率,视场,最大缩放因子,是否支持视频稳定等的方法.
- Capture支持全屏720p(1280 x 720像素)分辨率,每秒60帧(fps),包括视频稳定和可放置的P帧(H264编码电影的功能,即使在较慢和较旧的硬件上也能让电影播放顺畅。 )
- playback增强了对慢速和快速播放的音频支持,从而可以以更慢或更快的速度保留音频的时间间隔
- 编辑完全支持可变组合中的缩放编辑
- 当支持60 fps电影时导出提供两个选项。 可以保留可变帧速率,慢速或快速运动,或者将影片转换为任意较慢的帧速率,例如每秒30帧
播放
一个AVPlayer
实例通过设置setRate:
方法管理着大部分的播放速度.这个值被用作多倍播放速度.值1.0表示正常的播放速度,0.5表示一半的播放速度,5.0表示比正常播放快5倍的速度,等等.
AVPlayerItem
对象支持audioTimePitchAlgorithm
属性.这属性表明了当电影在多个帧速度下播放时,声音是怎么播放的.使用Time Pitch Algorithm Settings常量.
Time pitch algorithm | Quality | Snaps to specific frame rate | Rate range |
---|---|---|---|
AVAudioTimePitchAlgorithmLowQualityZeroLatency | Low quality,suitable for fast-forward, rewind, or low quality voice. | YES | 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates. |
AVAudioTimePitchAlgorithmTimeDomain | Modest quality, less expensive computationally, suitable for voice. | NO | 0.5–2x rates. |
AVAudioTimePitchAlgorithmSpectral | Highest quality, most expensive computationally, preserves the pitch of the original item. | NO | 1/32–32 rates. |
AVAudioTimePitchAlgorithmVarispeed | High-quality playback with no pitch correction. | NO | 1/32–32 rates. |