大多数浏览器和
Developer App 均支持流媒体播放。
-
通过 AV Foundation 和 Video Toolbox 解码 ProRes
让你的 Mac app 解码并显示 ProRes 内容更加简单:了解如何利用 AVFoundation 和 VideoToolbox 的解码功能完成最优图形管线。我们将分享让你的 app 实现这一点的最佳方法和性能注意事项,并向你展示如何将Afterburner 加速卡与你的管线集成,并逐步介绍如何使用 Metal 显示解码帧。
资源
-
下载
(你好 WWDC 2020) 你好 欢迎参加 WWDC (通过 AV Foundation 和 Video Toolbox) (解码 ProRes) 你好 欢迎参加“通过 AV Foundation 和 Video Toolbox 解码 ProRes”讲座 今天 我们的目标是将 ProRes 电影文件 或任何视频的路径 优化进你的 app
你在非常酷的 Metal 渲染引擎中 拥有一款优质的视频编辑 app 你希望确保 用户在使用 app 中的 ProRes 内容时 能够拥有最棒的体验
我们的目标是实现两个目标 利用 Afterburner 等可用的硬件解码器 以及确保你拥有优化、有效的路径 便于处理压缩数据流 和来自解码器的帧 首先 我们会概述一些 将视频集成进 app 的概念 之后 我们将讨论 AV Foundation 如何帮助实现这一切
然而 在 AV Foundation 级别上 实现这一切并不适用于所有人 因此 接着我们将讨论 如果你选择 通过 Video Toolbox 自行驱动解码器 需要如何获取或构建压缩样本
之后 我们会谈论如何使用 VTDecompressionSession 最后 我们将了解通过 Metal 集成解码视频帧的最佳做法
现在 让我们学习在平台上处理视频的基础知识 首先 我们简要了解下视频解码器 视频解码器会对比特流进行大量解析 比特流产生于不同的来源 并不总是始终由用户完全控制 这就给畸形媒体带来了机会 它们会破坏 app 的稳定性 甚至利用漏洞导致安全问题
为了减轻这些烦恼 Video Toolbox 会在沙盒服务器中 跨进程运行解码器
在权限受限的进程中运行解码器 不仅能够确保安全性 而且也会提高 app 的稳定性 如果视频解码器出现崩溃 结果会显示解码错误 整个 app 并不会受到破坏 现在 让我们谈谈 mac OS 中的媒体堆栈 在本次讲座中 我们会集中讲解视频
屏幕顶部是 AVKit AVKit 会提供极高级别的选项 便于在 app 中添加媒体功能 由于我们希望集成你的现有渲染管道 因此在这里 我们略过 AVKit
AV Foundation 能够提供强大、灵活的接口 便于使用媒体的所有功能 我们将查看 AV Foundation 框架中的 几个接口
我们已经谈论过 Video Toolbox 稍后还会继续进行讲解 它提供的低级接口能够便于 使用视频解码器和编码器
Core Media 框架 提供了许多基础的构建块 便于在平台上进行媒体操作 最后 Core Video 专门为使用视频 提供了基础的构建块 因此 我们将主要集中讲解这三大框架的接口 AV Foundation、Video Toolbox 以及 Core Video 在 AV Foundation 中 我们会详细讲解 AVAssetReader 和 AVSampleBufferGenerator 在 Video Toolbox 中 我们会仔细研究 VTDecompressionSession
最后 在 Core Video 中 我们会学习 通过 Metal 将 CVPixelBuffers 与 CVPixelBufferPools 集成
现在 让我们谈谈 在这些不同的 API 级别上 工作时的注意事项
首先 在当前 OS 版本中 所有媒体接口在可用时 都会自动启用硬件解码 这其中包含启用 Afterburner 稍后 我们将谈论 如何有选择地启用和禁用硬件解码 不过默认情况下 硬件解码始终启用
刚刚 我们谈论了 视频解码器如何在单独的进程中运行 如果 CMSampleBuffers 由 AV Foundation 创建 它们就会以某种形式自动生成 并从中得到优化 便于遍历 RPC 边界
在直接使用 VTDecompressionSession 时 能否获得优化 RPC 取决于 CMSampleBuffers 的生成方式 关于这一点 在我们稍后讨论生成 CMSampleBuffers 时会进一步说明
接下来 我想深入讲解术语表 首先是 CVPixelBuffers CVPixelBuffers 本质上是 围绕未压缩光栅图像数据块的包装器 它们的固定属性 包括像素格式、高度、宽度 行字节以及间距 同时 它们还可以携带描述图像数据的附件 例如颜色标签等等
其次是 CMBlockBuffer 该类型在 Core Media 框架中得到定义 这一基本类型负责包装任意数据块 通常是压缩样本数据
再次是 CMSampleBuffers CMSampleBuffers 拥有三大特色 第一 CMSampleBuffer 能够包装 含有压缩音频或视频数据的 CMBlockBuffer
第二 CMSampleBuffer 能够包装 含有未压缩光栅图像数据的 CVPixelBuffer
可以看到 两种类型的 CMSampleBuffers 都包含 CMTime 值 这些值描述了样本的呈现和解码时间戳 同时 它们还包含 CMFormatDescription 其中携带的信息 描述了 SampleBuffer 内的 数据格式
CMSampleBuffers 还可以携带附件 这就引出了 CMSampleBuffer 的 第三个类型 即标记 CMSampleBuffer 它并不包含 CMBlockBuffer 和 CVPixelBuffer 它的存在完全是为了 通过发送特定条件信号的媒体管道 携带定时附件
接下来 我们讲解 IOSurface IOSurface 是围绕一段内存的 非常聪明的抽象 经常应用于图像数据 之前 我们讨论了 CVPixelBuffer 中的光栅数据 那种光栅数据 通常就是以 IOSurface 的形式存在
此外 IOSurface 还可以用作该段内存的基础 或者应用于 Metal 纹理
IOSurface 能够帮助内存 在 Core Video 和 Metal 等 框架之间 在沙盒解码器和你的 app 等 进程之间 甚至是在不同的内存区域之间 有效移动 例如 在不同图形处理器的 VRAM 间进行转移
最后一个术语是 CVPixelBufferPool
CVPixelBufferPools 是来自 Core Video 的对象 允许视频管道有效回收 用于图像数据的缓冲区
在大多数场景中 CVPixelBuffers 会包装 IOSurfaces 当池分配的 CVPixelBuffer 得到释放 并不再使用时 IOSurface 将返回至 CVPixelBufferPool 以便池分配的下一个 CVPixelBuffer 能够重新使用该段内存 这意味着 CVPixelBufferPools 具有一些固定特性 例如 CVPixelBuffers 包含像素格式、高度 宽度、 行字节以及间距
现在 让我们进入正题 AV Foundation 如何帮助实现这一切 让我们回到原始问题 你拥有 ProRes 电影文件 以及优质的 Metal 渲染引擎 你希望将该电影中的帧添加进渲染器
AVAssetReader 就能够帮助你实现这一点 它会读取源文件内的样本 并进行优化 以便用于 将在 Video Toolbox 中发生的 RPC 它会在沙盒解码中解码视频数据 并以要求的输出格式提供解码后的 CVPixelBuffers
创建 AVAssetReader 非常简单 首先 通过 URL 将 AVAsset 创建进本地电影
之后 通过该 AVAsset 创建 AVAssetReader 但是它还不能加以使用
从 AVAssetReader 中请求解码数据 需要配置 AVAssetReaderTrackOutput
首先 我们需要获得视频轨道 在这里 我们获得了 电影中所有视频轨道的数组 之后 选择数组中的第一个轨道 选择轨道的逻辑可能会出现变化
现在 基于刚刚选择的视频轨道 创建 AVAssetReaderTrackOutput 在这一场景中 我选择配置输出 返回带有 alpha 的 16 位 4:4:4:4 YCbCr 或 Y416 Y416 是使用 ProRes 4444 内容时 可以应用的一种优质本机格式 接着 我们指示 AVAssetReaderTrackOutput 在返回样本时不进行复制 完成这一设置后 我们会获得最佳效率 这同时表明返回的 CMSampleBuffers 可能保留在任何位置 我们绝不能加以修改
最后 我们需要 将该输出添加进 AVAssetReader 运行 AVAssetReader 非常简单 在这里 我将展示它最简单的操作模式 首先 我们让它开始读取
之后 我们可以循环调用进 copyNextSampleBuffer 由于我们配置的目的是提供解码输出 因此 我们会为 CVImageBuffer 检查每个输出 CMSampleBuffer 我们将获得一些不包含图像缓冲区的 标记 CMSampleBuffers 这没有问题 使用 AVAssetReader 你可以设置时间范围 或者执行其他更高级的操作 而不是通过轨道完成这种简单的迭代 如果你的渲染器 能够以解码器本机的形式消耗缓冲区 那么你的视频管道将实现最佳效率 AVAssetReader 会将该解码器输出 从解码器本机格式 转换为你要求的输出格式 前提是解码器不支持你要求的格式 避免缓冲区副本 将大大提升你的 app 的效率 在这里 我要跟你分享一些指导方针 便于你选择 不会导致转换的输出像素格式 在之前的范例中 我们配置了 AVAssetReaderOutput 返回带有 alpha 的 16 位 4:4:4:4 YCbCr 或 Y416 内的 缓冲区 Y416 是使用 ProRes 4444 时的最佳格式
对于 ProRes 422 16 位 4:2:2 YCbCr 或 v216 是提出请求的最本机解码器格式 对于 ProRes RAW RGBA 半浮动或 RGhA 能够提供最本机的输出
有时 人们不希望 依赖 AVAssetReader 执行所有操作是有原因的 在这些场景中 你需要生成 CMSampleBuffers 直接提供给 Video Toolbox 完成这一步有三大选项 第一种是 就像我们刚刚描述的那样 可以使用 AVAssetReader 但是你可以要求它在未解码的情况下 提供压缩数据 这将提供轨道级别的媒体访问 包含对编辑日志和帧依赖关系的感知
第二种是 可以使用 AVSampleBufferGenerator 这可以提供媒体级别的样本访问 包含对编辑日志和帧依赖关系的感知 最后 你还可以自行构建 CMSampleBuffers
让 AVAssetReader 生成样本 会提供 从 AVAsset 中直接读取的压缩数据 AVAssetReader 会执行轨道级别的读取操作 这意味着它将提供在目标时间 显示帧所需的所有样本 包括处理编辑日志和帧依赖关系 此外 正如之前所提到的 AVAssetReader 将提供 为 RPC 进行优化的样本 从而在发送帧至 Video Toolbox 时 减少沙盒开销
为了从 AVAssetReader 获得原始压缩输出 你只需要和之前描述的那样 构建 AVAssetReader 但是在创建 AVAssetReaderTrackOutput 时 你需要将 outputSettings 设置为空值 而不是提供指定像素格式的代码字典
AVSampleBufferGenerator 可以提供 直接从 AVAssetTrack 内的媒体中 读取的样本 它使用 AVSampleBufferCursor 控制读取媒体轨道内的位置 它并不包含对帧依赖关系的固有感知 因此可以直接与 ProRes 一同使用 但是 在与 HEVC 和 H.264 等 带有帧间依赖关系的内容 一同使用该接口时 需要格外谨慎 这里简短的代码片段展示了创建 AVSampleBufferGenerator 的方法 首先 需要创建 AVSampleCursor 它将用于逐步样本 还需要创建 AVSampleBufferRequest 描述你将提出的实际样本请求
现在 你可以通过源 AVAsset 创建 AVSampleBufferGenerator
需要注意 在这里我将时基设为了空值 帮助实现同步操作 为在使用 AVSampleBufferGenerator 时 获得最佳性能 需要提供时基并异步运行请求
最后 循环调用进 createSampleBufferForRequest 并将光标一次向前移动一帧 同样 这是最简单的同步操作 为了实现最佳性能 我们需要使用请求的异步版本 最后 当你在执行文件读取 或是从互联网等其他来源 获取样本数据时 可以自行创建 CMSampleBuffers 需要注意 获得的样本数据 将不会为了遍历沙盒 RPC 进行优化 之前 我们谈论了 CMSampleBuffer 的组件 再次强调 CMBlockBuffer 包含 CMFormatDescription 和时间戳等数据 因此 首先你需要将样本数据打包进 CMBlockBuffer
之后 需要创建 CMVideoFormatDescription 描述数据 在这里 我们需要将颜色标签添加进 extensionsDictionary 从而确保视频能够实现良好的颜色管理
接着 你需要在 CMSampleTiming Info struct 中创建时间戳 最后 使用 CMBlockBuffer CMVideoFormatDescription 以及 CMSampleTimingInfo 创建 CMSampleBuffer
现在 你已经决定自行完成 也已经创建了 CMSampleBuffers 源 到 Video Toolbox 中 现在 让我们剖析下 VTDecompressionSession 当然 VTDecompressionSession 包含视频解码器 正如之前所说 该解码器会在单独的沙盒进程中运行
另外 该会话还包含 CVPixelBufferPool 用于创建解码视频帧的 输出缓冲区
最后 如果你请求的输出 格式与该解码器提供的格式不匹配 将需要 VTPixelTransferSession 执行所需转换 在你开始之前 如果你的 app 需要访问 为专业视频工作流分配的特定解码器组 app 就需要执行 VTRegisterProfessional- VideoWorkflowVideoDecoders 调用 这样的操作 app 只需执行一次 使用 VTDecompressionSession 的步骤 非常简单 首先 创建 VTDecompressionSession
其次 通过 VTSessionSetProperty 执行 VTDecompressionSession 所有必要配置 这一步有时并不需要
最后 开始使用调用发送帧 至 VTDecompressionSession- DecodeFrameWithOutputHandler 或 VTDecompressionSessionDecodeFrame
为了实现最佳性能 我们建议在 DecodeFrame 调用中 启用异步解码 让我们进一步学习创建 VTDecompressionSession 其中指定了三大选项 第一是 videoFormatDescription 它会告知 VTDecompressionSession 将使用的编解码器 并提供 CMSampleBuffers 内数据格式的 详细信息 它需要与你将发送至会话的 CMSampleBuffers CMVideoFormatDescription 相匹配
第二是 destinationImageBufferAttributes 它描述了输出 pixelBuffer 请求 如果你希望 Video Toolbox 将输出缩放至特定大小 可以将大小加入其中
如果渲染引擎提出要求 还可以包含特定 pixelFormat 如果你只知道如何使用 8 位 RGB 的样本 那么可以在这里提出请求
同时 这也可以是高级别指示 例如请求仅提供 Core Animation-compatible output
第三是 videoDecoderSpecification 它将提供影响解码器选择的因素的提示 在这里 你可以指定非默认硬件解码器请求
关于硬件解码器使用 正如之前所说 在当前 OS 版本中 硬件解码器使用在所有支持格式中 都会默认启用 这一点与几年前略有不同 当时为选择加入 在当前 OS 中 所有硬件加速的编解码器 均默认可用 无需选择加入 如果你希望确保 VTDecompressionSession 由硬件解码器创建 并且希望会话创建在不可能的情况下失败 那么你可以传递 Require- HardwareAcceleratedVideoDecoder 规范选项设为真
类似地 如果你希望禁用硬件解码 使用软件解码器 那么可以包含 EnableHardware- AcceleratedVideoDecoder 规范选项设为假 这两个密钥非常类似 因此再次强调 第一个密钥是 RequireHardware- AcceleratedVideoDecoder 第二个是 EnableHardware- AcceleratedVideoDecoder 这个样本显示了创建 VTDecompressionSession 的基础知识 首先 我们需要让 formatDescription 告知会话应该期待的数据类型 我们可以直接从之后会传至该会话的 CMSampleBuffer 中获取 如果你想要特定的输出 pixelFormat 就需要创建 pixelBufferAttributes dictionary 描述需求 因此 就像之前的 AVAssetReader 范例一样 我们请求带有 alpha 的 16 位 4:4:4:4 YCbCr 现在 我们可以创建 VTDecompressionSession 需要注意 我们为第三个参数 videoDecoderSpecification 传递 NULL NULL 意味着 Video Toolbox 将执行默认的硬件解码器选择
创建 VTDecompressionSession 后 DecodeFrame 调用就变得非常简单 正如之前所说 为了实现最佳性能 需要在 decodeFlag 中设置 kVTDecodeFrameEnable- AsynchronousDecompression 标志
基于块的 VTDecompressionSession- DecodeFrameWithOutputHandler 将获取压缩 sampleBuffer 控制解码器行为的 inFlags 以及将被调用的 包含解码操作结果的输出块 只要 VTDecompressionSession- DecodeFrameWithOutputHandler 调用不返回错误 输出块就将被调用 包含帧解码的结果 可能是 CVImageBuffer 也可能是解码器错误 让我们快速了解下解压输出 解码器输出为序列化 一次只能看到从解码器返回一个帧 如果阻塞解码器输出的内部 它就会有效堵塞随后的帧输出 最终会反过来导致解码器出现问题
为了实现最佳性能 需要确保处理和操作 在会话的输出块或回调之外完成
现在 让我们谈谈如何 通过 Metal 使用解码 CVPixelBuffers 在深入讲解之前 我们必须准确回顾 CVPixelBufferPool 的工作方式
正如之前所说 当 CVPixelBufferPool 分配的 CVPixelBuffer 得到释放时 它的 IOSurface 会返回至 CVPixelBufferPool 在池下一次分配 CVPixelBuffer 时 IOSurface 将被回收 并应用于新的 CVPixelBuffer
了解了这些 就容易理解 使用 CVPixelBuffers 和 Metal 时 会遇到的陷阱 我们需要确保 当 Metal 仍在使用 IOSurfaces 时 IOSurfaces 不会被回收
通过 Metal 使用 CVPixelBuffers 的方法主要有两种 一种是通过 IOSurface 的明显连接 CVPixelBuffer 包含 IOSurface 同时 Metal 了解 如何为了纹理使用 IOSurface 但是我们需要对这种路径额外注意
第二种路径是通过 Core Video 的 CVMetalTextureCache 它虽然相对不太明显 但通常使用起来既方便又安全 同时也可以提供一些性能优势
虽然直接通过 Metal 使用从 CVPixelBuffer 返回的 IOSurface 看上去非常简单 但当 Metal 仍在使用 IOSurface 时 要确保 CVPixelBufferPool 不会回收 IOSurface 也是有技巧的
要使用这种路径 首先需要 从 CVPixelBuffer 获得 IOSurface 之后就可以 通过该 IOSurface 创建 Metal 纹理
但是你需要确保 当 Metal 仍在 使用 IOSurface 时 CVPixelBufferPool 不会回收 IOSurface 因此 我们会使用 IOSurfaceIncrementUseCount 调用
为了在 Metal 使用完成后 将 IOSurface 释放回池中 我们需要设置 MTLCommandBuffer 完成处理程序 以便在 CommandBuffer 完成后加以运行 同时在这里 我们也会缩减 IOSurfaceUseCount
使用 CVMetalTextureCache 管理 CVPixelBuffer 和 MetalTexture 之间的接口可以简化操作 你无需手动追踪 IOSurfaces 和 IOSurfaceUseCounts
为了利用这一便利 首先需要创建 CVMetalTextureCache 你可以在这里 指定希望关联的 Metal 设备
现在 你可以调用 CVMetalTexture- CacheCreateTextureFromImage 创建关联 CVPixelBuffer 的 CVMetalTexture 对象
从 CVMetalTextureCache 获得实际的 Metal 纹理 只需要简单调用 CVMetalTextureGetTexture
最后需要牢记 再次强调 你必须在 Metal 指令缓冲区完成时 设置处理程序 否则就必须确保 在释放 CVMetalTexture 之前 Metal 已经处理了纹理
此外 当 CVPixelBufferPool 的 IOSurface 再次出现并重新使用时 CVMetalTextureCache 还可以 避免重复 IOSurface 纹理绑定 从而提高了效率
今天 我们谈论了几个话题 我们谈论了 获得硬件解码的时间及其控制方式 谈论了 AV Foundation 的 AVAssetReader 如何帮助更简单、更轻松地 将加速视频解码 与自定义渲染管道集成 谈论了当单独使用 AVAssetReader 不适用于你的使用场景时 如何构建 CMSampleBuffers 并通过 Video Toolbox 加以使用
最后 我们还了解了通过 Metal 使用 CVPixelBuffers 的最佳做法 希望今天所讲的内容 能够帮助你优化你令人赞叹的视频 app 感谢观看 希望 WWDC 能够让你有所收获
-
-
7:41 - Creating an AVAssetReader is pretty easy
// Constructing an AVAssetReader // Create an AVAsset with an URL pointing at a local asset AVAsset *sourceMovieAsset = [AVAsset assetWithURL:sourceMovieURL]; // Create an AVAssetReader for the asset AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:sourceMovieAsset error:&error];
-
7:58 - // Configuring AVAssetReaderTrackOutput
// Configuring AVAssetReaderTrackOutput // Copy the array of video tracks from the source movie NSArray<AVAssetTrack*> *tracks = [sourceMovieAsset tracksWithMediaType:AVMediaTypeVideo]; // Get the first video track AVAssetTrack *track = [sourceMovieVideoTracks objectAtIndex:0]; // Create the asset reader track output for this video track, requesting ‘y416’ output NSDictionary *outputSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_4444AYpCbCr16) }; AVAssetReaderTrackOutput* assetReaderTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:outputSettings]; // Set the property to instruct the track output to return the samples // without copying them assetReaderTrackOutput.alwaysCopiesSampleData = NO; // Connect the the AVAssetReaderTrackOutput to the AVAssetReader [assetReader addOutput:assetReaderTrackOutput];
-
8:57 - Running AVAssetReader
// Running AVAssetReader BOOL success = [assetReader startReading]; if (success) { CMSampleBufferRef sampleBuffer = NULL; // output is a AVAssetReaderOutput while ((sampleBuffer = [output copyNextSampleBuffer])) { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (imageBuffer) { // Use the image buffer here // if imageBuffer is NULL, this is likely a marker sampleBuffer } } }
-
11:40 - Prepareing CMSampleBuffers for optimized RPC transfer
AVAssetReaderTrackOutput* assetReaderTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:nil];
-
12:24 - How an AVSampleBufferGenerator is created
AVSampleCursor* cursor = [assetTrack makeSampleCursorAtFirstSampleInDecodeOrder]; AVSampleBufferRequest* request = [[AVSampleBufferRequest alloc] initWithStartCursor:cursor]; request.direction = AVSampleBufferRequestDirectionForward; request.preferredMinSampleCount = 1; request.maxSampleCount = 1; AVSampleBufferGenerator* generator = [[AVSampleBufferGenerator alloc] initWithAsset:srcAsset timebase:nil]; BOOL notDone = YES; while(notDone) { CMSampleBufferRef sampleBuffer = [generator createSampleBufferForRequest:request]; // do your thing with the sampleBuffer [cursor stepInDecodeOrderByCount:1]; }
-
13:40 - Pack your sample data into a CMBlockBuffer
CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, sampleData, sizeof(sampleData), kCFAllocatorMalloc, NULL, 0, sizeof(sampleData), 0, &blockBuffer); CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AppleProRes4444, 1920, 1080, extensionsDictionary, &formatDescription); CMSampleTimingInfo timingInfo; timingInfo.duration = CMTimeMake(10, 600); timingInfo.presentationTimeStamp = CMTimeMake(frameNumber * 10, 600); CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, formatDescription, 1, 1, &timingInfo, 1, &sampleSize, &sampleBuffer);
-
17:47 - VTDecompressionSession Creation
// VTDecompressionSession Creation CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); CFDictionaryRef pixelBufferAttributes = (__bridge CFDictionaryRef)@{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_4444AYpCbCr16) }; VTDecompressionSessionRef decompressionSession; OSStatus err = VTDecompressionSessionCreate(kCFAllocatorDefault, formatDesc, NULL, pixelBufferAttributes, NULL, &decompressionSession);
-
18:30 - Running a VTDecompressionSession
// Running a VTDecompressionSession uint32_t inFlags = kVTDecodeFrame_EnableAsynchronousDecompression; VTDecompressionOutputHandler outputHandler = ^(OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDurationVTDecodeInfoFlags) { // Handle decoder output in this block // Status reports any decoder errors // imageBuffer contains the decoded frame if there were no errors }; VTDecodeInfoFlags outFlags; OSStatus err = VTDecompressionSessionDecodeFrameWithOutputHandler(decompressionSession, sampleBuffer, inFlags, &outFlags, outputHandler);
-
20:54 - CVPixelBuffer to Metal texture: IOSurface
// CVPixelBuffer to Metal texture: IOSurface IOSurfaceRef surface = CVPixelBufferGetIOSurface(imageBuffer); id <MTLTexture> metalTexture = [metalDevice newTextureWithDescriptor:descriptor iosurface:surface plane:0]; // Mark the IOSurface as in-use so that it won’t be recycled by the CVPixelBufferPool IOSurfaceIncrementUseCount(surface); // Set up command buffer completion handler to decrement IOSurface use count again [cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { IOSurfaceDecrementUseCount(surface); }];
-
21:42 - Create a CVMetalTextureCacheRef
// Create a CVMetalTextureCacheRef CVMetalTextureCacheRef metalTextureCache = NULL; id <MTLDevice> metalDevice = MTLCreateSystemDefaultDevice(); CVMetalTextureCacheCreate(kCFAllocatorDefault, NULL, metalDevice, NULL, &metalTextureCache); // Create a CVMetalTextureRef using metalTextureCache and our pixelBuffer CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, metalTextureCache, pixelBuffer, NULL, pixelFormat, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer), 0, &cvTexture); id <MTLTexture> texture = CVMetalTextureGetTexture(cvTexture); // Be sure to release the cvTexture object when the Metal command buffer completes!
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。