媒体捕捉是AVFoundation的重点功能。其核心类是AVCaptureSession。用于连接输入输出的资源。捕捉会话有一个会话预设值,用于控制捕捉数据的格式和质量,默认为AVCaptureSessionPresentHigh。
AVCaptureDevice为诸如摄像头或麦克风等物理设备定义了一个接口。针对物理设备定义了大量控制方法,包括对焦、白平衡、曝光等。最常用的方法是
-(AVCaptureDevice*)defaultDeviceWithMediaType:
在使用捕捉设备进行处理前,首先要添加一个输入设备,不过一个捕捉设备不能直接添加到AVCaptureSession,但可以将它封装到AVCaptureDeviceInput实例中来添加,使用-deviceInputWithDevice:error:方法。
AVCaptureOutput是一个抽象基类,用于为从捕捉会话得到的数据输入到目的地。应使用这个类的一些派生类如:AVCaptureStillImageOuptut、AVCaptureMovieFileOutput等。
AVCaptureVideoPreviewLayer可满足在捕捉时的实时预览,类似于AVPlayerLayer的角色,支持重力概念,可控制视频内容渲染和缩放、拉伸效果。基本类的关系图如下所示:
创建捕捉会话及添加相关设备的基础代码如下所示:
当我们需要开始或停止捕捉的时候,使用AVCaptureSession的startRunning和stopRunning方法即可。
对于iPhone来说,屏幕的左上角为(0, 0),右下角为(320, 568),但是对于捕捉设备如摄像头来说,通常水平方向不支持旋转,并且左上角为(0, 0),右下角为(1, 1),那我们如何进行坐标转换呢?
在iOS6之后的版本,AVCaptureVideoPreviewLayer定义了两个转换方法:
我们在使用iPhone自带像机的时候,点击切换相机按钮,可以自由切换前置摄像头和后置摄像头,其实实现起来也很简单,代码如下所示:
关于代码的几点说明如下:
* 首先调用devicesWithMediaType:方法,传入AVMediaTypeVideo,获取本机所有的视频输入设备
* 遍历设备,找到当前指定位置的设备
* 在配置会话之前,需要先调用beginConfiguration,标志配置的开始
* 测试设备是否可以添加到会话,如果可以的话,查询会话中的视频输入设备,并移除原来的视频输入设备,并将当前的设备添加到会话中
* 配置结束之后,需要调用commitConfiguration,标志配置的结束,提交配置
* 使用时,只需要传入AVCaptureDevicePositionFront(前置摄像头)、AVCaptureDevicePositionBack(后置摄像头)即可。
打开iOS系统相机,在屏幕上点击一下,可以看到相机自动对焦到我们点击的位置。实现思路如下:
代码很简单,说明如下:
iOS系统相机,不但可以点击自动对焦,而且还能点击自动调整曝光,调整曝光的代码比较繁琐,如下所示:
相关代码说明如下:
iOS在控制中心,有一个手电筒的功能,打开之后,闪光灯会常亮,可用作手电筒,默认相机的闪光灯也有开、关、自动三种模式,调整闪光灯和手电筒的代码一样,可以设置三种模式
* AVCapture(Torch|Flash)ModeOn: 总是开启
* AVCapture(Torch|Flash)ModeOff: 总是关闭
* AVCapture(Torch|Flash)ModeAuto: 基于环境光自动调整
设置的代码,和之前的设置代码几乎一致,如下所示:
拍摄静态图片的通用代码如下所示:
代码说明如下:
视频录制的基础代码如下所示:
代码说明如下:
在iOS 7之前,苹果通过AVCaptureConnection的videoScaleAndCropFactor属性对摄像头缩放进行了有限制的支持。开发者可通过调整连接缩放的值从默认的1.0增加到videoMaxScaleAndCropFactor。但此属性使用非常麻烦,而且只能用于AVCaptureStillImageOutput中,这就导致了视频无法应用。iOS 7之后,AVCaptureDevice提供了videoZoomFactor属性,用于控制捕捉设备的缩放等级。其最小值为1.0, 最大值是由捕捉设备的activeFormat值确定,它是AVCaptureDeviceFormat实例,包括属性videoMaxZoomFactor。
在操作之前,首先应该确定捕捉设备是否支持缩放,如下所示:
缩放实现的代码和前述代码基本一致,如下所示:
上述代码不需要多解释,至于设备zoomFactor为指数形式,按照书上的说法,是这样可以提供线性增长的感觉。
设置videoZoomFactor属性会立即调整缩放级别,如果希望在一个时间段内将缩放级别从一个值逐渐调整为另一个值,写法类似以下代码:
其他rate,表示每秒增加缩放因子一倍。速率值通常介于1.0 至 3.0之间。
使用iOS系统相机,当视图中有人脸进入时会自动建立相应的焦点,一个黄色的矩形框会显示在检测到的人脸位置,并自动对焦,AVFoundation也提供了类似的功能。支持10个人脸的实时检测。
苹果首次提供人脸检测功能是在CoreImage框架中的CIDetector和CIFaceFeature两个对象,而AVFoundation中,则由AVCaptureMetadataOutput提供,它输出元数据,当使用人脸检测时,输入的具体对象是AVMetadataFaceObject。
AVMetadataFaceObject对象定义了多个用于描述检测到人脸的属性,其中最重要的一个属性是人脸的边界,它是一个设备坐标。另外还有用于检测人脸倾斜角度和偏转角度的属性rollAngle/yawAngle。
添加AVCaptureMetadataOutput输出的代码如下所示:
添加时设置metadataObjectTypes为AVMetadataObjectTypeFace,并设置代理,添加到AVCaptureSession当中即可。
实现AVCaptureMetadataOutputObjectsDelegate当中的-captureOutput:didOutputMetadataObjects:fromConnection:方法,如下所示:
AVFoundation另一个强大的功能就是识别机器可读代码,例如二维码等,此方面可以参考:使用AVCaptureSession扫描二维码
之前,使用AVCaptureMovieFileOutput,可以将摄像头的捕捉写入到文件,但无法同视频数据进行交互,如果要实现此功能,就要使用AVCaptureVideoDataOutput。它可以直接访问摄像头传感器捕捉到的视频帧。这样,我们可以完全控制视频数据的格式、时间和元数据。使用AVCaptureVideoDataOutput与前述AVCaptureMetadataOutput基本类似,不同的是AVCaptureVideoDataOutput需要通过AVCaptureVideoDataOutputSampleBufferDelegate协议输出,其中的方法如下:
CMSampleBuffer是一个由CoreMedia框架提供的对象,用于在媒体管道中传输数字样本,CMSampleBuffer的角色是将基础的样本数据进行封闭并提供格式和时间信息,还会加上所有在转换和处理数据时用到的元数据。
样本数据灰度处理的示例程序如下所示:
相关说明如下:
除了原始媒体样本本身之外,CMSampleBuffer还提供了CMFormatDescription对象的形式来访问样本的格式信息,下面请看使用CMFormatDescription来获取媒体类型的代码:
CMSampleBufferRef还定义了关于媒体样本的时间信息,可分别使用CMSampleBufferGetPresentationTimeStamp和CMSampleBufferGetDecodeTimeStamp函数来提取时间信息,以得到原始的表示时间和解码时间戳。
《AVFoundation开发秘藉》中有这样一个3D相机的视例,效果如下图所示:
结合OpenGLES实现,由于本人不懂OpenGL代码,所以只能照搬书上的代码:
和前述一样,在captureOutput:didOutputSampleBuffer:fromConnection: 中拿到CMSampleBufferRef,并从中取得相关数据,然后贴图到创建的立方体中。
AVCaptureVideoDataOutput虽然使我们能够与视频底层样本数据做交互,但却无法像AVCaptureMovieFileOutput一样,能将视频写入到文件。但如果我们需要这么做,又该怎么办呢?答案就是使用AVAssetReader和AVAssetWriter。
AVAssetReader用于从AVAsset中读取媒体样本数据,通常会配置一个或多个AVAssetReaderOutput实例,并通过copyNextSampleBuffer方法来访问音频样本和视频帧。
AVAssetWriter用于对媒体资源数据进行编码并写入到容器文件。它由一个或多个AVAssetWriterInput对象配置。
将媒体写入磁盘有两种方式,一种是按顺序写入,需要将所有的样本全部捕捉好,然后再写入,这会导致数据的低效率排列,另一种更好的方法是使用交错模式,AVAssetWriterInput提供一个readyForMoreMediaData来指示在保持所需要的交错情况下是否还可以附加更多数据。
AVAssetWriter可用于实时操作和离线操作两种情况:
下面通过基础实例学习在一个离线场景中如何使用AVAssetReader和AVAssetWriter。
上述代码中,创建AVAssetReader,传递读取的AVAsset实例,创建一个AVAssetReaderTrackOutput,从资源的视频轨道中读取样本,将压缩为BGRA格式,添加输出到读取器,并调用startReading开始读取。
示例中创建了一个新的AVAssetWriter对象,并传递一个新文件写入输出目的URL和文件类型,创建一个新的AVAssetWriterInput,它带有相应的媒体类型和输出设置,创建一个720p H.264格式的视频,添加输入到写入器,并调用startWriting准备开始写入,如果可以写入,调用startSessionAtSourceTime启动会话,然后再调用requestMediaDataWhenReadyOnQueue:usingBlock: 从输出入拿取数据并写入。写入完成之后,调用markAsFinished标记写入完成,最后调用finishWritingWithCompletionHandler:处理写入完成之后的流程。
实时写入的基础代码如上所示,说明如下:
- (NSDictionary *)recommendedVideoSettingsForAssetWriterWithOutputFileType:(NSString *)outputFileType;
- (NSDictionary *)recommendedAudioSettingsForAssetWriterWithOutputFileType:(NSString *)outputFileType;