iOS视频录制功能预研

两种方式

  1. 使用高度封装的类UIImagePickerController
  2. 使用AVFoundation框架

方式一:UIImagePickerController

特点: 可用于从相簿中选取照片、拍照、录制视频,使用简便,提供的配置有录制视频的质量、前后置摄像头、闪光灯。缺点是由于它的高度封装性,要进行某些自定义的工作比较复杂。录制的视频格式为MOV,可使用AVAssetExportSession进行视频转码为MP4格式。

使用UIImagePickerController来拍照或者录制视频通常可以分为如下步骤:

  1. 创建UIImagePickerController对象
  2. 指定拾取源,录制视频需要指定为摄像头类型
  3. 指定摄像头,前置摄像头或者后置摄像头
  4. 设置媒体类型mediaType
  5. 指定捕获模式
  6. 展示UIImagePickerController
  7. 录制视频结束后在代理方法中展示/保存视频

方式二:AVFoundation

AVFoundation框架用于创建和播放基于时间的影音媒体,可用来体验、创建、编辑媒体文件,也可以获取设备的输入流并在实时捕获和回放期间操作视频。

视频捕获

为了管理来自设备的捕获,例如相机和麦克风,可以组装对象来代表输入和输出,并使用AVCaptureSession实例对象来调配影音输入与输出之间的数据流。需要以下几点:

  • AVCaptureDevice实例来代表输入设备,例如相机或麦克风。
  • AVCaptureInput具体子类的实例来从输入设备配置端口。
  • AVCaptureOutput具体子类的实例来管理输出成视频文件或照片。
  • AVCaptureSession实例来协调从输入到输出的数据流。
  • AVCaptureVideoPreviewLayer图层实例来显示用户的预览视频记录。

可以配置多个输入和输出,通过一个单独的会话session来协调,如下图所示:

AVCaptureConnect对象代表捕获会话中捕获输入和捕获输出之间的连接。
AVCaptureInput捕获输入有一个或多个端口AVCaptureInputPort
AVCaptureOutput捕获输出可以从一个或多个源处接收数据,例如AVCaptureMovieFileOutput对象可以接收视频和音频数据。
当添加一个输入和输出到一个会话中时,会话在所有兼容的输入端口和输出端口形成一个连接。如图所示:


可以使用捕获连接来使给定的输入或输出的数据流可用或失效。也可以用来监控音频频道的平均波值和波峰值。

使用捕获会话来协调数据流

AVFoundation中关于视频捕获的主要的类是AVCaptureSession。它负责调配影音输入与输出之间的数据流:

使用一个 capture session,需要先实例化,添加输入与输出,接着启动从输入到输出之间的数据流:

AVCaptureSession *captureSession = [AVCaptureSession new];
AVCaptureDeviceInput *cameraDeviceInput = …
AVCaptureDeviceInput *micDeviceInput = …
AVCaptureMovieFileOutput *movieFileOutput = …
if ([captureSession canAddInput:cameraDeviceInput]) {
    [captureSession addInput:cameraDeviceInput];
}
if ([captureSession canAddInput:micDeviceInput]) {
    [captureSession addInput:micDeviceInput];
}
if ([captureSession canAddOutput:movieFileOutput]) {
    [captureSession addOutput:movieFileOutput];
}

[captureSession startRunning];
配置会话

使用一个预设的会话来指定想要的图像质量和分辨率。预设是一个常量来标识可能配置的数字之一。有些情况下特定设备的实际配置如下:

符号 分辨率 注解
AVCaptureSessionPresetHigh 最高质量录制,各个设备不同
AVCaptureSessionPresetMedium 适用Wi-Fi共享,真实的值可能改变
AVCaptureSessionPresetLow 适用3G共享,真实的值可能改变
AVCaptureSessionPreset640x480 640x480 VGA
AVCaptureSessionPreset1280x720 1280x720 720p高清
AVCaptureSessionPresetPhoto 图片 完整的图片分辨率,不支持视频输出

若要设置媒体帧指定大小的配置,应该在设置之前先检查是否支持,如下:

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
   session.sessionPreset = AVCaptureSessionPreset1280x720;
} else {
    // Handle the failure
}

如果需要调整会话参数,修改的代码应该写在beginConfigurationcommitConfiguration方法之间。如下:

[session beginConfiguration];
// 移除已经存在的捕获设备
// 添加新的捕获设备
// 重置预设
[session commitConfiguration];
监测捕获会话状态

捕获会话会在开始运行、停止运行或中断运行时发出通知。可以注册接收AVCaptureSessionRuntimeErrorNotification通知来接收运行时错误。可以调用会话的running属性来查看是否在运行,调用会话的interrupted属性来查看是否被中断。另外,runninginterrupted属性的KVO和通知都是在主线程执行。

输入

一个AVCaptureDevice对象代表一个输入设备对象,这些对象通过 AVCaptureDeviceInput连接上 capture session

可使用AVCaptureDevice的类方法devicesdevicesWithMediaType:来查看哪些捕获设备当前可用。以iPhone 6s为例:

(
    "<AVCaptureFigVideoDevice: 0x111e0e2c0 [Back Camera][com.apple.avfoundation.avcapturedevice.built-in_video:0]>",// 后置摄像头
    "<AVCaptureFigVideoDevice: 0x111d0fd50 [Front Camera][com.apple.avfoundation.avcapturedevice.built-in_video:1]>",// 前置摄像头
    "<AVCaptureFigAudioDevice: 0x17408abe0 [iPhone \U9ea6\U514b\U98ce][com.apple.avfoundation.avcapturedevice.built-in_audio:0]>"// 麦克风
)

设备特征
  • 可以使用hasMediaType:方法来测试是否提供一个特定的媒体类型
  • 可以使用supportsAVCaptureSessionPreset:方法来测试是否支持给定的捕获会话预设
摄像头的位置区分
  • 后置摄像头:AVCaptureDevicePositionBack
  • 前置摄像头:AVCaptureDevicePositionFront
聚焦模式
  • AVCaptureFocusModeLocked: 焦点的位置固定。适用于当想要用户组合场景然后锁定焦点时。
  • AVCaptureFocusModeAutoFocus: 相机执行单个扫描对焦,然后还原到锁定。适用于想要选择要聚焦的特定项目,然后将焦点保持在该项目上的情况,即使它不是场景的中心。
  • AVCaptureFocusModeContinuousAutoFocus: 相机会根据需要不断自动对焦。

可以使用isFocusModeSupported:方法查看设备是否支持一个给定的模式,然后再设置focusMode属性。
另外,设备可能支持关注焦点。使用focusPointOfInterestSupported方法来测试是否支持。如果支持,可以调用focusPointOfInterest:方法来设置焦点。{0, 0}代表图片区域的左上角, {1, 1}代表手机的右下角。

if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
    CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setFocusPointOfInterest:autofocusPoint];
    [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
曝光模式
  • AVCaptureExposureModeContinuousAutoExposure: 设备根据需要自动调整曝光级别
  • AVCaptureExposureModeLocked: 曝光级别固定在当前级别

可以使用isExposureModeSupport:方法查看设备是否支持给定的曝光模式,然后再使用exposureMode设置模式。
另外,设备可能支持关注点曝光。使用exposurePointOfInterestSupported方法测试是否支持。若支持,可以使用exposurePointOfInterest:方法设置曝光点。{0,0}代表图片区域的左上角,{1,1}代表图片区域的右下角。
可以使用adjustExposure属性来确定设备当前是否正在更改其曝光设置。可以使用KVO来观察曝光设置的开始和停止。

if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
    CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setExposurePointOfInterest:exposurePoint];
    [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
闪光模式
  • AVCaptureFlashModeOff: 闪光灯将永远不会闪光
  • AVCaptureFlashModeOn: 闪光灯将一直闪光
  • AVCaptureFlashModeAuto: 闪光灯将依赖环境光线条件决定是否闪光

使用hasFlash来确定设备是否有闪光灯。若返回YES,再使用isFlashModeSupported:方法,确定其是否支持给定闪光模式,若支持,再设置flashMode属性。

手电筒模式
  • AVCaptureTorchModeOff: 手电筒总是关闭
  • AVCaptureTorchModeOn: 手电筒总是开启
  • AVCaptureTorchModeAuto: 手电筒根据需要自动开启和关闭

使用hasTorch确定设备是否有手电筒,如果有,再使用isTorchModeSupported:方法来确定设备是否支持给定的手电筒模式,若支持,再设置torchMode模式。
对于有手电筒的设备,如果设备与正在运行的捕获会话相关联,则手电筒才会打开。

白平衡模式
  • AVCaptureWhiteBalanceModeLocked: 白平衡固定
  • AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance: 相机根据需要自动调节白平衡

可以使用isWhiteBalanceModeSupported:方法来确定设备是否支持给定的白平衡,若支持,再设置whiteBalanceMode属性
可以使用adjustingWhiteBalance属性来确定设备当前是否改变了白平衡设置。可以使用KVO来观察改变白平衡设置的开始和停止。

配置设备

设置设备的捕获属性时,先使用lockForConfiguration:方法获取配置锁。避免与其他应用的设置冲突。配置完之后需要unlock。

if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
    NSError *error = nil;
    if ([device lockForConfiguration:&error]) {
        device.focusMode = AVCaptureFocusModeLocked;
        [device unlockForConfiguration];
    }
    else {
        // respond to the failure as appropriate
    }
}
设备间切换

有的时候想要允许用户去切换输入设备,比如切换前置和后置摄像头。应使用beginConfigurationcommitConfiguration来支持配置更改:

AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];

[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];

[session commitConfiguration];
视频防抖

视频防抖 是在 iOS 6 和 iPhone 4S 发布时引入的功能。到了 iPhone 6,增加了更强劲和流畅的防抖模式,被称为影院级的视频防抖动。相关的 API 也有所改动 (目前为止并没有在文档中反映出来,不过可以查看头文件)。防抖并不是在捕获设备上配置的,而是在 AVCaptureConnection 上设置。由于不是所有的设备格式都支持全部的防抖模式,所以在实际应用中应事先确认具体的防抖模式是否支持:

AVCaptureConnection *connection = ...;

AVCaptureVideoStabilizationMode stabilizationMode = AVCaptureVideoStabilizationModeCinematic;
if ([device.activeFormat isVideoStabilizationModeSupported:stabilizationMode]) {
    [connection setPreferredVideoStabilizationMode:stabilizationMode];
}

iPhone 6 的另一个新特性就是视频 HDR (高动态范围图像),它是“高动态范围的视频流,与传统的将不同曝光度的静态图像合成成一张高动态范围图像的方法完全不同”,它是内建在传感器中的。有两种方法可以配置视频 HDR:直接将 capture devicevideoHDREnabled 设置为启用或禁用,或者使用 automaticallyAdjustsVideoHDREnabled 属性来留给系统处理。

使用捕获输入添加捕获设备到会话

捕获设备输入使用AVCaptureDeviceInput实例,它是抽象类AVCaptureInput的子类。捕获设备输入管理设备的端口。

NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
  // handle the error appropriately.
}

使用sessionaddInput:方法添加输入之前,最好先调用canAddInput:方法检查捕获输入是否存在.

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // handle the failure;
}
使用捕获输出从会话获取输出

输出是抽象类AVCaptureOutput的实例,应使用:

  • AVCaptureMovieFileOutput来输出一个影音文件。
  • AVCaptureVideoDataOutput,如果想要从被捕获的视频获取进程帧,比如来创建自定义视图图层。
  • AVCaptureAudioDataOutput,如果想要被捕获音频数据的过程
  • AVCaptureStillImageOutput,如果要使用伴随的元数据捕获静态图像

使用addOutput:方法添加输出到捕获会话中,先使用canAddOutput:方法检查输出是否存在。

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieOutput]) {
    [captureSession addOutput:movieOutput];
}
else {
    // handle the failure;
}
保存成影音文件

使用AVCaptureMovieFileOutput对象(是AVCaptureFileOutput抽象类的子类)来保存影音数据到文件中。可以配置一些参数,比如录制最大时长、最大文件大小。

AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;
开始录制

使用startRecordingToOutputFileURL:recordingDelegate:方法开始录制QuickTime影片,需要提供基于文件的URL和代理。代理对象必须遵守AVCaptureFileOutputRecordingDelegate协议,并实现captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error方法,将影片结果写入到相簿中。

AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];
确保文件被写入成功

不仅可以使用captureOutput:didFinishRecordingToOutputFileAtURL:fromConnnections:error:方法的实现内部检查文件是否被写入成功,也可以使用erroruserInfo字典里的AVErrorRecordingSuccessfullyFinishedKey的值来检查。

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
    BOOL recordedSuccessfully = YES;
    if ([error code] != noErr) {
        // a problem occurred: Find out if the recording was successful.
        id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (value) {
            recordedSuccessfully = [value boolValue];
        }
    }
}
  • 磁盘已满 - AVErrorDiskFull
  • 录制设备断开连接 - AVErrorDeviceWasDisconnected
  • 会话中断(比如有电话打进来) - AVErrorSessionWasInterrupted
实时预览

当使用 AVFoundation 来做图像捕获时,我们必须提供一套自定义的用户界面。其中一个关键的相机交互组件是实时预览图。最简单的实现方式是通过把 AVCaptureVideoPreviewLayer 对象作为一个 sublayer 加到相机图层上去:

AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
UIView *cameraView = ...;
previewLayer.frame = cameraView.bounds;
[cameraView.layer addSublayer:previewLayer];

创建并配置捕获会话

AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;

创建并配置设备和设备输入

捕获设备用AVCaptureDevice对象,设备有一个或多个端口,使用AVCaptureInput对象的子类配置。

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // handle the error appropriately.
}
[session addInput:input];

创建并配置视频数据输出

使用AVCaptureVideoDataOutput对象处理被捕获的视频中的未压缩帧。例如,可以使用videoSettings属性设置视频的像素格式,使用minFrameDuration属性设置上限帧率。

AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSetting = @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA)};
output.minFrameDuration = CMTimeMake(1, 15);

数据输出对象使用委托模式来管理视频帧,代理必须采纳AVCaptureVideoDataOutputSampleBufferDelegate协议,当设置数据输出对象的代理时,必须提供一个应该调用回调的队列。

dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

在代理类中,实现captureOutput:didOutputSampleBuffer:fromConnectin:方法,这个方法会在样本缓冲区被写入时调用。视频数据输出将帧作为CMSampleBuffer不透明类型传递,所以需要转换为UIImge对象。

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    UIImage *image = imageFromSampleBuffer(sampleBuffer);
    // add your code here that uses the image.
}

开始和停止数据流

配置捕获会话后,应确保相机具有根据用户喜好进行录制的权限

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消息来开始数据流,发送stopRunning消息来停止数据流。

访问权限

访问相机和麦克风需要先获得用户授权。相关权限需要在info.plist文件中添加如下key:

  • Privacy - Microphone Usage Description 需要您的同意,才能访问麦克风
  • Privacy - Camera Usage Description 需要您的同意,才能访问相机
  • Privacy - Photo Library Usage Description 需要您的同意,才能访问相册
使用AVFoundation拍照和录制视频的一般步骤如下:
  1. 创建AVCaptureSession对象。
  2. 使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
  3. 利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
  4. 初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
  5. 将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中。
  6. 创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSessionstartRuning方法开始捕获。
  7. 将捕获的音频或视频数据输出到指定文件。

Reference

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

推荐阅读更多精彩内容