版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wj610671226/article/details/83097883
AVFounction学习笔记之–媒体捕捉
- 基础知识
首先先介绍AVFounction捕捉相关的类。如下图所示
1、AVCaptureSession 捕捉会话核心类
2、AVCaptureDevice 捕捉设备,最常用的是音频和视频
3、AVCaptureVideoPreviewLayer 视频捕捉的预览图层
4、AVCaptureConnection 捕捉连接的类
5、AVCaptureDeviceInput 捕捉连接的类
6、AVCaptureOutput捕捉的输出。包括:
AVCaptureStillImageOutput(静态图片)
AVCaptureMovieFileOutput(视频)
AVCaptureAudioFileOutput(音频)
AVCapturePhotoOutput(照片)
AVCaptureMetadataOutput(元数据)
AVCaptureAudioDataOutput(音频流)
AVCaptureVideoDataOutput(视频流)
- 最简单的捕捉示例
核心步骤:
1、创建会话层
2、添加输入设备
3、添加输出设备
4、创建预览图层
5、启动会话
// 1、创建会话层
let session = AVCaptureSession()
let cameraDevice = AVCaptureDevice.default(for: AVMediaType.video)
do {
// 2、添加输入设备
let cameraDeviceInput = try AVCaptureDeviceInput(device: cameraDevice!)
if session.canAddInput(cameraDeviceInput) {
session.addInput(cameraDeviceInput)
}
// 3、添加输出设备
let imageOutput = AVCapturePhotoOutput()
if session.canAddOutput(imageOutput) {
session.addOutput(imageOutput)
}
// 4、创建预览图层
let layer = AVCaptureVideoPreviewLayer(session: session)
layer.frame = view.bounds
view.layer.addSublayer(layer)
// 5、启动会话
session.startRunning()
} catch let error {
print("创建捕捉视频设备失败 error = \(error)")
}
Demo例子讲解常用功能
下面利用一些代码片段来讲解一些常用API功能的使用。
- 构建基础会话
- (BOOL)setupSession:(NSError **)error {
// 初始化session
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
// 设置视频设备
AVCaptureDevice * videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
}
} else {
return NO;
}
// 设置音频设备
AVCaptureDevice * audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput * audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
if (audioInput) {
if ([self.captureSession canAddInput:audioInput]) {
[self.captureSession addInput:audioInput];
}
} else {
return NO;
}
// 设置image output
self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
self.imageOutput.outputSettings = @{AVVideoCodecKey: AVVideoCodecJPEG};
if ([self.captureSession canAddOutput:self.imageOutput]) {
[self.captureSession addOutput:self.imageOutput];
}
// 设置movie output
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
self.videoQueue = dispatch_queue_create("com.videoQueue", NULL);
return YES;
}
- 摄像头操作
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
// 获取有效的摄像头参数
NSArray * devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice * device in devices) {
if (device.position == position) {
return device;
}
}
return nil;
}
- (AVCaptureDevice *)activeCamera {
// 返回激活的捕捉设备输入的属性
return self.activeVideoInput.device;
}
- (AVCaptureDevice *)inactiveCamera {
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 {
// 判断是否有2个摄像头以上
return self.cameraCount > 1;
}
- (NSUInteger)cameraCount {
// 获取摄像头个数
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}
- 切换摄像头
- (BOOL)switchCameras {
// 判断是否可以切换摄像头
if (![self canSwitchCameras]) {
return NO;
}
NSError * error;
// 获取未激活的摄像头,并创建一个新的输入
AVCaptureDevice * videoDevoce = [self inactiveCamera];
AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevoce error:&error];
if (videoInput) {
// 移除旧的输入,添加新的输入
[self.captureSession beginConfiguration];
[self.captureSession removeInput:self.activeVideoInput];
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
self.activeVideoInput = videoInput;
} else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
return NO;
}
return YES;
}
- 摄像头对焦的实现
- (BOOL)cameraSupportsTapToFocus {
// 判断设备是否支持对焦的功能
return [[self activeCamera] isFocusPointOfInterestSupported];
}
- (void)focusAtPoint:(CGPoint)point {
AVCaptureDevice * device = [self activeCamera];
// 判断是否支持对焦并自动对焦
if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
NSError * error;
if ([device lockForConfiguration:&error]) {
// 自动对焦点
device.focusPointOfInterest = point;
device.focusMode = AVCaptureFocusModeAutoFocus;
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
}
- 摄像头曝光
// 点击曝光
- (BOOL)cameraSupportsTapToExpose {
// 判断激活设备是否支持曝光
return [[self activeCamera] isExposurePointOfInterestSupported];
}
static const NSString *THCameraAdjustingExposureContext;
- (void)exposeAtPoint:(CGPoint)point {
AVCaptureDevice * device = [self activeCamera];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {
NSError * error;
// 锁定设备配置,设置exposurePointOfInterest、exposureMode的值
if ([device lockForConfiguration:&error]) {
device.exposurePointOfInterest = point;
device.exposureMode = exposureMode;
// 判断设备是否支持自动曝光模式
if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&THCameraAdjustingExposureContext];
}
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == &THCameraAdjustingExposureContext) {
AVCaptureDevice * device = (AVCaptureDevice *)object;
// 判断设备是否不再调整曝光等级
if (!device.isAdjustingExposure && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
// 移除通知
[object removeObserver:self forKeyPath:@"adjustingExposure" context:&THCameraAdjustingExposureContext];
dispatch_async(dispatch_get_main_queue(), ^{
NSError * error;
if ([device lockForConfiguration:&error]) {
device.exposureMode = AVCaptureExposureModeLocked;
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
});
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
// 重新设置对焦和曝光
- (void)resetFocusAndExposureModes {
AVCaptureDevice * device = [self activeCamera];
AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
BOOL canResetFocus = [device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
BOOL canResetExposure = [device isExposurePointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
CGPoint centerPoint = CGPointMake(0.5, 0.5);
NSError * error;
if ([device lockForConfiguration:&error]) {
if (canResetFocus) {
device.focusMode = focusMode;
device.focusPointOfInterest = centerPoint;
}
if (canResetExposure) {
device.exposureMode = exposureMode;
device.exposurePointOfInterest = centerPoint;
}
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
- 闪光灯和手电筒模式
// 调整闪光灯和手电筒模式
- (BOOL)cameraHasFlash {
return [[self activeCamera] hasFlash];
}
- (AVCaptureFlashMode)flashMode {
return [[self activeCamera] flashMode];
}
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {
AVCaptureDevice * device = [self activeCamera];
if ([device isFlashModeSupported:flashMode]) {
NSError * error;
if ([device lockForConfiguration:&error]) {
device.flashMode = flashMode;
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
}
- (BOOL)cameraHasTorch {
return [[self activeCamera] hasTorch];
}
- (AVCaptureTorchMode)torchMode {
return [[self activeCamera] torchMode];
}
- (void)setTorchMode:(AVCaptureTorchMode)torchMode {
AVCaptureDevice * device = [self activeCamera];
if ([device isTorchModeSupported:torchMode]) {
NSError * error;
if ([device lockForConfiguration:&error]) {
device.torchMode = torchMode;
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
}
- 拍照并写入照片库
- (void)captureStillImage {
AVCaptureConnection * connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
// 调整方向
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
}
[self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError * _Nullable error) {
if (imageDataSampleBuffer != NULL) {
// 获取图片信息
NSData * imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage * image = [[UIImage alloc] initWithData:imageData];
NSLog(@"image = %@", image);
[self writeImageToAssetsLibrary:image];
} else {
NSLog(@"捕捉静态图片 sampleBuffer error = %@", error.localizedDescription);
}
}];
}
- (AVCaptureVideoOrientation)currentVideoOrientation {
AVCaptureVideoOrientation orientation;
// 根据设备方向切换AVCaptureVideoOrientation方向,注意左右是相反的
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
default:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
}
return orientation;
}
// 将图片写入照片库
- (void)writeImageToAssetsLibrary:(UIImage *)image {
// Listing 6.13
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(NSInteger)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) {
if (!error) {
NSLog(@"写入照片成功"),
} else {
NSLog(@"writeImageToAssetsLibrary error = %@", error.localizedDescription);
}
}];
}
- 录制视频并写入照片库
- (BOOL)isRecording {
// 是否录制中
return self.movieOutput.isRecording;
}
- (void)startRecording {
if (![self isRecording]) {
// 获取当前连接信息
AVCaptureConnection * videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
// 方向处理
if ([videoConnection isVideoOrientationSupported]) {
videoConnection.videoOrientation = self.currentVideoOrientation;
}
// 设置录制视频稳定
if ([videoConnection isVideoOrientationSupported]) {
videoConnection.enablesVideoStabilizationWhenAvailable = YES;
}
// 降低对焦速度,平滑对焦
AVCaptureDevice * device = [self activeCamera];
if (device.isSmoothAutoFocusSupported) {
NSError * error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = YES;
[device unlockForConfiguration];
} else {
NSLog(@"error = %@", error.localizedDescription);
}
}
// 获取视频存储地址
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]) {
[library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"error = %@", error.localizedDescription);
} else {
[self generateThumbnailForVideoAtURL:videoURL];
}
}];
}
}
// 获取视频第一个时间点的缩略图
- (void)generateThumbnailForVideoAtURL:(NSURL *)videoURL {
dispatch_async(self.videoQueue, ^{
AVAsset * asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator * imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
imageGenerator.maximumSize = CGSizeMake(100, 0);
// 捕捉获取缩略图方向
imageGenerator.appliesPreferredTrackTransform = YES;
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:nil];
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线层,其他操作");
});
});
}