Swift教程

iOS音视频(一) -- AVFoundation捕捉

本文主要是介绍iOS音视频(一) -- AVFoundation捕捉,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、AVFoundation简介

AVFoundation 是苹果在8.0之后推出的一个音视频框架.

AVFoundation 最强大的功能是对 照片&视频 的捕捉功能. 例如一些APP中的小视频、直播等, 可以通过AVFoundation来进行实现捕捉.

二、AVFoundation常用类

2.1、捕捉会话

捕捉会话主要是用到AVCaptureSession类, 它类似于一个排插, 各种设备都需要与捕捉会话关联起来。

2.2、捕捉设备

通过AVCaptureDevice可以获取到手机的各种硬件设备, 例如: 麦克风、前后摄像头、闪光灯等。

2.3、捕捉设备输入

通过AVCaptureDeviceInput可以捕捉到设备的输入。

在AVFoundation中, 捕捉设备输入是无法直接添加到Session中的, 所以需要将捕捉设备输入转化为捕捉设备添加进会话中。

2.4、捕捉设备输出

有输入就有输出。 在iOS10.0之后, 可以通过AVCapturePhotoOutput来进行获取图片的输出, 通过AVCaptureMovieFileOutput来进行视频文件的输出。 还有AVCaptureAudioDataOutput、还有AVCaptureVideoDataOutput等。

2.5、捕捉连接

AVCaptureConnection 可以根据捕捉的媒体的类型来建立一个连接

2.6、捕捉预览

AVCaptureVideoPreviewLayer主要是一个图层,主要是用来显示摄像头实时捕捉的内容。

三、AVFoundation的简单使用

这里涉及到摄像头、麦克风、相册, 需要配置用户隐私需求。

3.1、配置会话

  1. 创建session会话
  2. 设置分辨率
  3. 创建捕捉设备
  4. 将捕捉设备转化为捕捉设备输入
  5. 将捕捉设备输入添加到会话中(添加过程需要注意是否能够添加进会话中)
  6. 配置捕捉设备的输出
  7. 将捕捉设备输出添加到会话中(添加过程需要注意是否能够添加进会话中)
#pragma mark - session设置
/// 配置session
/// @param error 错误回调
- (BOOL)setupSession:(NSError **)error {
    /**
     * 添加视频的输入类别
     */
    //初始化
    self.captureSession = [[AVCaptureSession alloc] init];
    //设置分辨率
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    
    //拿到默认视频捕捉设备: iOS默认后置摄像头为默认视频捕捉色别
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    //一定要将捕捉设备转化AVCaptureDeviceInput
    //注意: 为session添加捕捉设备, 必须将此封装成AVCaptureDeviceInput对象
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    
    if (videoInput) {
        //摄像头不隶属于任何一个app, 是公共设备, 需要判断是否能添加
        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;
    }
    
    /**
     * 设置输出 (照片/视频文件)
     */
    //图片
    self.imageOutput = [[AVCapturePhotoOutput alloc] init];
    
    if ([self.captureSession canAddOutput:self.imageOutput]) {
        [self.captureSession addOutput:self.imageOutput];
    }

    //视频AVCaptureMovieFileOutput实例, QuickTime
    self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];
    if ([self.captureSession canAddOutput:self.movieOutput]) {
        [self.captureSession addOutput:self.movieOutput];
    }
    
    //视频队列
    self.videoQueue = dispatch_queue_create("glen.videoQueue", NULL);
    
    return YES;;
}

复制代码

配置完捕捉会话之后,就需要通过外界的按钮点击等操作来告诉AVFoundation来开启或停止捕捉会话。

/// 启动捕捉
- (void)startSession {
    if (![self.captureSession isRunning]) {
        dispatch_async(self.videoQueue, ^{
            [self.captureSession startRunning];
        });
    }
}
/// 停止捕捉
- (void)stopSession {
    if ([self.captureSession isRunning]) {
        dispatch_async(self.videoQueue, ^{
            [self.captureSession stopRunning];
        });
    }
}
复制代码

3.2、摄像头的切换

获取当前设备上可用的摄像头设备,并根据需求来获得指定的摄像头设备

/// 寻找指定摄像头
/// @param positon 指定摄像头设备
- (AVCaptureDevice *)cameraWithPositon:(AVCaptureDevicePosition)positon {
    
    AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera]
                                      mediaType:AVMediaTypeVideo
                                      position:AVCaptureDevicePositionUnspecified];
    //获取到所有设备
    NSArray *captureDevices = [captureDeviceDiscoverySession devices];
    //遍历设备
    for (AVCaptureDevice *device in captureDevices) {
        if (device.position == positon) {
            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 cameraWithPositon:AVCaptureDevicePositionFront];
        } else if ([self activeCamera].position == AVCaptureDevicePositionFront) {
            //前置变后置
            device = [self cameraWithPositon:AVCaptureDevicePositionBack];
        }
    }
    return device;;
}

复制代码

在进行切换之前,必须要知道其他的摄像头是否是一个可进行使用的状态

/// 是否能切换摄像头
- (BOOL)canSwitchCameras {
    return self.cameraCount > 1;
}
复制代码

接下来就是对摄像头进行切换

/// 切换摄像头
- (BOOL)switchCameras {
    
    //判断是否能切换
    if (![self canSwitchCameras]) {
        return NO;
    }
    
    //获取当前设备的反向设备(不活跃的摄像头)
    AVCaptureDevice *device = [self inactiveCamera];
    
    //将device添加进AVCaptureDeviceInput
    NSError *error;
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device 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 {
        //如果错误! 设备添加错误
        return NO;
    }
    return YES;
}
复制代码

3.3、聚焦

  1. 拿到当前设备
  2. 需要判断设备是否支持对焦等。
  3. 配置时不能让多个对象对他进行更改,所以锁定该设备
  4. 设置对焦点、对焦模式等。
  5. 解锁设备
/// 询问当前活跃的摄像头是否支持兴趣点对焦
- (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 {
            //设备错误
            
        }
    }
}

复制代码

3.4、曝光

  1. 拿到当前活跃设备
  2. 创建曝光mode
  3. 判断设备是否支持指定的模式
  4. 锁定该设备
  5. 设置曝光点、曝光模式
  6. 判断是否支持锁定曝光
    1. 支持则使用KVO去设置状态
    2. 监听回调,获取设备
    3. 判断是否支持曝光
    4. 移除观察者,修改设备的曝光模式
  7. 解锁设备
  8. 对外声称一个重设对焦曝光的接口
static const NSString *CameraAdjustingExposureContext;

/// 当前活跃摄像头是否支持曝光
- (BOOL)cameraSupportsTapToExpose {
    return [[self activeCamera] isExposurePointOfInterestSupported];
}

- (void)exposeAtPoint:(CGPoint)point {
    
    //获取活跃摄像头
    AVCaptureDevice *device = [self activeCamera];
    //设置根据场景曝光
    AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
    //活跃摄像头是否支持曝光 并且支持’根据场景曝光‘这个模式
    if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {

        //过程锁定
        NSError *error;
        if ([device lockForConfiguration:&error]) {
            //设备曝光点
            device.exposurePointOfInterest = point;
            //设置曝光模式
            device.exposureMode = exposureMode;
            
            //是否支持锁定曝光
            if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
                //使用kvo确定设备的adjustingExposure属性状态
                [device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&CameraAdjustingExposureContext];
            }
            
            //解锁
            [device unlockForConfiguration];

        }
        
    }
    
}

/// 观察者回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if (context == &CameraAdjustingExposureContext) {
        
        //获取设备
        AVCaptureDevice *device = (AVCaptureDevice *)object;
        //判断设备是否不再调整曝光等级,确认设备的exposureMode是否可以设置为AVCaptureExposureModeLocked
        if (!device.isExposurePointOfInterestSupported && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
            
            //移除作为adjustingExposure 的self,就不会得到后续变更的通知
            [object removeObserver:self forKeyPath:@"adjustingExposure" context:&CameraAdjustingExposureContext];

            //
            dispatch_async(dispatch_get_main_queue(), ^{
                if ([device lockForConfiguration:nil]) {
                    device.exposureMode = AVCaptureExposureModeLocked;
                    [device unlockForConfiguration];
                } else {
                    //设备错误回调
                }
            });
        } 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 isFocusPointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
    
    //回顾一下,捕捉设备空间左上角(0,0),右下角(1,1) 中心点则(0.5,0.5)
    CGPoint centPoint = CGPointMake(0.5f, 0.5f);
    
    NSError *error;
    
    //锁定设备,准备配置
    if ([device lockForConfiguration:&error]) {
        
        //焦点可设,则修改
        if (canResetFocus) {
            device.focusMode = focusMode;
            device.focusPointOfInterest = centPoint;
        }
        
        //曝光度可设,则设置为期望的曝光模式
        if (canResetExposure) {
            device.exposureMode = exposureMode;
            device.exposurePointOfInterest = centPoint;
            
        }
        
        //释放锁定
        [device unlockForConfiguration];
        
    }else
    {
        
        //设备错误回调
    }
    
}

复制代码

3.5、拍照

  1. 对外暴露拍照接口 captureStillImage
  2. 对图片输出设置setting和代理
  3. 通过代理获取到图片的data数据
  4. 将图片的data数据转化为UIImage
  5. 将UIImage通过PHPhotoLibrary保存进手机相册,并通知外部一个UIImage用作显示略缩图
#pragma mark - 拍照
- (void)captureStillImage {

    //捕捉到图片存储格式jpg
    NSDictionary *setDic = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};
    AVCapturePhotoSettings *outputSettings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];
    [self.imageOutput capturePhotoWithSettings:outputSettings delegate:self];

}

//代理方法
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error {

    //图片数据
    NSData *imageData = photo.fileDataRepresentation;
    UIImage *image = [[UIImage alloc] initWithData:imageData];
    
    //将图片写入到Library
    [self writeImageToAssetsLibrary:image];
}


/// 写入到相册
/// @param image 图片
- (void)writeImageToAssetsLibrary:(UIImage *)image {

    __block PHObjectPlaceholder *assetPlaceholder = nil;
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
        assetPlaceholder = changeRequest.placeholderForCreatedAsset;
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
        NSLog(@"OK");
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
            [nc postNotificationName:ThumbnailCreatedNotification object:image];
        });
    
    }];

}

复制代码
这篇关于iOS音视频(一) -- AVFoundation捕捉的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!