AVFoundation学习笔记: 媒体捕捉读取及写入
Posted 人生如梦91
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVFoundation学习笔记: 媒体捕捉读取及写入相关的知识,希望对你有一定的参考价值。
媒体捕捉
捕捉会话
媒体捕捉是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定义了两个转换方法:
- captureDevicePointOfInterestForPoint: 获取屏幕坐标系中的CGPoint数据,返回转换得到的设备坐标系的CGPoint数据
- pointForCaptureDevicePointOfInterest: 获取输入设备的CGPoint数据,返回转换得到的屏幕坐标系的CGPoint数据
摄像头切换
我们在使用iPhone自带像机的时候,点击切换相机按钮,可以自由切换前置摄像头和后置摄像头,其实实现起来也很简单,代码如下所示:
关于代码的几点说明如下:
* 首先调用devicesWithMediaType:方法,传入AVMediaTypeVideo,获取本机所有的视频输入设备
* 遍历设备,找到当前指定位置的设备
* 在配置会话之前,需要先调用beginConfiguration,标志配置的开始
* 测试设备是否可以添加到会话,如果可以的话,查询会话中的视频输入设备,并移除原来的视频输入设备,并将当前的设备添加到会话中
* 配置结束之后,需要调用commitConfiguration,标志配置的结束,提交配置
* 使用时,只需要传入AVCaptureDevicePositionFront(前置摄像头)、AVCaptureDevicePositionBack(后置摄像头)即可。
调整对焦
打开iOS系统相机,在屏幕上点击一下,可以看到相机自动对焦到我们点击的位置。实现思路如下:
- 获取用户在相机上的点击坐标,然后根据前述方法,将坐标转换为设备坐标
- 设置和调整焦聚焦距,方法如下:
代码很简单,说明如下:
- 首先检查设备是否支持对焦及是否支持自动对焦模式,对于iPhone手机来说,一般只有后置摄像头支持对焦。
- 如果支持对焦,需要调用lockForConfiguration: 锁定设备,进行配置
- 设置属性focusPointOfInterest对焦点为转换后的设备坐标
- 设置属性focusMode为AVCaptureFocusModeAutoFocus(自动对焦)
- 设置结束之后,需要调用unlockForConfiguration 解锁设备,提交配置。
调整曝光
iOS系统相机,不但可以点击自动对焦,而且还能点击自动调整曝光,调整曝光的代码比较繁琐,如下所示:
相关代码说明如下:
- 首先检查设备是否支持兴趣点曝光及自动连续曝光
- 锁定设备,配置exposurePointOfInterest和exposureMode属性为正确的期望值
- 判断设备是否支持锁定曝光模式,如果支持,使用KVO来监听adjustingExposure的状态,通过观察该属性值,可以知道系统调整曝光在何时完成,从而使我们在该点上锁定曝光
- 将曝光模式修改为AVCaptureExposureLocked,锁定曝光。
- 上述代码忘记了一点,在监听完成之后,我们应该移除监听器。
调整闪光灯和手电筒模式
iOS在控制中心,有一个手电筒的功能,打开之后,闪光灯会常亮,可用作手电筒,默认相机的闪光灯也有开、关、自动三种模式,调整闪光灯和手电筒的代码一样,可以设置三种模式
* AVCapture(Torch|Flash)ModeOn: 总是开启
* AVCapture(Torch|Flash)ModeOff: 总是关闭
* AVCapture(Torch|Flash)ModeAuto: 基于环境光自动调整
设置的代码,和之前的设置代码几乎一致,如下所示:
拍摄静态图片
拍摄静态图片的通用代码如下所示:
代码说明如下:
- 首先通过调用AVCaptureStillImageOutput的connectionWithMediaType:方法来获取当前AVCaptureConnection对象的指针,传递一个AVMediaType类型
- 当设备旋转时,根据旋转方向,设置视频方向,注意到当左旋转和右旋转的时候,摄像头的方向和屏幕方向是相反的
- 如果接收到一个有效的CMSampleBuffer,调用AVCaptureStillImageOutput的jpegStillImageNSDataRepresentation类方法,返回一个表示图片字节的NSData
视频录制
视频录制的基础代码如下所示:
代码说明如下:
- 首先通过调用AVCaptureMovieFileOutput的connectionWithMediaType:方法来获取当前AVCaptureConnection对象的指针,传递一个AVMediaType类型
- 检查AVCaptureMovieFileOuput的isRecording属性,确定当前状态,如果不是在录制状态,则继续
- 设置enablesVideoStabilizationWhenAvaliable,支持视频稳定可以显著提高捕捉到的视频质量
- 可以开启AVCaptureDevice的smoothAutoFocusEnabled属性,允许进行平滑对焦,减慢摄像头对焦速度,提供更自然的视频录制效果
- 调用startRecordingToOutputFileURL:recordingDelegate:方法,开始录制
- 可调用stopRecording方法来停止视频录制
高级捕捉功能
视频缩放
在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
AVMetadataFaceObject对象定义了多个用于描述检测到人脸的属性,其中最重要的一个属性是人脸的边界,它是一个设备坐标。另外还有用于检测人脸倾斜角度和偏转角度的属性rollAngle/yawAngle。
添加输出
添加AVCaptureMetadataOutput输出的代码如下所示:
添加时设置metadataObjectTypes为AVMetadataObjectTypeFace,并设置代理,添加到AVCaptureSession当中即可。
AVCaptureMetadataOutputObjectsDelegate
实现AVCaptureMetadataOutputObjectsDelegate当中的-captureOutput:didOutputMetadataObjects:fromConnection:方法,如下所示:
机器可读代码识别
AVFoundation另一个强大的功能就是识别机器可读代码,例如二维码等,此方面可以参考:使用AVCaptureSession扫描二维码
视频处理
之前,使用AVCaptureMovieFileOutput,可以将摄像头的捕捉写入到文件,但无法同视频数据进行交互,如果要实现此功能,就要使用AVCaptureVideoDataOutput。它可以直接访问摄像头传感器捕捉到的视频帧。这样,我们可以完全控制视频数据的格式、时间和元数据。使用AVCaptureVideoDataOutput与前述AVCaptureMetadataOutput基本类似,不同的是AVCaptureVideoDataOutput需要通过AVCaptureVideoDataOutputSampleBufferDelegate协议输出,其中的方法如下:
- -captureOutput:didOutputSampleBuffer:fromConnection: 每当有一个新的视频帧写入时,该方法就会被调用。
- -captureOutput:didDropSampleBuffer:fromConnection: 每当一个迟到的视频帧被丢弃时,该方法就会被调用。
CMSampleBuffer
CMSampleBuffer是一个由CoreMedia框架提供的对象,用于在媒体管道中传输数字样本,CMSampleBuffer的角色是将基础的样本数据进行封闭并提供格式和时间信息,还会加上所有在转换和处理数据时用到的元数据。
样本数据灰度处理
样本数据灰度处理的示例程序如下所示:
相关说明如下:
- 首先使用CMSampleBufferGetImageBuffer函数从CMSampleBufferRef中获取最基本的CVPixelBuffer。CVPixelBuffer在主内存中保存像素数据,提供了操作的机会。
- 在与CVPixelBuffer数据交互之前,必须调用CVPixelBufferLockBaseAddress获取一个相应的内存块的锁
- 使用CVPixelBufferGetWidth和CVPixelBufferGetHeight来确定像素的宽和高
- 使用CVPixelBufferGetBaseAddress得到像素buffer的基址指针
- 处理完毕之后,需调用CVPixelBufferUnlockBaseAddress释放锁
格式描述
除了原始媒体样本本身之外,CMSampleBuffer还提供了CMFormatDescription对象的形式来访问样本的格式信息,下面请看使用CMFormatDescription来获取媒体类型的代码:
时间信息
CMSampleBufferRef还定义了关于媒体样本的时间信息,可分别使用CMSampleBufferGetPresentationTimeStamp和CMSampleBufferGetDecodeTimeStamp函数来提取时间信息,以得到原始的表示时间和解码时间戳。
示例
《AVFoundation开发秘藉》中有这样一个3D相机的视例,效果如下图所示:
结合OpenGLES实现,由于本人不懂OpenGL代码,所以只能照搬书上的代码:
和前述一样,在captureOutput:didOutputSampleBuffer:fromConnection: 中拿到CMSampleBufferRef,并从中取得相关数据,然后贴图到创建的立方体中。
资源的读取与写入
AVCaptureVideoDataOutput虽然使我们能够与视频底层样本数据做交互,但却无法像AVCaptureMovieFileOutput一样,能将视频写入到文件。但如果我们需要这么做,又该怎么办呢?答案就是使用AVAssetReader和AVAssetWriter。
AVAssetReader
AVAssetReader用于从AVAsset中读取媒体样本数据,通常会配置一个或多个AVAssetReaderOutput实例,并通过copyNextSampleBuffer方法来访问音频样本和视频帧。
AVAssetWriter
AVAssetWriter用于对媒体资源数据进行编码并写入到容器文件。它由一个或多个AVAssetWriterInput对象配置。
将媒体写入磁盘有两种方式,一种是按顺序写入,需要将所有的样本全部捕捉好,然后再写入,这会导致数据的低效率排列,另一种更好的方法是使用交错模式,AVAssetWriterInput提供一个readyForMoreMediaData来指示在保持所需要的交错情况下是否还可以附加更多数据。
AVAssetWriter可用于实时操作和离线操作两种情况:
- 实时:当处理实时资源时,比如从AVCaptureVideoDataOutput写入捕捉的样本时,AVAssetWriterInput应令expectsMediaDataInRealTime为YES来确保readyForMoreMediaData的值被正确计算。
- 离线:当从离线资源读取资源时,如从AVAssetReader读取样本,在附加样本数据时仍需要观察readyForMoreMediaData属性,但可用requestMediaDataWhenREadyOnQueue:usingBlock:来控制数据的提供。
读写示例
下面通过基础实例学习在一个离线场景中如何使用AVAssetReader和AVAssetWriter。
读取
上述代码中,创建AVAssetReader,传递读取的AVAsset实例,创建一个AVAssetReaderTrackOutput,从资源的视频轨道中读取样本,将压缩为BGRA格式,添加输出到读取器,并调用startReading开始读取。
离线写入
示例中创建了一个新的AVAssetWriter对象,并传递一个新文件写入输出目的URL和文件类型,创建一个新的AVAssetWriterInput,它带有相应的媒体类型和输出设置,创建一个720p H.264格式的视频,添加输入到写入器,并调用startWriting准备开始写入,如果可以写入,调用startSessionAtSourceTime启动会话,然后再调用requestMediaDataWhenReadyOnQueue:usingBlock: 从输出入拿取数据并写入。写入完成之后,调用markAsFinished标记写入完成,最后调用finishWritingWithCompletionHandler:处理写入完成之后的流程。
实时写入
实时写入的基础代码如上所示,说明如下:
- 这个方法处理音频和视频两类样本,通过CMFormatDescription来确定样本类型
- 如果处理的是第一个样本,则调用AVAssetWriter的startWriting和startSessionAtSourceTime启动一个新的写入会话
- 如果视频输入的readyForMoreMediaData属性为YES,则将像素buffer连同CMTime附加到AVAssetWriterPixelBufferAdaptor。
- 如果处理的是音频数据,则询问音频AVAssetWriterInput是否准备接收更多数据,如果可以,将数据添加到输入。
- 写入完成之后,调用finishWritingWithCompletionHandler:将数据写入磁盘并处理后续流程。
- videoSettings 和 audioSettings 可以使用AVCaptureVideoDataOutput和AVCaptureAudioDataOutput的方法。
- (NSDictionary *)recommendedVideoSettingsForAssetWriterWithOutputFileType:(NSString *)outputFileType;
- (NSDictionary *)recommendedAudioSettingsForAssetWriterWithOutputFileType:(NSString *)outputFileType;
以上是关于AVFoundation学习笔记: 媒体捕捉读取及写入的主要内容,如果未能解决你的问题,请参考以下文章