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 对象会添加两个层,这并不理想,因为硬件上可用叠加层的数量存在限制。