一、视频渲染实现思路

① 思路说明
  • 通过AVFoundation进行视频数据的采集,并将采集到的原始数据存储到CMSampleBufferRef中,即视频帧数据(视频帧其实本质也是一张图片)。
  • 通过CoreVideo将CMSampleBufferRef中存储的图像数据,转换为Metal可以直接使用的纹理。
  • 将Metal纹理进行渲染,并即刻显示到屏幕上。
② 思路实现
  • 在实际的开发应用中,AVFoundation 提供了一个 layer,即AVCaptureVideoPreviewLayer 预览层,我们可以使用预览层直接预览视频采集后的即可渲染,用于直接实现上面思路中的第二步和第三步。
  • 根据官方文档之AVCaptureVideoPreviewLayer说明,AVCaptureVideoPreviewLayer 是 CALayer 的子类,用于在输入设备捕获视频时显示视频,此预览图层与捕获会话结合使用,主要有以下三步:
    • 创建预览层对象;
    • 将预览层与captureSession链接;
    • 将预览层加到view的子layer中。
 // 创建一个预览层let previewLayer = AVCaptureVideoPreviewLayer()// 将预览层与捕获会话连接previewLayer.session = captureSession// 将预览图层添加到视图的图层层次结构中view.layer.addSublayer(previewLayer)
③ 整体流程
  • viewDidLoad函数:初始化Metal和视频采集的准备工作;
  • MTKViewDelegate协议方法:视频采集数据转换为纹理;
  • AVCaptureVideoDataOutputSampleBufferDelegate协议方法:将采集转换后的纹理渲染到屏幕上。

二、初始化 Metal 和视频采集的准备工作

① 整体流程如下:

② setupMetal函数
  • 初始化MTKView,用于显示视频采集数据转换后的纹理;
 self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds device:MTLCreateSystemDefaultDevice()];[self.view insertSubview:self.mtkView atIndex:0];self.mtkView.delegate = self;
  • 创建命令队列:通过MTKView中的device创建;
 self.commandQueue = [self.mtkView.device newCommandQueue];
  • 设置MTKView的读写操作 & 创建纹理缓冲区:

    • MTKView中的framebufferOnly属性,默认的帧缓存是只读的即YES,由于view需要显示纹理,所以需要该属性改为可读写即NO;
    • 通过CVMetalTextureCacheCreate方法创建CoreVideo中的metal纹理缓存区,因为采集的视频数据是通过CoreVideo转换为metal纹理的,主要的用于存储转换后的metal纹理;
 //注意: 在初始化MTKView 的基本操作以外. 还需要多下面2行代码./*1. 设置MTKView 的drawable 纹理是可读写的(默认是只读);2. 创建CVMetalTextureCacheRef _textureCache; 这是Core Video的Metal纹理缓存*///允许读写操作self.mtkView.framebufferOnly = NO;/*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);
③ setupCaptureSession函数
  • 设置AVCaptureSession & 视频采集的分辨率;
 // 设置AVCaptureSession & 视频采集的分辨率self.mCaptureSession = [[AVCaptureSession alloc] init];self.mCaptureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
  • 创建串行队列:串行队列创建的目的在于处理captureSession的交互时,不会影响主队列,在苹果官方文档中有如下图示,表示captureSession是如何管理设备的输入 & 输出,以及与主队列之间的关系,session管理输入和输出图示如下:

  • 关于串行队列在官方文档中的描述:

  • 实现逻辑如下:
     self.mProcessQueue = dispatch_queue_create("mProcessQueue", DISPATCH_QUEUE_SERIAL);
  • 设置输入设备

    • 获取后置摄像头设备AVCaptureDevice:通过获取设备数组,循环判断找到后置摄像头,将后置摄像头设备为当前的输入设备;
    • 通过摄像头设备创建AVCaptureDeviceInput:将AVCaptureDevice 转换为 AVCaptureDeviceInput,主要是因为 AVCaptureSession 无法直接使用 AVCaptureDevice,所以需要将device转换为deviceInput;
    • 输入设备添加到captureSession中:在添加之前,需要通过captureSession的canAddInput函数判断是否可以添加输入设备,如果可以,则通过session的addInput函数添加输入设备;
 // 获取摄像头设备(前置/后置摄像头设备)NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];AVCaptureDevice *inputCamera = nil;// 循环设备数组,找到后置摄像头.设置为当前inputCamerafor (AVCaptureDevice *device in devices) {if ([device position] == AVCaptureDevicePositionBack) {//  拿到后置摄像头inputCamera = device;}}// 将AVCaptureDevice 转换为 AVCaptureDeviceInput,即输入// AVCaptureSession 无法直接使用 AVCaptureDevice,所哟需要将device转换为deviceInputself.mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];// 将设备添加到captureSession中,需要先判断能否添加输入if ([self.mCaptureSession canAddInput:self.mCaptureDeviceInput]) {[self.mCaptureSession addInput:self.mCaptureDeviceInput];}
  • 设置输出设备

    • 创建AVCaptureVideoDataOutput对象,即输出设备;
    • 设置输出设备的setAlwaysDiscardsLateVideoFrames属性(表示视频帧延时使是否丢弃数据)为NO:
      • YES:处理现有帧的调度队列,在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止时,对象会立即丢弃捕获的帧;
      • NO:在丢弃新帧之前,允许委托有更多的时间处理旧帧,但这样可能会内存增加
    • 设置输出设备的setVideoSettings属性(即像素格式),表示每一个像素点颜色保存的格式,且设置的格式是BGRA,而不是YUV,主要是为了避免Shader转换,如果使用了YUV格式,就需要编写shader来进行颜色格式转换;
    • 设置输出设备的视频捕捉输出的delegate;
    • 将输出设备添加到captureSession中;
 // 创建AVCaptureVideoDataOutput对象,即输出 & 设置输出相关属性self.mCaptureDeviceOutput = [[AVCaptureVideoDataOutput alloc] init];/*设置视频帧延迟到底时是否丢弃数据YES: 处理现有帧的调度队列在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止时,对象会立即丢弃捕获的帧。NO: 在丢弃新帧之前,允许委托有更多的时间处理旧帧,但这样可能会内存增加.*/// 视频帧延迟是否需要丢帧[self.mCaptureDeviceOutput setAlwaysDiscardsLateVideoFrames:NO];// 设置像素格式:每一个像素点颜色保存的格式// 这里设置格式为BGRA,而不用YUV的颜色空间,避免使用Shader转换,如果使用YUV格式,需要编写shade来进行颜色格式转换// 注意:这里必须和后面CVMetalTextureCacheCreateTextureFromImage 保存图像像素存储格式保持一致.否则视频会出现异常现象.[self.mCaptureDeviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];// 设置视频捕捉输出的代理方法:将采集的视频数据输出[self.mCaptureDeviceOutput setSampleBufferDelegate:self queue:self.mProcessQueue];// 添加输出,即添加到captureSession中if ([self.mCaptureSession canAddOutput:self.mCaptureDeviceOutput]) {[self.mCaptureSession addOutput:self.mCaptureDeviceOutput];}
  • 输入与输出链接 & 设置视频输出方向:通过AVCaptureConnection链接输入和输出,并设置connect的视频输出方向,即设置videoOrientation属性;
 // 输入与输出链接AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];// 设置视频输出方向// 注意: 一定要设置视频方向.否则视频会是朝向异常的.[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
  • 输入与输出链接 & 设置视频输出方向:通过AVCaptureConnection链接输入和输出,并设置connect的视频输出方向,即设置videoOrientation属性;
 // 输入与输出链接AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];// 设置视频输出方向// 注意: 一定要设置视频方向.否则视频会是朝向异常的[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
  • 开始捕捉,即开始视频采集,也可以通过一个按钮来控制视频采集的开始与停止

    • startRunning:开启捕捉
    • stopRunning:停止捕捉
 // 开始捕捉[self.mCaptureSession startRunning];

三 、AVCaptureVideoDataOutputSampleBufferDelegate协议

① 整体流程
  • 在视频采集的同时,采集到的视频数据,即视频帧会自动回调视频采集回调方法captureOutput:didOutputSampleBuffer:fromConnection:,在该方法中处理采集到的原始视频数据,将其转换为metal纹理;
  • didOutputSampleBuffer代理方法:主要是获取视频的帧数据,将其转换为metal纹理,函数流程如下:

② 流程分解说明
  • 从sampleBuffer中获取位图:通过CMSampleBufferGetImageBuffer函数从sampleBuffer形参中获取视频像素缓存区对象,即视频帧数据,平常所说的位图:
 // 从sampleBuffer 获取视频像素缓存区对象,即获取位图CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  • 获取捕捉视频帧的宽高:通过CoreVideo中的CVPixelBufferGetWidth和CVPixelBufferGetHeight函数获取宽高;
 size_t width = CVPixelBufferGetWidth(pixelBuffer);size_t height = CVPixelBufferGetHeight(pixelBuffer);
  • 将位图转换为metal纹理:

    • 通过CVMetalTextureRef创建临时纹理;
    • 通过CVMetalTextureCacheCreateTextureFromImage函数创建metal纹理缓冲区,赋值给临时纹理;
    • 判断临时纹理是否创建成功,如果临时纹理创建成功,则继续往下执行;
    • 设置MTKView中的drawableSize属性,即表示可绘制纹理的大小;
    • 通过CVMetalTextureGetTexture函数,获取纹理缓冲区的metal纹理对象;
    • 释放临时纹理;
 // 将位图转换为纹理// 方法来自CoreVideo/* 根据视频像素缓存区 创建 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纹理缓冲区。*/// 创建临时纹理CVMetalTextureRef tmpTexture = NULL;CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);// 判断 tmpTexture 是否创建成功if (status == kCVReturnSuccess) {// 创建成功// 设置可绘制纹理的大小self.mtkView.drawableSize = CGSizeMake(width, height);// 返回纹理缓冲区的metal纹理对象self.texture = CVMetalTextureGetTexture(tmpTexture);// 使用完毕,释放tmptextureCFRelease(tmpTexture);}

四、MTKViewDelegate协议

① 说明
  • 将获取的metal纹理即刻渲染并显示到屏幕上,这里是通过MTKViewDelegate协议的drawInMTKView代理方法渲染并显示。
  • drawInMTKView代理方法
    MTKView默认的帧速率与屏幕刷新频率一致,所以每当屏幕刷新时,都会回调 视频采集方法和视图渲染方法,以下是视图渲染方法执行流程:

② 具体步骤
  • 判断纹理是否获取成功:即纹理不为空,如果纹理为空,则没必要执行视图渲染流程;
  • 通过commandQueue创建commandBuffer命令缓存区;
  • 将MTKView的纹理作为目标渲染纹理,即获取view中纹理对象
  • 设置高斯模糊滤镜:
    • MetalPerformanceShaders是Metal的一个集成库,有一些滤镜处理的Metal实现;
    • 此时的滤镜就等价于Metal中的MTLRenderCommandEncoder渲染命令编码器,类似于GLSL中program;
    • 高斯模糊滤镜在渲染时,会触发离屏渲染,且其中的sigma值设置的越高,图像越模糊;
     // 设置滤镜(Metal封装了一些滤镜)// 高斯模糊 渲染时,会触发 离屏渲染/*MetalPerformanceShaders是Metal的一个集成库,有一些滤镜处理的Metal实现;MPSImageGaussianBlur 高斯模糊处理;*/// 创建高斯滤镜处理filter// 注意:sigma值可以修改,sigma值越高图像越模糊;MPSImageGaussianBlur *filter = [[MPSImageGaussianBlur alloc] initWithDevice:self.mtkView.device sigma:5];// MPSImageGaussianBlur以一个Metal纹理作为输入,以一个Metal纹理作为输出;// 输入:摄像头采集的图像 self.texture// 输出:创建的纹理 drawingTexture(其实就是view.currentDrawable.texture)// filter等价于Metal中的MTLRenderCommandEncoder 渲染命令编码器,类似于GLSL中的program[filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];
  • 将获取的纹理显示到屏幕上;
  • 将commandBuffer通过commit提交给GPU;
  • 清空当前纹理,为下一次纹理数据读取做准备;
    如果不清空,也是可以的,下一次的纹理数据会将上次的数据覆盖;
 // 展示显示的内容[commandBuffer presentDrawable:view.currentDrawable];// 提交命令[commandBuffer commit];// 清空当前纹理,准备下一次的纹理数据读取// 如果不清空,也是可以的,下一次的纹理数据会将上次的数据覆盖self.texture = NULL;

五、总结

① 视频采集流程
  • 设置session;
  • 创建串行队列;
  • 设置输入设备;
  • 设置输出设备;
  • 输入与输出链接;
  • 设置视频输出方向;
  • 开始捕捉,即开始视频采集;
  • AVCaptureVideoDataOutputSampleBufferDelegate协议处理采集后的视频数据;

② 如何判断采集的数据是音频还是视频
  • 通过AVCaptureConnection判断

    • 视频:包含视频输入设备 & 视频输出设备,通过AVCaptureConnection链接起来
    • 音频:包含音频输入设备 & 音频输出设备,同样通过AVCaptureConnection链接起来
    • 如果需要判断当前采集的输出是视频还是音频,需要将connect对象设置为全局变量,然后在采集回调方法captureOutput:didOutputSampleBuffer:fromConnection:中判断全局的connection 是否等于代理方法参数中的coneection ,如果相等,就是视频,反之是音频;
  • 通过AVCaptureOutput判断
    在采集回调方法captureOutput:didOutputSampleBuffer:fromConnection:中判断output形参的类型,如果是AVCaptureVideoDataOutput 类型则是视频,反之,是音频。

Metal之实现视频采集与实时渲染相关推荐

  1. iOS音视频开发七:视频采集

    将通过拆解采集 → 编码 → 封装 → 解封装 → 解码 → 渲染流程并实现 Demo 来向大家介绍如何在 iOS/Android 平台上手音视频开发. 这里是第七篇:iOS 视频采集 Demo.这个 ...

  2. 【Android音视频开发】- 实时采集视频

    前言 通过我的上一篇文章,可以知道直播大致有几个步骤:音视频采集 -> 美颜/滤镜/特效处理 -> 编码 -> 封包 -> 推流 -> 分发 -> 解码/渲染/播放 ...

  3. 高糊视频秒变4K!Facebook发布低分辨率视频实时渲染算法,网友:是好东西,但是玩不起...

    贾浩楠 发自 凹非寺 量子位 报道 | 公众号 QbitAI 还记得那个引来巨大争议,最后把LeCun逼退推特的低分辨率图像还原算法PULSE吗? PULSE是针对低分辨率图像进行还原的,而就在PUL ...

  4. 高糊视频秒变4K!Facebook发布低分辨率视频实时渲染算法,网友:是好东西,但是玩不起

    还记得那个引来巨大争议,最后把LeCun逼退推特的低分辨率图像还原算法PULSE吗? PULSE是针对低分辨率图像进行还原的,而就在PULSE问世不久后,一个针对模糊视频进行实时高分辨率渲染的算法问世 ...

  5. Android视频采集实时推送RTP/RTSP/RTMP

    因为在工作中,接触到了视频相关的开发工作:同时,大多数android处理音视频多半都是有C++工程师提供处理库,所以,在这里记录一下我自己在工作中遇到的问题. 主要功能: 采集Android摄像头数据 ...

  6. android视频采集

    视频画面的采集主要是使用各个平台提供的摄像头API来实现的, 在为摄像头设置了合适的参数之后,将摄像头实时采集的视频帧渲染到 屏幕上提供给用户预览,然后将该视频帧编码到一个视频文件中,其使 用的编码格 ...

  7. WeBRTC IOS视频采集流程

    因CSDN MardDown语法问题,流程图部分不兼容有道云笔记,所以流程图部分请拷贝到有道云笔记生成查看. iOS视频录制: 同拍照一样视频录制功能有两种实现方式 UIImagePickerView ...

  8. 声网3D在线互动场景空间音频的实时渲染——如何把“声临其境”推向极致

    编者按: 千人有千耳,不同的人耳对于声音方位的适应已形成习惯,但在Meta RTC场景中如何让不同人也能畅想"身临其境"的感觉?3D在线互动场景空间音频的实时渲染又有哪些应用?Li ...

  9. 实时渲染、离线渲染、实时云渲染、混合渲染是什么?

    渲染,就是将 3D 模型转换成 2D 图像,并最终呈现在屏幕上的过程.虽然这里只有一句话,但是这一句话里面包含了太多的数学.物理和计算机方面的知识,它描述了我们用计算机来虚拟化真实世界的基本逻辑.渲染 ...

最新文章

  1. 管理中应避免“妇人之仁”
  2. asp.net 二级域名(路由方式实现)
  3. C提高_day03_两个辅助指针变量挖字符串(强化4)
  4. python 加密解密_python加密解密
  5. spring 测试 事务_Spring陷阱:事务测试被认为是有害的
  6. 1075 链表元素分类 (25 分)
  7. 实验 5 编写、调试具有多个段的
  8. Docker入门安装教程
  9. 物体检测object detection object recognition和coco数据集 动手学深度学习v2 pytorch
  10. linux系统软件包下载
  11. 哪有没时间这回事-读后感
  12. vue 动态的获取屏幕高度
  13. 数据湖实践 | Iceberg 在网易云音乐的实践
  14. TCP协议——SYN/ACK的使用以及滑动窗口机制
  15. mac桌面键盘快捷键_使用键盘快捷键更改桌面分辨率
  16. 《沧海一声笑》简谱图修正版
  17. 计算机应用基础7次作业答案,北京中医药大学远程教育“计算机应用基础”第7次作业(14页)-原创力文档...
  18. 红米Redmi品牌独立后首发新品 雷军:『友商就不要用性价比这个词了』
  19. mac修改localhost为指定ip_使用 macOS 服务器更改服务器的 IP 地址
  20. mysql中各种类型的锁

热门文章

  1. VMware Workstation Pro通过ISO系统镜像安装ubuntu-18.04.2
  2. 关于Verilog中begin···end语句执行顺序
  3. Vue.js 笔记之 img src
  4. Grafana+Prometheus系统监控之MySql
  5. 51nod 1277 KMP 前缀出现次数
  6. 你人脉网中应该有的10种人
  7. java 二进制图片上传_Spring MVC上传图片,Java二进制图片写入数据库,生成略缩图...
  8. python打印字节流_Python 调用系统命令的模块 Subprocess
  9. Java黑皮书课后题第8章:**8.14(探讨矩阵)编写程序,提示用户输入一个方阵的长度,随机地在矩阵中填入0和1,打印这个矩阵,然后找出整行、整列或者对角线都是1或0的行、列和对角线
  10. Java黑皮书课后题第6章:**6.22(数学:平方根的近似求法)实现Math类中dsqrt方法的技术:巴比伦法nextGuess = (lastGuess + n / lastGuess) / 2