管理相机或麦克风等设备捕捉音视频,生成对象来表示输入输出,并且用 AVCaptureSession 对象来协调他们之间的数据流。需要用到下面几个类:
. AVCaptureDevice 实例,表示输入设备。如:相机或麦克风;
. AVCaptureInput 子类的实例,配置输入设备的端口;
. AVCaptureOutput子类的实例,管理到视频文件或静态图的输出;
. AVCaptureSession 实例,协调从输入到输出的数据流。
为了向用户展示相机正在录取的图像,会用到 AVCaptureVideoPreviewLayer (CALayer 的子类)
通过一个单独的session,可以配置多个输入输出。
当把一个input或一个output加入到一个session中时,session就在所有input端口和output之间建立连接。
你可以使用 capture connection 来开启或禁用来自输入或输出设备上的数据流。
注:media capture 不支持在一个设备上用前,后摄像头同时捕获。
1. 用 Capture Session 来协调数据流
AVCaptureSession 是用来管理数据流的中心协调对象。使用此对象的实例来协调AV inputs 到 outputs的输出。把你想要的capture设备和输出加入到session中,用 startRunning 来开始数据流,用 stopRunning 来停止数据流。
code:
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
1)配置 Session
可以使用sessionPreset 来指定图像的质量和分辨率。preset是一个常量,在某些情况下,实际的配置是设备特定的。
如果你想设置媒体的帧的大小,你应该检查它是否支持此种设置,如下:
code:
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
如果你需要更精确的级别调整session或你改变运行的session,你需要在 beginCOnfiguration 和 commitConfiguration 方法中改变。
beginConfiguration 和 commitConfiguration 确保设备作为一个群体变化,减少不一致的状态。
在beginConfiguration 后,你可以增加或者移除output,改变 sessionPreset属性值,或者配置个人capture 输入或输出属性。只有调用commitConfiguration后,状态才能生效。
code:
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
2)监听Capture Session 的状态
capture session 会发送 notifications,你可以监听这些消息,例如,当它开始或停止运行时,或者中断。
如果发生运行错误,你可以通过注册消息AVCaptureSessionRuntimeErrorNotification来接收。
你也可以查询session的running属性来判断是否允许,interrupter属性来判断是否中断。另外running和interrupted属性是KVO。
2. AVCaptureDevice 对象代表一个输入设备
AVCaptureDevice 对象抽象出一个物理捕捉设备,提供输入数据给 AVCaptureSession。每个输入设备都是一个对象。
你可以找到当前有效的capture 设备,用 AVCaptureDevice 类方法 devices 和 devicesWithMediaType:。有效设备列表可能会改变,你可以注册接收消息 AVCaptureDeviceWasConnectedNotification 和
AVCaptureDeviceWasDisconnectedNotification,来获取当前设备的改变。
1)设备特性
你可以询问设备关于不同的特性。测试设备是否支持媒体类型:hasMediaType;测试设备是否支持给定的catpure session 的预设值:supportsAVCapureSessionPreset:。
下面代码遍历所有可用设备,输出他们的名字,并且视频设备还输出了位置。
code:
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");
}
}
}
2)设置Capture设备
不同设备有不同功能。例如:有些设备支持不同的聚焦或闪关灯模式。
下面展现了在所有视频输入设备中,查找支持手电筒并且支持capture session 预设的:
code:
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
[if ([device hasTorch] &&
[device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
[torchDevices addObject:device];
}
}
用类似的方式使用不同功能。通过询问设备是否支持一个功能。当一个功能改变时,可以观察到要通知的属性。在所有情况下,改变一个特定功能之前应该锁定设备。
a. 对焦模式
三种模式:AVCaptureFocusModeLocked,AVCaptureFocusModeAutoFocus,AVCaptureFocusModeContinuousAutoFocus
isFocusModeSupported:判断是否支持该模式;focusMode:设置模式;
另外,设备有可能支持“感兴趣”的焦点,测试:focusPointOfInterestSupports,如果支持,设置 focusPointOfInterest(左上角CGPoint为{0,0},右下角{1,1})。
你可以使用 adjustingFocus 属性确定设备是否正在对焦。可用通过KVO监听该属性,当对焦开始,停止时。
code:
if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
[currentDevice setFocusPointOfInterest:autofocusPoint];
[currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
b. 曝光模式
两种模式:AVCaptureExposureModeContinuousAutoExposure, AVCaptureExposureModeLocked
isExposureModeSupported:是否支持曝光模式;exposureMode:设置;
exposurePointOfInterestSupported :是否支持“感兴趣"曝光;exposurePointOfInterest 设置。
adjustingExposure属性,发生改变时。 可通过KVO 监听曝光开始和结束。
code:
if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
[currentDevice setExposurePointOfInterest:exposurePoint];
[currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
c. 闪光灯模式
三种类型:AVCaptureFlashModeOff,AVCaptureFlashModeOn,AVCaptureFlashModeAuto;
hasFlash:是否有闪光灯,如果有用 isFlashModeSupported 看是否支持该模式;
flashMode:设置模式。
d. 手电筒模式
三种类型:AVCaptureTorchModeOff,AVCaptureTorchModeOn,AVCaptureTorchModeAuto;
方法:hasTorch,isTorchModeSupported,torchMode。
e. 视频稳定
videoStabilizationEnabled 属性:是否支持;
enablesVideoStabilizationWhenAvailable 属性:如果相机支持,设置程序自动开启。
f . 白平衡
两个模式:AVCaptureWhiteBalanceModeLocked,AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
isWhiteBalanceModeSupported:是否支持;whiteBalanceMode:设置;
adjustingWhiteBalance属性:是否正在改变;可通过KVO监听开始和停止。
g. 设置定位装置
code:
AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
[captureConnection setVideoOrientation:orientation];
}
3)配置设备
在设备上设置capture属性是,必须先lock devices:lockForConfiguration。这样避免在其他应用程序中设置更改导致不兼容。
code:
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
}
else {
// Respond to the failure as appropriate.
尽量不用不必要的锁,否则会降低其他应用程序中的捕获质量。
4)切换设备
code:
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
3. 添加Capture Device 到 Session
添加 capture device 到 capture session,你需啊哟创建 AVCaptureDeviceInput 的实例。capture device input 管理 设备的端口。
code:
NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
添加inputs 到 session 使用 addInput:,应该检测capture input 是否和存在的 session 是否兼容,canAddInput:,
code:
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 对象之间的映射。
4. 用 Capture Outputs 从 Session 中得到 Output
添加一个或多个 outputs,从 capture session 获取输出。一个 output 是AVCaptureOutput 子类的实例。
. AVCaptureMovieFileOutput 输出视频文件;
. AVCaptureVIdeoDataOutput 如果你想在视频捕捉时处理帧,例如,创建你创建自定义视图层;
. AVCaptureAudioDataOutput 捕捉音频数据是,处理音频数据;
. AVCaptureStillImageOutput 捕捉静态图片;
addOutput:添加输出到capture session;
canAddOutput:检测capture output 是否和已存在的 session 兼容;
session运行时,可以添加和删除输出。
code:
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.
}
1)保存视频文件
AVCaptureMovieFileOutput用来保存视频数据到文件。你可以配置视频文件输出的各个方面,例如,拍摄的最大时间,最大文件容量。
当小于给的磁盘空间数量时,也会被禁止记录。
code:
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#>;
a. 开始记录
用startRecordingToOutputFileURL:recordingDelegate:开始录制视频。URL 不能是一个存在的文件,因为输出的文件不能覆盖存在的资源。还必须有权限写入指定位置。
code:
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 album。
b. 确保文件写入成功
code:
- (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 表明录制被限制了- 例如, AVErrorMaximumDurationReached
orAVErrorMaximumFileSizeReached。其他导致录制停止的原因:
AVErrorDiskFull : 磁盘慢了;
AVErrorDeviceWasDisconnected:录制设备断开;
AVErrorSessionWasInterrupted:session 被中断(例如,手机来电)。
c. 添加元数据到文件
任何时候在视频文件中你都可以设置元数据,即使正在录制。
有种情况,当开始记录时,信息不可用,可能是位置信息。
输出文件元数据由AVMetadataItem对象数组表示;用其子类AVMutableMetadataItem的实例创建自己的元数据。
code:
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;
2)处理视频帧
设置delegate,必须用串行队列,确保帧数据以正确顺序传送到delegate。你可以用队列修改帧数据。
captureOutput:didOutputSampleBuffer:fromConnection:。
可以用 videoSettings 属性指定自定义输出格式。视频设置属性是个字典,当前只支持关键字
kCVPixelBufferPixelFormatTypeKey。推荐的像素格式有
availableVideoCVPixelFormatTypes 返回的属性和支持availableVideoCodecTypes 属性返回值。
code:
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];
处理视频的性能考虑
3)捕捉静态图
AVCaptureStillImageOutput 捕捉静态图片。
像素和编码格式
不同设备支持不同图片格式。你可以用 availableImageDataCVPixelFormatTypes
和 availableImageDataCodecTypes
找到支持的像素和编码类型。每个方法返回一个支持的数组。
code:
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
捕捉JPEG 图像时,通常不用指定压缩格式,应该让静态图像输出设置压缩格式,因为他的压缩是硬件加速。
如果需要一个image 的data,可以用 jpegStillImageNSDataRepresentation: 获取无压缩数据对象,即使你修改了图像的元数据。
捕获图像
当捕获图像时,发送captureStillImageAsynchronouslyFromConnection:completionHandler:
message
code:
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; }
}
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// Do something with the attachments.
}
// Continue as appropriate.
}];
5. 显示录制
你可以为用户提供一个相机(用preview layer)或麦克风(监听音频通道)录制的预览。
1)视频预览
通过 AVCaptureVideoPreviewLayer 对象可以提供给用户预览录制功能。AVCaptureVideoPreviewLayer 是 CALayer 的子类。不需要任何output到显示预览。
用 AVCaptureVideoDataOutput 类提供访问视频像素的功能。
和capture output 不同的是,视频预览层强引用到session。这时确保layer播放视频时,session不被销毁。
code:
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对象一样。你可以缩放图像和执行转换,旋转等待。
不同的是你需要设置层的 orientation 属性,用来指导如何选择相机的图像。
另外,你可以通过 supportsVideoMirroring 属性测试是否支持镜像,可以设置 videoMirrored 属性,但是当 automaticallyAdjustsVideoMirroring 属性设为 YES时(默认),镜像值自动设置层基于session 的配置。
预览层支持三种显示模式:AVLayerVideoGravityResizeAspect,AVLayerVideoGravityResizeAspectFill,AVLayerVideoGravityResize
2)显示音频levels
AVCaptureAudioChannel 对象提供音频通道的平均值和峰值power levels。
音频levels 没有 KVO,所以必须轮询levels来更新用户界面。
code:
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.
}
}
6. 捕捉视频帧UIImage 对象
下面的例子说明怎样捕捉视频并且转换帧为UIImage 对象。
. 创建 AVCaptureSession 对象协调数据流从输入设备到输出;
. 通过需要的输入设备类型查找 AVCaptureDevice 对象;
. 通过设备创建 AVCaptureDeviceInput 对象;
. 创建 AVCaptureVideoDataOutput 对象来提供视频帧;
. 实现 AVCaptureVideoDataOutput 的delegate来处理视频帧;
. 实现一个函数转换 CMSampleBuffer 到 UIImage 对象;
1)创建并配置 Capture Session
code:
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
2)创建并配置Device和Device Input
code:
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
[session addInput:input];
3) 创建并配置Video Data Output
code:
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);
4) 实现 Sample Buffer Delegate (AVCaptureVideoDataOutputSampleBufferDelegate)
code:
- (void) captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
5)开始和停止录制
配置完捕获session后,需要确保有相机录制权限。
code:
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 消息开始录制。
[session startRunning];
7. 高帧率视频捕获
iOS 7.0 引入高帧率视频捕获(也称“SloMo”视频)。
用 AVCaptureDeviceFormat 类确定设备的捕获能力。该类有返回支持的媒体类型,帧速率,视图字段,最大缩放因子,是否支持视频稳定等。