美文网首页
AVFoundation - 媒体捕捉

AVFoundation - 媒体捕捉

作者: chrisdev | 来源:发表于2018-07-25 15:52 被阅读0次

    一.  捕捉会话AVCaptureSession.

    AVCaptureSession用于连接输入和输出的资源. 捕捉会话管理从物理设备(比如: 摄像头, 麦克风)得到的数据流, 输出到一个或多个目的地. 可以动态的配置输入和输出线路, 让开发者能够在会话中重新配置捕捉环境.

    捕捉会话可以配置会话预设值(session preset), 用来控制捕捉数据的格式和质量. 会话设置默认: AVCaptureSessionPresetHigh

    二. 捕捉设备

    AVCaptureDevice用于访问系统的捕捉设备, 最常用的方法: defaultDeviceWithMediaType: 

    AVCaptureDeveice为摄像头, 麦克风等物理设备定义了接口, 这些设备内置于Mac, iPhone中, 也可能是外部数码相机或者摄像机等. AVCaptureDevice针对物理设备提供了大量的方法, 比如控制摄像头的对焦, 曝光, 白平衡或者闪光灯等.

    三. 捕捉设备的输入

    在使用捕捉设备进行处理前, 需要将其加入到会话中, 捕捉设备没法直接添加到AVCaptureSession中, 需要将他封装在AVCaptureDeviceInput实例中. AVCaptureDeviceInput对象在设备输出数据和捕捉会话间扮演桥接作用. AVCaptureDeviceInput的创建方式

    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

    四. 捕捉设备的输出

    AVFoundation定义了AVCaptureOutput的许多扩展类, AVCaptureOutput是一个抽象基类. 用于为捕捉会话得到的数据寻找输出目的地. 框架定义了一下这个类的高级扩展类.

    AVCaptureDeviceStillImageOutput: 捕捉静态图片.

    AVCaptureMovieFileOutput: 捕捉音频和视频数据

    底层的输出类: AVCaptureAudioDataOutput和AVCaptureVideoDataOutput, 使用它们可以直接访问硬件捕捉到数字样本. 使用底层的输出类可以对音频和视频进行实时处理.

    五. 捕捉连接

    AVCaptureConenction: 捕捉输入和输出的连接, 可以用来启用或者禁用给定输入或给定输出的数据流. 也可以使用连接来监听音频信道中的平均和峰值.

    六. 捕捉预览

    AVCaptureVideoPreviewLayer: 对捕捉数据进行实时预览, 类似于AVPlayerLayer, 不过针对摄像头的捕捉进行了定制. AVCaptureVideoPreviewLayer也支持视频重力的概念, 可以控制视频内容的缩放和拉伸效果.

    AVLayerGravityResizeAspect: 保持视频宽高比

    AVLayerGravityResizeAspectFill: 保持宽高比, 填满整个图层, 会造成视频裁剪

    AVLayerGravityResize: 填满图层, 视频变形.

    //捕捉会话的创建

    AVCaptureSession *session = [[AVCaptureSession alloc] init];

    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];    

    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];

    if ([session canAddInput:input]) {[session addInput:input];}

    AVCaptureStillImageOutput *output = [ [AVCaptureStillImageOutput alloc] init];

    if([session canAddOutput:output]) {[session addOutput:output];}

    七.知识点

    1. 坐标空间转换, 捕捉设备坐标系和屏幕坐标系不同左上角, 捕捉设备坐标系是基于摄像头传感器的本地设置, 水平方向不可旋转, 左上角为(0, 0), 右下角为(1, 1)

    2. AVCaptureVideoPreViewLayer提供了两个方法用于两个坐标系间进行转换. 

        captureDevicePointOfInterestForPoint: 获取屏幕坐标系point, 返回设备坐标系point

        pointForCaptureDeviceOfInterest: 获取摄像头坐标系, 返回屏幕坐标系

        点击对焦和点击曝光通常会用到转换坐标.

    3. 捕捉会话设置

    // CameraController

    - (Bool)setupSession {

            self.captureSession = [[AVCaptureSession alloc] init];        //创建捕捉会话

            AVCaptureDevice *videoDevice = [AVCaptureDevice deviceWithMediaType: AVMediaTypeVideo];    //得到系统默认捕捉设备指针, 返回手机后置摄像头

            AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice: videoDevice error: error];    

            if (videoInput && [self.captureSession canAddInput: videoInput]) {

                    [self.captureSession addInput: videoInput];

                    self.activeVideoInput = videoInput;

            } else {

                    return NO;

            }

            AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMeidaTypeAudio];        //创建捕捉设备的输入

            if (audioDevice && [self.captureSession canAddInput:audioInput]) {

                   [ self.captureSession addInput: audioInput]

            } else {

                    return NO;

            }

            self.imageOutput = [[AVCaptureStillImageOutput alloc] init];    //捕捉静态图片

            self.imageOutput.outputSettings = @{AVVideoCodeKey: VAVideoCodecJPEG};

            if ([self.captureSession canAddOutput: self.imageOutput]) {

                    [self.captureSession addOutput: self.imageOutput];

            }

            self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; //保存到文件系统

            if ([self.captureSession canAddOutput: self.movieOutput]) {

                    [self.captureSession addOutput: self.movieOutput];

            }

            self.videoQueue = dispatch_queue_create("com.vedioqueue", NULL);

            return YES;

    }

    4. 启动和停止会话, 使用捕捉会话之前首先要启动会话, 启动会话的第一步是启动数据流, 使他处于准备捕捉图片和视频的状态. a: 检查设备是否处于活跃状态, 如果没有准备好则调用startRunning方法, 这是一个同步调用会消耗一定的时间, 所以要异步方式在videoQueue排队调用. b: stopRunning停止系统中数据流, 也是一个同步调用, 所以也要采用异步方式调用.

    - (void)startSession { //a

            if (![self.captureSession isRunning]) {

                    dispatch_async(self.videoQueue, ^{

                            [self.captureSession startRunning];

                    });

            }

    }

    - (void)stopSession {    //b

            if ([self.captureSession isRunning]) {

                    dispatch_async(self.videoQueue, ^{

                            [self.captureSession stopRunning];

                    });

           }

    }

    5. 切换摄像头

    - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {

       NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMidaTypeVideo];

        for (AVCaptureDevice *device in devices) {

                if (device.position == position) {

                        return device;

                }

                return nil;

        }

    }

    - (AVCaputreDevice *)activeCamera {

        return self.activeVideoInput.device;

    }

    - (AVCAptureDevice *)inactiveCamera {

            AVCaptureDevice *device = nil;

            if (self.cameraCount > 1) {

                    if  ([self activeCamera].position == AVCaptureDevicePositionBack) {

                            device = [self cameraWithPosition: AVCaptureDevicePositionFront];

                    } else {

                            device = [self cameraWithPosition: AVCaptureDevicePositionBack];

                    }

            }

            return device;

    }

    - (BOOL)canSwitchCameras {

            return self.cameraCount > 1;

    }

    - (NSUInteger)cameraCount {

            return [[AVCaptureDevice deviceWithMediaType:AVMediaTypeVideo] count];

    }

    切换前置和后置摄像头需要重新配置捕捉会话, 幸运的是可以动态配置AVCaptureSession, 所以不必担心停止会话和重启会话带来的开销, 不过对会话进行的任何改变都要通过beginConfiguration和commitConfiguration进行单独的原子性的变化.

    - (void)switchCameras {

            if (![self canSwitchCameras]) {    return NO;    }

            NSError *error;

            AVCaptureDevice *videoDevice = [self inactiveCamera];

            AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice 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.activeInput];    //防止添加失败

                    }

                   //分批将所有变更整合在一起, 得到一个有关会话单独的原子的修改

                    [self.captureSession commitConfiguration];

            } else {

                    return NO;        //返回之前可以做一些处理

            }

            return YES;

    }

    6. 配置捕捉设备, AVCaptureDevice定义了很多方法可以让开发者控制摄像头,尤其是可以独立调整和锁定摄像头的焦距, 曝光和白平衡. 对焦和曝光还可以支持特定的兴趣点设置, 实现点击曝光和对焦的功能. AVCaptureDevice还可以控制设备的LED作为拍照的闪光灯或手电筒使用. 每当修改摄像头设备时, 一定要先检查该动作是否被设备支持, 否则会出现异常. 并不是所有摄像头都能支持所有的功能. 例如: 前置摄像头不支持对焦. 后置摄像头可以支持全尺寸对焦.

    AVCaptureDevice *device = ...

    if ([devcie isFocusModeSupported: AVCaptureFocusModeAutoFocus]) {

        //当配置修改可以支持时, 修改技巧为: 先锁定设备准备配置, 执行所需要的修改, 最后解锁配置

        if ([device lockForConfiguration: &error]) {

                device.focusMode = AVCaptureFocusModeAutoFocus;

                [device unlockForConfiguration];

        } else {

                //handle error

        }

    }

    7. 调整焦距, a: 询问摄像头是否支持兴趣点对焦 b: 传递一个point, 已经从屏幕坐标系转化为捕捉设备坐标. c: 是否支持兴趣点对焦并确认是否支持自动对焦模式, 这一模式会使用单独扫描的自动对焦.

    点击对焦的实现:

    - (BOOL)cameraSupportTapToFocus {     //a

            return [[self activeCamera] isFocusPointOfInterestSupported];

    }

    - (void)focusAtPoint:(CGPoint)point {    //b

            AVCaptureDevice *device = [self activeCamera];

            if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported: AVCaptureFocusModeAutoFocus]) {    //c

                    if ([device lockForConfiguration: &error]) {

                            device.focusPointOfInterest = point;

                            [device unlockForConfiguration];

                    } else {

                            //错误处理

                    }

            }

    }

    8. 点击曝光, a: 是否支持对兴趣点曝光. b: 判断设备是否支持锁定曝光模式, 如果支持使用KVO来确定设备adjustingExposure属性的状态, 观察该属性可以知道曝光何时调整完成, 让我们有机会在改点上锁定曝光. c: 判断设备不在调整曝光等级, 确认设备的exposureMode可以设置为AVCaptureExposueModeLocked.

    - (BOOL)cameraSupportsTapToExpose {    //a

            return [[self activeCamera] isExposurePointOfInterestSupported];

    }

    static const NSString *THCameraAdjustingExposureContext;

    - (void)exposeAtPoint:(CGPoint)point {

            AVCaptureDevice *device = [self activeCamera];

            AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;

            if (device.isExposurePointOfInterestSuppoted && [device isExposureModeSupported: exposureMode]) {    

                    if ([device lockForConfiguration:&error]) {

                            device.exposurePointOfInterest = point;

                            device.exposureMode = exposureMode;

                            if ([device isExposureModeSupported: AVCaptureExposureModelLocked]) {    //b

                                    [device addObserve: self forKeyPath: @"adjustingExposure" oprions: NSKeyValueObservingOptionNes context: &THCameraAdjustingExposureContext];

                            }

                            [device unlockForConfiguration];

                    }

            } else {

            // handle error        

            }

        }

    }

    - (void)observeValueForKeyPath:(NSSting *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

        if (context == &THCameraAdjustingExposureContext) {

            AVCaptureDevice *device = (AVCaptureDevice *)object;

            if (!device.isAjustingExposure && [device isExposureModeSupported: AVCaptureExposureModeLocked]) {    //c

                [object removeObserver: self forKeyPath: @"ajustingExposure" ...];

                dispatch_async(dispatch_get_main_queue(), ^{

                    if ([device lockForConfiguration: &error]) {

                        device.exposureMode = AVCaptureEXposureModeLocked;

                        [device unlockConfiguration];

                    } else {

                         //错误处理

                    }

                });

          } else {

            [super observeValueForKeyPath: keyPath ofObject:....];

        }

    }

    9. 重新设置对焦和曝光

    - (void)resetFocusAndExposureMode {

        AVCaptureDevice *device = [self activeCamera];

        AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;

        BOOL canResetFocus = [device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode];

        AVCaptureExpsureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;

        BOOL canResetExposure = [device isExposurePointOfInterestSuppported] && [device isFocusModeSupported:focusMode];

        CGPoint centerPoint = CGPointMake(0.5, 0.5);

        NSError *error;

        if ([device lockForConfiguration:&error]) {

            if (canResetFocus) {

                device.focusMode = focusMode;

                device.focusPointOfInterest = centerPoint;

            }

            if (canResetExposure) {

                device.exposureMode = exposureMode;

               device.exposurePointOfInterest = centerPoint;

            }

            [device unlockForConfiguration];

        } else {

            //错误处理

        }

    }

    10. 调整闪光灯和手电筒模式, AVCaptureDevice可以让开发者修改摄像头的闪光灯和手电筒模式, 设备后面的LED灯, 当拍摄静态图片时用作闪光灯, 当拍摄视频时用作连续的灯光(手电筒). 捕捉设备的flashMode和torchMode属性可以被设置为AVCapture(Torch|Flash)(On| Off | Auto), 3个值中的一个.

    - (BOOL)cameraHasFlash {

        return [[self activeCamera] hasFlash];

    }

    - (AVCaptureFlashMode)flashMode {

        return [[self activeCamera] flashMode];

    }

    - (void)setFlashMode:(AVCaptureFlashMode)flashMode {

        AVCaptureDevice *device = [self activeCamera];

        if ([device isFlashModeSupported:flashMode]) {

            if ([device lockForConfiguration: &error]) {

                device.flashMode = flashMode;

                [device unlockForConfiguration];

            } else {

                //错误处理

            }

        }

    }

    - (BOOL)cameraHasTorch {

        return [[self activeCamera] hasTorch];

    }

    - (AVCaptureTorchMode)torchMode {

        return [[self activeCamera] torchMode];

    }

    - (void)setTorchMode:(AVCaptureTorchMode)torchMode {

        AVCaptureDevice *device = [self activeCamera];

        if ([device isTorchModeSupported:torchMode]) {

            NSError *error;

            if ([device lockForConfiguration: &error]) {

                device.torchMode = torchMode;

                [device unlockForConfiguration];

            } else {

                //异常处理

            }

        }

    }

    11. 拍摄静态图片, 在setupSession方法的实现中, 我们将一个AVCaptureStillImageOutput实例添加到捕捉会话中, 这个是AVCaptureOutput子类, 用于捕捉静态图片. a: 获取AVCaptureStillImageOutput对象使用的当前AVCaptureConnection指针, 当查找AVCaptureStillImageOutput连接时一般会传递AVMediaTypeVideo媒体类型.

    - (void)captureStillImage {

        AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];    //a

        if (connection.isVideoOrientationSupported) {

            connection.videoOrientation = [self currentVideoOrientation];

        }

        id handler = ^(CMSampleBufferRef sampleBuffer, NSError *error) {

            if (sampleBuffer != NULL) {

                NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDatRepresentation:sampleBuffer];

                UIImage *image = [[UIImage alloc] initWithData:imageData];

            } else {

                //错误处理

             }

        };

        [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection  completionHandler:handler];

    }

    - (AVCaptureVideoOrientation)currentVideoOrientation {

        AVCaptureVideoOrientation orientation;

        switch ([UIDevice currentDevice].orientation) {

            case UIDeviceOrientationPortrait: 

                        orientation = UIDeviceOrientationPortrait;

                        break;

            case  UIDeviceOrientationLandscapeRight:

                        orientation =  UIDeviceOrientationLandscapeLeft;

                        break;

            case UIDeviceOrientationUpsideDown:

                        orientation = UIDeviceOrientationUpsideDown;              

                        break;

            default:

                    orientation = UIDeviceOrientationLandscapeRight;

                    break;

        }

        return orientation;

    }

    12. 使用Assets Library框架, Assets Library可以让开发者管理用户的相册和视频库.

    AVAuthorizationStatus status = [ALAssetLibrary authorizationStatus];

    if (status == AVAuthorizationStatusDenied) {    

        //without access

    } else {   

         //perform authorized access to the library

    }

    - (void)writeImageToAssetsLibrary:(UIImage *)image {    //将图片写到本地

        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

        [library writeImageToSavePhotosAlbum:image.CGImage orientation:image.orientation completionHander:^(NSURL *assetURL, NSError *error){               

          }];

    }

    13. 视频捕捉, AVCaptureMovieFileOutput开始录制时, 会在头文件前面写入一个最小化的头信息, 随着录制的进行, 片段会按照一定的周期写入. 创建完整的头信息. 间隔可以通过修改捕捉输出的movieFragmentInterval属性来改变. a: 判断AVCaptureMovieFileOutput状态. b: 视频稳定, 支持稳定可以显著提高捕捉到视频的质量. c:摄像头平滑对焦模式, 减慢摄像头对焦的速度. 通常情况下用户移动摄像头会尝试快速自动对焦, 这会在捕捉的视频中出现脉冲式效果, 平滑对焦会降低对焦速度, 从而提供更加自然的录制效果. 

    - (BOOL)isRecording {    //a

        return self.movieOutput.isRecording;

    }

    - (void)startRecording {

        if (![self isRecording]) {

            AVCaptureConnection *videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];

            if ([videoConnection isVideoOrientationSupported]) {

                videoConnection.videoOrientation = self.currentVideoOrientation;

            }

            if ([videoConnection isVideoStabilizationSupported]) {    //b

                videoConnection.enablesVideoStabilizationWhenAvailable = YES;

            }

            AVCaptureDevice *device = [self activeCamera];

            if (device.isSmoothAutoFocusSupported) {    //c

                NSError *error;

                if ([device lockForConfiguration: &error]) {

                       device.smoothAutoFocusEnabled = YES;          

                        [device unlockForConfiguration];

                } else {

                        //错误处理

                }

            }

            self.outputURL = [self uniqueURL];

            [self.movieOutput startRecordingToOutputFileURL:self.outputURL recordingDelegate:self];

        }

    }

    - (NSURL *)uniqueURL {

        NSFileManager *fileManager = [NSFileManager defaultManager];

        NSString *dirPath = [fileManager temporaryDirectoryWithTemplateString:@"temp"];

        if (dirPath) {

            NSString *filePath = [dirPath stringByAppendingPatchComponent:@"1.mov"];

            return [NSURL fileURLWithPath:filePath];

        }    

        return nil;

    }

    - (void)stopRecording {

        if ([self isRecording]) {

            [self.movieOutput stopRecording];

        }

    }

    14. 实现AVCaptureFileOutputRecordingDelegate协议, a:向资源库写入前检查视频是否可以被写入.  b:根据视频的宽高比设置图片高度, 还需要设置appliesPreferredTrackTransform为YES, 这样捕捉缩略图时会考虑视频的变化.

    - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {

        if (error) {

            [self.delegate mediaCaptureFailedWithError:error];

        } else {

            [self writeVideoToAssetsLibrary:[self.outputURL copy]];

        }

        self.outURL = nil;

    }

    - (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {

        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

        if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) {    //a

            ALAssetsLibraryWriteVideoCompletionBlock completionBlock;

            completionBlock = ^(){

                if (error) {

                    [self.delegate assetLibraryWriteFailedWithError:error];

                } else {

                    [self generateThumbnailForVideoAtURL:videoURL];

                }

            };

                        [library writeVideoAtPathToSavePhotosAlbum:videoURL completionBlock:completionBlock];

        }

    }

    - (void)generateThumbnailForVideoAtURL:(NSURL *)videoURL {

        dispatch_async(self.videoQueue, ^{

            AVAsset *asset = [AVAsset assetWithURL:videoURL];

            AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];

            imageGenerator.maximumSize = CGSizeMake(100.0f, 0.0f);    //b       

            imageGenerator.appliesPreferredTrackTransform = YES;

            CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error: nil];

            UIImage *image = [UIImage imageWithCGImage:imageRef];

            CGImageRelease(imageRef);

            dispatch_async(dispatch_get_main_queue(), ^{

                //...

            });

        });

    }

    相关文章

      网友评论

          本文标题:AVFoundation - 媒体捕捉

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