SurfaceTexture

SurfaceTexture 是 surface 和 OpenGL ES (GLES) 纹理的组合。SurfaceTexture 实例用于提供输出到 GLES 纹理的 surface。

SurfaceTexture 包含 BufferQueue 的一个实例,应用是该实例的消费者。当生产者将新缓冲区排队时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage(),该调用会释放先前持有的缓冲区,从队列中获取新缓冲区,并进行 EGL 调用以使缓冲区作为外部纹理提供给 GLES。

外部 GLES 纹理

外部 GLES 纹理 (GL_TEXTURE_EXTERNAL_OES) 与传统 GLES 纹理 (GL_TEXTURE_2D) 在以下方面有所不同

  • 外部纹理直接从 BufferQueue 收到的数据渲染纹理多边形。
  • 外部纹理渲染器的配置方式与传统 GLES 纹理渲染器不同。
  • 外部纹理无法执行所有传统 GLES 纹理活动。

外部纹理的主要优势在于它们能够直接从 BufferQueue 数据进行渲染。当 SurfaceTexture 实例为外部纹理创建 BufferQueue 实例时,会将消费者使用标志设置为 GRALLOC_USAGE_HW_TEXTURE,以确保缓冲区中的数据可被 GLES 识别。

由于 SurfaceTexture 实例与 EGL 上下文交互,因此应用只能在其方法被调用的线程上,在拥有纹理的 EGL 上下文为当前上下文时调用这些方法。有关详情,请参阅 SurfaceTexture 类文档。

时间戳和转换

SurfaceTexture 实例包括 getTimeStamp() 方法(用于检索时间戳)和 getTransformMatrix() 方法(用于检索转换矩阵)。调用 updateTexImage() 会同时设置时间戳和转换矩阵。BufferQueue 传递的每个缓冲区都包含转换参数和时间戳。

转换参数对于提高效率非常有用。在某些情况下,源数据对于消费者而言可能方向不正确。与其在将数据发送给消费者之前旋转数据,不如以其原始方向发送数据,并使用转换来更正它。当使用数据时,转换矩阵可以与其他转换合并,从而最大限度地减少开销。

时间戳对于时间相关的缓冲区源非常有用。例如,当 setPreviewTexture() 将生产者接口连接到相机的输出时,来自相机的帧可用于创建视频。每个帧都需要具有帧捕获时的呈现时间戳,而不是应用接收帧时的时间戳。相机代码设置缓冲区提供的时间戳,从而产生更一致的时间戳序列。

案例研究:Grafika 的连续捕获

Grafika 的连续捕获 涉及录制设备相机中的帧,并将这些帧显示在屏幕上。要录制帧,请使用 MediaCodec 类的 createInputSurface() 方法创建一个 surface,并将该 surface 传递给相机。要显示帧,请创建 SurfaceView 的一个实例,并将该 surface 传递给 setPreviewDisplay()。请注意,同时录制和显示帧是一个更复杂的过程。

持续捕获 活动会在录制视频的同时显示来自摄像头的视频。在这种情况下,编码后的视频会被写入内存中的循环缓冲区,可以随时保存到磁盘。

此流程涉及三个缓冲区队列

  • App — 应用使用 SurfaceTexture 实例接收来自摄像头的帧,并将其转换为外部 GLES 纹理。
  • SurfaceFlinger — 应用声明一个 SurfaceView 实例来显示帧。
  • MediaServer — 配置一个带有输入界面的 MediaCodec 编码器以创建视频。

在下图中,箭头指示来自摄像头的数据传播。BufferQueue 实例以彩色显示(生产者为青色,消费者为绿色)。

Grafika continuous
capture activity

图 1. Grafika 的持续捕获活动

编码后的 H.264 视频进入应用进程 RAM 中的循环缓冲区。当用户按下捕获按钮时,MediaMuxer 类会将编码后的视频写入磁盘上的 MP4 文件。

所有 BufferQueue 实例都在应用中的单个 EGL 上下文中处理,而 GLES 操作在 UI 线程上执行。编码数据的处理(管理循环缓冲区并将其写入磁盘)在单独的线程上完成。

当使用 SurfaceView 类时,surfaceCreated() 回调会为显示和视频编码器创建 EGLContextEGLSurface 实例。当新帧到达时,SurfaceTexture 执行四项活动
  1. 获取帧。
  2. 使帧作为 GLES 纹理可用。
  3. 使用 GLES 命令渲染帧。
  4. 为每个 EGLSurface 实例转发变换和时间戳。

然后,编码器线程从 MediaCodec 中拉取编码后的输出并将其存储在内存中。

安全纹理视频播放

Android 支持对受保护的视频内容进行 GPU 后处理。这使应用可以将 GPU 用于复杂的非线性视频效果(例如扭曲)、将受保护的视频内容映射到纹理以在通用图形场景中使用(例如,使用 GLES)以及虚拟现实 (VR)。

Secure Texture Video Playback

图 2. 安全纹理视频播放

通过以下两个扩展启用支持

  • EGL 扩展 — (EGL_EXT_protected_content) 启用受保护的 GL 上下文和表面的创建,它们都可以对受保护的内容进行操作。
  • GLES 扩展 — (GL_EXT_protected_textures) 启用将纹理标记为受保护,以便可以将它们用作帧缓冲区纹理附件。

即使窗口的表面不排队到 SurfaceFlinger,Android 也能使 SurfaceTexture 和 ACodec (libstagefright.so) 发送受保护的内容,并提供受保护的视频表面以在受保护的上下文中使用。这是通过在受保护的上下文中创建的表面上设置受保护的消费者位 (GRALLOC_USAGE_PROTECTED) 来完成的(由 ACodec 验证)。

安全纹理视频播放为 OpenGL ES 环境中强大的 DRM 实现奠定了基础。如果没有强大的 DRM 实现(例如 Widevine Level 1),许多内容提供商不允许在 OpenGL ES 环境中渲染其高价值内容,从而阻止了重要的 VR 用例,例如在 VR 中观看受 DRM 保护的内容。

AOSP 包含用于安全纹理视频播放的框架代码。驱动程序支持取决于 OEM。设备实现者必须实现 EGL_EXT_protected_contentGL_EXT_protected_textures extensions。当使用您自己的编解码器库(以替换 libstagefright)时,请注意 /frameworks/av/media/libstagefright/SurfaceUtils.cpp 中的更改,这些更改允许将标记有 GRALLOC_USAGE_PROTECTED 的缓冲区发送到 ANativeWindow(即使 ANativeWindow 不直接排队到窗口合成器),只要消费者使用位包含 GRALLOC_USAGE_PROTECTED 即可。有关实现扩展的详细文档,请参阅 Khronos 注册表 (EGL_EXT_protected_contentGL_EXT_protected_textures)。