本案例实现使用Metal读取视频文件,并渲染到屏幕上。(此时显示是没有声音的)
思路:

  1. 使用AVFundation中的AVAssetReaderTrackOutput方法,并将原始数据传入到CMSempleBuffer中。CMSempleBuffer存储的是每一帧的数据。
  2. Metal渲染回调将 CMSempleBuffer中的数据转成CVPixelBufferRef。
  3. 使用CoreVideo获取Y纹理和UV纹理。
  4. 自定义片元函数将YUV转成RGBA,显示出来。

1. 顶点函数和片元函数

  • 定点函数

在顶点函数中需要传入顶点坐标和纹理坐标。

//结构体(用于顶点函数输出/片元函数输入)
typedef struct
{float4 clipSpacePosition [[position]]; // position的修饰符表示这个是顶点float2 textureCoordinate; // 纹理坐标} RasterizerData;//RasterizerData 返回数据类型->片元函数
// vertex_id是顶点shader每次处理的index,用于定位当前的顶点
// buffer表明是缓存数据,0是索引
vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],constant CCVertex *vertexArray [[ buffer(CCVertexInputIndexVertices) ]])
{RasterizerData out;//顶点坐标out.clipSpacePosition = vertexArray[vertexID].position;//纹理坐标out.textureCoordinate = vertexArray[vertexID].textureCoordinate;return out;
}
  • 片元函数
    YUV转RGB使用转换矩阵:
float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);

在片元函数中需要传入Y、UV纹理和转换矩阵。

fragment float4
samplingShader(RasterizerData input [[stage_in]],texture2d<float> textureY [[ texture(CCFragmentTextureIndexTextureY) ]],texture2d<float> textureUV [[ texture(CCFragmentTextureIndexTextureUV) ]],constant CCConvertMatrix *convertMatrix [[ buffer(CCFragmentInputIndexMatrix) ]])
{//1.获取纹理采样器constexpr sampler textureSampler (mag_filter::linear,min_filter::linear);/*2. 读取YUV 颜色值textureY.sample(textureSampler, input.textureCoordinate).r从textureY中的纹理采集器中读取,纹理坐标对应上的R值.(Y)textureUV.sample(textureSampler, input.textureCoordinate).rg从textureUV中的纹理采集器中读取,纹理坐标对应上的RG值.(UV)*/float3 yuv = float3(textureY.sample(textureSampler, input.textureCoordinate).r,textureUV.sample(textureSampler, input.textureCoordinate).rg);//3.将YUV 转化为 RGB值.convertMatrix->matrix * (YUV + convertMatrix->offset)float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);//4.返回颜色值(RGBA)return float4(rgb, 1.0);
}

2. 设置纹理

  • 从现有图像缓冲区创建核心视频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纹理缓冲区。// Mapping a BGRA buffer:CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &outTexture);// Mapping the luma plane of a 420v buffer:CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatR8Unorm, width, height, 0, &outTexture);// Mapping the chroma plane of a 420v buffer as a source texture:CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatRG8Unorm width/2, height/2, 1, &outTexture);// Mapping a yuvs buffer as a source texture (note: yuvs/f and 2vuy are unpacked and resampled -- not colorspace converted)CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatGBGR422, width, height, 1, &outTexture);
  • 从CMSampleBuffer读取CVPixelBuffer
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  • 返回纹理缓冲区的Metal纹理对象。 将临时的纹理对象赋值到全局的纹理对象。
CVMetalTextureGetTexture(tmpTexture);

示例代码:(示例代码使用的OC)

 //1.从CMSampleBuffer读取CVPixelBuffer,CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);id<MTLTexture> textureY = nil;id<MTLTexture> textureUV = nil;//textureY 设置{//2.获取纹理的宽高size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);//3.像素格式:普通格式,包含一个8位规范化的无符号整数组件。MTLPixelFormat pixelFormat = MTLPixelFormatR8Unorm;//4.创建CoreVideo的Metal纹理CVMetalTextureRef texture = NULL;/*5. 根据视频像素缓存区 创建 Metal 纹理缓存区*/CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);//6.判断textureCache 是否创建成功if(status == kCVReturnSuccess){//7.转成Metal用的纹理textureY = CVMetalTextureGetTexture(texture);//8.使用完毕释放CFRelease(texture);}}//9.textureUV 设置(同理,参考于textureY 设置){size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);MTLPixelFormat pixelFormat = MTLPixelFormatRG8Unorm;CVMetalTextureRef texture = NULL;CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 1, &texture);if(status == kCVReturnSuccess){textureUV = CVMetalTextureGetTexture(texture);CFRelease(texture);}}//10.判断textureY 和 textureUV 是否读取成功if(textureY != nil && textureUV != nil){//11.向片元函数设置textureY 纹理[encoder setFragmentTexture:textureY atIndex:CCFragmentTextureIndexTextureY];//12.向片元函数设置textureUV 纹理[encoder setFragmentTexture:textureUV atIndex:CCFragmentTextureIndexTextureUV];}//13.使用完毕,则将sampleBuffer 及时释放CFRelease(sampleBuffer);

3. Metal渲染


metal渲染的具体逻辑步骤请看博客:
https://blog.csdn.net/weixin_40918107/article/details/108135662
metalYUV转RGB具体逻辑步骤请看博客https://blog.csdn.net/weixin_40918107/article/details/108269790

本片文章中就不会重新分析每个步骤,而是直接上代码:

  • 设置MTKView
//1.初始化mtkViewself.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds];// 获取默认的deviceself.mtkView.device = MTLCreateSystemDefaultDevice();//设置self.view = self.mtkView;self.view = self.mtkView;//设置代理self.mtkView.delegate = self;//获取视口sizeself.viewportSize = (vector_uint2){self.mtkView.drawableSize.width, self.mtkView.drawableSize.height};
  • 设置渲染管道
 //1 获取.metal/*newDefaultLibrary: 默认一个metal 文件时,推荐使用newLibraryWithFile:error: 从Library 指定读取metal 文件newLibraryWithData:error: 从Data 中获取metal 文件*/id<MTLLibrary> defaultLibrary = [self.mtkView.device newDefaultLibrary];// 顶点shader,vertexShader是函数名id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];// 片元shader,samplingShader是函数名id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"samplingShader"];//2.渲染管道描述信息类MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];//设置vertexFunctionpipelineStateDescriptor.vertexFunction = vertexFunction;//设置fragmentFunctionpipelineStateDescriptor.fragmentFunction = fragmentFunction;// 设置颜色格式pipelineStateDescriptor.colorAttachments[0].pixelFormat = self.mtkView.colorPixelFormat;//3.初始化渲染管道根据渲染管道描述信息// 创建图形渲染管道,耗性能操作不宜频繁调用self.pipelineState = [self.mtkView.device newRenderPipelineStateWithDescriptor:pipelineStateDescriptorerror:NULL];//4.CommandQueue是渲染指令队列,保证渲染指令有序地提交到GPUself.commandQueue = [self.mtkView.device newCommandQueue];
  • 设置顶点
//1.顶点坐标(x,y,z,w);纹理坐标(x,y)//注意: 为了让视频全屏铺满,所以顶点大小均设置[-1,1]static const CCVertex quadVertices[] ={   // 顶点坐标,分别是x、y、z、w;    纹理坐标,x、y;{ {  1.0, -1.0, 0.0, 1.0 },  { 1.f, 1.f } },{ { -1.0, -1.0, 0.0, 1.0 },  { 0.f, 1.f } },{ { -1.0,  1.0, 0.0, 1.0 },  { 0.f, 0.f } },{ {  1.0, -1.0, 0.0, 1.0 },  { 1.f, 1.f } },{ { -1.0,  1.0, 0.0, 1.0 },  { 0.f, 0.f } },{ {  1.0,  1.0, 0.0, 1.0 },  { 1.f, 0.f } },};//2.创建顶点缓存区self.vertices = [self.mtkView.device newBufferWithBytes:quadVerticeslength:sizeof(quadVertices)options:MTLResourceStorageModeShared];//3.计算顶点个数self.numVertices = sizeof(quadVertices) / sizeof(CCVertex);
  • 设置YUV转RGB转换的矩阵
//1.转化矩阵// BT.601, which is the standard for SDTV.matrix_float3x3 kColorConversion601DefaultMatrix = (matrix_float3x3){(simd_float3){1.164,  1.164, 1.164},(simd_float3){0.0, -0.392, 2.017},(simd_float3){1.596, -0.813,   0.0},};// BT.601 full rangematrix_float3x3 kColorConversion601FullRangeMatrix = (matrix_float3x3){(simd_float3){1.0,    1.0,    1.0},(simd_float3){0.0,    -0.343, 1.765},(simd_float3){1.4,    -0.711, 0.0},};// BT.709, which is the standard for HDTV.matrix_float3x3 kColorConversion709DefaultMatrix[] = {(simd_float3){1.164,  1.164, 1.164},(simd_float3){0.0, -0.213, 2.112},(simd_float3){1.793, -0.533,   0.0},};//2.偏移量vector_float3 kColorConversion601FullRangeOffset = (vector_float3){ -(16.0/255.0), -0.5, -0.5};//3.创建转化矩阵结构体.CCConvertMatrix matrix;//设置转化矩阵/*kColorConversion601DefaultMatrix;kColorConversion601FullRangeMatrix;kColorConversion709DefaultMatrix;*/matrix.matrix = kColorConversion601FullRangeMatrix;//设置offset偏移量matrix.offset = kColorConversion601FullRangeOffset;//4.创建转换矩阵缓存区.self.convertMatrix = [self.mtkView.device newBufferWithBytes:&matrixlength:sizeof(CCConvertMatrix)options:MTLResourceStorageModeShared];
  • Draw
 //1.每次渲染都要单独创建一个CommandBufferid<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];//获取渲染描述信息MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;//2. 从CCAssetReader中读取图像数据CMSampleBufferRef sampleBuffer = [self.reader readBuffer];//3.判断renderPassDescriptor 和 sampleBuffer 是否已经获取到了?if(renderPassDescriptor && sampleBuffer){//4.设置renderPassDescriptor中颜色附着(默认背景色)renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.5, 0.5, 1.0f);//5.根据渲染描述信息创建渲染命令编码器id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];//6.设置视口大小(显示区域)[renderEncoder setViewport:(MTLViewport){0.0, 0.0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0 }];//7.为渲染编码器设置渲染管道[renderEncoder setRenderPipelineState:self.pipelineState];//8.设置顶点缓存区[renderEncoder setVertexBuffer:self.verticesoffset:0atIndex:CCVertexInputIndexVertices];//9.设置纹理(将sampleBuffer数据 设置到renderEncoder 中)[self setupTextureWithEncoder:renderEncoder buffer:sampleBuffer];//10.设置片元函数转化矩阵[renderEncoder setFragmentBuffer:self.convertMatrixoffset:0atIndex:CCFragmentInputIndexMatrix];//11.开始绘制[renderEncoder drawPrimitives:MTLPrimitiveTypeTrianglevertexStart:0vertexCount:self.numVertices];//12.结束编码[renderEncoder endEncoding];//13.显示[commandBuffer presentDrawable:view.currentDrawable];}//14.提交命令[commandBuffer commit];

4. 使用AVFundation将视频文件存储到CMSempleBuffer


使用AVFundation中的AVAssetReaderTrackOutput方法,并将原始数据传入到CMSempleBuffer中。CMSempleBuffer存储的是每一帧的数据。

在ViewController中设置myAssetReader

//注意myAssetReader 支持MOV/MP4文件都可以//1.视频文件路径//NSURL *url = [[NSBundle mainBundle] URLForResource:@"my" withExtension:@"mov"];NSURL *url = [[NSBundle mainBundle] URLForResource:@"mingren" withExtension:@"mp4"];//2.初始化myAssetReaderself.reader = [[CCAssetReader alloc] initWithUrl:url];//3._textureCache的创建(通过CoreVideo提供给CPU/GPU高速缓存通道读取纹理数据)CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);

在本案例中封装成了一个类:

@implementation myAssetReader
{//轨道AVAssetReaderTrackOutput *readerVideoTrackOutput;//AVAssetReader可以从原始数据里获取解码后的音视频数据AVAssetReader   *assetReader;//视频地址NSURL *videoUrl;//锁NSLock *lock;
}//初始化
- (instancetype)initWithUrl:(NSURL *)url{self = [super init];if(self != nil){videoUrl = url;lock = [[NSLock alloc]init];[self setUpAsset];}return self;
}//Asset 相关设置
-(void)setUpAsset{//AVURLAssetPreferPreciseDurationAndTimingKey 默认为NO,YES表示提供精确的时长NSDictionary *inputOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];//1. 创建AVURLAsset 是AVAsset 子类,用于从本地/远程URL初始化资源AVURLAsset *inputAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:inputOptions];//2.异步加载资源//weakSelf 解决循环引用__weak typeof(self) weakSelf = self;//定义属性名称NSString *tracks = @"tracks";//对资源所需的键执行标准的异步载入操作,这样就可以访问资源的tracks属性时,就不会受到阻碍.[inputAsset loadValuesAsynchronouslyForKeys:@[tracks] completionHandler: ^{//延长self 生命周期__strong typeof(self) strongSelf = weakSelf;//开辟子线程并发队列异步函数来处理读取的inputAsset
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSError *error = nil;//获取状态码.AVKeyValueStatus tracksStatus = [inputAsset statusOfValueForKey:@"tracks" error:&error];//如果状态不等于成功加载,则返回并打印错误信息if (tracksStatus != AVKeyValueStatusLoaded){NSLog(@"error %@", error);return;}//处理读取的inputAsset[weakSelf processWithAsset:inputAsset];});}];}//处理获取到的asset
- (void)processWithAsset:(AVAsset *)asset
{//锁定[lock lock];NSLog(@"processWithAsset");NSError *error = nil;//1.创建AVAssetReaderassetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];//2.kCVPixelBufferPixelFormatTypeKey 像素格式./*kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : 420vkCVPixelFormatType_32BGRA : iOS在内部进行YUV至BGRA格式转换*/NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary];[outputSettings setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey];/*3. 设置readerVideoTrackOutputassetReaderTrackOutputWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings参数1: 表示读取资源中什么信息参数2: 视频参数*/readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings];//alwaysCopiesSampleData : 表示缓存区的数据输出之前是否会被复制.YES:输出总是从缓存区提供复制的数据,你可以自由的修改这些缓存区数据readerVideoTrackOutput.alwaysCopiesSampleData = NO;//4.为assetReader 填充输出[assetReader addOutput:readerVideoTrackOutput];//5.assetReader 开始读取.并且判断是否开始.if ([assetReader startReading] == NO){NSLog(@"Error reading from file at URL: %@", asset);}//取消锁[lock unlock];
}//读取Buffer 数据
- (CMSampleBufferRef)readBuffer {//锁定[lock lock];CMSampleBufferRef sampleBufferRef = nil;//1.判断readerVideoTrackOutput 是否创建成功.if (readerVideoTrackOutput) {//复制下一个缓存区的内容到sampleBufferRefsampleBufferRef = [readerVideoTrackOutput copyNextSampleBuffer];}//2.判断assetReader 并且status 是已经完成读取 则重新清空readerVideoTrackOutput/assetReader.并重新初始化它们if (assetReader && assetReader.status == AVAssetReaderStatusCompleted) {NSLog(@"customInit");readerVideoTrackOutput = nil;assetReader = nil;[self setUpAsset];}//取消锁[lock unlock];//3.返回读取到的sampleBufferRef 数据return sampleBufferRef;
}@end

Metal(六) 案例之视频文件的渲染相关推荐

  1. html5 mp4转换ogv格式,如何把OGV转换为MP4?用它,轻松转换视频文件!

    原标题:如何把OGV转换为MP4?用它,轻松转换视频文件! 在我们日常娱乐和日常工作中,如果将OGV视频文件转换为MP4视频文件要怎么操作? 可能有人会问你说的OGV视频文件是什么?我怎么没见过?其实 ...

  2. bin文件怎么转换成文本文档_怎么把视频文件转换成MP3?这款工具六步帮你实现...

    在我们的生活当中,如果我们在看视频的时候,碰到了一则带有背景音乐的视频,此时我们不需要视频上的画面内容,只想要保留视频里的音乐的话,那么我们就需要通过将视频转换成MP3音频格式文件,才能够获得视频当中 ...

  3. 【开源项目----Android OPenGLES渲染YUV视频文件】

    [开源项目----Android OPenGLES渲染YUV视频文件] OpenGLES对YUV渲染相关文章参考

  4. ijkplayer播放器剖析(六)视频同步与渲染机制分析

    一.引言: 在前面的博客中,将音频解码播放及视频解码都分析了,这篇博客将单独针对视频同步及渲染来分析,看下ijkplayer是如何做的.本博客分析的同步方式为以音频为主,视频去同步音频. 二.同步前提 ...

  5. python拉取rtsp流、打开摄像头、读文件、渲染并播放视频

    系列Python开发 文章目录 系列Python开发 前言 一.python实现拉流 二.代码实现 1. 拉流渲染时间并播放 总结 前言 一.python实现拉流 本问用python实现了拉流(rts ...

  6. PyQt+moviepy音视频剪辑实战2:一个剪裁视频文件精华内容留存工具的实现

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.引言 ...

  7. pyqt tablewidget 设置一行的背景_PyQt+moviepy音视频剪辑实战2:一个剪裁视频文件精华内容留存工具的实现...

    一.引言 最近网上会议很多,网上会议工具大多提供了录播的功能,有些会议内容比较精彩,但中间穿插有些无用的内容,或者有些只有几段精彩,大部分内容可以去除.这就需要对该录播文件进行剪辑,取其精华留存,这样 ...

  8. 视频教程-Python数据分析案例实战 视频课程-Python

    Python数据分析案例实战 视频课程 计算机硕士,多年工作经验,技术和产品负责人. 多年推荐系统/NLP/大数据工作经验. 负责公司多个AI项目产品落地,包括文本分类.关键词抽取.命名实体识别.对话 ...

  9. N5105 软路由安装 ESXi 7 直通核显给 Debian / Ubuntu 虚拟机通过 Docker 实现 jellyfin 硬件转码视频文件(硬解/编码)

    摘要 在ESXi 7.0u3e里直通N5105的核显给虚拟机Debian 11/Ubuntu 22.04(更新到5.18内核),再套用Docker镜像nyanmisaka/jellyfin (10.8 ...

最新文章

  1. 动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind
  2. pytorch神经网络之卷积层与全连接层参数的设置
  3. Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信
  4. 计算python执行时间
  5. mysql导出document_Mysql导入导出
  6. 适用于孩子,父母和祖父母的JBoss HornetQ –第1章
  7. 高斯课堂数电讲义笔记_【法考经验贴】40岁三战主观题127分!他的笔记学习法助他逆袭!...
  8. 数据结构7.3_图的遍历
  9. 昔年浅谈化工平台网站怎么吸引客户咨询入驻呢?
  10. centos5.2 lamp安装指南
  11. android实现qq登录功能实现原理,Android实现QQ登录功能
  12. linux 下搭建自己的 git 服务器以及配置多用户
  13. babel 无法解析jsx (webpack react )
  14. 拓端tecdat|R语言文本主题模型之潜在语义分析(LDA:Latent Dirichlet Allocation)
  15. How can I add a site title refiner
  16. Dnf资源分析与提取工具(附代码)
  17. pano2vr 制作交互热点模板时常显示文字
  18. android 传感器使用与开发---陀螺仪传感器
  19. OpenStack 虚机异常断电处理
  20. 水波纹特效怎么制作?这波水波纹特效拉动满满复古感

热门文章

  1. 软件设计师考试-软件工程
  2. 团队管理11--管理规划四要素及四步走
  3. 人工雨量计_自动与人工雨量计观测降水量的差异分析
  4. 继牛津大学后,加大伯克利分校等多家美国高校终止与华为合作
  5. 《数据结构》专题9--图的遍历DFSBFS
  6. python 正则提取 中文,汉字
  7. 分享网页相关好用小工具
  8. matlab用双重循环实现费诺编码
  9. 在FreeBSD下安装subversion
  10. 爬取MOOC课程评论区的评论数据