美文网首页
使用AVFoundation拍照实践 2022-05-01 周日

使用AVFoundation拍照实践 2022-05-01 周日

作者: 勇往直前888 | 来源:发表于2022-05-01 17:18 被阅读0次

简介

一般拍照,直接使用UIImagePickerController就可以了。使用方法简单,拍照效果很好。
当前有个应用,默认的拍照页面不能满足需求,比如要求拍多张才能退出拍照页面,所以就需要自定义。AVFoundation框架下的AVCaptureSession可以完成这个功能。

涉及的对象

// 捕获会话类
@property (strong, nonatomic) AVCaptureSession *session;

// 捕获设备类
@property (strong, nonatomic) AVCaptureDevice *device;

// 捕获设备输入类
@property (strong, nonatomic) AVCaptureDeviceInput *deviceInput;

// 捕获照片输出类
@property (strong, nonatomic) AVCapturePhotoOutput *photoOutput;

// 捕获连接类
@property (strong, nonatomic) AVCaptureConnection *connection;

// 捕获照片设置类
@property (strong, nonatomic) AVCapturePhotoSettings *photoSettings;

// 捕获视频预览层
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer;

1. AVCaptureSession

  • 这个可以看做输入输出设备的整合者。

  • 图片质量,或者视频质量在这里设置。

  • 视频和图片使用这一套功能。

    self.session = [[AVCaptureSession alloc] init];
    
    // 预设为照片: AVCaptureSessionPresetPhoto 4032*3024
    if ([self.session canSetSessionPreset:AVCaptureSessionPresetPhoto]) {
        self.session.sessionPreset = AVCaptureSessionPresetPhoto;
    }

2. AVCaptureDevice,AVCaptureDeviceInput

  • AVCaptureDevice可以看做具体的设备,比如摄像头,麦克风等等。

  • AVCaptureDeviceInput 是配合AVCaptureSession的输入设备,通过AVCaptureDevice创建。

  • 这两者可以视为一个整体,当做一个组件来看待。

  • 所谓的“拍照”,相当于视频中的抓图片,所以这里选择视频设备AVMediaTypeVideo

    // 输入设备
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error = nil;
    self.deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:&error];
    if ([self.session canAddInput:self.deviceInput]) {
        [self.session addInput:self.deviceInput];
    }

3. AVCapturePhotoOutput

  • 输出设备有很多种,有视频,有图片

  • 图片的视频输出设备目前是AVCapturePhotoOutput

  • AVCaptureConnectionAVCapturePhotoSettings可以看做是两个辅助对象,对照片进行各种设置。

要注意的是,竖屏还是横屏在AVCaptureConnection中设置。这里影响的是最后输出的照片的实际朝向。

    // 输出设备
    self.photoOutput = [[AVCapturePhotoOutput alloc] init];
    if ([self.session canAddOutput:self.photoOutput]) {
        [self.session addOutput:self.photoOutput];
    }
    self.connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
    // 固定横屏
    if ([self.connection isVideoOrientationSupported]) {
        self.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
    }
    self.photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey : AVVideoCodecTypeJPEG}];

4. AVCaptureVideoPreviewLayer

  • 视频预览层。要注意的是这是layer,不是view;区别就是是否能够响应事件。单从显示来说,layer更高效一点。

  • 大多数时候,这个layer都是给Controller的self.view,这样就导致全屏展示,这是没有必要的。只要加入要显示的view就可以了,很多时候没有必要全屏展示的。

  • 视屏的显示模式分为三种。对应于图片的Aspect Fit; Aspect Fill; Scale toFill三种模式。最好能够和图片的content mode对应上,不然的话,会引起误解。

  • 这个确实是“预览”,只是你看到的部分。比如说,这里也有对应AVCaptureConnection,也能这是竖屏还是横屏。但是这里设置的只是你能看到的,并不能设置照片实际的朝向。

    // 照相视图
    self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    // 固定横屏
    if(self.previewLayer.connection.isVideoOrientationSupported) {
        self.previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
    }
    [self.photoView.layer insertSublayer:self.previewLayer atIndex:0];

启动,停止

  • 这里的启动停止可以理解为扫描。一旦开始,就能在预览视图中看到视频界面。

  • 启动过程比较耗时,最好不要在主线程做。

  • 这个是通过AVCaptureSession这个对象来实现的。

// 开始扫描
- (void)start {
    if (!self.session.isRunning) {
        // 比较耗时,放入一个串行队列中单独执行
        dispatch_queue_t queue = dispatch_queue_create("拍照的串行队列", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            [self.session startRunning];
        });
    }
}

// 停止扫描
- (void)stop {
    if (self.session.isRunning) {
        [self.session stopRunning];
    }
}

拍照

  • 拍照功能是通过设置代理的方式来完成的,并且还自带音效。这感觉非常奇怪。

  • 并且默认不能重拍,需要用到一点小技巧才行。

// 照相按钮
- (IBAction)takePhotoButtonTouched:(id)sender {
    // 防抖,0.5秒
    self.photoButton.enabled = NO;
    [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:NO block:^(NSTimer * _Nonnull timer) {
        self.photoButton.enabled = YES;
    }];
    
    // 拍照,设置代理就是拍照,确实有点奇怪
    [self.photoOutput capturePhotoWithSettings:self.photoSettings delegate:self];
    
    // 为下次拍照做准备,没有这句会崩溃;AVCapturePhotoSettings里面有一个uniqueID,不重新生成一下,会导致重复拍照崩溃
    self.photoSettings = [AVCapturePhotoSettings photoSettingsFromPhotoSettings:self.photoSettings];
}
  • 照片在代理方法中获得,不是很方便。这套API设计得非常差劲。
#pragma mark - AVCapturePhotoCaptureDelegate
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error {
    if (error == nil) {
        NSData *imageData = [photo fileDataRepresentation];
        UIImage *image = [UIImage imageWithData:imageData];
        // 这个image就是此次拍摄的照片
    }
}

闪光灯

  • 在AVCapturePhotoSettings中设定,这是最新的方法。以前在device中设定。
// 开闪光灯
- (void)switchOnFlash {
    if ([self.photoOutput.supportedFlashModes containsObject:@(AVCaptureFlashModeOn)]) {
        self.photoSettings.flashMode = AVCaptureFlashModeOn;
    }
}

// 关闪光灯
- (void)switchOffFlash {
    if ([self.photoOutput.supportedFlashModes containsObject:@(AVCaptureFlashModeOff)]) {
        self.photoSettings.flashMode = AVCaptureFlashModeOff;
    }
}

视频缩放

这个是AVCaptureDevice提供的功能

// 视频缩放
- (void)zoomVideoWithFactor:(CGFloat)factor {
    NSError *error = nil;
    if ([self.device lockForConfiguration:&error]) {
        if (factor < self.device.activeFormat.videoMaxZoomFactor) {
            [self.device rampToVideoZoomFactor:factor withRate:10];
        }
        [self.device unlockForConfiguration];
    } else {
        [PDAHUD toast:error.localizedDescription];
    }
}

相关文章

网友评论

      本文标题:使用AVFoundation拍照实践 2022-05-01 周日

      本文链接:https://www.haomeiwen.com/subject/gslpyrtx.html