[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MEzYA1RB-1675666218742)(http://ichenwin.qiniudn.com/avfoundation.png)]
导入AVFoundation库,并将它加入.pch
预编译文件
给相机预览控制器DTCameraPreviewController
添加四个私有成员,获取AVFoundation
的“终端”、“输入”、“输出”、“管理员”对象:
@implementation DTCameraPreviewController
{
AVCaptureDevice *_camera;
AVCaptureDeviceInput *_videoInput;
AVCaptureStillImageOutput *_imageOutput;
AVCaptureSession *_captureSession;
}
AVCaptureDevice
提供了一个类方法,指定一种媒体类型(AVMediaTypeVideo
or AVMediaTypeAudio
)它便能返回对应的录制设备。其他媒体类型可以在AVMediaFormat.h
中找到,不过它们不需要录制设备(如文本、字幕等)。
在DTCameraPreviewController.m
中实现_setupCamera
方法,用来初始化若干个AVFoundation
中用于录制的对象,
- (void)_setupCamera {
//获取到一个录制设备(摄像头)
_camera = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
//创建摄像头的输入,initWithDevice:方法自动为设备分配了一个端口,每个端口只能传输一路媒体数据
NSError *error;
_videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_camera error:&error];
if (!_videoInput) {
NSLog(@"Error connecting video input: %@", [error localizedDescription]);
return;
}
}
AVCaptureSession
是媒体录制进程的的管理员。控制着设备的输入输出。将输入添加至设备(_setupCamera
方法):
//创建录制“管理进程”,将输入添加至设备
_captureSession = [[AVCaptureSession alloc] init];
if (![_captureSession canAddInput:_videoInput]) {
NSLog(@"Unable to add video input to capture session");
return;
}
[_captureSession addInput:_videoInput];
苹果提供了预览层AVCaptureVideoPreviewLayer
,它可以提供摄像头画面的实时预览。因为它是CALayer的子类,将它封装至UIView
,方便使用。所以新建一个继承自UIView
的DTVideoPreviewView
类。头文件中,定义一个属性以获取视频预览层:
@interface DTVideoPreviewView : UIView
@property (readonly) AVCaptureVideoPreviewLayer *previewLayer;
@end
实现文件:
@implementation DTVideoPreviewView
//代码创建实例时调用
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
[self _commonSetup];
}
return self;
}
//通过Nib创建
- (void)awakeFromNib {
[self _commonSetup];
}
//替代默认的CALayer
+ (Class)layerClass {
return [AVCaptureVideoPreviewLayer class];
}
- (void)_commonSetup {
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColor blackColor];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
//类型转换
- (AVCaptureVideoPreviewLayer *)previewLayer {
return (AVCaptureVideoPreviewLayer *)self.layer;
}
@end
将storyboard中的根视图类型改为DTVideoPreviewView
。
在DTCameraPreviewController
中添加以下viewDidLoad
方法:
- (void)viewDidLoad {
[super viewDidLoad];
NSAssert([self.view isKindOfClass:[DTVideoPreviewView class]], @"Wrong root view class %@ in %@", NSStringFromClass([self.view class]), NSStringFromClass([self class]));
_videoPreview = (DTVideoPreviewView *)self.view;
[self _setupCamera];
}
以及在_setupCamera
最后将预览图层添加至管理进程中:
_videoPreview.previewLayer.session = _captureSession;
至此,我们已将流程图中的AVCaptureDeviceInput
连至预览图层。
启动摄像头需调用-startRunning
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[_captureSession startRunning];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_captureSession stopRunning];
}
- (void)_setupTorchToggleButton {
if ([_camera hasTorch]) {
self.toggleTorchButton.hidden = NO;
} else {
self.toggleTorchButton.hidden = YES;
}
}
- (IBAction)toggleTorch:(id)sender {
if ([_camera hasTorch]) {
BOOL torchActive = [_camera isTorchActive];
if ([_camera lockForConfiguration:nil]) {
if (torchActive) {
if ([_camera isTorchModeSupported:AVCaptureTorchModeOff]) {
[_camera setTorchMode:AVCaptureTorchModeOff];
}
} else {
if ([_camera isTorchModeSupported:AVCaptureTorchModeOn]) {
[_camera setTorchMode:AVCaptureTorchModeOn];
}
}
[_camera unlockForConfiguration];
}
}
}
完整的_setupCamera
:
- (void)_setupCamera {
_camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (!_camera) {
[self.snapButton setTitle:@"No Camera Found" forState:UIControlStateNormal];
self.snapButton.enabled = NO;
[self _informUserAboutNoCam];
return;
}
NSError *error;
_videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_camera error:&error];
if(!_videoInput) {
NSLog(@"error connectiong video input: %@", [error localizedDescription]);
return;
}
_captureSession = [[AVCaptureSession alloc] init];
if (![_captureSession canAddInput:_videoInput]) {
NSLog(@"Unable to add video input to capture session");
return;
}
[_captureSession addInput:_videoInput];
// [self _configureCurrentCamera];
_imageOutput = [AVCapturePhotoOutput new];
if (![_captureSession canAddOutput:_imageOutput]) {
NSLog(@"Unable to add still image output to capture session");
return;
}
[_captureSession addOutput:_imageOutput];
_videoPreview.previewLayer.session = _captureSession;
}
获取当前链路:
- (AVCaptureConnection *)_captureConnection {
for (AVCaptureConnection *connection in _imageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([port.mediaType isEqual:AVMediaTypeVideo]) {
return connection;
}
}
}
return nil;
}
拍照:
- (IBAction)snap:(id)sender {
if (!_camera) {
return;
}
AVCaptureConnection *videoConnection = [self _captureConnection];
if (!videoConnection) {
NSLog(@"Error:No Video connection found on still image output");
}
[_imageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (error) {
NSLog(@"Error capturing still image: %@", [error localizedDescription]);
return;
}
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: imageSampleBuffer];
UIImage *image = [UIImage imageWithData:imageData];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}];
}
iOS有三种对焦模式:
AVCaptureFocusModeContinuousAutoFocus
AVCaptureFocusModeAutoFocus
AVCaptureFocusModeLocked
监测扫描区域的变化:
- (void)_configureCurrentCamera {
if ([_camera isFocusModeSupported:AVCaptureFocusModeLocked]) {
if ([_camera lockForConfiguration:nil]) {
_camera.subjectAreaChangeMonitoringEnabled = YES;
[_camera unlockForConfiguration];
}
}
}
一旦画面有变化,iOS系统就会发出AVCaptureDeviceSubjectAreaDidChangeNotification
通知,我们可以再-viewDidLoad
中订阅这一通知:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSAssert([self.view isKindOfClass:[CWVideoPreviewView class]],
@"Wrong root view class %@ in %@",
NSStringFromClass([self.view class]),
NSStringFromClass([self class]));
_videoPreview = (CWVideoPreviewView *)self.view;
[self _setupCamera];
_videoPreview.previewLayer.session = _captureSession;
[self _setupCameraAfterCheckingAuthorization];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self.view addGestureRecognizer:tap];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(subjectChanged:)
name:AVCaptureDeviceSubjectAreaDidChangeNotification
object:nil];
}