Still and Video Media Caputre(静态图片和视频媒体的捕获)

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

  • AVCaptureDevice的实例对象去代表输入设备,比如相机或者麦克风

  • 一个AVCaptureInput的具体的子类(AVCaptureDeviceInput)去配置输入设备的参数

  • AVCaptureOutput的具体子类(AVCaptureMovieFileOutput AVCaptureStillImageOutput AVCaptureVideoDataOutput AVCaptureAudioDataOutput)去管理输出到视频文件或者静态图片

  • AVCaptureSession的实例去协调从输入设备到输出设备到数据流,它是inputoutput的桥梁

  • 展示相机的预览画面,需要创建一个AVCaptureVideoPreviewLayer(CALayer的子类)

捕获会话中的捕获输入和捕获输出之间的连接由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加入到捕获会话中时,会话在所有兼容的捕捉输入端口和捕捉输出之间形成连接(看清楚是“捕捉输入端口和捕捉输出之间形成连接”)

  • 注意:媒体捕获不支持同时捕获iOS设备上的前置和后置摄像头

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(焦点模式)

这里有三种焦点模式

  • AVCaptureFocusModeLocked:锁焦
  • AVCaptureFocusModeAutoFocus:自动对焦
  • AVCaptureFocusModeContinuousAutoFocus:连续对焦

你可以用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(曝光模式)

这里有两种曝光模式

  • AVCaptureExposureModeContinuousAutoExposure:自动曝光
  • AVCaptureExposureModeLocked:锁定曝光

你可以用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(闪光模式)

这里有三种闪光灯模式

  • AVCaptureFlashModeOff:闪光灯关

  • AVCaptureFlashModeOn:闪光灯开

  • AVCaptureFlashModeAuto:自动模式

你可以用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(手电筒模式)

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

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

这里有三种手电筒模式

  • AVCaptureTorchModeOff :手电筒关闭
  • AVCaptureTorchModeOn : 手电筒开启
  • AVCaptureTorchModeAuto :自动

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

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

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

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

这里有两种白平衡的模式

  • AVCaptureWhiteBalanceModeLocked:锁定
  • AVCaptureWhiteBalanceModeContinousAutoWhiteBalance:连续自动
-(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具体子类的实例)
如下

  • AVCaptureMovieFileOutput:输出到视频文件

  • AVCaptureVideoDataOutput:视频中的画面帧

  • AVCaptureAudioDataOutput:视频中的音频

  • AVCaptureStillImageOutput:静态图片,可以理解为拍照

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支持三种重力模式

  • AVLayerVideoGravityResizeAspect:

  • AVLayerVideoGravityResizeAspectFill:

  • AVLayerVideoGravityResize:

一般用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对象

  • 创建AVCaptureSession对象用来协调数据流从输入设备到输出

  • 配置输入设备AVCaptureDevice实例

  • 根据输入设备创建AVCaptureInput

  • 创建AVCaptureVideoDataOutput用来产生视频帧

  • 实现AVCaptureVideoDataOutput的代理去处理视频帧

  • 实现一个方法将代理例接受到的CMSampleBuffer转换为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;

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容