1. 视频缩放
AVCaptureDevice提供了videoZoomFactor的属性, 用于控制捕捉设备的缩放等级. 这个属性的最小值为1.0, 既不能进行缩放图片. 最大值由捕捉设备的activeFormat值确定, 他是新类型AVcaptureDeviceFormat的一个实例. 这个类定义了活动捕捉格式功能的细节 ,其中包括videoMaxZoomFactor. 视频执行缩放效果是通过居中裁剪由摄像头传感器捕捉到的图片实现. 在哪个点开始放大图像需要由AVCaptureDeviceFormat的videoZoomFactorUpscaleThreshold值确定.
- (BOOL)cameraSupportsZoom { //设备是否支持缩放.
return self.activeCamera.activeFormat.videoMaxZoomFactor > 1.0f;
}
- (CGFloat)maxZoomFactor {
return MIN(self.activeCAmera.activeFormat.videoMaxZoomFactor, 4.0); //最大缩放因子
}
- (void)setZoomValue:(CGFloat)zoomValue { //zoomValue值从0到1, 连续型控制
if (!self.activeCamera.isRampingVideoZoom) {
NSError *error;
if ([self.activeCamera lockForConfiguration:&error]) {
CGFloat zoomFactor = pow([self maxZoomFactor], zoomValue);
self.activeCamera.videoZoomFactor = zoomFactor;
[self.activeCamera unlockForConfiguration];
} else {
//错误处理
}
}
}
逐渐调整缩放值方法 a: 增加相应的缩放因子
- (void)rampZoomToValue:(CGFloat)zoomValue {
CGFloat zoomFactor = pow([self maxZoomFactor], zoomValue);
NSError *error;
if ([self.activeCamera lockForConfiguration:&error]) {
[self.activeCamera rampToVideoZoomFactor:zoomFactor withRate:THZoomRate]; //a
[self.activeCamera unlockForConfiguration];
} else {
//错误处理
}
}
- (void)cancelZoom {
NSError *error;
if ([self.activeCamera lockForConfiguration:&error]) {
[self.activeCamera cancelVideoZoomRamp];
[self.activeCamera unlockForConfiguration];
} else {
//异常处理
}
}
2. 人脸检测, 苹果公司的首次人脸检测功能是在Core Image框架中给出的, Core Image框架定义了CIDetector和CIFaceFeature两个对象, 提供了非常强大的人脸检测功能. 不过这些方法没有对实时性进行优化, 导致现代摄像头和视频应用程序要求帧率之下很难应用. 一个新的硬件加速特性被直接加入到AVFoundation中, 这次支持10个人脸进行实时监测. 通过AVCaptureOutput 类型AVCaptureMetadataOutput实现上述功能. 不同于输出静态图片和影片, AVCaptureMetadataOutput 输出的是元数据, 这个元数据来自于一个AVMetadataObject抽象类的形式. 该类定义了很多处理元数据类型的接口, 当使用人脸检测时, 会输出一个具体的子类型AVMetadataFaceObject. AVMetadataFaceObject定义了多个用于描述被检测到人脸的属性, 最重要的就是人脸边界. AVMetadataFaceObject还可以检测到人脸倾斜度和偏转角的参数, 倾斜度便是人的头部向肩膀方向的倾斜角度. 偏转角表示人脸绕y轴旋转的角度.
- (BOOL)setupSessionOutputs:(NSError **)error {
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
if ([self.captureSession canAddOutput:self.metadataOutput]) {
[self.captureSession addOutput:self.metadataOutput];
NSArray *metadataObjectTypes = @[AVMetadataObjectTypeFace]; //指定输出对象类型
self.metadataOutput.metadataObjectTypes = metadataObjectTypes;
dispatch_queue_t mainQueue = dispatch_get_main_queue();
[self.metadataOutput setMetadataObjectsDelegate: self queue:mainQueue];
return YES;
} else {
return NO; //错误处理
}
}
AVCaptureMetadataOutputObjectsDelegate协议.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
for (AVMetadataFaceObject *face in metadataObjects) {
NSLog(@"Face detected with ID: %d", face.faceID);
NSLog(@"Face bounds: %@", NSStringFromCGRect(face.bounds));
}
}
3. 元数据转换, AVCaptureMediadataOutput对象捕捉的数据位于设备空间, 要使用这一元数据, 首先将该数据转换到视图坐标系空间. 使用AVCaptureVideoPreviewLayer进行.
- (void)didDetectFaces:(NSArray *)faces {
NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
}
- (void)transfromedFacesFromFaces:(NSArray *)faces {
NSMutableArray *transformedFaces = [NSMutableArray array];
for (AVMetadataObject *face in faces) {
AVMetadataObject *face = [self.priviewLayer transformedMetadataObjectForMetadataObject:face];
[transformedFaces addObject:face];
}
return transformedFaces;
}
4. 高帧率捕捉, 首先高帧率(FPS)通常具有更逼真的效果和更好的清晰度. 另一方面可以支持高质量的慢动作效果. iPhone5s可以支持120FPS的速率捕捉视频. 这意味着播放速度1/4的帧率还可以达到30FPS速率进行播放. 播放体验更加流畅.
for (AVCaptureDeviceFormat *format in self.formats) {
FourCharCode codecType = CMVideoFormatDescriptionGetCodesType(format.formatDescription);
if (codecType == kCVPixelFormatType_420YpCbCr8BiPanarVideoRange) {
NSArray *frameRateRanges = format.videoSupportedFrameRateRanges;
for (AVFrameRateRange *range in frameRateRanges) {
if (range.maxFrameRate > maxFrameRateRange.maxFrameRate) {
maxFormat = format;
maxFrameRateRange = range;
}
}
}
}
5. 视频处理, 使用AVCaptureMovieFileOutput捕捉影片无法通视频数据进行交互. 要想进行更底层的控制时, 需要用到框架提供的最底层视频捕捉输出AVCaptureVideoDataOutput. AVCaptureVideoDataOutput是一个AVCaptureOutput子类, 可以直接访问摄像头传感器捕捉到的视频帧, 这样我们就完全控制了视频格式, 时间和元数据, 按照需求处理视频. 处理过程都是使用OpenGL ES或Core Image, 不过Quartz可以处理一些简单的要求. AVCaptureVideoDataOutput需要通过AVCaptureVideoDataOutputSampleBufferDelegate协议包含视频数据
captureOutput:didOutputSampleBuffer:fromConnection: //当有一个新的视频帧写入时该方式时会被调用. 数据会基于视频数据输出的videoSettings属性进行解码或重新编码.
captureOutput:didDropSampleBuffer:fromConnection //每当一个迟到的视频帧被丢弃时会调用该方法. 通常是因为didOutputSampleBuffer调用中消耗了太多处理时间就会调用该方法.
6. CMSampleBuffer, CMSampleBuffer是由Core Media框架提供的, 用于在媒体管道中传输数字样本. CMSampleBuffer的角色将基础样本数据进行封装并提供格式和时间信息, 还会加上所有在转换和处理数据时用到的元数据.
7. 样本数据, 在使用AVCaptureVideoDataOutput时, sample buffer会包含一CVPixelBuffer, 他是一个带有单个视频帧原始像素数据的Core Video对象, 下面展示直接操作CVPixelBuffer的内容为捕捉到的图片buffer应用一个灰度效果. a:CVPixelBuffer在数据交互前, 必须调用此方法获取一个相应内存块的锁. b:获取像素buffer的基址指针. c: 执行简单的RGB像素灰度平均.
const int BYTES_PER_PIXEL = 4;
CMSampleBufferRef sampleBuffer = // obtained sample buffer
CVPIxelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, 0); //a
size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
unsigned char *pixel = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);//b
unsigned char grayPixel;
for (int row = 0; row < bufferHeight; row++) {
for (int column = 0; column < bufferWidth; column++) {
grayPixel = (pixel[0] + pixel[1] + pixel[2]);
pixel[0] =pixel[1] = pixel[2] = grayPixel; //c
pixel += BYTES_PRE_PIXEL;
}
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
8. 格式描述, CMSampleBuffer提供了以CMFormatDescription对象的形式来访问样本的格式信息. CMVideoFormatDescription和CMAudioFormatDescription分别用于获取视频和音频的细节.
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescritption (sampleBuffer);
CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDescription);
if (mediaType == kCMMediaType_Video) {
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// process the frame of video
}else if (mediaType == kCMMediaType_Audio) {
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
// process audio samples
}
9. 时间信息, CMSampleBuffer还定义了关于媒体样本的时间信息, 可以分别使用CMSampleBufferGetPresentationTimeStamp和CMSampleBufferGetDecodeTimeStamp函数提取时间信息来得到原始的表示时间戳和解码时间戳.
10. 附加元数据, Core Media还在CMAttachment.h中定义了一个CMAttachment形式的元数据协议, API提供了读取和写入底层元数据的基础架构, 比如可交换图片文件格式(Exif)标签,
CFDictionaryRef exifAttachments = (CFDictionaryRef)CMGetAttachment(sampleBuffer, kCGImagePropertyExifDictionary, NULL); //获取Exif元数据
11. AVCaptureVideoDataOutput, 前置摄像头捕捉视频并将这些视频帧映射为一个旋转方块的OpenGL贴图. Core Video提供了一个对象类型CVOpenGLESTextureCache, 作为Core Video像素buffer和OpenGL ES贴图之间的桥梁. 缓存的目的减少数据从CPU到GPU的开销. 创建贴图缓存
@property (nonatomic, weak) EAGLConext *context;
@property (nonatomic) CVOpenGLESTextureCacheRef textureCache;
@property (nonotomic) CVOpenGLESTextureRef cameraTexture;
- (instancetype)initWithConext:(EAGLContext *)context {
self = [super init];
if (self) {
_context = context;
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, _context, NULL, &_textureCache);
if (err != kCVReturnSuccess) {
NSLog(@"Error creating texture cache. %d", err);
}
}
return self;
}
12. 贴图函数, CVOpenGLESTextureCreateTextureFromImage, 这个函数在委托回调中使用, 为每个视频帧创建一个新的贴图. a:从捕捉到的CMSampleBuffer获取CVImageBuffer(CVImageBuffer是CVPixelBufferRef的类型定义) b.获取CMSampleBuffer的CMFormatDescription. 并通过CMVideoFormatDescriptionGetDimensions函数通过格式描述信息获取视频帧维度. 他会返回一个带有款和高信息的CMVideoDeimensions结构. c:使用CVOpenGLESTextureCacheCreateTexttureFromImage创建一个OpenGL ES贴图. 注意使用height作为宽和高的两个参数值, 因为希望在水平方向裁剪视频. d: target和name用来将贴图对象与旋转的小方块的表面进行合适的绑定. e:textureCreateWithTarget执行实际的GL贴图绑定. f: 调用cleanupTextures来释放贴图并刷新贴图缓存.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connect {
CVReturn err;
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); //a
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(formatDescription); //b
CMVideoDimensions dimensions = CMVideoFormatDescriptionDimensions(formatDesciption);
err = CVOpenGLESTextureCacheCreateTexttureFromImage(kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_RGBA, dimensions.height, dimensions.height, CL_BGRA, GL_UNSIGNED_BYTE, 0, &_cameraTexture); //c
if (!err) {
GLenum target = CVOpenGLESTextureGetTarget(_cameraTexture); //d
GLUint name = CVOpenGLESTextureGetName(_cameraTexture);
[self.textureDelegate textureCreateWithTarget:target name:name]; //e
}else {
NSLog("error: %d", err);
}
[self cleanupTextures];
}
- (void)cleanupTextures {
if (_cameraTexture) {
CFRelease(_cameraTexture); //f
_cameraTexture = NULL:
}
CVOpenGLESTextureCacheFlush(_textureCache, 0);
}