88e848bfd791e541a5692c2d9c69a085
WWDC20 10090 - 使用 AVFoundation 和 VideoToolBox 做视频处理

Session: https://developer.apple.com/videos/play/wwdc2020/10090/

前言

AVFoundation 可能是移动端最强的视频处理框架,这一点做过音视频的同学、尤其是跨平台框架的同学应该会有深刻的感触。

这个 Session 虽然是在 macOS 系统中解码 ProRes 视频,但其实内容上介绍了基于 AVFoundation 的视频处理 pipeline,基本上提到的所有技术在 iOS 上都可以直接使用。所以我简单改了一下标题。建议搞音视频的同学都可以看看。

目标

本文介绍的视频处理管线的目标很简单:把视频文件解码并送给渲染引擎进行处理或者渲染。具体来看,主要包含以下两点内容:

  1. 如何能够使用硬件来加速解码(比如 macOS 的 Afterburner 卡)
  2. 如何优化压缩前后 buffer 的使用

文章结构

本文会基于如下结构来描述:

  1. 视频处理概述
  2. 使用 AVFoundation
  3. 获取解压前的数据
  4. 使用 VTDecompressionSession
  5. 使用 CVPixelBuffer 和 Metal 交互

概述

*OS 的视频解码器,往往都跑在独立的沙盒进程中,这样有两个好处:

  1. 增加了安全性。通过限制沙盒进程的权限,可以避免视频数据中掺杂恶意数据来损坏系统。
  2. 增加了稳定性,如果解码器崩溃了,用户进程也不会崩溃,只是接收到解码失败的信息。

接下来,介绍一下 *OS 的视频处理技术栈。最上层是 AVKit,提供了最易于接入的 API,往下一层是 AVFoundation。由于这篇文章 focus 的是我们如何把视频解码集成到我们自己的渲染管线,所以本文不会关注 AVKit。AVFoundation 的下一层就是视频的编解码框架:VideoToolbox, 该框架提供了 low level 的编解码 API。再下一层是 CoreMedia,提供了媒体处理的封装,CoreMedia 基于 CoreVideo 和 CoreAudio 框架实现。这里我们主要还是 focus 在视频的处理。

根据目标章节,今天我们的内容主要是介绍 AVFoundation、VideoToolbox 和 CoreVideo 中的相关类的使用。

这些类都和解码有关,这就带来了两个问题:

  1. 如何使用硬件加速?
  2. 既然解码器是独立进程,RPC 的性能损耗如何解决?

既然 AVFoundation 是世界上最好的音视频处理框架,这些自然不用开发者担心。如下图所示。以下几个类都会自动使用硬件加速。并且默认会进行 RPC 的优化,除了 VTDecompressionSession的部分场景(主要是你的 CMSampleBuffer 是怎么创建的),需要开发者自己处理 RPC(后续会讲到)

视频核心数据结构

CVPixelBuffer

CVPixelBuffer 类似Android 的 bitmap,核心是封装了已经解压后的图像数据。保存了像素的 format,图像宽高和 buffer 指针等信息。

CMBlockBuffer

可以封装任意类型的二进制数据,一般用来存储压缩后的媒体数据。

CMSampleBuffer

CMSampleBuffer 有两种形式,一种是包含了压缩后的 CMBlockBuffer,一种是包含了未解压状态的 CVPixelBuffer。除了数据区外,还包含了两个视频帧的重要属性,一个是图像在视频中的 PTS:CMTime,以及对应的格式描述:CMFormatDesc.

其实还有第三种 CMSampleBuffer,即只包含 flag。不包含具体的数据,但一般用的比较少。

IOSurface

这玩意儿就值得大家认真看一下了。官方的说法,IOSurface 用来抽象一块内存区域。CVPixelBuffer 中的图像数据一般都是用 IOSurface 来表示的,同样,MTLTexture 中也是。为什么这玩意儿牛逼呢? 可以理解成,IOSurface 是不同框架、设备中交换图像数据的高速公路,这种交换存在于以下几种场景:

  1. 不同框架之间:CoreVideo 和 Metal
  2. 不同进程之间:Decoder 进程和 App 进程
  3. 不同的内存区:显存与内存。

最常见的用法是,在你通过 Metal 或者 OpenGL 进行特效渲染后,可以通过 Zero Copy的形式在 CPU 上直接访问你处理完毕之后的数据。


CVPixelBufferPool

CVPixelBufferPool,主要是实现了 CVPixelBuffer 中的 IOSurface 的复用与回收,这里不做过多介绍。主要也是从性能和效率的角度考虑。

无所不能的 AVFoundation

读取一个视频,并访问每一帧的数据,最好的帮手就是使用 AVAssetReader, 他不仅可以高效的完成解封装解码,还能优化 App 进程和 Decoder 进程的 RPC 调用,最终给用户直接访问 CVPixelBuffer。如下图所示。

接下来,通过一段代码来演示一下如何使用 AVAssetReader。大概有以下几个步骤

  1. 通过文件的 URL 构建 AVAsset 对象;
  2. 从 AVAsset 创建 AVAssetReader
top Created with Sketch.