摄像头实时采集内容,并基于 Metal 实时渲染所需要的几个框架
- 摄像头采集内容:
AVFoundation框架捕获视频 - 将视频帧转化为纹理对象:
CoreVideo框架的CMSampleBufferRef对象 - 渲染纹理:
Metal框架中的MetalPerformanceShaders
Metal 相关设置
- 创建和初始化 MTKView
- 设置 MTKView 的
drawable,默认的帧缓存是只读,设为 NO 即可读写,但会牺牲性能 - 创建
CVMetalTextureCacheRef纹理缓存,这是Core Video的 Metal 纹理缓存
- (void)setupMetal {
self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds device:MTLCreateSystemDefaultDevice()];
[self.view insertSubview:self.mtkView atIndex:0];
self.mtkView.delegate = self;
// 设置MTKView的drawable纹理是可读写的;(默认是只读)
self.mtkView.framebufferOnly = false;
self.commandQueue = [self.mtkView.device newCommandQueue];
/*
CVMetalTextureCacheCreate(CFAllocatorRef allocator,
CFDictionaryRef cacheAttributes,
id <MTLDevice> metalDevice,
CFDictionaryRef textureAttributes,
CVMetalTextureCacheRef * CV_NONNULL cacheOut )
功能: 创建纹理缓存区
参数1: allocator 内存分配器.默认即可.NULL
参数2: cacheAttributes 缓存区行为字典.默认为NULL
参数3: metalDevice
参数4: textureAttributes 缓存创建纹理选项的字典. 使用默认选项NULL
参数5: cacheOut 返回时,包含新创建的纹理缓存。
*/
CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
}
采集
初始化视频采集的准备工作
初始化
AVCaptureSession对象,并设置视频采集的分辨率创建串行队列
将与AVCaptureSession的任何交互(包括其输入和输出)委托给专用的串行调度队列(sessionQueue),以使该交互不会阻塞主队列。

设置输入设备
AVCaptureDeviceInput- 获取摄像头设备
AVCaptureDevice - 根据
AVCaptureDevice对象创建AVCaptureDeviceInput输入设备 - 将输入设备添加到
AVCaptureSession,添加之前需要判断是否可以添加当前输入设备
- 获取摄像头设备
设置输出
AVCaptureVideoDataOutput- 创建
AVCaptureVideoDataOutput对象,即输出设备 - 设置捕获输出的
alwaysDiscardsLateVideoFrames属性(表示视频帧延时使是否丢弃数据)为NO - 设置捕获输出的
videoSettings属性,指定输出内容格式,这里将像素格式设置为BGRA的格式 - 设置捕获输出的代理以及串行调度队列
- 创建
创建
AVCaptureSession会话中一对特定的输入和输出对象之间的连接AVCaptureConnection
- (void)setupCaptureSession {
self.session = [[AVCaptureSession alloc] init];
self.session.sessionPreset = AVCaptureSessionPreset1920x1080;
self.processQueue = dispatch_queue_create("captureProcess", DISPATCH_QUEUE_SERIAL);
NSArray *devices;
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *devicesIOS10 = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
devices = devicesIOS10.devices;
}else {
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
}
AVCaptureDevice *inputCamera = nil;
for (AVCaptureDevice *device in devices) {
if ([device position] == AVCaptureDevicePositionBack) {
inputCamera = device;
}
}
self.deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];
if ([self.session canAddInput:self.deviceInput]) {
[self.session addInput:self.deviceInput];
}
self.deviceOutput = [[AVCaptureVideoDataOutput alloc] init];
/**< 视频帧延迟是否需要丢帧 */
[self.deviceOutput setAlwaysDiscardsLateVideoFrames:false];
[self.deviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[self.deviceOutput setSampleBufferDelegate:self queue:self.processQueue];
if ([self.session canAddOutput:self.deviceOutput]) {
[self.session addOutput:self.deviceOutput];
}
// 链接输入与输出
AVCaptureConnection *connection = [self.deviceOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
[self.session startRunning];
}
视频帧转化
这是案例的核心内容
从摄像头回传CMSampleBufferRef数据获取CVPixelBufferRef视频像素对象(即位图),
通过CVMetalTextureCacheCreateTextureFromImage创建 CoreVideo 的 Metal 纹理缓存CVMetalTextureRef,最后通过CVMetalTextureGetTexture得到 Metal 纹理
- 获取
CVPixelBufferRef视频像素对象 - 创建 Metal 纹理缓存
CVMetalTextureRef - 通过
CVMetalTextureGetTexture得到 Metal 纹理
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
// 从 CMSampleBufferRef 获取视频像素缓存区对象,即获取位图
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// 获取捕获视频帧的 size
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
// 将位图转化为 Metal 纹理
CVMetalTextureRef tmpTexture = NULL;
/* 根据视频像素缓存区 创建 Metal 纹理缓存区
CVReturn CVMetalTextureCacheCreateTextureFromImage(CFAllocatorRef allocator, CVMetalTextureCacheRef textureCache,
CVImageBufferRef sourceImage,
CFDictionaryRef textureAttributes,
MTLPixelFormat pixelFormat,
size_t width,
size_t height,
size_t planeIndex,
CVMetalTextureRef *textureOut);
功能: 从现有图像缓冲区创建核心视频Metal纹理缓冲区。
参数1: allocator 内存分配器,默认kCFAllocatorDefault
参数2: textureCache 纹理缓存区对象
参数3: sourceImage 视频图像缓冲区
参数4: textureAttributes 纹理参数字典.默认为NULL
参数5: pixelFormat 图像缓存区数据的Metal 像素格式常量.注意如果MTLPixelFormatBGRA8Unorm和摄像头采集时设置的颜色格式不一致,则会出现图像异常的情况;
参数6: width,纹理图像的宽度(像素)
参数7: height,纹理图像的高度(像素)
参数8: planeIndex 颜色通道.如果图像缓冲区是平面的,则为映射纹理数据的平面索引。对于非平面图像缓冲区忽略。
参数9: textureOut,返回时,返回创建的Metal纹理缓冲区。
*/
CVReturn res = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatRGBA8Unorm, width, height, 0, &tmpTexture);
if (res == kCVReturnSuccess) {
设置绘制大小
self.mtkView.drawableSize = CGSizeMake(width, height);
// 从纹理缓存中返回Metal纹理对象
self.texture = CVMetalTextureGetTexture(tmpTexture);
CFRelease(tmpTexture);
}
}
渲染
MetalPerformanceShaders是Metal的一个集成库,有一些滤镜处理的Metal实现
MPSImageGaussianBlur是用作高斯模糊处理的,等价于Metal中的MTLRenderCommandEncoder渲染命令编码器
MPSImageGaussianBlur以一个Metal纹理作为输入,以一个Metal纹理作为输出,在这里:
输入的纹理是从摄像头采集的视频帧,也就是上面创建的纹理对象
输出的纹理是 MTKView 的
currentDrawable.texture,即当前帧缓存的纹理对象
- (void)drawInMTKView:(nonnull MTKView *)view {
if (self.texture) {
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
id<MTLTexture> drawingTexture = view.currentDrawable.texture;
MPSImageGaussianBlur *filter =
[[MPSImageGaussianBlur alloc] initWithDevice:self.mtkView.device sigma:1];
[filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];
[commandBuffer presentDrawable:view.currentDrawable];
[commandBuffer commit];
self.texture = nil;
}
}
总结
摄像头采集渲染的两个核心点:
- 从
CVPixelBufferRef创建 Metal 纹理 - 使用以及理解
MetalPerformanceShaders
在实际的开发应用中,
AVFoundation提供了一个AVCaptureVideoPreviewLayer预览 layer,直接预览视频采集后的即时渲染,官方文档