2024年3月12日发(作者:)

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

SurfaceView, GLSurfaceView, SurfaceTexture以及TextureView是Android当中名字比较绕,

关系

又比较密切的几个类。本文基于Android 5.0(Lollipop)的代码理一下它们的基本原理,联系

与区别。

SurfaceView从Android 1.0(API level 1)时就有 。它继承自类View,因此它本质上是一个

View。但与普通View不同的是,它有自己的Surface。我们知道,一般的Activity包含的多

个 View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,

才是对WMS可见的。这个DecorView在WMS中有一个对应的 WindowState。相应地,在

SF中对应的Layer。而SurfaceView自带一个Surface,这个Surface在WMS中有自己对应

的WindowState,在SF中也会有自己的Layer。如下图所示:

也 就是说,虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与

宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染 时可

以有自己的GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影

响主线程对事件的响应。但它也有缺点,因为这个Surface不在View hierachy中,它的显示

也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup

中,一些View中的特性也无 法使用。

GLSurfaceView从Android 1.5(API level 3)开始加入,作为SurfaceView的补充。它可以看作

是SurfaceView的一种典型使用模式。在SurfaceView的基础上,它加入了 EGL的管理,并

自带了渲染线程。另外它定义了用户需要实现的Render接口,提供了用Strategy pattern更改

具体Render行为的灵活性。作为GLSurfaceView的Client,只需要将实现了渲染函数的

Renderer的实现类设 置给GLSurfaceView即可。如:

[java] view plain copy

1. public class TriangleActivity extends Activity {

2. protected void onCreate(Bundle savedInstanceState) {

3. mGLView = new GLSurfaceView(this);

4. derer(new RendererImpl(this));

相 关类图如下。其中SurfaceView中的SurfaceHolder主要是提供了一坨操作Surface的接

口。GLSurfaceView中的 EglHelper和GLThread分别实现了上面提到的管理EGL环境和渲

染线程的工作。GLSurfaceView的使用者需要实现 Renderer接口。

SurfaceTexture从Android 3.0(API level 11)加入。和SurfaceView不同的是,它对图像流的

处理并不直接显示,而是转为GL外部纹理,因此可用于图像流数据的二次处理(如Camera

滤镜,桌面特效等)。比如Camera的预览数据,变成纹理后可以交给GLSurfaceView直接

显示,也可以通过SurfaceTexture交给 TextureView作为View heirachy中的一个硬件加速层

来显示。首先,SurfaceTexture从图像流(来自Camera预览,视频解码,GL绘制场景等)

中获得帧数 据,当调用updateTexImage()时,根据内容流中最近的图像更新SurfaceTexture

对应的GL纹理对象,接下来,就可以像操作普通 GL纹理一样操作它了。从下面的类图中

可以看出,它核心管理着一个BufferQueue的Consumer和Producer两端。Producer端 用于

内容流的源输出数据,Consumer端用于拿GraphicBuffer并生成纹理。

eAvailableListener用于让SurfaceTexture的使用者知道有新数据到

来。 JNISurfaceTextureContext是OnFrameAvailableListener从Native到Java的JNI跳板。其

中 SurfaceTexture中的attachToGLContext()和detachToGLContext()可以让多个GL context共

享同一个内容源。

Android 5.0中将BufferQueue的核心部分分离出来,放在BufferQueueCore这个类中。

BufferQueueProducer和 BufferQueueConsumer分别是它的生产者和消费者实现基类(分别实

现了IGraphicBufferProducer和 IGraphicBufferConsumer接口)。它们都是由BufferQueue的

静态函数createBufferQueue()来创建的。 Surface是生产者端的实现类,提供

dequeueBuffer/queueBuffer等硬件渲染接口,和lockCanvas /unlockCanvasAndPost等软件渲

染接口,使内容流的源可以往BufferQueue中填graphic buffer。GLConsumer继承自

ConsumerBase,是消费者端的实现类。它在基类的基础上添加了GL相关的操作,如将

graphic buffer中的内容转为GL纹理等操作。到此,以SurfaceTexture为中心的一个pipeline

大体是这样的:

TextureView在4.0(API level 14)中引入。它可以将内容流直接投影到View中,可以用于实

现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为

View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,

动画等变化。值得注意的是TextureView必须在硬 件加速的窗口中。它显示的内容流数据可

以来自App进程或是远端进程。从类图中可以看到,TextureView继承自View,它与其它的

View一样 在View hierachy中管理与绘制。TextureView重载了draw()方法,其中主要把

SurfaceTexture中收到的图像数据作为纹理更新到 对应的HardwareLayer中。

eAvailableListener用于通知TextureView 内容流有新图像到来。

SurfaceTextureListener接口用于让TextureView的使用者知道SurfaceTexture已准备 好,这

样就可以把SurfaceTexture交给相应的内容源。Surface为BufferQueue的Producer接口实现

类,使生产者可以通 过它的软件或硬件渲染接口为SurfaceTexture内部的BufferQueue提供

graphic buffer。

下面以 (位于/frameworks/base/media/tests/MediaDump/src/com

/android/mediadump/)为例分析下SurfaceTexture的使用。这个例子的效果是从MediaPlayer

中拿到视频帧,然后 显示在屏幕上,接着把屏幕上的内容dump到指定文件中。因为

SurfaceTexture本身只产生纹理,所以这里还需要GLSurfaceView配 合来做最后的渲染输

出。

首 先,VideoDumpView是GLSurfaceView的继承类。在构造函数VideoDumpView()中会创

建 VideoDumpRenderer,也就是er的实例,然后调setRenderer()将之

设成 GLSurfaceView的Renderer。

[java] view plain copy

1. 109 public VideoDumpView(Context context) {

2. ...

3. 116 mRenderer = new VideoDumpRenderer(context);

4. 117 setRenderer(mRenderer);

5. 118 }

随后,GLSurfaceView中的GLThread启动,创建EGL环境后回调VideoDumpRenderer中的

onSurfaceCreated()。

[java] view plain copy

1. 519 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {

2. ...

3. 551 // Create our texture. This has to be done each time the surface is created.

4. 552 int[] textures = new int[1];

5. 553 extures(1, textures, 0);

6. 554

7. 555 mTextureID = textures[0];

8. 556 Texture(GL_TEXTURE_EXTERNAL_OES, mTextureID);

9. ...

10. 575 mSurface = new SurfaceTexture(mTextureID);

11. 576 rameAvailableListener(this);

12. 577

13. 578 Surface surface = new Surface(mSurface);

14. 579 face(surface);

这里,首先通过GLES创建GL的外部纹理。外部纹理说明它的真正内容是放在ion分配出

来的系统物理内存中,而不是GPU中,GPU中只是维护了其元数据。接着根据前面创建的

GL纹理对象创建SurfaceTexture。流程如下:

SurfaceTexture 的参数为GLES接口函数glGenTexture()得到的纹理对象id。在初始化函数

SurfaceTexture_init()中,先创建 GLConsumer和相应的BufferQueue,再将它们的指针通过

JNI放到SurfaceTexture的Java层对象成员中。

[cpp] view plain copy

1. 230static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,

2. 231 jint texName, jboolean singleBufferMode, jobject weakThiz)

3. 232{

4. ...

5. 235 BufferQueue::createBufferQueue(&producer, &consumer);

6. ...

7. 242 sp surfaceTexture;

8. 243 if (isDetached) {

9. 244 surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES

,

10. 245 true, true);

11. 246 } else {

12. 247 surfaceTexture = new GLConsumer(consumer, texName,

13. 248 GL_TEXTURE_EXTERNAL_OES, true, true);

14. 249 }

15. ...

16. 256 SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);

17. 257 SurfaceTexture_setProducer(env, thiz, producer);

18. ...

19. 266 sp ctx(new JNISurfaceTextureContext(env, weak

Thiz,

20. 267 clazz));

21. 268 surfaceTexture->setFrameAvailableListener(ctx);

22. 269 SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);

由 于直接的Listener在Java层,而触发者在Native层,因此需要从Native层回调到Java

层。这里通过 JNISurfaceTextureContext当了跳板。JNISurfaceTextureContext的

onFrameAvailable() 起到了Native和Java的桥接作用:

[cpp] view plain copy

1. 180void JNISurfaceTextureContext::onFrameAvailable()

2. ...

3. 184 env->CallStaticVoidMethod(mClazz, ent, mWeakThiz);

其中的 ent早在SurfaceTexture_classInit()中被初始化为SurfaceTexture的

postEventFromNative()函数。这个函数往所在线程的消息队列中放入消息,异步调用

VideoDumpRenderer的 onFrameAvailable()函数,通知VideoDumpRenderer有新的数据到

来。

回到onSurfaceCreated(),接下来创建供外部生产者使用的Surface类。Surface的构造函数之

一带有参数SurfaceTexture。

[java] view plain copy

1. 133 public Surface(SurfaceTexture surfaceTexture) {

2. ...

3. 140 setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));

它实际上是把SurfaceTexture中创建的BufferQueue的Producer接口实现类拿出来后创建了

相应的Surface类。

[cpp] view plain copy

1. 135static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,

2. 136 jobject surfaceTextureObj) {

3. 137 sp producer(SurfaceTexture_getProducer(env, surfaceText

ureObj));

4. ...

5. 144 sp surface(new Surface(producer, true));

这 样,Surface为BufferQueue的Producer端,SurfaceTexture中的GLConsumer为

BufferQueue的 Consumer端。当通过Surface绘制时,SurfaceTexture可以通过

updateTexImage()来将绘制结果绑定到GL的纹理 中。

回到 onSurfaceCreated()函数,接下来调用setOnFrameAvailableListener()函数将

VideoDumpRenderer(实现eAvailableListener接口)作为

SurfaceTexture的Listener,因为它要监听内容流上是否有新数据。接着将SurfaceTexture传

给MediaPlayer, 因为这里MediaPlayer是生产者,SurfaceTexture是消费者。后者要接收前

者输出的Video frame。这样,就通过Observer pattern建立起了一条通知链:MediaPlayer ->

SurfaceTexture -> VideDumpRenderer。在onFrameAvailable()回调函数中,将updateSurface

标志设为true,表示有新的图像 到来,需要更新Surface了。为毛不在这儿马上更新纹理

呢,因为当前可能不在渲染线程。SurfaceTexture对象可以在任意线程被创建(回调 也会在

该线程被调用),但updateTexImage()只能在含有纹理对象的GL context所在线程中被调

用。因此一般情况下回调中不能直接调用updateTexImage()。

与此同时,GLSurfaceView中的GLThread也在运行,它会调用到VideoDumpRenderer的绘

制函数onDrawFrame()。

[java] view plain copy

1. 372 public void onDrawFrame(GL10 glUnused) {

2. ...

3. 377 if (updateSurface) {

4. ...

5. 380 TexImage();

6. 381 nsformMatrix(mSTMatrix);

7. 382 updateSurface = false;

8. ...

9. 394 // Activate the texture.

10. 395 veTexture(_TEXTURE0);

11. 396 Texture(GL_TEXTURE_EXTERNAL_OES, mTextureI

D);

12. ...

13. 421 // Draw a rectangle and render the video frame as a texture on it.

14. 422 Arrays(_TRIANGLE_STRIP, 0, 4);

15. ...

16. 429 DumpToFile(frameNumber);

这里,通过SurfaceTexture的updateTexImage()将内容流中的新图像转成GL中的纹理,再进

行坐标转换。绑定刚生成的纹理,画到屏幕上。整个流程如下:

最后onDrawFrame()调用DumpToFile()将屏幕上的内容倒到文件中。在DumpToFile()中,先

用glReadPixels()从屏幕中把像素数据存到Buffer中,然后用FileOutputStream输出到文件。

上面讲了 SurfaceTexture,下面看看TextureView是如何工作的。还是从例子着手,Android

的关于TextureView的官方文档

(/reference/android/view/)给了 一个简洁的例子

LiveCameraActivity。它它可以将Camera中的内容放在View中进行显示。在onCreate()函数

中首先创建 TextureView,再将Activity(实现了eTextureListener接口)传

给 TextureView,用于监听SurfaceTexture准备好的信号。

[java] view plain copy

1. protected void onCreate(Bundle savedInstanceState) {

2. ...

3. mTextureView = new TextureView(this);

4. faceTextureListener(this);

5. ...

6. }

TextureView的构造函数并不做主要的初始化工作。主要的初始化工作是在

getHardwareLayer()中,而这个函数是在其基类View的draw()中调用。TextureView重载了

这个函数:

[java] view plain copy

1. 348 HardwareLayer getHardwareLayer() {

2. ...

3. 358 mLayer = TextureLayer();

4. 359 if (!mUpdateSurface) {

5. 360 // Create a new SurfaceTexture for the layer.

6. 361 mSurface = new SurfaceTexture(false);

7. 362 faceTexture(mSurface);

8. 363 }

9. 364 aultBufferSize(getWidth(), getHeight());

10. 365 nCreateNativeWindow(mSurface);

11. 366

12. 367 rameAvailableListener(mUpdateListener, mAttachInfo.

mHandler);

13. 368

14. 369 if (mListener != null && !mUpdateSurface) {

15. 370 aceTextureAvailable(mSurface, getWidth(), getHeigh

t());

16. 371 }

17. ...

18. 390 applyUpdate();

19. 391 applyTransformMatrix();

20. 392

21. 393 return mLayer;

22. 394 }

因 为TextureView是硬件加速层(类型为LAYER_TYPE_HARDWARE),它首先通过

HardwareRenderer创建相应的 HardwareLayer类,放在mLayer成员中。然后创建

SurfaceTexture类,具体流程见前文。之后将HardwareLayer与 SurfaceTexture做绑定。接着

调用Native函数nCreateNativeWindow,它通过SurfaceTexture中的 BufferQueueProducer创

建Surface类。注意Surface实现了ANativeWindow接口,这意味着它可以作为EGL Surface

传给EGL接口从而进行硬件绘制。然后setOnFrameAvailableListener()将监听者

mUpdateListener注册到SurfaceTexture。这样,当内容流上有新的图像到来,

mUpdateListener的 onFrameAvailable()就会被调用。然后需要调用注册在TextureView中的

SurfaceTextureListener的 onSurfaceTextureAvailable()回调函数,通知TextureView的使用者

SurfaceTexture已就绪。整个流程大 体如下:

注意这里这 里为TextureView创建了DeferredLayerUpdater,而不是像Android 4.4(Kitkat)中

返回GLES20TextureLayer。因为Android 5.0(Lollipop)中在App端分离出了渲染线程,并将

渲染工作放到该线程中。这个线程还能接收VSync信号,因此它还能自己处理动画。事实

上,这里DeferredLayerUpdater的创建就是通过同步方式在渲染线程中做的。

DeferredLayerUpdater,顾名思义,就是 将Layer的更新请求先记录在这,当渲染线程真正

要画的时候,再进行真正的操作。其中的setSurfaceTexture()会调用 HardwareLayer的Native

函数nSetSurfaceTexture()将SurfaceTexture中的 surfaceTexture成员(类型为GLConsumer)

传给DeferredLayerUpdater,这样之后要更新纹理时 DeferredLayerUpdater就知道从哪里更新

了。

前面提到初始化中会调用onSurfaceTextureAvailable()这个回调函数。在它的实现中,

TextureView的使用者就可以将准备好的SurfaceTexture传给数据源模块,供数据源输出之

用。如:

[java] view plain copy

1. public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

2. mCamera = ();

3. ...

4. viewTexture(surface);

5. review();

6. ...

7. }

看一下setPreviewTexture()的实现,其中把SurfaceTexture中初始化时创建的

GraphicBufferProducer拿出来传给Camera模块。

[cpp] view plain copy

1. 576static void android_hardware_Camera_setPreviewTexture(JNIEnv *env,

2. 577 jobject thiz, jobject jSurfaceTexture)

3. ...

4. 585 producer = SurfaceTexture_getProducer(env, jSurfaceTexture);

5. ...

6. 594 if (camera->setPreviewTarget(producer) != NO_ERROR) {

到 这里,一切都初始化地差不多了。接下来当内容流有新图像可用,TextureView会被通知

到(通过 eAvailableListener接口)。

eAvailableListener是SurfaceTexture有新内容来时的回调接口。

TextureView中的mUpdateListener实现了该接口:

[java] view plain copy

1. 755 public void onFrameAvailable(SurfaceTexture surfaceTexture) {

2. 756 updateLayer();

3. 757 invalidate();

4. 758 }

可 以看到其中会调用updateLayer()函数,然后通过invalidate()函数申请更新UI。

updateLayer()会设置 mUpdateLayer标志位。这样,当下次VSync到来时,Choreographer通

知App通过重绘View hierachy。在UI重绘函数performTranversals()中,作为View hierachy

的一分子,TextureView的draw()函数被调用,其中便会相继调用applyUpdate()和

HardwareLayer 的updateSurfaceTexture()函数。

[java] view plain copy

1. 138 public void updateSurfaceTexture() {

2. 139 nUpdateSurfaceTexture(());

3. 140 yerUpdate(this);

4. 141 }

updateSurfaceTexture() 实际通过JNI调用到

android_view_HardwareLayer_updateSurfaceTexture()函数。在其中会设置相应

DeferredLayerUpdater的标志位mUpdateTexImage,它表示在渲染线程中需要更新该层的纹

理。

前 面提到,Android 5.0引入了渲染线程,它是一个更大的topic,超出本文范围,这里只说

相关的部分。作为背景知识,下面只画出了相关的类。可以看 到,ThreadedRenderer作为新

的HardwareRenderer替代了Android 4.4中的Gl20Renderer。其中比较关键的是RenderProxy

类,需要让渲染线程干活时就通过这个类往渲染线程发任务。 RenderProxy中指向的

RenderThread就是渲染线程的主体了,其中的threadLoop()函数是主循环,大多数时间它会

poll在 线程的Looper上等待,当有同步请求(或者VSync信号)过来,它会被唤醒,然后

处理TaskQueue中的任务。TaskQueue是 RenderTask的队列,RenderTask代表一个渲染线

程中的任务。如DrawFrameTask就是RenderTask的继承类之一,它主 要用于渲染当前帧。

而DrawFrameTask中的DeferredLayerUpdater集合就存放着之前对硬件加速层的更新操作申

请。

当 主线程准备好渲染数据后,会以同步方式让渲染线程完成渲染工作。其中会先调用

processLayerUpdate()更新所有硬件加速层中的属性,继而 调用到DeferredLayerUpdater的

apply()函数,其中检测到标志位mUpdateTexImage被置位,于是会调用 doUpdateTexImage()

真正更新GL纹理和转换坐标。

最后,总结 下这几者的区别和联系。简单地说,SurfaceView是一个有自己Surface的

View。它的渲染可以放在单独线程而不是主线程中。其缺点是不能 做变形和动画。

SurfaceTexture可以用作非直接输出的内容流,这样就提供二次处理的机会。与SurfaceView

直接输出相比,这样会有若 干帧的延迟。同时,由于它本身管理BufferQueue,因此内存消

耗也会稍微大一些。TextureView是一个可以把内容流作为外部纹理输出在上 面的View。

它本身需要是一个硬件加速层。事实上TextureView本身也包含了SurfaceTexture。它与

SurfaceView+SurfaceTexture组合相比可以完成类似的功能(即把内容流上的图像转成纹理,

然后输出)。区别在于 TextureView是在View hierachy中做绘制,因此一般它是在主线程

上做的(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。而

SurfaceView+SurfaceTexture在单独的Surface上做绘制,可以是用户提 供的线程,而不是系

统的主线程或是渲染线程。另外,与TextureView相比,它还有个好处是可以用Hardware

overlay进行显示。