TextureView 类是一个将视图与 SurfaceTexture 结合在一起的视图对象。
使用 OpenGL ES 渲染
TextureView 对象封装了 SurfaceTexture,用于响应回调并获取新缓冲区。当 TextureView 获取新缓冲区时,TextureView 会发出视图失效请求,并使用最新缓冲区的内容作为其数据源进行绘制,从而在视图状态指示的位置和方式进行渲染。
OpenGL ES (GLES)可以通过将 SurfaceTexture 传递给 EGL 创建调用来在 TextureView 上渲染,但这会产生一个问题。当 GLES 在 TextureView 上渲染时,BufferQueue 生产者和消费者位于同一线程中,这可能会导致缓冲区交换调用停滞或失败。例如,如果生产者从 UI 线程快速连续提交多个缓冲区,则 EGL 缓冲区交换调用需要从 BufferQueue 中出列一个缓冲区。但是,由于消费者和生产者在同一线程上,因此不会有任何可用的缓冲区,并且交换调用会挂起或失败。
为了确保缓冲区交换不会停滞,BufferQueue 始终需要有一个可用的缓冲区用于出列。为了实现这一点,当新缓冲区入队时,BufferQueue 会丢弃先前获取的缓冲区的内容,并对最小和最大缓冲区计数施加限制,以防止消费者一次性消耗所有缓冲区。
选择 SurfaceView 或 TextureView
SurfaceView 和 TextureView 填充类似的角色,并且都是视图层次结构的组成部分。但是,SurfaceView 和 TextureView 具有不同的实现。SurfaceView 采用与其他视图相同的参数,但 SurfaceView 内容在渲染时是透明的。
TextureView 比 SurfaceView 具有更好的 Alpha 和旋转处理能力,但当合成叠加在视频上的 UI 元素时,SurfaceView 具有性能优势。当客户端使用 SurfaceView 渲染时,SurfaceView 为客户端提供单独的合成层。如果设备支持,SurfaceFlinger 会将单独的层合成为硬件叠加层。当客户端使用 TextureView 渲染时,UI 工具包会将 TextureView 的内容与 GPU 合成到视图层次结构中。对内容的更新可能会导致其他视图元素重绘,例如,如果其他视图位于 TextureView 的顶部。在视图渲染完成后,SurfaceFlinger 会合成应用 UI 层和所有其他层,以便每个可见像素都被合成两次。
案例分析:Grafika 的“播放视频”
Grafika 的“播放视频”包含一对视频播放器,一个使用 TextureView 实现,另一个使用 SurfaceView 实现。该活动的视频解码部分将来自 MediaCodec 的帧发送到 TextureView 和 SurfaceView 的表面。实现之间最大的区别是呈现正确纵横比所需的步骤。
缩放 SurfaceView 需要自定义 FrameLayout 实现。WindowManager 需要向 SurfaceFlinger 发送新的窗口位置和新的尺寸值。缩放 TextureView 的 SurfaceTexture 需要使用 TextureView#setTransform()
配置变换矩阵。
在呈现正确的纵横比之后,两种实现都遵循相同的模式。当 SurfaceView/TextureView 创建表面时,应用代码会启用播放。当用户点按播放时,它会启动视频解码线程,并将表面作为输出目标。之后,应用代码什么都不做,合成和显示由 SurfaceFlinger(对于 SurfaceView)或 TextureView 处理。
案例分析:Grafika 的“双重解码”
Grafika 的“双重解码”演示了 TextureView 内部 SurfaceTexture 的操作。
Grafika 的“双重解码”使用一对 TextureView 对象来并排显示两个正在播放的视频,模拟视频会议应用。当屏幕方向改变且活动重启时,MediaCodec 解码器不会停止,从而模拟实时视频流的播放。为了提高效率,客户端应保持表面的活动状态。表面是 SurfaceTexture 的 BufferQueue 中生产者接口的句柄。由于 TextureView 管理 SurfaceTexture,因此客户端需要保持 SurfaceTexture 的活动状态才能保持表面的活动状态。
为了保持 SurfaceTexture 的活动状态,Grafika 的“双重解码”从 TextureView 对象获取对 SurfaceTexture 的引用,并将它们保存在静态字段中。然后,Grafika 的“双重解码”从 TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
返回 false
,以防止 SurfaceTexture 被销毁。然后,TextureView 将 SurfaceTexture 传递给 onSurfaceTextureDestroyed()
,该 SurfaceTexture 可以在活动配置更改中保持,客户端通过 setSurfaceTexture()
将其传递给新的 TextureView。
单独的线程驱动每个视频解码器。Mediaserver 将带有解码输出的缓冲区发送到 SurfaceTextures(BufferQueue 消费者)。TextureView 对象执行渲染并在 UI 线程上执行。
使用 SurfaceView 实现 Grafika 的“双重解码”比使用 TextureView 实现更难,因为 SurfaceView 对象会在方向更改期间销毁表面。此外,使用 SurfaceView 对象会增加两个层,这并不理想,因为硬件上可用的叠加层数量有限制。