本章译自Apple官方文档AVFoundation Programming Guide, 是AVFoundation系列译文第五篇, 介绍了AVFoundation框架中利用硬件设备进行音视频或静态图像采集的相关内容, 全部译文参见我的GitBook: AVFoundation编程指南.
通过输入(inputs)和输出(outputs)对象来对采集设备(比如摄像头或麦克风)进行管理. 使用AVCaptureSession对象协调inputs和outputs之间的数据.
- AVCaptureDevice代表输入设备, 比如摄像头和麦克风
- AVCaptureInput的子类用来对输入设备进行配置
- AVCaptureOutput的子类用来设置输出结果为图片或者视频
- AVCaptureSession用来协调inputs和outputs之间的数据
使用CALayer的子类AVCaptureVideoPreviewLayer, 可以展示摄像头采集的画面预览.
对于一个session, 可以配置多个inputs和outputs, 如图所示:
对于大部分的应用而言, 这已经足够了. 但是有些情况下, 会涉及到如何表示一个inputs的多个端口(ports), 以及这些ports如何连接到outputs.
Capture session中使用AVCaptureConnection表示inputs和outputs之间的连接. 一个Inputs包含一个或多个input ports(AVCaptureInputPort). Outputs可以从一个或多个来源接收数据, 比如AVCaptureMovieFileOutput可以同时接收视频和音频数据.
如下图所示, 当在session中添加一个input或output时, session会为所有可匹配的inputs和outputs之前生成connections(AVCaptureConnection).
可以使用一个connection来开启或关闭一个input或output数据流. 也可以使用connection监控一个audio频道的码率平均值和峰值.
注意: 媒体捕捉不支持模拟器,也不能同时使用iOS设备上的前置摄像头和后置摄像头进行捕捉
使用Capture Session协调数据流
数据采集管理的核心是AVCaptureSession. 在session中添加采集设备并对output进行配置之后, 可以向session发送startRunning消息开始采集, 发送stopRunning消息停止采集.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
配置Capture Session
使用session的sessionPreset
属性指定图片质量和分辨率:
- AVCaptureSessionPresetHigh: 高分辨率, 最终效果根据设备不同有所差异
- AVCaptureSessionPresetMedium: 中等分辨率, 适合Wi-Fi分享. 最终效果根据设备不同有所差异
- AVCaptureSessionPresetLow: 低分辨率, 适合3G分享, 最终效果根据设备不同有所差异
- AVCaptureSessionPreset640x480: 640x480, VGA
- AVCaptureSessionPreset1280x720: 1280x720, 720p HD
- AVCaptureSessionPresetPhoto: 全屏照片, 不能用来作为输出视频
在设置一个preset之前, 需要判断设备是否支持该preset值:
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
如果需要设置一个更高分辨率的preset, 或者在session运行时修改一些配置, 需要在beginConfiguration和commitConfiguration之间完成修改. beginConfiguration和commitConfiguration方法确保所有的修改被整体应用, 减少对预览状态的影响. 在调用beginConfiguration之后, 可以添加或移除一个output, 修改sessionPreset
属性, 或者单独配置input和output的属性. 只有调用了commitConfiguration方法, 改变才会生效.
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
监测Capture Session的状态
可以使用通知(NSNotification)监测session的状态, 并且所有的通知都在主线程中发送. 注册监听AVCaptureSessionRuntimeError通知可以捕捉运行时发生的错误. 也可以使用session的running
属性判断当前的运行状态, interrupted
属性则可以判断当前是否中断. 此外, running
和interrupted
属性都可以通过KVO进行监听.
使用AVCaptureDevice表示输入设备
AVCaptureDevice是对实际的物理捕捉设备的抽象, 物体捕捉设备向AVCaptureSession
提供数据. 每个AVCaptureDevice
对象代表一个实际的输入设备, 例如前摄像头或后摄像头, 或麦克风.
使用AVCaptureDevice
类的devices和devicesWithMediaType:方法可以获取当前可用的捕捉设备. 而且可以获取捕捉设备的设备特性(参见Device Capture Settings). 当前的可用设备的状态可能会发生改变, 当前使用的输入设备可能会变为不可用状态(如果设备被另外一个应用使用), 也可能会有新的设备变为可用状态(被其他应用释放). 注册接收AVCaptureDeviceWasConnectedNotification
和AVCaptureDeviceWasDisconnectedNotification
通知可以得知可用设备列表的变化.
设备特性
可以获取一个设备的设备特性. 也可以通过hasMediaType:方法判断设备是否支持特定媒体类型的捕捉, 通过supportsAVCaptureSessionPreset:判断设备是否支持特定的分辨率. 当要提供一个可用的捕捉设备列表给用户进行选择时, 获取展示出设备的位置以及名称(比如前摄像头或后摄像头)拥有更好的用户体验.
下图展示了前摄像头(AVCaptureDevicePositionFront
)和后摄像头(AVCaptureDevicePositionBack
):
下面的代码遍历了所有的可用设备并打印其名称, 如果是视频设备, 则打印其位置:
NSArray *devices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in devices) {
NSLog(@"Device name: %@", [device localizedName]);
if ([device hasMediaType:AVMediaTypeVideo]) {
if ([device position] == AVCaptureDevicePositionBack) {
NSLog(@"Device position : back");
}
else {
NSLog(@"Device position : front");
}
}
}
此外, 还可以获取设备的model ID以及unique ID.
捕捉设置
不同的设备之间存在性能差异, 比如一些设备支持特殊的对焦或闪光灯模式, 某些设备还支持兴趣点对焦.
下面的代码示例了如何找出一个支持手电筒模式和特定preset的设备:
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
[if ([device hasTorch] &&
[device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
[torchDevices addObject:device];
}
}
如果找到了多个符合要求的设备, 你可能需要让用户选择其中的某一个设备, 这时可以使用localizedName属性获取设备的描述信息.
可以用类似的方式实现各种不同的捕捉设置. 框架预定义了一些常量用来代表特定的捕捉模式, 你可以使用这些常量以便于判断设备是否支持特定的模式. 在大部分情况下, 可以通过属性监听获取设备特性的变化状态. 任何情况下, 在改变设备的捕捉设置之前, 都应该先锁定设备, 详见下节设备的配置
.
兴趣点对焦模式和兴趣点曝光模式是互斥的, 正如对焦模式和曝光模式也是互斥的一样
对焦模式
有三种对焦模式:
AVCaptureFocusModeLocked
: 固定焦点AVCaptureFocusModeAutoFocus
: 自动对焦然后锁定焦点AVCaptureFocusModeContinuousAutoFocus
: 连续自动对焦
使用isFocusModeSupported:方法判断设备是否支持给定的对焦模式, 然后设置属性focusMode改变对焦模式.
此外, 一些设备还支持兴趣点对焦模式. 通过方法focusPointOfInterestSupported判断是否支持该模式, 然后使用属性focusPointOfInterest设置焦点. 无论设备是横屏(Home键靠右)或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.
属性adjustingFocus可以用来判断当前设备是否正在对焦中. 可以使用KVO监听该属性获取对焦开始与结束的通知.
设置对焦模式的示例代码如下:
if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
[currentDevice setFocusPointOfInterest:autofocusPoint];
[currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
曝光模式
有两种曝光模式:
AVCaptureExposureModeContinuousAutoExposure
: 自动调整曝光等级AVCaptureExposureModeLocked
: 固定曝光等级
使用isExposureModeSupported:方法判断设备是否支持给定的曝光模式, 然后设置属性exposureMode改变曝光模式.
此外, 一些设备还支持兴趣点曝光模式. 通过方法exposurePointOfInterestSupported判断是否支持该模式, 然后使用属性exposurePointOfInterest设置曝光点. 无论设备是横屏(Home键靠右)或竖屏模式, CGPoint{0,0}代表设备左上角, CGPoint{1,1}代表设备右下角.
属性adjustingExposure可以用来判断当前设备是否正在改变曝光设置中. 可以使用KVO监听该属性获取开始设置曝光模式与结束设置曝光模式的通知.
设置曝光模式的示例代码如下:
if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
[currentDevice setExposurePointOfInterest:exposurePoint];
[currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
闪光模式
有三种闪光模式:
AVCaptureFlashModeOff
: 关闭AVCaptureFlashModeOn
: 打开AVCaptureFlashModeAuto
: 根据环境亮度自动开启或关闭
使用方法hasFlash判断一个设备是否有闪光灯. 使用方法isFlashModeSupported:判断是否支持某个闪光模式, 使用属性flashMode设置闪光灯模式.
手电筒模式
手电筒模式下, 闪光灯会一直处于开启状态, 用于视频捕捉. 有三种手电筒模式:
AVCaptureTorchModeOff
: 关闭AVCaptureTorchModeOn
: 打开AVCaptureTorchModeAuto
: 根据需要自动开启或关闭
使用方法hasTorch判断一个设备是否有闪光灯. 使用方法isTorchModeSupported:判断是否支持某个手电筒模式, 使用属性torchMode设置手电筒模式.
对于一个有手电筒的设备, 手电筒只有在设备与一个运行中的 capture session进行了关联后才可以设置为开启.
视频稳定性
依赖于某些特殊的硬件设备, 视频会有更好设置达到电影级别的稳定性. 但并不支持所有的视频格式和分辨率.
开启电影级别的视频稳定性特性在捕捉视频时可能会增加延迟. 使用属性videoStabilizationEnabled可以判断当前是否使用了视频稳定性特性. 属性enablesVideoStabilizationWhenAvailable可以在设备支持的情况下自动开启视频稳定性特性, 该属性默认为关闭状态.
白平衡
有两种白平衡模式:
AVCaptureWhiteBalanceModeLocked
: 固定参数的白平衡AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance
: 由相机自动调整白平衡参数
使用方法isWhiteBalanceModeSupported:判断设备是否支持给定的白平衡模式, 然后通过属性whiteBalanceMode设置白平衡模式.
使用属性adjustingWhiteBalance判断当前是否正在修改白平衡模式. 可以使用KVO监听该属性获取开始设置白平衡模式与结束设置白平衡模式的通知.
设置设备方向
可以在AVCaptureConnection
上指定期望的设备方向, 用来设置输出时AVCaptureOutput
(AVCaptureMovieFileOutput
, AVCaptureStillImageOutput
和AVCaptureVideoDataOutput
)的设备方向.
使用属性AVCaptureConnectionsupportsVideoOrientation
判断设备是否支持修改视频方向, 使用属性videoOrientation
指定一个方向. 下面的代码将AVCaptureConnection
的方向设置为AVCaptureVideoOrientationLandscapeLeft
:
AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
[captureConnection setVideoOrientation:orientation];
}
设备配置
要修改设备的捕捉属性, 首先需要使用方法lockForConfiguration:锁定设备, 这样可以避免与其他应用的设置产生冲突.
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
}
else {
// Respond to the failure as appropriate.
}
切换设备
某些场景下可能需要允许用户切换输入设备, 比如前后摄像头. 为了避免卡顿, 可以重新配置正在运行的session, 但是嵌套使用beginConfiguration和commitConfiguration方法.
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
当最后的commitConfiguration
方法被调用时, 所有的设置变化会一起执行, 确保了切换的流畅性.
使用AVCaptureInput添加输入设备
要把一个capture device添加到capture session中, 需要使用AVCaptureDeviceInput(抽象类AVCaptureInput
的子类). Capture device input 管理设备的端口.
NSError *error;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
使用addInput:添加输入. 使用canAddInput:判断该设备是否可以被添加到session中.
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
[captureSession addInput:captureDeviceInput];
}
else {
// Handle the failure.
}
一个AVCaptureInput
对象包含一个或多个数据流. 例如, 输入设备可能同时提供音频和视频数据. 每个AVCaptureInputPort对象代表一个媒体数据流. Capture session使用一个AVCaptureConnection
对象定义一组AVCaptureInputPort
和一个AVCaptureOutput
之间的映射关系.
使用AVCaptureOutput输出数据
要从capture session中输出数据, 可以向其添加一个或多个outputs(AVCaptureOutput的子类), 你可以使用:
- AVCaptureMovieFileOutput: 输出为电影文件
- AVCaptureVideoDataOutput: 可以逐帧处理捕捉到的视频
- AVCaptureAudioDataOutput: 可以处理捕捉到的音频数据
- AVCaptureStillImageOutput: 输出为静态图片
使用方法addOutput:在capture session中添加outputs. 使用方法canAddOutput:判断是否可以添加一个给定的output. 可以根据需要在session运行过程中添加或移除一个output.
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieOutput]) {
[captureSession addOutput:movieOutput];
}
else {
// Handle the failure.
}
输出为视频文件
使用AVCaptureMovieFileOutput将视频数据保存为一个本地文件. 可以对movie file output的参数进行配置, 比如最大的录制时长, 最大的录制文件大小, 如果设备磁盘空间不足的话, 还可以阻止用户进行视频录制.
AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;
输出的分辨率和码率依赖于capture session的 sessionPreset
属性, 常用的视频编码格式是H.264, 音频编码格式是AAC. 实际的编码格式可能由于设备不同有所差异.
开始录制
使用方法startRecordingToOutputFileURL:recordingDelegate:开始录制一段QuickTime视频, 方法中需要传入一个本地文件的URL和一个录制的delegate. 传入的本地URL不能是已经存在的文件, 因为movie file output不会对已存在的文件进行重写, 而且对传入的文件路径, 程序必须有写入权限. 传入的delegate必须遵循AVCaptureFileOutputRecordingDelegate协议, 且必须实现captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:方法. 在这个代理方法中, delegate可能会向相册写入数据, 需要对错误进行检测.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];
确保文件写入成功
要判断文件是否写入成功, 在captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:方法中不仅需要检测error, 还需要对error中的user info字典中的AVErrorRecordingSuccessfullyFinishedKey进行判断.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
BOOL recordedSuccessfully = YES;
if ([error code] != noErr) {
// A problem occurred: Find out if the recording was successful.
id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value) {
recordedSuccessfully = [value boolValue];
}
}
// Continue as appropriate...
需要对AVErrorRecordingSuccessfullyFinishedKey
进行判断是因为即使写入过程中抛出了一个error, 文件也可能被成功写入了. 抛出的error可能是因为达到了一些设置的限制约束条件, 比如AVErrorMaximumDurationReached, 以及AVErrorMaximumFileSizeReached. 其他可能导致录制中断的情况如下:
- 磁盘已满 - AVErrorDiskFull
- 与录制的设备的连接断开 - AVErrorDeviceWasDisconnected
- session中断(比如有电话接入) - AVErrorSessionWasInterrupted
在文件中添加元数据
可在任何时刻对文件的元数据(metadata)进行设置, 哪怕是在录制过程中. 一个file output的metadata由一个AVMetadataItem对象的数组来表示. 可以使用其可变子类AVMutableMetadataItem创建自定义的metadata.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSArray *existingMetadataArray = aMovieFileOutput.metadata;
NSMutableArray *newMetadataArray = nil;
if (existingMetadataArray) {
newMetadataArray = [existingMetadataArray mutableCopy];
}
else {
newMetadataArray = [[NSMutableArray alloc] init];
}
AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataCommonKeyLocation;
CLLocation *location - <#The location to set#>;
item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
location.coordinate.latitude, location.coordinate.longitude];
[newMetadataArray addObject:item];
aMovieFileOutput.metadata = newMetadataArray;
处理视频帧
AVCaptureVideoDataOutput使用代理模式来对视频帧进行处理. 使用方法setSampleBufferDelegate:queue:设置代理, 此外还需要传入代理方法被调用的队列. 必须使用同步队列确保视频帧按照录制顺序被传递到代理方法中. 可以使用队列修改视频帧传递处理的优先级, 参见示例SquareCam.
在代理方法captureOutput:didOutputSampleBuffer:fromConnection:中, 视频帧由CMSampleBufferRef类型表示. 默认情况下, buffers被设置为当前设备相机效率最高的格式. 也可以使用属性videoSettings自定义输出格式. videoSettings
属性是一个字典类型, 目前只支持kCVPixelBufferPixelFormatTypeKey
. 系统建议的视频格式可以通过属性availableVideoCVPixelFormatTypes获取, 属性availableVideoCodecTypes返回支持的编码格式. Core Graphics和OpenGL都很好的兼容了BGRA
格式.
AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new];
NSDictionary *newSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
videoDataOutput.videoSettings = newSettings;
// discard if the data output queue is blocked (as we process the still image
[videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];)
// create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured
// a serial dispatch queue must be used to guarantee that video frames will be delivered in order
// see the header doc for setSampleBufferDelegate:queue: for more information
videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
[videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
AVCaptureSession *captureSession = <#The Capture Session#>;
if ( [captureSession canAddOutput:videoDataOutput] )
[captureSession addOutput:videoDataOutput];
视频处理时的性能考虑
导出视频应当尽可能的使用低分辨率, 高分辨率会消耗额外的CPU和电量.
确保在代理方法captureOutput:didOutputSampleBuffer:fromConnection:
中处理sample buffer时不要使用耗时操作, 如果处理占用时间过长, AV Foundation会停止向代理方法中传递视频帧, 而且会停止其他的输出, 比如preview layer上的预览.
可以设置capture video data的属性minFrameDuration通过降低帧率来确保有足够的时间对视频帧进行处理. 将属性alwaysDiscardsLateVideoFrames设置为YES
(默认值)的话, 后面的视频帧将会被丢弃, 而不是排队等待处理. 如果你并不介意延迟, 而且需要处理所有的视频帧, 也可以将alwaysDiscardsLateVideoFrames
设置为NO
(即使如此, 也可能会出现掉帧的情况).
捕捉静态图像
使用AVCaptureStillImageOutput捕捉带元数据的静态图像. 图片的分辨率依赖于session的preset设置和具体的硬件设备.
像素和编码格式
不同的设备支持不同的图片格式. 可以使用availableImageDataCVPixelFormatTypes获取设备支持的所有像素格式, 使用availableImageDataCodecTypes可以获取设备支持的图像编码类型. 设置属性字典outputSettings可以指定需要的图片格式:()
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
如果需要的是JPEG图片, 则不要指定压缩格式. 相反, 应该让still image output进行压缩(硬件加速). 可以使用jpegStillImageNSDataRepresentation:将图片转换为无压缩的NSData
对象.
捕捉图片
使用方法captureStillImageAsynchronouslyFromConnection:completionHandler:捕捉图片. 第一个参数是需要捕捉的connection, 需要判断当前的connection中哪个input正在采集视频.
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
videoConnection = connection;
break;
}
}
if (videoConnection) { break; }
}
方法的第二个参数是一个有两个参数的block
: 一个包含图像数据的CMSampleBuffer
类型, 另一个是NSError对象. Sample buffer自身包含了元数据, 比如EXIF信息字典, 可以对这些元数据进行修改.
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments =
CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// Do something with the attachments.
}
// Continue as appropriate.
}];
录制预览
可以提供给用户一个preview, 用来展示正在通过摄像头录制的内容(使用preview layer), 或者正在通过麦克风记录的音频内容(通过监听audio channel).
视频预览
使用 AVCaptureVideoPreviewLayer可以进行视频预览. AVCaptureVideoPreviewLayer
是CALayer
的子类. 进行视频预览不需要设置任何的output对象.
使用AVCaptureVideoDataOutput类可以在视频展示给用户预览之前对视频进行处理.
与capture output不同, 一个video preview layer会强引用与其相关联的session. 这是为了确保在进行视频预览时session不会被销毁.
AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];
大体上, video preview layer的性质与CALayer
类似. 你可以对图像进行缩放, 向操作其他任何layer一样进行transformations, rotations等操作. 一个不同点在于你可能需要设置layer的orientation
属性指定如何对摄像头捕捉的图像方向进行旋转. 此外, 通过属性supportsVideoMirroring可以判断设备是否支持预览镜像. 属性automaticallyAdjustsVideoMirroring的默认值为YES
, 但是仍然可以根据需要设置属性videoMirrored进行修改.
视频重力模式
Preview layer支持三种重力模式, 可以使用属性videoGravity进行设置:
AVLayerVideoGravityResizeAspect
: 保持视频款高比, 当视频内容不能铺满屏幕时, 不足的部分使用黑色背景进行填充.AVLayerVideoGravityResizeAspectFill
: 保持视频款高比, 但是会铺满整个屏幕, 必要时会对视频内容进行裁剪.AVLayerVideoGravityResize
: 拉伸视频内容铺满屏幕, 可能导致图像变形.
预览时使用点击聚焦功能
在preview layer上实现点击聚焦功能时, 需要注意视频方向, 视频重力模式以及可能设置了视频镜像. 参见代码示例AVCam-iOS: Using AVFoundation to Capture Images and Movies.
展示声音等级
要在capture connection中检测声音的均值和峰值, 可以使用AVCaptureAudioChannel对象. 声音等级不能使用KVO的方式获取, 所以需要根据界面更新的需求定时进行轮询(比如每秒10次).
AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {
// There should be only one connection to an AVCaptureAudioDataOutput.
AVCaptureConnection *connection = [connections objectAtIndex:0];
NSArray *audioChannels = connection.audioChannels;
for (AVCaptureAudioChannel *channel in audioChannels) {
float avg = channel.averagePowerLevel;
float peak = channel.peakHoldLevel;
// Update the level meter user interface.
}
}
示例: 捕捉视频帧为UIImage对象
接下来的代码简单示例了如何捕捉视频, 并将捕捉到的视频帧转换为UIImage对象:
- 创建
AVCaptureSession
对象 - 找到合适类型的
AVCaptureDevice
对象进行输入 - 为设备创建
AVCaptureDeviceInput
对象 - 创建
AVCaptureVideoDataOutput
对象获取视频帧 - 实现
AVCaptureVideoDataOutput
的代理 - 实现一个方法将接收到的
CMSampleBuffer
转换为UIImage
提示: 为了展示核心代码, 这份示例省略了某些内容, 比如内存管理和通知的移除等. 使用AV Foundation之前, 你最好已经拥有Cocoa框架的使用经验.
创建和配置Capture Session
AVCaptureSession
用来协调input和output之间的数据流.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
创建和配置Device和Device Input
AVCaptureDevice
表示捕捉设备, AVCaptureInput
用来配置捕捉设备的端口
AVCaptureDevice *device =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
[session addInput:input];
创建和配置Video Data Output
使用AVCaptureVideoDataOutput
处理未压缩的视频帧.
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
实现Sample Buffer 代理方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
将转换为UIImage的操作代码参见 Converting CMSampleBuffer to a UIImage Object.
开始和停止录制
配置capture session之后, 需要确保应用有访问相机的权限.
NSString *mediaType = AVMediaTypeVideo;
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
if (granted)
{
//Granted access to mediaType
[self setDeviceAuthorized:YES];
}
else
{
//Not granted access to mediaType
dispatch_async(dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:@"AVCam!"
message:@"AVCam doesn't have permission to use Camera, please change privacy settings"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
[self setDeviceAuthorized:NO];
});
}
}];
当获取到相应的访问权限之后, 可以使用startRunning
方法开始录制. startRunning
会阻塞线程, 所以需要异步调用, 以免阻塞主线程.
[session startRunning];
类似的调用stopRunning
可以停止录制.