我们先来看下利用苹果提供的 索贝尔滤镜(MPSImageSobel
)渲染的效果图:
苹果提供了几十种着色器,请查看官方文档 Metal Performance Shaders。
案例使用到的库
-
MetalKit
:使用MTKView
作为渲染图层。 -
AVFoundation
:用来捕获摄像机内容。了解AVFoundation
的应该都知道AVFoundation
为我们封装了AVCaptureVideoPreviewLayer
作为渲染图层,此案例并为使用,而是使用的MTKView
自己实现渲染。 -
CoreMedia
:CMSampleBufferGetImageBuffer()
用于获取视频像素缓存区对象CVPixelBufferRef
。 -
CoreVideo
:利用像素缓存区对象CVPixelBufferRef
创建纹理缓存CVMetalTextureCacheRef
。 -
MetalPerformanceShaders
:苹果提供的几十种着色器 Metal Performance Shaders。
一、创建渲染图层MTKView
- (void)setupMTKView {
MTKView *myView = [[MTKView alloc] init];
myView.device = MTLCreateSystemDefaultDevice();
if (!myView.device) {
NSLog(@"Metal is not supported on this device");
return;
}
myView.framebufferOnly = NO;//设置纹理可读、可写,默认只读。
myView.delegate = self;
[self.view addSubview:myView];
self.mtkView = myView;
self.commandQueue = [myView.device newCommandQueue];
}
-
device
:创建系统默认设备MTLCreateSystemDefaultDevice()
。 -
framebufferOnly
:设置纹理的可读、可写。默认只读。该案例需要对纹理进行读写操作,所以设置为NO
。 -
delegate
:设置MTKView
的代理。有两个方法
- (void)drawInMTKView:(nonnull MTKView *)view;
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size;
-
commandQueue
:创建的命令队列id
。
二、创建纹理缓存区
CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
- 参数1
allocator
:内存分配器,用于分配缓存。可能是NULL。 - 参数2
cacheAttributes
:缓存属性,含缓存本身属性的一个字典包。可能是NULLL。 - 参数3
metalDevice
:将要创建纹理对象的metal设备。 - 参数4
textureAttributes
:纹理属性。一个包含 用于创建CVMetalTexture
对象属性 的字典。 - 参数5
cacheOut
:缓存输出。新创建的纹理缓存将放置在此处。 - 返回值:
CVReturn
类型,kCVReturnSuccess
表示成功。
三、设置视频采集
- (void)setupVideoCapture {
//1.
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
//2.
AVCaptureDeviceDiscoverySession *deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInTelephotoCamera,AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
AVCaptureDevice *inputCamera = nil;
for (AVCaptureDevice *device in deviceDiscoverySession.devices) {
if ([device position] == AVCaptureDevicePositionBack) {
inputCamera = device;
break;
}
}
//3.创建输入设备
NSError *error;
self.captureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:&error];
if (error) {
NSLog(@"AVCaptureDeviceInput error:%@", error);
}
if ([self.captureSession canAddInput:self.captureDeviceInput]) {
[self.captureSession addInput:self.captureDeviceInput];
}
//4.创建输出设备
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.captureVideoDataOutput setAlwaysDiscardsLateVideoFrames:NO];
NSDictionary *videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32BGRA)};
[self.captureVideoDataOutput setVideoSettings:videoSettings];
self.captureVideoDataQueue = dispatch_queue_create("mProcessQueue", DISPATCH_QUEUE_SERIAL);
[self.captureVideoDataOutput setSampleBufferDelegate:self queue:self.captureVideoDataQueue];
if ([self.captureSession canAddOutput:self.captureVideoDataOutput]) {
[self.captureSession addOutput:self.captureVideoDataOutput];
}
//5.创建输入与输出链接对象
AVCaptureConnection *captureConnection = [self.captureVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
//设置视频方向
[captureConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
//6.开始采集
[self.captureSession startRunning];
}
- 1.创建采集会话对象
AVCaptureSession
。 - 2.获取摄像机设备
AVCaptureDevice
。利用AVCaptureDeviceDiscoverySession
获取:
+ (instancetype)discoverySessionWithDeviceTypes:(NSArray
*)deviceTypes mediaType:(nullable AVMediaType)mediaType position:(AVCaptureDevicePosition)position;
AVCaptureDeviceType
采集设备类型:
1.VCaptureDeviceTypeBuiltInMicrophone
:话筒
2.AVCaptureDeviceTypeBuiltInWideAngleCamera
:广角照相机
3.AVCaptureDeviceTypeBuiltInTelephotoCamera
:长焦照相机
4.AVCaptureDeviceTypeBuiltInUltraWideCamera
:超宽摄影机
5.AVCaptureDeviceTypeBuiltInDualCamera
:双摄像头
6.AVCaptureDeviceTypeBuiltInDualWideCamera
:双宽摄像头
7.AVCaptureDeviceTypeBuiltInTripleCamera
:三重摄影机
8.AVCaptureDeviceTypeBuiltInTrueDepthCamera
:真深度照相机
9.AVCaptureDeviceTypeBuiltInDuoCamera
:双后置摄像头
AVMediaType
:媒体类型,这里用的是视频AVMediaTypeVideo
。
AVCaptureDevicePosition
:采集设备位置,这里用的是背面摄像头。 - 3.创建输入采集设备
AVCaptureDeviceInput
。并且添加到AVCaptureSession
采集会话里面。 - 4.创建输出设备
AVCaptureVideoDataOutput
:
a、alwaysDiscardsLateVideoFrames
:置视频帧延迟到底时是否丢弃数据。NO
在丢弃新帧之前,允许委托有更多的时间处理旧帧,但这样可能会内存增加。YES
处理现有帧的调度队列在captureOutput:didOutputSampleBuffer:FromConnection:Delegate
方法中被阻止时,对象会立即丢弃捕获的帧。
b、setVideoSettings
:设置视频的配置,这里我们只设置了像素格式32BGRA
。
c、创建处理队列captureVideoDataQueue
。
d、- (void)setSampleBufferDelegate: queue:
设置样例缓冲代理。
e、加入到AVCaptureSession
采集会话里面。 - 5.创建输入与输出连接对象
AVCaptureConnection
。videoOrientation
视频方向。 - 6.
startRunning
开始采集。
四、视频采集代理回调
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
//1.
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
//2.根据视频像素缓存区 创建 Metal 纹理缓存区
CVMetalTextureRef tmpTexture = NULL;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);
if(status == kCVReturnSuccess) {
//设置可绘制纹理的当前大小。
self.mtkView.drawableSize = CGSizeMake(width, height);
//返回纹理缓冲区的Metal纹理对象。
self.texture = CVMetalTextureGetTexture(tmpTexture);
//使用完毕,则释放tmpTexture
CFRelease(tmpTexture);
}
}
-
CMSampleBufferRef
:样例缓存对象。 -
CMSampleBufferGetImageBuffer ()
:获取视频像素缓存区对象CVPixelBufferRef
。 -
CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);
:根据视频像素缓存区 创建 Metal 纹理缓存区:
参数1allocator
:用于分配Metal
纹理对象的内存分配器。可能为空。
参数2textureCache
:将管理纹理的纹理缓存对象。
参数3sourceImage
:要从中创建Metal
纹理对象的图片缓存CVImageBuffer
,这里就是用我们上面的CVPixelBufferRef
。
参数4textureAttributes
:纹理属性。一个包含 用于创建Metal
纹理对象属性 的字典。肯能为NULL。
参数5pixelFormat
:指定的Metal
像素格式。需要跟上面设置setVideoSettings
的像素格式保持一致。
参数6width
:纹理图像的宽度(像素)。
参数7height
:纹理图像的高度(像素)。
参数8planeIndex
:如果图像缓冲区是平面的,则为映射纹理数据的平面索引。对于非平面图像缓冲区忽略。
参数9textureOut
:将要创建的Metal
纹理对象。
返回值:CVReturn
类型,kCVReturnSuccess
表示成功。 - 设置可绘制纹理的当前大小。
- 返回纹理缓冲区的
Metal
纹理对象。然后释放临时纹理对象。
五、绘制
- (void)drawInMTKView:(nonnull MTKView *)view {
if (self.texture) {
//1.
id commandBuffer = [self.commandQueue commandBuffer];
//2.
id drawingTexture = view.currentDrawable.texture;
if (drawingTexture == nil) {
NSLog(@"Current drawable texture is nil!");
return;
}
//3.创建滤镜
//索贝尔滤镜
MPSImageSobel *filter = [[MPSImageSobel alloc] initWithDevice:self.mtkView.device];
//4.将源纹理和目标纹理 编码到命令缓冲区
[filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];
//5.展示显示的内容
[commandBuffer presentDrawable:view.currentDrawable];
//6.提交命令
[commandBuffer commit];
self.texture = NULL;
}
}
- 1、创建命令缓冲区
id
。 - 2、将
MTKView
当前可绘制的纹理(currentDrawable.texture
) 作为目标渲染纹理。 - 3、创建索贝尔滤镜
MPSImageSobel
。 - 4、将源纹理(摄像头采集的图像)和目标纹理(
currentDrawable.texture
) 编码到命令缓冲区 - 5、展示显示的内容
presentDrawable:
- 6、提交命令