AVFoundation

1:基础

/*
 1:获取可用输入设备 AVCaptureDevice
 2:设置输入设备: [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:nil]
 3:设置输出设备: AVCaptureStillImageOutput 如果iOS10以上 AVCapturePhotoOutput
 4:设置Session:AVCaptureSession初始化,设置Preset,添加输入输出设备。
 5:预览layer:   AVCaptureVideoPreviewLayer, + (instancetype)layerWithSessionWithNoConnection:(AVCaptureSession *)session
 6:获取照片:    AVCapturePhotoOutput通过代理可以获取图片,AVCaptureStillImageOutput通过Block获取
 
 https://blog.csdn.net/vkooy/article/details/60867268
 1) 前后置摄像头的切换
 
   前后值不能切换,各种尝试找了半天没找到有原因。后来发现我在设置图片尺寸的时候设置为1080P [self.session canSetSessionPreset: AVCaptureSessionPreset1920x1080] ,前置摄像头并不支持这么大的尺寸,所以就不能切换前置摄像头。我验证了下 前置摄像头最高支持720P,720P以内可*切换。  当然也可以在前后置摄像头切换的时候,根据前后摄像头来设置不同的尺寸,这里不在赘述。
 2)焦点位置
 
   CGPoint focusPoint = CGPointMake( point.y /size.height ,1-point.x/size.width );
 setExposurePointOfInterest:focusPoint 函数后面Point取值范围是取景框左上角(0,0)到取景框右下角(1,1)之间。官方是这么写的:
   The value of this property is a CGPoint that determines the receiver's focus point of interest, if it has one. A value of (0,0) indicates that the camera should focus on the top left corner of the image, while a value of (1,1) indicates that it should focus on the bottom right. The default value is (0.5,0.5).
   我也试了按这个来但位置就是不对,只能按上面的写法才可以。前面是点击位置的y/PreviewLayer的高度,后面是1-点击位置的x/PreviewLayer的宽度
 
 3)对焦和曝光
 
   我在设置对焦是 先设置了模式setFocusMode,后设置对焦位置,就会导致很奇怪的现象,对焦位置是你上次点击的位置。所以一定要先设置位置,再设置对焦模式。
   曝光同上
 */

2:代码

#import "FFCameraHelper.h"
#define FFCameraHelperScreenWidth  ([UIScreen mainScreen].bounds.size.width)
#define FFCameraHelperScreenHeight ([UIScreen mainScreen].bounds.size.height)
#define FFCameraHelperFocusLayerWidth (80)

@interface FFCameraHelper ()<AVCapturePhotoCaptureDelegate>
/// 可用的设备
@property (nonatomic, strong) NSArray<AVCaptureDevice *> *captureDeviceList;
/// 捕获的设备
@property (nonatomic, strong) AVCaptureDevice            *captureDevice;
/// 输入设备
@property (nonatomic, strong) AVCaptureDeviceInput       *captureInput;
/// 输出图片
@property (nonatomic, strong) AVCapturePhotoOutput       *photoOutput;
/// 输出数据
@property (nonatomic, strong) AVCaptureMetadataOutput    *metaDataOutput;

/// 输出图片 iOS10.0 以下
@property (nonatomic, strong) AVCaptureStillImageOutput  *imageOutput;

/// 流: 把输入流和输出流结合在一起,并启动设备
@property (nonatomic, strong) AVCaptureSession           *captureSession;

///
@property (nonatomic, strong) CAShapeLayer               *focusLayer;

@end

@implementation FFCameraHelper
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self FF_ReqeuestAuthortion];
        /// 授权
        AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        if (status == AVAuthorizationStatusAuthorized) {
            [self FF_initSetUp];
        }
    }
    return self;
}



/// 请求授权
- (void)FF_ReqeuestAuthortion {
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (status == AVAuthorizationStatusNotDetermined) {
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            
        }];
    }
}

- (void)FF_initSetUp {
    // 1: 设置后置摄像头
    [self FF_AcquireNeedDevice:AVCaptureDevicePositionBack];
    // 2: 设置输入设备
    self.captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:nil];
    // 3: 设置输出内容
    if (@available(ios 10.0, *)) {
        self.photoOutput = [AVCapturePhotoOutput new];
    }else {
        self.imageOutput = [AVCapturePhotoOutput new];
    }
    // 4: 设置流
    self.captureSession = [[AVCaptureSession alloc] init];
    if ([self.captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) {
        [self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
    }else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) {
        [self.captureSession setSessionPreset:AVCaptureSessionPresetMedium];
    }
    if ([self.captureSession canAddInput:self.captureInput]) {
        [self.captureSession addInput:self.captureInput];
    }
    if (@available(ios 10.0, *)) {
        if ([self.captureSession canAddOutput:self.photoOutput]) {
            [self.captureSession addOutput:self.photoOutput];
        }
    }else if ([self.captureSession canAddOutput:self.imageOutput]) {
        [self.captureSession addOutput:self.imageOutput];
    }
    // 5: layer
    self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
    self.previewLayer.frame = CGRectMake(0, 0, FFCameraHelperScreenWidth, FFCameraHelperScreenHeight);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
}

/**
 更改捕获设备
 */
- (void)FF_ChangeCaptureDevice {
    [self FF_PreviewLayerTransform:self.captureDevice.position == AVCaptureDevicePositionBack];
    if (self.captureDevice == nil) {
    }else if (self.captureDevice.position == AVCaptureDevicePositionBack) {
        [self FF_AcquireNeedDevice:AVCaptureDevicePositionFront];
    }else {
        [self FF_AcquireNeedDevice:AVCaptureDevicePositionBack];
    }
    [self.captureSession beginConfiguration];
    [self.captureSession removeInput:self.captureInput];
    self.captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:nil];
    if ([self.captureSession canAddInput:self.captureInput]) {
        [self.captureSession addInput:self.captureInput];
    }
    [self.captureSession commitConfiguration];
}

/// 前后摄像头切换动画
- (void)FF_PreviewLayerTransform:(BOOL)isBack {
//    转场动画可以设置的值 系统提供的(type)
//    kCATransitionFade       淡出
//    kCATransitionMoveIn  覆盖原图
//    kCATransitionPush       推出
//    kCATransitionReveal底部显出来
//    如果为隐藏的效果,要使用kvc 即【Animation setType:@“”】;
//    pageCurl               向上翻一页
//    pageUnCurl             向下翻一页
//    rippleEffect             滴水效果
//    suckEffect 收缩效果,如一块布被抽走
//    cube                   立方体效果
//    oglFlip              上下翻转效果
    [self.previewLayer removeAllAnimations];
    CATransition *transition = [CATransition animation];
    [transition setType:@"oglFlip"];
    transition.subtype = isBack ? kCATransitionFromRight : kCATransitionFromLeft;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.duration = 0.27;
    [self.previewLayer addAnimation:transition forKey:@"filp"];
    
}

/**
 开始捕获
 */
- (void)FF_StartCapture {
    [self.captureSession startRunning];
}


- (void)FF_AcquireNeedDevice:(AVCaptureDevicePosition)position {
    self.captureDevice = nil;
    if (@available(ios 10.0, *)) {
        self.captureDeviceList = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:position].devices;
        for (AVCaptureDevice *device in self.captureDeviceList) {
            if (device.position == position) {
                self.captureDevice = device;
                return;
            }
        }
    }else {
        self.captureDeviceList = [AVCaptureDevice devices];
        for (AVCaptureDevice *device in self.captureDeviceList) {
            if (device.position == position) {
                self.captureDevice = device;
                return;
            }
        }
    }
}

/// 获取照片
- (void)FF_AcquireNeedImage {
    AVCaptureConnection *connection = nil;
    if (@available(ios 10.0, *)) {
        connection = [self.photoOutput connectionWithMediaType:AVMediaTypeAudio];
    }else {
        connection = [self.imageOutput connectionWithMediaType:AVMediaTypeAudio];
    }
    if (!connection) {
        NSLog(@"拍照失败");
    }
    if (@available(ios 10.0, *)) {
        [self.photoOutput capturePhotoWithSettings:[AVCapturePhotoSettings photoSettings] delegate:self];
    }else {
        __block UIImage *img = nil;
        [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef  _Nullable imageDataSampleBuffer, NSError * _Nullable error) {
            if (imageDataSampleBuffer != nil) {
                NSData *data = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                img = [UIImage imageWithData:data];
            }
        }];
        if (self.delegate && [self.delegate respondsToSelector:@selector(FF_CaptureImage:)]) {
            [self.delegate FF_CaptureImage:img];
        }
    }
}

/**
 ios 11.0 后输出的图片(拍照)
*/
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error {
    if (error == nil) {
        NSData *data = [photo fileDataRepresentation];
        UIImage *img = [UIImage imageWithData:data];
        if (self.delegate && [self.delegate respondsToSelector:@selector(FF_CaptureImage:)]) {
            [self.delegate FF_CaptureImage:img];
        }
    }
}
/**
 ios 10.0 后输出的图片(拍照)
 */
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(nullable CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(nullable CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(nullable AVCaptureBracketedStillImageSettings *)bracketSettings error:(nullable NSError *)error {
    if (error) {
        NSLog(@"%@", error.localizedDescription);
    }else {
        NSData *data = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer:previewPhotoSampleBuffer];
        UIImage *img = [UIImage imageWithData:data];
        if (self.delegate && [self.delegate respondsToSelector:@selector(FF_CaptureImage:)]) {
            [self.delegate FF_CaptureImage:img];
        }
    }
}

/**
 设置曝光和自动对焦 ,有问题

 @param point 对焦重点
 */
- (void)FF_SetupFocusAndWhiteBalance:(CGPoint)point {
    NSError *error = nil;
    [self.captureDevice lockForConfiguration:&error];
    if (error) {
        NSLog(@"%@", error.localizedDescription);
        return;
    }
    /// 设置白平衡
    if ([self.captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance]) {
        self.captureDevice.whiteBalanceMode = AVCaptureWhiteBalanceModeAutoWhiteBalance;
    }
    /// 设置闪光灯
    if (@available(ios 10.0, *)) {
    }else {
        if ([self.captureDevice isFlashModeSupported:AVCaptureFlashModeAuto]) {
            self.captureDevice.flashMode = AVCaptureFlashModeAuto;
        }
    }
    /// 设置焦点
    if ([self.captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
        self.captureDevice.focusPointOfInterest = CGPointMake(point.y / FFCameraHelperScreenHeight, 1 - point.x / FFCameraHelperScreenWidth);
        self.captureDevice.focusMode = AVCaptureFocusModeAutoFocus;
    }
    /// 设置曝光
    if ([self.captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
        self.captureDevice.exposurePointOfInterest = CGPointMake(point.y / FFCameraHelperScreenHeight, 1 - point.x / FFCameraHelperScreenWidth);
        self.captureDevice.exposureMode = AVCaptureExposureModeAutoExpose;
    }
    [self.captureDevice unlockForConfiguration];
    /// 对焦动画
//    self.focusLayer.hidden = NO;
//    [UIView animateWithDuration:3 animations:^{
//        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(point.x - 60, point.y - 60, 120, 120)];
//        self.focusLayer.path = path.CGPath;
//    } completion:^(BOOL finished) {
//        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(point.x - 40, point.y - 40, 80, 80)];
//        self.focusLayer.path = path.CGPath;
//    }];

//    [UIView animateWithDuration:1 animations:^{
//        self.focusLayer.transform = CATransform3DMakeScale(1.25, 1.25, 1.0);
//    } completion:^(BOOL finished) {
//        [UIView animateWithDuration:1 animations:^{
//            self.focusLayer.transform = CATransform3DIdentity;
//            self.focusLayer.hidden = YES;
//            [self.focusLayer removeFromSuperlayer];
//        }];
//    }];

    
}

/**
 对焦时的提示框
*/
- (void)FF_SetUpFocusLayerWithPoint:(CGPoint)point {
    if (self.focusLayer == nil) {
        self.focusLayer = [CAShapeLayer layer];
        self.focusLayer.strokeColor = [UIColor lightGrayColor].CGColor;
        self.focusLayer.fillColor = [UIColor clearColor].CGColor;
        self.focusLayer.lineDashPhase = 2;
        self.focusLayer.lineWidth = 1;
        self.focusLayer.lineCap = kCALineCapRound;
    }else {
        [self.focusLayer removeFromSuperlayer];
    }
    CGFloat radiu = FFCameraHelperFocusLayerWidth / 2;
    if (point.x - radiu < 0) {
        point.x = radiu;
    }else if (point.x + radiu > FFCameraHelperScreenWidth) {
        point.x = FFCameraHelperScreenWidth - radiu;
    }
    
    if (point.y - radiu < 22) {
        point.y = radiu + 22;
    }else if (point.y + radiu + 200 > FFCameraHelperScreenHeight) {
        point.y = FFCameraHelperScreenHeight - 200 - radiu;
    }
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(point.x - radiu, point.y - radiu, radiu * 2 , radiu * 2)];
    self.focusLayer.path = path.CGPath;
    [self.previewLayer addSublayer:self.focusLayer];
    [self FF_SetupFocusAndWhiteBalance:point];
}

@end