Metal 渲染摄像机内容

我们先来看下利用苹果提供的 索贝尔滤镜(MPSImageSobel)渲染的效果图:

索贝尔滤镜

苹果提供了几十种着色器,请查看官方文档 Metal Performance Shaders。

案例使用到的库

  • MetalKit:使用MTKView作为渲染图层。
  • AVFoundation:用来捕获摄像机内容。了解 AVFoundation 的应该都知道 AVFoundation 为我们封装了AVCaptureVideoPreviewLayer作为渲染图层,此案例并为使用,而是使用的MTKView自己实现渲染。
  • CoreMediaCMSampleBufferGetImageBuffer()用于获取视频像素缓存区对象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);
  • 参数1allocator:内存分配器,用于分配缓存。可能是NULL。
  • 参数2cacheAttributes:缓存属性,含缓存本身属性的一个字典包。可能是NULLL。
  • 参数3metalDevice:将要创建纹理对象的metal设备。
  • 参数4textureAttributes:纹理属性。一个包含 用于创建CVMetalTexture对象属性 的字典。
  • 参数5cacheOut:缓存输出。新创建的纹理缓存将放置在此处。
  • 返回值: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.创建输入与输出连接对象AVCaptureConnectionvideoOrientation视频方向。
  • 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、提交命令

你可能感兴趣的:(Metal 渲染摄像机内容)