TextureView

TextureView 类是一个结合了 View 和 SurfaceTexture 的 View 对象。

使用 OpenGL ES 呈现

TextureView 对象会对 SurfaceTexture 进行包装,从而响应回调以及获取新的缓冲区。在 TextureView 获取新的缓冲区时,TextureView 会发出 View 失效请求,并使用最新缓冲区的内容作为数据源进行绘图,根据 View 状态的指示,以相应的方式在相应的位置进行呈现。

OpenGL ES (GLES) 可以将 SurfaceTexture 传递到 EGL 创建调用,从而在 TextureView 上呈现内容,但这样会引发问题。当 GLES 在 TextureView 上呈现内容时,BufferQueue 生产方和使用方位于同一线程中,这可能导致缓冲区交换调用暂停或失败。例如,如果生产方以快速连续的方式从界面线程提交多个缓冲区,则 EGL 缓冲区交换调用需要使一个缓冲区从 BufferQueue 出列。不过,由于使用方和生产方位于同一线程中,因此不存在任何可用的缓冲区,而且交换调用会挂起或失败。

为了确保缓冲区交换不会停止,BufferQueue 始终需要有一个可用的缓冲区能够出列。为了实现这一点,BufferQueue 在新缓冲区加入队列时舍弃之前加入队列的缓冲区的内容,并对最小缓冲区计数和最大缓冲区计数施加限制,以防使用方一次性消耗所有缓冲区。

选择 SurfaceView 或 TextureView

SurfaceView 和 TextureView 扮演的角色类似,且都是视图层次结构的组成部分。不过,SurfaceView 和 TextureView 拥有截然不同的实现。SurfaceView 采用与其他 View 相同的参数,但 SurfaceView 内容在呈现时是透明的。

与 SurfaceView 相比,TextureView 具有更出色的 Alpha 版和旋转处理能力,但在视频上以分层方式合成界面元素时,SurfaceView 具有性能方面的优势。当客户端使用 SurfaceView 呈现内容时,SurfaceView 会为客户端提供单独的合成层。如果设备支持,SurfaceFlinger 会将单独的层合成为硬件叠加层。当客户端使用 TextureView 呈现内容时,界面工具包会使用 GPU 将 TextureView 的内容合成到视图层次结构中。对内容进行的更新可能会导致其他 View 元素重绘,例如,在其他 View 被置于 TextureView 顶部时。View 呈现完成后,SurfaceFlinger 会合成应用界面层和所有其他层,以便每个可见像素合成两次。

案例研究:Grafika 的视频播放

Grafika 的视频播放包括一对视频播放器,一个用 TextureView 实现,另一个用 SurfaceView 实现。对于 TextureView 和 SurfaceView 而言,activity 的视频解码部分会将帧从 MediaCodec 发送到 Surface。这两种实现之间最大的区别是呈现正确宽高比所需的步骤。

缩放 SurfaceView 需要 FrameLayout 的自定义实现。 WindowManager 需要向 SurfaceFlinger 发送新的窗口位置和尺寸值。缩放 TextureView 的 SurfaceTexture 需要使用 TextureView#setTransform() 配置转换矩阵。

在呈现正确的宽高比之后,两种实现均遵循相同的模式。当 SurfaceView/TextureView 创建 Surface 时,应用代码会启用播放。当用户点按播放时,系统会启动视频解码线程,并将 Surface 作为输出目标。之后,应用代码不需要执行任何操作,SurfaceFlinger(适用于 SurfaceView)或 TextureView 会处理合成和显示。

案例研究:Grafika 的双重解码

Grafika 的双重解码演示了在 TextureView 中对 SurfaceTexture 的操控。

Grafika 的双重解码会使用一对 TextureView 对象显示两个并排播放的视频,模拟视频会议应用。当屏幕方向发生变化且 Activity 重启时,MediaCodec 解码器不会停止,而是模拟实时视频串流的播放。为了提高效率,客户端应该确保 Surface 保持活动状态。Surface 是 SurfaceTexture 的 BufferQueue 中生产方接口的句柄。由于 TextureView 管理着 SurfaceTexture,因此客户端需要使 SurfaceTexture 保持活动状态,才能使 Surface 保持活动状态。

为了使 SurfaceTexture 保持活动状态,Grafika 的双重解码会从 TextureView 对象中获取对 SurfaceTexture 的引用,并会将它们保存在静态字段中。 然后,Grafika 的双重解码会从 TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() 返回 false,以防 SurfaceTexture 遭到破坏。然后,TextureView 会将 SurfaceTexture 传递到可以在 Activity 配置更改期间保持不变的 onSurfaceTextureDestroyed(),而客户端通过 setSurfaceTexture() 将其传递到新的 TextureView。

单独的线程驱动各个视频解码器。Mediaserver 将具有解码输出的缓冲区发送给 BufferQueue 使用方,即 SurfaceTexture。TextureView 对象执行呈现,并在界面线程上执行。

在实现 Grafika 的双重解码方面,使用 SurfaceView 比使用 TextureView 更难,因为 SurfaceView 对象会在方向发生变化时破坏 Surface。此外,使用 SurfaceView 对象会添加两个层,这并不理想,因为硬件上可用叠加层的数量存在限制。