iOS AVCaptureSession + AVCaptureMovieFileOutput 录制视频和拍照

545 阅读3分钟

大神链接

利用 AVCaptureSession和AVCaptureMovieFileOutput得到一个视频文件或照片

  • 流程
1. 创建捕捉会话
2. 设置视频的输入
3. 设置音频的输入
4. 配置输出
5. 添加视频预览层
6. 开始采集数据,这个时候还没有写入数据,用户点击录制后就可以开始写入数据
  • 先导入 AVFoundation
#import <AVFoundation/AVFoundation.h>
  • 摄像机权限
-(void)authorizationForVideo
{
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    switch (status) {
        case AVAuthorizationStatusNotDetermined: // 未授权
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if (granted) {
                    NSLog(@"相机授权成功");
                } else {
                    NSLog(@"相机授权失败");
                }
            }];
            break;
        case AVAuthorizationStatusRestricted:
            NSLog(@"AVAuthorizationStatusRestricted");
            break;
        case AVAuthorizationStatusDenied:
            NSLog(@"AVAuthorizationStatusDenied");
            break;
        case AVAuthorizationStatusAuthorized:
            NSLog(@"AVAuthorizationStatusAuthorized");
            break;
        default:
            NSLog(@"Video Unknow");
            break;
    }
}
  • 检查麦克风权限
-(void)authorizationForAudio
{
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
    switch (status) {
        case AVAuthorizationStatusNotDetermined:
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
                if (granted) {
                    NSLog(@"麦克风授权成功");
                } else {
                    NSLog(@"麦克风授权失败");
                }
            }];
            break;
        case AVAuthorizationStatusRestricted:
            NSLog(@"AVAuthorizationStatusRestricted");
            break;
        case AVAuthorizationStatusDenied:
            NSLog(@"AVAuthorizationStatusDenied");
            break;
        case AVAuthorizationStatusAuthorized:
            NSLog(@"AVAuthorizationStatusAuthorized");
            break;
        default:
            NSLog(@"Audio Unknow");
            break;
    }
}
1. 创建捕捉会话
-(void)configCaptureSession
{
    self.session = [[AVCaptureSession alloc] init];
    if ([self.session canSetSessionPreset:AVCaptureSessionPresetHigh]) { // 设置分辨率
        [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    }
}
2. 设置视频的输入
-(void)configVideoInput
{
    NSError *error;
    // 添加视频捕捉设备
    // AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 默认获取后置摄像头
    AVCaptureDevice *captureDevice = [self getCameraPosition:AVCaptureDevicePositionBack];
    // 将捕捉设备转化为 AVCaptureDeviceInput
    // 不能直接使用AVCaptureDevice,必须将AVCaptureDevice转化为AVCaptureDeviceInput
    self.videoInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
    // 将捕捉设备添加给会话
    if (self.videoInput && [self.session canAddInput:self.videoInput]) {
        [self.session addInput:self.videoInput];
    }
}
  • 移除视频输入设备
-(void)removeVideoDeviceInput
{
    if (self.videoInput) [self.session removeInput:self.videoInput];
    self.videoInput = nil;
}
  • 获取摄像头
-(AVCaptureDevice *)getCameraPosition:(AVCaptureDevicePosition)position
{
    /*
     AVCaptureDeviceTypeBuiltInWideAngleCamera 广角(默认设备,28mm左右焦段)
     AVCaptureDeviceTypeBuiltInTelephotoCamera 长焦(默认设备的2x或3x,只能使用AVCaptureDeviceDiscoverySession获取)
     AVCaptureDeviceTypeBuiltInUltraWideCamera 超广角(默认设备的0.5x,只能使用AVCaptureDeviceDiscoverySession获取)
     AVCaptureDeviceTypeBuiltInDualCamera (一个广角一个长焦(iPhone7P,iPhoneX),可以自动切换摄像头,只能使用AVCaptureDeviceDiscoverySession获取)
     AVCaptureDeviceTypeBuiltInDualWideCamera (一个超广一个广角(iPhone12 iPhone13),可以自动切换摄像头,只能使用AVCaptureDeviceDiscoverySession获取)
     AVCaptureDeviceTypeBuiltInTripleCamera (超广,广角,长焦三摄像头,iPhone11ProMax iPhone12ProMax iPhone13ProMax,可以自动切换摄像头,只能使用AVCaptureDeviceDiscoverySession获取)
     AVCaptureDeviceTypeBuiltInTrueDepthCamera (红外和摄像头, iPhone12ProMax iPhone13ProMax )
     */
    NSArray *deviceTypes;
    if (position == AVCaptureDevicePositionBack) {
        deviceTypes = @[
            AVCaptureDeviceTypeBuiltInDualCamera,
            AVCaptureDeviceTypeBuiltInDualWideCamera,
            AVCaptureDeviceTypeBuiltInTripleCamera
        ];
    } else {
        deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
    }
    AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
    if (deviceSession.devices.count) return deviceSession.devices.firstObject;
    
    if (position == AVCaptureDevicePositionBack) { // 非多摄手机
        deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
        AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
        if (deviceSession.devices.count) return deviceSession.devices.firstObject;
    }
    return nil;
}
3. 设置音频的输入
-(void)configAudioInput
{
    NSError *error;
    // 添加音频捕捉设备 ,如果只是拍摄静态图片,可以不用设置
    AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    self.audioInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
    if (self.audioInput && [self.session canAddInput:self.audioInput]) {
        [self.session addInput:self.audioInput];
    }
}
  • 移除音频输入设备
-(void)removeAudioDeviceInput
{
    if (self.audioInput) [self.session removeInput:self.audioInput];
    self.audioInput = nil;
}
4. 配置输出
-(void)configMoiveFileOutput
{
    self.moiveOutput = [[AVCaptureMovieFileOutput alloc] init];
    if ([self.session canAddOutput:self.moiveOutput]) {
        [self.session addOutput:self.moiveOutput];
    }
}
  • 移除视频输出
-(void)removeMoiveOutput
{
    if (self.moiveOutput) [self.session removeOutput:self.moiveOutput];
    self.moiveOutput = nil;
}
5. 预览层
-(void)configPreviewLayer
{
    UIView *mainPreView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 50.0, [UIScreen mainScreen].bounds.size.width, 400.0)];
    mainPreView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:mainPreView];
    
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] init];
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    self.previewLayer.session = self.session;
    self.previewLayer.frame = CGRectMake(0.0, 0.0, [UIScreen mainScreen].bounds.size.width, 400.0);
    [mainPreView.layer addSublayer:self.previewLayer];
}
6. 开始会话 一定要是异步
-(void)startSessionAction
{
    // 检查是否运行状态
    if (![self.session isRunning]) {
        __weak typeof (self) weakSelf = self;
        dispatch_async(self.captureSession, ^{
            [weakSelf.session startRunning];
        });
    }
}
  • 结束会话
-(void)stopSessionAction
{
    if ([self.session isRunning]) {
        __weak typeof (self) weakSelf = self;
        dispatch_async(self.captureSession, ^{
            [weakSelf.session stopRunning];
        });
    }
}
开始录制视频配置相关
// 1. 创建捕捉会话
[self configCaptureSession];
// 2. 设置视频的输入
[self configVideoInput];
// 3. 设置音频的输入
[self configAudioInput];
// 4. 输出源设置
[self configMoiveFileOutput];
// 5. 预览层
[self configPreviewLayer];
  • 开始录像
-(void)startVideoAction
{
    NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *videoDirectoryPath = [documentDirectory stringByAppendingPathComponent:@"/Video"];
    NSString *videoTemPath = [videoDirectoryPath stringByAppendingPathComponent:@"temp_video.mov"];
    NSURL *url = [NSURL fileURLWithPath:videoTemPath];
    [self.moiveOutput startRecordingToOutputFileURL:url recordingDelegate:self];
}
  • 结束录像
-(void)stopVideoAction
{
    [self.moiveOutput stopRecording];
}
  • AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(nullable NSError *)error
{
    NSLog(@"error -%@",error.localizedDescription);
}

结束录像后音频文件就保存在沙盒目录下的Video的temp_video.mov

拍照相关
  • 拍照不需要音频因此不需要配置音频输入设备
// 1. 创建捕捉会话
[self configCaptureSession];
// 2. 设置视频的输入
[self configVideoInput];
// 4. 输出源设置
[self configPhotoOutput];
// 5. 预览层
[self configPreviewLayer];
  • 静态图片输出配置
-(void)configPhotoOutput
{
    self.photoOutput = [[AVCapturePhotoOutput alloc] init];
    NSDictionary *settings = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};
    AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:settings];
    [self.photoOutput setPhotoSettingsForSceneMonitoring:photoSettings];
    if (self.photoOutput && [self.session canAddOutput:self.photoOutput]) {
        [self.session addOutput:self.photoOutput];
    }
}

// 移除静态图片输出
-(void)removePhotoOutput
{
    if (self.photoOutput) [self.session removeOutput:self.photoOutput];
    self.photoOutput = nil;
}
  • 点击拍照
-(void)takePhotoAction
{
    // 每次拍照一定要重新设置 AVCapturePhotoSettings , 不能复用
    NSDictionary *settings = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};
    AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:settings];
    [self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
}
  • AVCapturePhotoCaptureDelegate 保存到相册
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error
{
    NSLog(@"didFinishProcessingPhoto");
    if (error != nil) {
        NSLog(@"拍照出错 - %@",error.localizedDescription);
        return;
    }
    
    NSData *imageData = photo.fileDataRepresentation;
    UIImage *image = [UIImage imageWithData:imageData];
    
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [PHAssetChangeRequest creationRequestForAssetFromImage:image];
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
        if (success == NO) {
            NSLog(@"保存图片出错 - %@",error.localizedDescription);
        } else {
            NSLog(@"成功保存图片");
        }
    }];
}