
AV Foundation捕捉会话的核心是AVCaptureSession。一个捕捉会话相当于一个虚拟的“插线板”,用于连接输入和输出的资源。捕捉会话管理从设备得到的数据流,比如摄像头和麦克风设备,输出一个或多个目的地。
捕捉设备
AVCaptureDevice是诸如摄像头和麦克风等物理设备定义了一个接口。AVCaptureDevice针对物理硬件设备定义了大量的控制方法,比如摄像头的对焦、曝光、白平衡和闪光灯等。
捕捉设备的输入
AVCaptureDevice需要包装成AVCaptureDeviceInput实例来与AVCaptureSession进行对接
捕捉设备的输出
上图AVCaptureSession的右边即设备的输出。
捕捉预览
与视频播放类似预览层也是一个有Core Animation的CALayer的子类满足的----AVCaptureVideoPreviewLayer.
代码部分
创建捕捉控制器
THCameraController.h
//引入AVFoundation头文件
#import <AVFoundation/AVFoundation.h>
extern NSString *const THThumbnailCreatedNotification;
@protocol THCameraControllerDelegate <NSObject> // 1 定义了当有错误发生时,委托代理的方法
- (void)deviceConfigurationFailedWithError:(NSError *)error;
- (void)mediaCaptureFailedWithError:(NSError *)error;
- (void)assetLibraryWriteFailedWithError:(NSError *)error;
@end
@interface THCameraController : NSObject
@property (weak, nonatomic) id<THCameraControllerDelegate> delegate;
@property (nonatomic, strong, readonly) AVCaptureSession *captureSession;
// 配置会话 // 2 配置会话 传入一个error对象的指针的指针,一旦出错外界可以直接获取错误信息
- (BOOL)setupSession:(NSError **)error;
- (void)startSession;
- (void)stopSession;
// 摄像头相关 // 3
- (BOOL)switchCameras; //切换摄像头
- (BOOL)canSwitchCameras; // 能否切换摄像头
@property (nonatomic, readonly) NSUInteger cameraCount; //能用的摄像头的个数
@property (nonatomic, readonly) BOOL cameraHasTorch; //是否支持手电筒模式
@property (nonatomic, readonly) BOOL cameraHasFlash; //是否支持闪光灯模式
@property (nonatomic, readonly) BOOL cameraSupportsTapToFocus;//是否支持聚焦
@property (nonatomic, readonly) BOOL cameraSupportsTapToExpose; // 是否支持曝光
@property (nonatomic) AVCaptureTorchMode torchMode; //手电筒模式
@property (nonatomic) AVCaptureFlashMode flashMode; //闪光灯模式
/** Media Capture Methods **/ // 5 捕捉图片和视频
- (void)captureStillImage;
// Video Recording
- (void)startRecording;
- (void)stopRecording;
- (BOOL)isRecording;
- (CMTime)recordedDuration;
@end
@interface THCameraController () <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (weak, nonatomic) AVCaptureDeviceInput *activeVideoInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;
@property (strong, nonatomic) NSURL *outputURL;
@end
@implementation THCameraController
- (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 把AVCaptureDevice包装成AVCaptureDeviceInput对象添加到会话中
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
if ([self.captureSession canAddInput:videoInput]) { // 4是否可以被添加到会话中,如果可以则添加
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
}
} else {
return NO;
}
AVCaptureDevice *audioDevice = // 5 获取音频设备的指针
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioInput = // 6 包装audioDevice的输入
[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
if ([self.captureSession canAddInput:audioInput]) { // 7是否可以被添加到会话中,如果可以则添加
[self.captureSession addInput:audioInput];
}
} else {
return NO;
}
self.imageOutput = [[AVCaptureStillImageOutput alloc] init]; // 8 创建一个AVCaptureStillImageOutput实例,用于从摄像头中捕捉静态图片
self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; // 9 创建一个AVCaptureMovieFileOutput实例用于输出视频文件。
if ([self.captureSession canAddOutput:self.movieOutput]) { //同样方式添加输出
[self.captureSession addOutput:self.movieOutput];
}
return YES;
}
- (void)startSession {
if (![self.captureSession isRunning]) { // 10 开始会话 异步调用不会阻塞主线程
dispatch_async([self globalQueue], ^{
[self.captureSession startRunning];
});
}
}
- (void)stopSession {
if ([self.captureSession isRunning]) { // 11 停止会话
dispatch_async([self globalQueue], ^{
[self.captureSession stopRunning];
});
}
}
//返回一个队列
- (dispatch_queue_t)globalQueue {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
// 摄像头
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { // 12返回指定位置的摄像头
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) { // 遍历可用的视频设备并返回对对应的设备
if (device.position == position) {
return device;
}
}
return nil;
}
- (AVCaptureDevice *)activeCamera { // 13 返回当前激活的视频设备
return self.activeVideoInput.device;
}
- (AVCaptureDevice *)inactiveCamera { // 14 返回当前未激活的设备
AVCaptureDevice *device = nil;
if (self.cameraCount > 1) {
if ([self activeCamera].position == AVCaptureDevicePositionBack) { //
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
} else {
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
}
return device;
}
- (BOOL)canSwitchCameras { // 15 返回可用视频捕捉设备
return self.cameraCount > 1;
}
- (NSUInteger)cameraCount { //
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}
//切换摄像头
- (BOOL)switchCameras {
if (![self canSwitchCameras]) { // 16 是否可以切换摄像头(摄像头至少多余1个)
return NO;
}
NSError *error;
AVCaptureDevice *videoDevice = [self inactiveCamera]; // 17 获取为激活摄像头指针,并包装一个AVCaptureDeviceInput输入
AVCaptureDeviceInput *videoInput =
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (videoInput) {
[self.captureSession beginConfiguration]; // 18 开始设置会话
[self.captureSession removeInput:self.activeVideoInput]; // 19 移除正在使用的激活摄像头
if ([self.captureSession canAddInput:videoInput]) { // 20 添加17不创建的新的AVCaptureDeviceInput输入
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
} else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration]; // 21 提交设置 18-21步固定步骤, 先锁住 然后切换 自后提交配置
} else {
[self.delegate deviceConfigurationFailedWithError:error]; // 22 错误回调
return NO;
}
return YES;
}
// 闪光 和 手电筒模式
- (BOOL)cameraHasFlash {
return [[self activeCamera] hasFlash];
}
- (AVCaptureFlashMode)flashMode {
return [[self activeCamera] flashMode];
}
// 设置闪光灯模式
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {
AVCaptureDevice *device = [self activeCamera];
if (device.flashMode != flashMode &&
[device isFlashModeSupported:flashMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) { // 锁定设置
device.flashMode = flashMode; // 解锁设置
[device unlockForConfiguration]; // 解锁设置
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
- (BOOL)cameraHasTorch {
return [[self activeCamera] hasTorch];
}
- (AVCaptureTorchMode)torchMode {
return [[self activeCamera] torchMode];
}
// 设置手电筒模式
- (void)setTorchMode:(AVCaptureTorchMode)torchMode {
AVCaptureDevice *device = [self activeCamera];
if (device.torchMode != torchMode &&
[device isTorchModeSupported:torchMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) {
device.torchMode = torchMode;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
}
// 获取静态图片
- (void)captureStillImage {
AVCaptureConnection *connection =
[self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
}
id handler = ^(CMSampleBufferRef sampleBuffer, NSError *error) {
if (sampleBuffer != NULL) {
NSData *imageData =
[AVCaptureStillImageOutput
jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
// 1
} else {
NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
}
};
// Capture still image 一个异步方法
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
completionHandler:handler];
}
// 录制视频部分
- (BOOL)isRecording { //
return self.movieOutput.isRecording;
}
- (void)startRecording {
if (![self isRecording]) {
AVCaptureConnection *videoConnection = // 获取AVCaptureConnection指针
[self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) { //
videoConnection.videoOrientation = self.currentVideoOrientation;
}
if ([videoConnection isVideoStabilizationSupported]) { //
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
videoConnection.enablesVideoStabilizationWhenAvailable = YES; // 是否支持视频稳定
} else {
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
}
AVCaptureDevice *device = [self activeCamera];
if (device.isSmoothAutoFocusSupported) { // 摄像头平滑对焦操作
NSError *error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = NO;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
self.outputURL = [self uniqueURL]; //
[self.movieOutput startRecordingToOutputFileURL:self.outputURL // 设置代理和视频存放路径 开始录制
recordingDelegate:self];
}
}
- (CMTime)recordedDuration {
return self.movieOutput.recordedDuration;
}
- (NSURL *)uniqueURL { //
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *dirPath =
[fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];
if (dirPath) {
NSString *filePath =
[dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
return [NSURL fileURLWithPath:filePath];
}
return nil;
}
- (void)stopRecording { // 停止录制
if ([self isRecording]) {
[self.movieOutput stopRecording];
}
}
#pragma mark - AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
if (error) {
[self.delegate mediaCaptureFailedWithError:error];
} else {
[self writeVideoToAssetsLibrary:[self.outputURL copy]];
}
self.outputURL = nil;
}
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; //
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) { //
ALAssetsLibraryWriteVideoCompletionBlock completionBlock;
completionBlock = ^(NSURL *assetURL, NSError *error){ //
if (error) {
[self.delegate assetLibraryWriteFailedWithError:error];
}
};
[library writeVideoAtPathToSavedPhotosAlbum:videoURL // 写入资源库中
completionBlock:completionBlock];
}
}
@end
预览层
AVCaptureVideoPreviewLayer需要相关的session(AVCaptureSession)才能激活。
//空间坐标转换的方法。
- (CGPoint)captureDevicePointForPoint:(CGPoint)point {
AVCaptureVideoPreviewLayer *layer =
(AVCaptureVideoPreviewLayer *)self.layer;
return [layer captureDevicePointOfInterestForPoint:point];
}
// 重写layerClass方法 返回预览层
+ (Class)layerClass {
return [AVCaptureVideoPreviewLayer class];
}
- (AVCaptureSession*)session {
return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
// 设置session会话,关键layer和session。激活相应的layer
- (void)setSession:(AVCaptureSession *)session {
[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
AVCaptureVideoPreviewLayer的两个坐标转换的方法。
获取屏幕坐标系数据 返回得到额设备坐标系数据
- (CGPoint)captureDevicePointOfInterestForPoint:(CGPoint)pointInLayer;
获取摄像头坐标系的数据,返回转换得到的屏幕坐标系数据
- (CGPoint)pointForCaptureDevicePointOfInterest:(CGPoint)captureDevicePointOfInterest;
网友评论