TextureView

TextureView クラスは、ビューを SurfaceTexture と組み合わせたビュー オブジェクトです。

OpenGL ES とのレンダリング

TextureView オブジェクトは、SurfaceTexture をラップし、コールバックに応答して新しいバッファを取得します。新しいバッファを取得した 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 よりもアルファ版と回転の処理が優れていますが、SurfaceView には UI 要素を動画に合成した場合にパフォーマンス上のメリットがあります。クライアントが SurfaceView でレンダリングすると、SurfaceView はクライアントに別の合成レイヤを提供します。SurfaceFlinger は、デバイスがサポートしている場合、ハードウェアのオーバーレイとして別のレイヤを作成します。クライアントが TextureView でレンダリングすると、UI ツールキットは TextureView のコンテンツを GPU でビュー階層に合成します。コンテンツを更新すると、他のビュー要素が再描画されることがあります(他のビューが TextureView の上に配置されている場合など)。ビューのレンダリングが完了すると、SurfaceFlinger はアプリの UI レイヤと他のすべてのレイヤを合成し、すべての可視ピクセルが 2 回合成されるようにします。

ケーススタディ: Grafika の Play Video

Grafika の Play Video には、TextureView で実装された動画プレーヤーと SurfaceView で実装された動画プレーヤーのペアが含まれます。アクティビティの動画デコード部分は、MediaCodec から TextureView と SurfaceView の両方のサーフェスにフレームを送信します。実装における最大の違いは、正しいアスペクト比の提示に必要な手順です。

SurfaceView のスケーリングには、FrameLayout のカスタム実装が必要です。 WindowManager は、SurfaceFlinger に新しいウィンドウの位置と新しいサイズ値を送信する必要があります。TextureView の SurfaceTexture をスケーリングするには、変換行列を TextureView#setTransform() で設定する必要があります。

正しいアスペクト比を提示した後、両方の実装は同じパターンに従います。SurfaceView / TextureView でサーフェスを作成すると、アプリのコードによる再生が可能になります。ユーザーが [再生] をタップすると、サーフェスを出力先として、動画のデコード スレッドが開始します。その後、アプリのコードは何もしません。合成と表示は SurfaceFlinger(SurfaceView の場合)または TextureView によって処理されます。

ケーススタディ: Grafika の Double Decode

Grafika の Double Decode は、TextureView 内の SurfaceTexture の操作を示しています。

Grafika の Double Decode では、TextureView オブジェクトのペアを使用して 2 本の動画を並べて表示し、ビデオ会議アプリをシミュレートします。画面の向きが変わり、アクティビティが再開すると、MediaCodec デコーダは停止せず、リアルタイムの動画ストリームの再生をシミュレートします。効率を上げるために、クライアントはサーフェスを有効な状態に保つ必要があります。サーフェスは、SurfaceTexture の BufferQueue にあるプロデューサー インターフェースへのハンドルです。TextureView は SurfaceTexture を管理するため、クライアントは SurfaceTexture を維持してサーフェスを有効な状態に保つ必要があります。

SurfaceTexture を有効な状態に維持するために、Grafika の Double Decode は TextureView オブジェクトから SurfaceTexture への参照を取得し、それらを静的フィールドに保存します。次に、Grafika の Double Decode は、falseTextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() から返し、SurfaceTexture の破壊を防ぎます。その後、TextureView は SurfaceTexture をアクティビティの設定変更全体で維持できる onSurfaceTextureDestroyed() に渡し、さらにクライアントが setSurfaceTexture() を介して新しい TextureView に渡します。

個別のスレッドが、各動画デコーダを駆動します。Mediaserver は、デコードされた出力を含むバッファを SurfaceTexture、BufferQueue の消費者に送信します。TextureView オブジェクトはレンダリングを行い、UI スレッドで実行します。

Grafika の Double Decode を SurfaceView で実装するのは、TextureView で実装するよりも困難です。SurfaceView オブジェクトが、向きの変更中にサーフェスを破棄するためです。さらに、SurfaceView オブジェクトを使用すると、2 つのレイヤが追加されますが、ハードウェアで使用できるオーバーレイの数に制限があるため、理想的ではありません。