Still and Video Media Caputre(静态

2018-03-01  本文已影响45人  edison0428

为了管理从设备(比如相机 麦克风)获取到的捕捉,你应该创建对象去代表输入和输出,并且利用的AVCaptureSession的实例对象去协调这些输入输出设备的数据流
所以你至少得实现如下几步

捕获会话中的捕获输入和捕获输出之间的连接由AVCaptureSession对象表示

捕获输入(AVCaptureInput)有一个或者多个捕获端口(AVCaptureInputPort),捕获输出(AVCaptureOutput)可以从一个或者几个来源接受数据(比如AVCaptureMovieFileOutput可以同时接受视频和音频数据

4C1A2270-02B7-4AF9-8EA0-FC4764A76EB7.png

关于AVCaptureInputPort,其实没大弄明白

  //获取视频输入设备 如果是类型为AVMediaTypeVideo 则默认返回内置相机的后置摄像头
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    //为捕捉设备添加输入 不能添加到AVCapture中 必须通过将它封装到一个AVCaptureDeviceInput实例中 这个对象在设备输出数据和捕捉hui
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    
    NSLog(@"输入设备1的port 个数 :%d",videoInput.ports.count);
    for (AVCaptureInputPort * port in videoInput.ports) {
        NSLog(@"==%@",port.mediaType);
    }

参考苹果的图,一个后置摄像头作为视频输入设备,有三个port,打印出的这三个,应该就是视频 音频 元数据吧(没真正弄明白)

2018-03-01 11:32:23.160801+0800 AVFoundation_Capture[1381:310194] 输入设备1的port 个数 :3
2018-03-01 11:32:23.160946+0800 AVFoundation_Capture[1381:310194] ==vide
2018-03-01 11:32:23.161016+0800 AVFoundation_Capture[1381:310194] ==mobj
2018-03-01 11:32:23.161078+0800 AVFoundation_Capture[1381:310194] ==meta

当你把Input和Output加入到捕获会话中时,会话在所有兼容的捕捉输入端口和捕捉输出之间形成连接(看清楚是“捕捉输入端口和捕捉输出之间形成连接”)

Use a Capture Session to Coordinate Data Flow(使用捕获会话来协调数据流)

AVCaptureSession对象是用于管理数据捕获的中心协调对象。您使用一个实例来协调从AV输入设备到输出的数据流。您将您想要的捕获设备和输出添加到会话中,然后通过发送一个startRunning消息来启动数据流,并通过发送一个stopRunning消息来停止数据流

_captureSession = [[AVCaptureSession alloc]init];
// add inputs and outputs

 AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
   
 AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    
 if (videoInput) {
     if ([_captureSession canAddInput:videoInput]){
          [_captureSession addInput:videoInput];
          _deviceInput = videoInput;
     }
 }
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];

if ([_captureSession canAddInput:audioInput]){
    [_captureSession addInput:audioInput];
    _audioInput = audioInput;
} 
if (!_captureSession.isRunning){
        //使用同步调用会损耗一定的时间,则需要异步的方式处理
        dispatch_async(_videoQueue, ^{
            [_captureSession startRunning];
        });
 }
1.Configuring a Session(配置捕获会话)

您可以在会话中使用预设值来指定您想要的图像质量和分辨率。一个预设值是一个常数,用来确定若干可能的配置之一;在某些情况下,实际的配置是特定于设备的

截取官网图一张,解释已经很清楚了,就不一一说明


912FD3D5-F606-4E23-847B-5CCF947500F8.png

如果你想要设定的配置,应该先看设备支持与否,操作方法如下

 if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
        [_captureSession setSessionPreset:AVCaptureSessionPreset1280x720];
    }
    else{
        [_captureSession setSessionPreset:AVCaptureSessionPresetMedium];
    }

如果你想要在捕捉会话运行期间(startRunning的之后),对捕获会话做出一些改变比如(改变捕获会话的sessionPreset,改变输入设备输出设备(比如切换前后置摄像头)),那么应该把这些改变的代码放在beginConfigurationcommitConfiguration的方法中间以确保设备的改变是作为一个组的改变在发生改变,再beginConfiguration之后你可以添加或者删除输出,更改sessionPreset属性或者配置单个捕获输入或者输出属性,在调用commitConfiguration之前,实际上并没有进行任何更改,在这个期间它们一起被应用了

作者自己遇到过一种情况,就是录制视频的期间需要切换前后摄像头,

/**
 切换摄像头的方法
 */
-(void)switchCamersType{
    if ([self canSwitchCameraType]) {
        
        NSError * error;
        //获取到没在活跃状态的摄像头
        AVCaptureDevice * videoDevice = [self inavtiveCameraDevice];
        AVCaptureDeviceInput * deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
        if (videoDevice) {
            [_captureSession beginConfiguration];
            [_captureSession removeInput:_deviceInput];
            if ([_captureSession canAddInput:deviceInput]) {
                [_captureSession addInput:deviceInput];
                _deviceInput=deviceInput;
            }
            [_captureSession commitConfiguration];
            
            //切换摄像头后,原来的视频输出会无效 所以需要把原来的视频输出删除 在重新加一个新的视频输出进去
            [_captureSession beginConfiguration];
            [_captureSession removeOutput:_videoOutput];
            
            dispatch_queue_t queue = dispatch_queue_create("com.edison.captureQueue", DISPATCH_QUEUE_SERIAL);
            AVCaptureVideoDataOutput * videoOut = [[AVCaptureVideoDataOutput alloc] init];
            [videoOut setAlwaysDiscardsLateVideoFrames:YES];
            [videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}];
            [videoOut setSampleBufferDelegate:self queue:queue];
            if ([_captureSession canAddOutput:videoOut]) {
                [_captureSession addOutput:videoOut];
                _videoOutput = videoOut;
            }
            else{
                NSLog(@"11111");
            }
            _videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];
            _videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
            [_captureSession commitConfiguration];
        }
        
    }
    else{
        //显示提示信息
    }
}

####### 2.Monitoring Capture Session State(监视捕获会话的状态)
关于捕获会话的状态和错误产生,我们都可以注册通知去监听

//session运行期间发生错误
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSessionState:) name:AVCaptureSessionRuntimeErrorNotification object:nil];
    //session开始运行的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSessionStartRunning:) name:AVCaptureSessionDidStartRunningNotification object:nil];
    //session停止运行的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSessionStopRunning:) name:AVCaptureSessionDidStopRunningNotification object:nil];
    //session被打断的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSessionInterrpute:) name:AVCaptureSessionWasInterruptedNotification object:nil];

当然如果你想查询捕获会话的状态,也可以查询属性得知

_captureSession.interrupted
_captureSession.running

An AVCaptureDeivce Object Represents an Input Device(一个AVCaptureDeivce对象表示输入设备)

AVCaptureDeivce对象抽象了一个物理捕获设备,该设备将输入数据(如音频或视频)提供给AVCaptureSession对象,每一个输入设备都是一个AVCaptureDeivce对象,比如两个视频输入:一个前置摄像头,一个后置摄像头,音频输入:麦克风

您可以使用AVCaptureDevice类方法设备和devicesWithMediaType找到当前可用的捕获设备。而且,如果有必要,你可以找到iPhone、iPad或iPod提供的功能(参见设备捕捉设置)。不过,可用设备的列表可能会改变。当前的输入设备可能会变得不可用(如果它们被另一个应用程序使用),并且新的输入设备可能会变得可用,(如果它们被另一个应用程序所放弃)。你应该注册接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification通知时要提醒可用设备列表的变化

/*
    // 获取视频所有的输入设备 包括前后摄像头 外接设备  你可以选择以个输入设备作为当前的输入
    NSArray * videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice * device in videoDevices) {
        NSLog(@"localizedName=%@ connected=%d uniqueID=%@",device.localizedName,device.connected,device.uniqueID);

         2018-02-09 15:09:24.534553+0800 AVFoundation_Capture[871:210113] localizedName=Back Camera 1
         2018-02-09 15:09:24.534973+0800 AVFoundation_Capture[871:210113] localizedName=Front Camera 1
    }
    */
    
    //获取视频输入设备 如果是类型为AVMediaTypeVideo 则默认返回内置相机的后置摄像头
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    //为捕捉设备添加输入 不能添加到AVCapture中 必须通过将它封装到一个AVCaptureDeviceInput实例中 这个对象在设备输出数据和捕捉hui
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    
  
    //判断videoInput是否有效
    if (videoInput) {
        //判断是否能被添加到会话中去
        if ([_captureSession canAddInput:videoInput]){
            //把videoInput添加到会话当中去
            [_captureSession addInput:videoInput];
            _deviceInput = videoInput;
        }
    }
    
    /*
    // 获取音频所有的输入设备 包括内置麦克风  你可以选择以个输入设备作为当前的输入
    NSArray * audioDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
    for (AVCaptureDevice * device in audioDevices) {
        NSLog(@"localizedName=%@ connected=%d uniqueID=%@",device.localizedName,device.connected,device.uniqueID);
        
        //localizedName=iPhone 麦克风 connected=1 uniqueID=com.apple.avfoundation.avcapturedevice.built-in_audio:0
    }
    */
    
    // 音频输入 跟视频类似的处理方式
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
    NSLog(@"输入设备2的port 个数 :%d",audioInput.ports.count);
    if ([_captureSession canAddInput:audioInput]){
        [_captureSession addInput:audioInput];
        _audioInput = audioInput;
    }

而关于刷新可用设备的通知AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification我觉得好像暂时不需要,一般app不用也可以

1.Device Characteristics(设备的特征属性)

你可以访问一个设备的不同特性,你也可以查询它是否提供了一个特定的媒体类型(AVMediaType),也可以查询它是否提供一个给定捕捉会话支持的预设,可以获取它对应的本地化名称

BOOL isSupportAudioType=[videoDevice hasMediaType:AVMediaTypeAudio];
BOOL isSupportSessionPresetHigh=[videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPresetHigh];
NSString * localName = videoDevice.localizedName;
//audioDevice.position
NSLog(@"isSupportAudioType=%d isSupportSessionPresetHigh=%d localName=%@ position=%d",isSupportAudioType,isSupportSessionPresetHigh,localName,videoDevice.position);
//isSupportAudioType=0 isSupportSessionPresetHigh=1 localName=Back Camera position=1
2.Device Capture Settings(设备的捕获设置)

不同的设备有不同的功能,比如,一些支持不同的对焦或闪光灯模式,一些支持支持点击对焦

如下代码片段展示了如果找到具有手电筒模式并支持给定捕捉会话预设的视频输入设备

NSArray * videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice * device in videoDevices) {
     if ([device hasTorch] && [device supportsAVCaptureSessionPreset:AVCaptureSessionPresetHigh]) {
            NSLog(@"localizedName=%@ connected=%d ",device.localizedName,device.connected);
            //localizedName=Back Camera connected=1 
          //后置摄像头
        }
 }

聚焦点和曝光点是相互排斥的,焦点模式和曝光模式也是相互排斥的

2.1 Focus Modes(焦点模式)

这里有三种焦点模式

你可以用isFocusModeSupported:判断设备是否支持焦点模式,如果支持的话就可以设置焦点模式了focusMode
查询一个设备是否支持点击聚焦,判断方法是focusPointOfInterestSupported,如果支持你就可以设置焦点了focusPointOfInterest,如果你设置focusPointOfInterest的值为{0,0}表示左上{1,1}表示右下, landscape和portrait模式通用

-(id)setFocusOnPoint:(CGPoint)point{
    AVCaptureDevice * device = [self activeCameraDeivce];
    //当前活跃相机是否支持 按点对焦 和是否支持 自动对焦模式
    if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
        NSError * error = nil;
        //指示设备是否已成功锁定配置
        /*
         为了在AVCaptureDevice上设置硬件属性,例如focusMode和exposureMode,客户端必须首先获取设备上的锁。如果需要设置设备属性保持不变,则客户端应该只保留设备锁。不必要地持有设备锁可能会降低其他共享该设备的应用程序的捕获质量
         */
        if ([device lockForConfiguration:&error]) {
            device.focusPointOfInterest = point;
            device.focusMode = AVCaptureFocusModeAutoFocus;
            [device unlockForConfiguration];
        }
        //NSLog(@"error=%@",error.description);
        return error;
    }
    return nil;
}
2.2 Exposure Modes(曝光模式)

这里有两种曝光模式

你可以用isExposureModeSupported:判断设备是否支持曝光模式,如果支持的话就可以设置曝光模式了exposureMode

查询一个设备是否支持点击曝光,判断方法是exposurePointOfInterestSupported,如果支持你就可以设置焦点了exposurePointOfInterest,如果你设置focusPointOfInterest的值为{0,0}表示左上{1,1}表示右下, landscape和portrait模式通用

-(id)setExposureOnPoint:(CGPoint)point{
    AVCaptureDevice * device = [self activeCameraDeivce];
    //当前活跃相机是否支持 按点曝光 和是否支持 自动曝光模式
    if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
        NSError * error = nil;
        //指示设备是否已成功锁定配置
        /*
         为了在AVCaptureDevice上设置硬件属性,例如focusMode和exposureMode,客户端必须首先获取设备上的锁。如果需要设置设备属性保持不变,则客户端应该只保留设备锁。不必要地持有设备锁可能会降低其他共享该设备的应用程序的捕获质量
         */
        if ([device lockForConfiguration:&error]) {
            device.exposurePointOfInterest = point;
            device.exposureMode = AVCaptureExposureModeAutoExpose;
            [device unlockForConfiguration];
        }
        //NSLog(@"error=%@",error.description);
        return error;
    }
    return nil;
}
2.3 Flash Modes(闪光模式)

这里有三种闪光灯模式

你可以用hasFlash来判断设备是否有闪光功能,用isFlashModeSupported方法支持特定的闪光模式,然后再去设置闪光模式flashMode

-(id)setFlashMode:(AVCaptureFlashMode)flashMode{
    
    AVCaptureDevice * device = [self activeCameraDeivce];
    if ([device hasFlash] && [device isFlashModeSupported:flashMode]) {
        NSError * error = nil;
        if ([device lockForConfiguration:&error]) {
            //如果手电筒开启 先关闭
            if (device.torchMode == AVCaptureTorchModeOn) {
                device.torchMode = AVCaptureFlashModeOff;
            }
            device.flashMode = flashMode;
            [device unlockForConfiguration];
        }
        NSLog(@"error=%@",error.description);
        return error;
    }
    return nil;
}
2.4 Torch Mode(手电筒模式)

在手电筒模式下,闪光灯在低功率下连续开启,以照亮视频捕捉(这是苹果官方的解释)

但是作者参考系统相机和其它一些资料上的整理理解为:开启闪光灯最好先关闭手电筒,开启手电筒最好先关闭闪光灯

这里有三种手电筒模式

你可以用hasTorch来判断设备是否有手电筒功能,用isTorchModeSupported方法支持特定的手电筒模式,然后再去设置手电筒模式torchMode

2.5 Video Stabilization(视频稳定 即防抖)

视频防抖只对视频的connection有效,也就是说它是AVCaptureConnection的属性

 _videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];
    //设备的connection是否支持防抖
if (_videoConnection.isVideoStabilizationSupported) {
        _videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
2.6 White Balance(白平衡)

这里有两种白平衡的模式

-(id)setWhiteBalanceMode:(AVCaptureWhiteBalanceMode)whiteBalanceMode{
    
    AVCaptureDevice * device = [self activeCameraDeivce];
    if ([device isWhiteBalanceModeSupported:whiteBalanceMode]) {
        NSError * error = nil;
        if ([device lockForConfiguration:&error]) {
           
            device.whiteBalanceMode = whiteBalanceMode;
            [device unlockForConfiguration];
        }
        NSLog(@"error=%@",error.description);
        return error;
    }
    return nil;
}
2.7 Setting Device Orientation(设置设备的方向)

你可以在AVCaptureConnection上设置所需要的方向,以指定如何将面向AVCaptureOutput(AVCaptureMovieFileOutput,AVCaptureStillImageOutput,AVCaptureVideoDataOutput)的显示

if ([_videoConnection isVideoOrientationSupported]) {
       _videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
}
2.8 Configuring a Device(配置设备)

要在设备上设置捕获属性,必须首先使用lockForConfiguration获取设备上的锁:。这避免了可能与其他应用程序中的设置不兼容的更改。下面的代码片段演示了如何在设备上首先确定是否支持模式,然后尝试锁定设备进行重新配置,从而在设备上更改焦点模式。只有在获得锁后,焦点模式才会改变,并且锁在之后立即释放

并不是太明白这个锁的概念,官方这么推荐就这么写吧

以上设置白平衡 聚焦 曝光都是这么操作

-(id)setExposureOnPoint:(CGPoint)point{
    AVCaptureDevice * device = [self activeCameraDeivce];
    //当前活跃相机是否支持 按点曝光 和是否支持 自动曝光模式
    if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
        NSError * error = nil;
        //指示设备是否已成功锁定配置
        /*
         为了在AVCaptureDevice上设置硬件属性,例如focusMode和exposureMode,客户端必须首先获取设备上的锁。如果需要设置设备属性保持不变,则客户端应该只保留设备锁。不必要地持有设备锁可能会降低其他共享该设备的应用程序的捕获质量
         */
        if ([device lockForConfiguration:&error]) {
            device.exposurePointOfInterest = point;
            device.exposureMode = AVCaptureExposureModeAutoExpose;
            [device unlockForConfiguration];
        }
        //NSLog(@"error=%@",error.description);
        return error;
    }
    return nil;
}
2.8 Switching Between Devices(在设备之间切换)

有些需求是你允许用户在输入设备之间切换,比如切换前后置摄像头,为了避免停顿卡顿,你可以在捕获会话运行期间重新配置会话,你应该在把你的重新配置代码放在beginConfigurationcommitConfiguration之间,当最外层commitConfiguration执行时

AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
 
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];

[session commitConfiguration];

Use Capture Inputs to Add a Capture Device to a Session

添加一个设备(AVCaptureDevice)到AVCaptureSession中去,不能直接添加,得用AVCaptureInput

//获取视频输入设备 如果是类型为AVMediaTypeVideo 则默认返回内置相机的后置摄像头
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    //为捕捉设备添加输入 不能添加到AVCapture中 必须通过将它封装到一个AVCaptureDeviceInput实例中 这个对象在设备输出数据和捕捉hui
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    //判断videoInput是否有效
    if (videoInput) {
        //判断是否能被添加到会话中去
        if ([_captureSession canAddInput:videoInput]){
            //把videoInput添加到会话当中去
            [_captureSession addInput:videoInput];
            _deviceInput = videoInput;
        }
    }

Use Capture Output to Get Output from a Session(使用捕获输出从捕获会话中获取输出)

为了从捕获会话中获取输出,你可以添加一个或者多个输出(AVCaptureOutput具体子类的实例)
如下

4.1 Saving to a Moving File (AVCaptureMovieFileOutput)直接保存为视频文件

使用AVCaptureMovieFileOutput对象将捕获的数据直接保存到视频文件(AVCaptureMovieFileOutputAVCaptureFileOutput的具体子类,AVCaptureFileOutputAVCaptureOutput的具体子类)
AVCaptureFileOutput定义了很多基本的行为,你可以配置视频文件输出的各个方面,比如录制的最长持续时间,比如录制的文件的最大size

AVCaptureFileOutputiOS上不能使用暂停录制和恢复录制MAC上可以使用

分辨率和输出比特率取决于捕捉会话的sessionpreset.视频编码通常都是H264.音频编码通常都是AAC.实际值要因设备而异

/ 视频输出
    //初始化输出对象 用于获得数据
    _movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    _movieFileOutput.maxRecordedDuration = CMTimeMake(10, 1);
    AVCaptureConnection * movieFileConnection = [_movieFileOutput connectionWithMediaType:AVMediaTypeVideo];

设置录制的最长时间代码如上
先来看maxRecordedDuration

此属性指定记录文件持续时间的硬限制。
录音停止当达到极限时,
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:错误:委托方法调用适当的错误。
该属性的默认值是kCMTimeInvalid,这表示没有限制

也就是说,假如你设置的最长时间是10,但是你录制的时间刚好为10s,那么AVCaptureFileOutputRecordingDelegate回调里就会报错,因为你达到了最大时间的极限,所以你成功录制视频的最长时间应该小于你设置好的值
如果操作应该如下

-(void)startRecording{
    
    [self removeBeforeVideo:[self videoSaveUrl]];
    [_timer invalidate];
    _timer = nil;
    
    [_movieFileOutput startRecordingToOutputFileURL:[self videoSaveUrl] recordingDelegate:self];
    
}

-(void)stopRecording{
    
    [_movieFileOutput stopRecording];
}
#pragma mark - 录制视频  中 AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)output didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections{
    
    NSLog(@"开始写入视频");
   
    _timer=[NSTimer scheduledTimerWithTimeInterval:9.9 target:self selector:@selector(stopRecording) userInfo:nil repeats:NO];
}

- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(nullable NSError *)error{
    
    NSLog(@"11111");
    BOOL recordedSuccessfully = YES;
    if ([error code] != noErr) {
        /*
         官网说:即使出现了错误,文件也可能保存成功,所以判断文件保存成功与否一定要依据AVErrorRecordingSuccessfullyFinishedKey
         */
        //出现了问题,查明录制是否成功 AVErrorRecordingSuccessfullyFinishedKey:查看是否录制成功
        
        id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (value) {
            recordedSuccessfully = [value boolValue];
        }
    }
    if (recordedSuccessfully) {
        [self writeVideoToAssetsLibrary:[self videoSaveUrl]];
    }
    else{
        NSLog(@"录制视频出问题:%@",error);
    }
    
}
4.1.1 Starting a Recording(开始录制)

startRecordingToOutputFileURL:(NSURL *)outputFileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate开启录制QuickTime movie,参数一个是基于文件的url一个是代理,url不能是先有的文件(因为视频文件输出不会覆盖现有资源)

4.1.2 Ensuring That the File Was Written Successfully(确保文件写入成功)

官网说:即使出现了错误,文件也可能保存成功,所以判断文件保存成功与否一定要依据AVErrorRecordingSuccessfullyFinishedKey,代码在如上图中

如果出错,要看错误的原因是什么,信息在error

4.1.3 Adding Metadata to a File(将原数据添加到文件)
4.2 Capture Still Images(捕捉静止图片 拍照)

AVCaptureStillImageOutput捕捉静止图片,图片的分辨率取决于捕获会话的预设,还有设备不同分辨率可能也不一样

4.2.1 Pixel and Encoding Formats(像素和编码格式)

不同的设备支持不同的图像格式,你可以用availableImageDataCVPixelFormatTypesavailableImageDataCodeTypes来找出支持哪些像素和编码器类型

// 静态图片输出
    AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];
    /*
     outputSettings 是个字典 目前为止只支持两个key,AVVideoCodecKey和kCVPixelBufferPixelFormatTypeKey 这两个键是互斥的,只能选其一
     */
    imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};//See AVVideoSettings.h

如果你想要获取JPEG图像,通常不应该制定自己的压缩格式,相反你应该让图像输出为你压缩,因为它是硬件压缩的,如果你需要图像的数据表示,你应该用jpegStillImageNSDataRepresentation:去获取到一个NSData的对象而不用重新压缩数据,即使你修改了原数据

4.2.2 捕捉一张图片
-(void)takePhotoImage{
    //建立连接
    AVCaptureConnection * connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo];
    //程序只支持纵向 如果用户横向拍照时 需要调整结果照片的方向
    //判断是否支持设置视频方向
    if (connection.isVideoOrientationSupported) {
        connection.videoOrientation = [self currentVideoOrientation];
        
        if (connection.isVideoStabilizationSupported) {
            connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeStandard;
        }
    }
    
    id handler = ^(CMSampleBufferRef sampleBuffer,NSError * error){
        if (sampleBuffer == NULL) {
            NSLog(@"拍照出错");
            return ;
        }
        NSData * imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
        UIImage * image = [UIImage imageWithData:imageData];
        //捕捉图片成功 将照片处理
        [self writeImageToAssetLibrary:image];
        
    };
    [_imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:handler];
}

Showing the User what's Being Recorded(显示用户正在记录什么)

你可以向用户提供相机(使用预览图层)或麦克风(通过监视音频通道)记录内容的预览

Video Preview(视频预览)

使用AVCaptureVideoPreviewLayer(CALayer的子类)提供预览,如果只是预览的话,根本需要任何输出(AVCaptureOutput)

与捕获输出不同,视频预览对与之关联的捕获会话保持了强饮用,以确保会话不会被释放,而图层则显示视频画面

AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
 
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];

因为AVCaptureVideoPreviewLayerCALayer的子类,所以也可以缩放旋转等

Video Gravity Modes(视频重力模式)

设置视频预览Layer的如何在播放器层边界内现实视频湖面
AVCaptureVideoPreviewLayer支持三种重力模式

一般用AVLayerVideoGravityResizeAspectFill

Show Audio Levels(现实音频的级别)

监听捕获连接中音频通道的平均功率电平和峰值功率电平
主要类AVCaptureAudioChannel,音频的电平不是kvo,所以如果你需要用到它并且刷新ui,得自己一段时间去获取一次,AVCaptureAudioChannel实例中可以获取当前通道的平均功率电平和峰值功率电平以及当前音量

AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;

NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {

   // There should be only one connection to an AVCaptureAudioDataOutput.</pre>

    AVCaptureConnection *connection = [connections objectAtIndex:0];
    NSArray *audioChannels = connection.audioChannels;
    for (AVCaptureAudioChannel *channel in audioChannels) {
        float avg = channel.averagePowerLevel;

   float peak = channel.peakHoldLevel;       // Update the 
}

Putting It All Together:Capture Video Frames as UIImage Objects(将一张张图片作为视频帧组成视频)

如下几个步骤示例如果捕获视频并将你或者到的帧转化为UIImage对象

前面五步骤没什么可说的,代码都说过的
重点讲解最后一步:CMSampleBuffer转换为UIImage对象
如下是苹果官方推荐的转换代码

// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
    
    // Get the number of bytes per row for the pixel buffer
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
    
    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
    
    // Create a device-dependent RGB color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
    // Create a bitmap graphics context with the sample buffer data
    CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
                                                 bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    // Create a Quartz image from the pixel data in the bitmap graphics context
    CGImageRef quartzImage = CGBitmapContextCreateImage(context);
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);
    
    // Free up the context and color space
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    
    // Create an image object from the Quartz image
    UIImage *image = [UIImage imageWithCGImage:quartzImage];
    
    // Release the Quartz image
    CGImageRelease(quartzImage);
    
    return (image);
}

但是有些debug信息会报错:


image.png

这是因为出现这类型错误,多半是创建内容画布的时候,height or width = 0, 因此在逻辑上添加判断(排除产生height or width = 0 的情况),可以消除这种错误
如果当height or width = 0的情况出现时,网上有人说随便设置个值,那么你照片将会是空白的
如果我加了个判断

image.png

苹果特意指出:下面的代码展示了如何将CMSampleBuffer转换为UIImage对象。在使用之前,你应该仔细考虑你的要求。执行转换是比较昂贵的操作。例如,它适合于每隔一秒就从一帧视频数据中创建静态图像。你不应该用这个方法来操作实时捕获设备的每一帧视频

所以在回调里我是这样处理,代码没封装,逻辑来了就行

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    //NSLog(@"进入回调:%d %@ %@",_isRecording,sampleBuffer,_assetWriter);
    
    //将CMSampleBuffer 转换为 UIImage对象的定时器,因为这种操作比较昂贵,所以不适合试试转换每帧数据,1s 或者 2s
    if (_beginNewTime==0) {
        _beginNewTime = [[NSDate date] timeIntervalSince1970];
        CFRetain(sampleBuffer);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            UIImage * image = [self imageFromSampleBuffer:sampleBuffer];
            if (image) {
                [self writeImageToAssetLibrary:image];
            }
            else{
            }
            CFRelease(sampleBuffer);
        });
    }
    else{
        NSTimeInterval  nowTime = [[NSDate date] timeIntervalSince1970];
        if (nowTime - _beginNewTime >= 1) {
            
            NSLog(@"diff =%f",nowTime - _beginNewTime);
            _beginNewTime = nowTime;
            CFRetain(sampleBuffer);
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                UIImage * image = [self imageFromSampleBuffer:sampleBuffer];
                
                if (image) {
                    [self writeImageToAssetLibrary:image];
                }
                else{
                }
                CFRelease(sampleBuffer);
            });

        }
    }
  }

切记,一定要释放内存
要不然xcode可能会因为内存问题停止运行的你的项目

Restore the connection to ““XXX”的 iPod” and run “XXX” again, or if “XXX” is still running, you can attach to it by selecting Debug > Attach to Process > XXX. 
Starting and Stopping Recording(开始和停止录制)

在配置好所有的信息后,应该要确保获取到了相机的用户权限才行

NSString *mediaType = AVMediaTypeVideo;

[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
    if (granted)
    {
        //Granted access to mediaType
        [self setDeviceAuthorized:YES];
    }
    else
    {
        //Not granted access to mediaType
        dispatch_async(dispatch_get_main_queue(), ^{
            [[[UIAlertView alloc] initWithTitle:@"AVCam!"
                                        message:@"AVCam doesn't have permission to use Camera, please change privacy settings"
                                       delegate:self
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
            [self setDeviceAuthorized:NO];
        });
    }
}];

确保获取到权限后(相机和麦克风以及相册的的权限)后,调用startRunning
如果设置了

[videoOut setSampleBufferDelegate:self queue:captureQueue];
    [audioOut setSampleBufferDelegate:self queue:captureQueue];

那么就会马上进入回调

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

直到stopRunning

startRunningstopRunning会阻塞线程,可能需要一点时间,因此应该在串行队列上执行

High Frame Rate Video Capture(高帧率视频采集)

Recording(录制)

您可以使用AVCaptureMovieFileOutput类捕获高帧率视频,该类自动支持高帧率记录。它将自动选择正确的H264音高水平和比特率

要进行自定义录制,您必须使用AVAssetWriter类,这需要一些额外的设置

assetWriterInput.expectsMediaDataInRealTime=YES;

该设置确保捕获能够与传入的数据保持同步

上一篇下一篇

猜你喜欢

热点阅读