捕捉功能综述
捕捉会话
AVFoundation捕捉栈的核心类是AVCaptureSession
。一个捕捉会话相当于一个虚拟的“插线板”,用于连接输入和输出的资源。捕捉会话管理从物理设备得到的数据流,比如摄像头和麦克风设备,输出到一个或多个目的地。可以动态配置输入输出的线路,让开发者能够在会话进行中按照需要重新配置捕捉环境,比如音频输入源在手机麦克风和耳机等其他设备间的切换。
捕捉会话还可以额外配置一个会话预设值(session preset),用来控制捕捉数据格式和质量。会话预设值默认为AVCaptureSessionHigh
,它适用于大多数情况,不过框架仍然提供了多个预设值对输出进行控制,以满足应用程序的特殊需求。
捕捉设备
AVCaptureDevice
为诸如摄像头或麦克风等物理设备定义了一个接口。大多数情况下,这些设备都内设于Mac、iPhone、iPad中,不过也可能是外部数码相机或便携摄像机。AVCaptureDevice
针对物理硬件设备定义了大量的控制方法,比如摄像头聚焦,曝光,白平衡,和闪光灯等。
AVCaptureDevice
定义了大量类方法用于访问系统的捕捉设备,最常用的一个方法是defaultDeviceWithMediaType:
,他会根据给定的媒体类型返回一个系统指定的默认设备。例如:
AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
实例中我们请求一个默认的视频设备,在包含前置摄像头和后置摄像头的iOS系统下,这个方法会返回后置摄像头,因为它是系统默认摄像头。在带有摄像头的Mac机器上,会返回内置的FaceTime摄像头。
捕捉设备输入
在使用捕捉设备进行处理前,首先需要将它添加为捕捉会话的输入。不能直接添加到AVCaptureSession中,但是可以通过将它封装在一个AVCaptureDeviceInput
实例中添加。这个对象在设备输出数据和捕捉会话间扮演着接线板的作用。使用deviceInputWithDevice:error:
方法创建AVCaptureDeviceInput
,如下所示:
NSError * error = nil;
AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
捕捉的输出
AVFoundation定义了AVCaptureOutput
的许多扩展类。AVCaptureOutput
是一个抽象基类,用于为获得的数据寻找输出目的地。框架定义了一些这个抽象类的高级扩展类,比如AVCaptureStillImageOutput
和AVCaptureMovieFileOutput
,使用它们很容易实现捕捉静态图片和视频的功能。还可以在这里找到底层的扩展,比如AVCaptureAudioDataOutput
和AVCaptureVideoDataOutput
,使用它们可以直接访问硬件捕捉到的数字样本数据。使用这些底层输出类需要对捕捉设备的数据渲染有更好的理解,不过这些类可以提供更强大的功能,比如对音频和视频流进行实时处理。
捕捉连接
在上面的插图中有一个类没有明确的名字,而是由一个连接不同组件的黑色箭头所表示,这就是AVCaptureConnect
类。捕捉会话首先需要确定由给定捕捉设备输入渲染的媒体类型,并自动建立其到能接收改媒体类型的捕捉输出端的连接。比如AVCaptureMovieFileOutput
可以接收音频和视频数据,所以会话会确定哪些输入产生视频,哪些产生音频,并正确地建立改连接。对这些连接的访问可以让开发者对信号流进行底层的控制,比如禁用某些特定的连接,或在音频连接中访问单独的音频轨道。
捕捉预览
如果不能在影像捕捉中看到正在捕捉的场景,那么应用就不会好用。幸运的是框架定义了一个AVCaptureVideoPreviewLayer
类来满足该需求。预览层是一个Core Animation的CALayer子类,对捕捉视频数据进行实时预览。这个类所扮演的角色类似于AVPlayerLayer
,不过还是针对摄像头捕捉的需求进行了定制。像AVPlayerLayer
一样,AVCaptureVideoPreviewLayer
也支持视频重力的概念,可以控制视频内容渲染的缩放和拉伸效果。
新建一个THCameraController
类负责处理摄像头多媒体信息采集处理
配置媒体采集会话AVCaptureSession
- (BOOL)setupSession:(NSError **)error {
self.captureSession = [[AVCaptureSession alloc] init]; // 1
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
// Set up default camera device
AVCaptureDevice *videoDevice = // 2
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput = // 3
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
if ([self.captureSession canAddInput:videoInput]) { // 4
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
}
} else {
return NO;
}
// Setup default microphone
AVCaptureDevice *audioDevice = // 5
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioInput = // 6
[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
if ([self.captureSession canAddInput:audioInput]) { // 7
[self.captureSession addInput:audioInput];
}
} else {
return NO;
}
// Setup the still image output
self.imageOutput = [[AVCaptureStillImageOutput alloc] init]; // 8
self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
// Setup movie file output
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; // 9
if ([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
return YES;
}
在子线程里开启捕捉会话
- (void)startSession {
if (![self.captureSession isRunning]) { // 1
dispatch_async([self globalQueue], ^{
[self.captureSession startRunning];
});
}
}
- (void)stopSession {
if ([self.captureSession isRunning]) { // 2
dispatch_async([self globalQueue], ^{
[self.captureSession stopRunning];
});
}
}
- (dispatch_queue_t)globalQueue {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
在外面的显示界面启动会话,并指定预览layer,从这里开始视频数据流就开始在应用中流转了。
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.cameraController.captureSession];
previewLayer.frame = self.view.bounds;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer insertSublayer:previewLayer atIndex:0];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.cameraController = [[THCameraController alloc] init];
NSError *error;
if ([self.cameraController setupSession:&error]) {
[self.cameraController startSession];
}else {
NSLog(@"errpr:%@", [error localizedDescription]);
}
}