| <html devsite><head> |
| <title>TextureView</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p>我们在 Android 4.0 中引入了 TextureView 类,它结合了 View 与 SurfaceTexture,是我们在此讨论的最复杂的 View 对象。</p> |
| |
| <h2 id="render_gles">使用 GLES 呈现</h2> |
| <p>我们已经知道,SurfaceTexture 是一个“GL 消费者”,它会占用图形数据的缓冲区,并将它们作为纹理进行提供。TextureView 会对 SurfaceTexture 进行封装,并接管对回调做出响应以及获取新缓冲区的责任。新缓冲区的就位会导致 TextureView 发出 View 失效请求。当被要求进行绘图时,TextureView 会使用最近收到的缓冲区的内容作为数据源,并根据 View 状态的指示,以相应的方式在相应的位置进行呈现。</p> |
| |
| <p>您可以使用 GLES 在 TextureView 上呈现内容,就像在 SurfaceView 上一样。只需将 SurfaceTexture 传递到 EGL 窗口创建调用即可。不过,这样做会导致潜在问题。</p> |
| |
| <p>在我们看到的大部分内容中,BufferQueue 是在不同进程之间传递缓冲区。当使用 GLES 呈现到 TextureView 时,生产者和消费者处于同一进程中,它们甚至可能会在单个线程上得到处理。假设我们以快速连续的方式从界面线程提交多个缓冲区。EGL 缓冲区交换调用需要使一个缓冲区从 BufferQueue 出列,而在有可用的缓冲区之前,它将处于暂停状态。只有当消费者获取一个缓冲区用于呈现时才会有可用的缓冲区,但是这一过程也会发生在界面线程上…因此我们陷入了困境。</p> |
| |
| <p>解决方案是让 BufferQueue 确保始终有一个可用的缓冲区能够出列,以使缓冲区交换始终不会暂停。要保证能够实现这一点,一种方法是让 BufferQueue 在新缓冲区加入队列时舍弃之前加入队列的缓冲区的内容,并对最小缓冲区计数和最大获取缓冲区计数施加限制(如果您的队列有三个缓冲区,而所有这三个缓冲区均被消费者获取,那么就没有可以出列的缓冲区,缓冲区交换调用必然会暂停或失败。因此我们需要防止消费者一次获取两个以上的缓冲区)。丢弃缓冲区通常是不可取的,因此仅允许在特定情况下发生,例如生产者和消费者处于同一进程中时。</p> |
| |
| <h2 id="surface_or_texture">SurfaceView 还是 TextureView?</h2>SurfaceView 和 TextureView 扮演的角色类似,但是拥有截然不同的实现。要作出最合适的选择,则需要了解它们各自的利弊。<p></p> |
| |
| <p>因为 TextureView 是 View 层次结构的固有成员,所以其行为与其他所有 View 一样,可以与其他元素相互叠加。您可以执行任意转换,并通过简单的 API 调用将内容检索为位图。</p> |
| |
| <p>影响 TextureView 的主要因素是合成步骤的表现。使用 SurfaceView 时,内容可以写到 SurfaceFlinger(理想情况下使用叠加层)合成的独立分层中。使用 TextureView 时,View 合成往往使用 GLES 执行,并且对其内容进行的更新也可能会导致其他 View 元素重绘(例如,如果它们位于 TextureView 上方)。View 呈现完成后,应用界面层必须由 SurfaceFlinger 与其他分层合成,以便您可以高效地将每个可见像素合成两次。对于全屏视频播放器,或任何其他相当于位于视频上方的界面元素的应用,SurfaceView 可以带来更好的效果。</p> |
| |
| <p>如之前所述,受 DRM 保护的视频只能在叠加平面上呈现。支持受保护内容的视频播放器必须使用 SurfaceView 进行实现。</p> |
| |
| <h2 id="grafika">案例研究:Grafika 的视频播放 (TextureView)</h2> |
| |
| <p>Grafika 包括一对视频播放器,一个用 TextureView 实现,另一个用 SurfaceView 实现。对于这两个视频播放器来说,仅将帧从 MediaCodec 发送到 Surface 的视频解码部分是一样的。这两种实现之间最有趣的区别是呈现正确宽高比所需的步骤。</p> |
| |
| <p>SurfaceView 需要 FrameLayout 的自定义实现,而要重新调整 SurfaceTexture 的大小,只需使用 <code>TextureView#setTransform()</code> 配置转换矩阵即可。对于前者,您会通过 WindowManager 向 SurfaceFlinger 发送新的窗口位置和大小值;对于后者,您仅仅是在以不同的方式呈现它。</p> |
| |
| <p>否则,两种实现均遵循相同的模式。创建 Surface 后,系统会启用播放。点击“播放”时,系统会启动视频解码线程,并将 Surface 作为输出目标。之后,应用代码不需要执行任何操作,SurfaceFlinger(适用于 SurfaceView)或 TextureView 会处理合成和显示。</p> |
| |
| <h2 id="decode">案例研究:Grafika 的双重解码</h2> |
| |
| <p>此操作组件演示了在 TextureView 中对 SurfaceTexture 的操控。</p> |
| |
| <p>此操作组件的基本结构是一对显示两个并排播放的不同视频的 TextureView。为了模拟视频会议应用的需求,我们希望在操作组件因屏幕方向发生变化而暂停和恢复时,MediaCodec 解码器能保持活动状态。原因在于,如果不对 MediaCodec 解码器使用的 Surface 进行完全重新配置,就无法更改它,而这是成本相当高的操作;因此我们希望 Surface 保持活动状态。Surface 只是 SurfaceTexture 的 BufferQueue 中生产者界面的句柄,而 SurfaceTexture 由 TextureView 管理;因此我们还需要 SurfaceTexture 保持活动状态。那么我们如何处理 TextureView 被关闭的情况呢?</p> |
| |
| <p>TextureView 提供的 <code>setSurfaceTexture()</code> 调用正好能够满足我们的需求。我们从 TextureView 获取对 SurfaceTexture 的引用,并将它们保存在静态字段中。当操作组件被关闭时,我们从 <code>onSurfaceTextureDestroyed()</code> 回调返回“false”,以防止 SurfaceTexture 被销毁。当操作组件重新启动时,我们将原来的 SurfaceTexture 填充到新的 TextureView 中。TextureView 类负责创建和破坏 EGL 上下文。</p> |
| |
| <p>每个视频解码器都是从单独的线程驱动的。乍一看,我们似乎需要每个线程的本地 EGL 上下文;但请注意,具有解码输出的缓冲区实际上是从 mediaserver 发送给我们的 BufferQueue 消费者 (SurfaceTexture)。TextureView 会为我们处理呈现,并在界面线程上执行。</p> |
| |
| <p>使用 SurfaceView 实现该操作组件可能较为困难。我们不能只创建一对 SurfaceView 并将输出引导至它们,因为 Surface 在屏幕方向改变期间会被销毁。此外,这样做会增加两个层,而由于可用叠加层的数量限制,我们不得不尽量将层数量减到最少。与上述方法不同,我们希望创建一对 SurfaceTexture,以从视频解码器接收输出,然后在应用中执行呈现,使用 GLES 将两个纹理间隙呈现到 SurfaceView 的 Surface。</p> |
| |
| </body></html> |