美文网首页
Metal学习笔记(五)-- 使用Metal实现摄像预览渲染

Metal学习笔记(五)-- 使用Metal实现摄像预览渲染

作者: iOSer_jia | 来源:发表于2020-08-28 18:03 被阅读0次

通常我们可以使用AVCaptureVideoPreviewLayer实现摄像头捕捉图像的渲染展示,但学习完Metal后,本文尝试使用Metal实现摄像头预览的渲染。

基本思路

首先需要用AVFoundation采集摄像头数据得到CMSampleBufferRef,然后通过CoreVideo的方法将CMSampleBufferRef转化为MetalMTLTexture对象,最后用MetalPerformanceShaders的高斯模糊滤镜对图像进行处理,最后渲染到屏幕上。

实现步骤

1.AVCaptureSession配置

- (void)setupCaptureSession {
    _captureSession = [[AVCaptureSession alloc] init];
    _captureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
    
    self.processQueue = dispatch_queue_create("progress queue", DISPATCH_QUEUE_SERIAL);
    
    AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInTelephotoCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
    
    if (!inputDevice) {
        NSLog(@"没有摄像头");
        return;
    }
    
    _deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputDevice error:nil];
    
    if ([self.captureSession canAddInput:_deviceInput]) {
        [self.captureSession addInput:_deviceInput];
    }
    
    _videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    
    /*设置视频帧延迟到底时是否丢弃数据.
    YES: 处理现有帧的调度队列在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止时,对象会立即丢弃捕获的帧。
    NO: 在丢弃新帧之前,允许委托有更多的时间处理旧帧,但这样可能会内存增加.
    */
    _videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
    
    _videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
    
    [_videoDataOutput setSampleBufferDelegate:self queue:self.processQueue];
    
    if ([self.captureSession canAddOutput:_videoDataOutput]) {
        [self.captureSession addOutput:_videoDataOutput];
    }
    
    AVCaptureConnection *connect = [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
    
    [connect setVideoOrientation:AVCaptureVideoOrientationPortrait];
}

这里只是常规的捕捉数据代码,需要注意的是AVCaptureSession添加AVCaptureDeviceInput需要先判断能否添加,避免摄像头被其他进程占用导致添加失败。另外AVCaptureVideoDataOutput的输出颜色格式参数需要与后面的纹理像素格式一致,否则会出现色差。

2.Metal相关设置

- (void)setupMetal:(CGRect)frame {
    _mtkView = [[MTKView alloc] initWithFrame:frame device:MTLCreateSystemDefaultDevice()];
    if (!_mtkView.device) {
        NSLog(@"not metal deivce support");
        return;
    }
    
    _commandQueue = [_mtkView.device newCommandQueue];
    _mtkView.framebufferOnly = NO;
    
    _mtkView.delegate = self;
    
    CVMetalTextureCacheCreate(NULL, NULL, _mtkView.device, NULL, &_textureCacehe);
}

这些只是常规的设置,另外需要将MTKView的drawable设置为可读可写(默认是只读),另外需要创建一个纹理CVMetalTextureCacheRef(来自Core Video框架)

3.处理采集回调

摄像头捕捉数据后回调用代理方法captureOutput:didOutputSampleBuffer:fromConnection:,可以在这里进行数据转化。

- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    
    CVMetalTextureRef tmpTexture = NULL;
    
    /*3. 根据视频像素缓存区 创建 Metal 纹理缓存区    
    功能: 从现有图像缓冲区创建核心视频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 status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacehe, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);
    
    
    if (status == kCVReturnSuccess) {
        self.mtkView.drawableSize = CGSizeMake(width, height);
        self.texture = CVMetalTextureGetTexture(tmpTexture);
        CFRelease(tmpTexture);
    }
}

这里首先需要调用CMSampleBufferGetImageBuffer方法将CMSampleBufferRef转化为CVPixelBufferRef,然后调用CVMetalTextureCacheCreateTextureFromImageCVPixelBufferRef转化为CVMetalTextureRef并保存到纹理缓冲区,再通过CVMetalTextureGetTexture获的MTLTexture对象,这些方法都是由Core Video提供。

4.渲染处理

- (void)drawInMTKView:(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:0];
        // 把摄像头返回图像数据的原始数据
        [filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];

        [commandBuffer presentDrawable:self.mtkView.currentDrawable];
        
        [commandBuffer commit];
        
        self.texture = NULL;
    }
}

也可以使用常规的渲染管道一步步渲染,不过为了方便,这里使用了苹果提供的滤镜。

5.开始捕捉

- (void)startRunning {
    [self.captureSession startRunning];
}

最终效果

metalCamera.gif

相关文章

网友评论

      本文标题:Metal学习笔记(五)-- 使用Metal实现摄像预览渲染

      本文链接:https://www.haomeiwen.com/subject/nwzlsktx.html