求教,如何把surfacetexture 拷贝的内容拷贝到texture中

TextureView+SurfaceTexture+OpenGL ES来播放视频(三) - 简书
TextureView+SurfaceTexture+OpenGL ES来播放视频(三)
做好的Demo截图
opengl-video
讲了这么多,可能有人要问了,播放视频用个android封装的VideoView或者用MediaPlayer+SurfaceView来进行播放视频不就得了吗,干嘛还要整这么麻烦。OK,为了回答这个问题,我们先看看OpenGL ES干什么的,它是OpenGL三维图形API的子集,图形硬件的一种软件接口,针对手机、PDA和游戏主机等嵌入式设备而设计。我想如果是做游戏类开发的肯定对一些图形库不会陌生,其实很多游戏引擎内部都是封装的像OpenGL,DirectX 3D之类的图形库,然后开发者就可以通过这些图形库,来开发很多好玩的东西,如游戏,动画等等,那么我现在用Opengl es来绘制视频是不是也可以定制很多有意思的东西,比如开发一个左右分屏视频播放器,然后在虚拟现实(VR)头盔上来观看2d的视频,如果用opengl去绘制,那简直分分中搞定,因为这里,每一帧的视频在opengl 看来只是一张纹理贴图而已,那么我想把这个贴图贴在哪里就贴在哪里。总之,用opengl可以开发出很多有意思的二维,三维的图形应用出来。
说了这么多,咱们开始吧,
* 在这个类里面对视频纹理进行绘制工作,继承了 {@link TextureSurfaceRenderer},
* 并实现了{@link SurfaceTexture.OnFrameAvailableListener}
public class VideoTextureSurfaceRenderer extends TextureSurfaceRenderer implements
SurfaceTexture.OnFrameAvailableListener
public static final String TAG = VideoTextureSurfaceRenderer.class.getSimpleName();
/**绘制的区域尺寸*/
private static float squareSize = 1.0f;
private static float squareCoords[] = {
-squareSize,
squareSize, 0.0f,
// top left
-squareSize, -squareSize, 0.0f,
// bottom left
squareSize, -squareSize, 0.0f,
// bottom right
squareSize,
squareSize, 0.0f
// top right
/**绘制次序*/
private static short drawOrder[] = {
* 用来缓存纹理坐标,因为纹理都是要在后台被绘制好,然
* 后不断的替换最前面显示的纹理图像
private FloatBuffer textureB
/**纹理坐标*/
private float textureCoords[] = {
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f
/**生成的真实纹理数组*/
private int[] textures = new int[1];
/**着色器脚本程序的handle(句柄)*/
private int shaderP
/**squareCoords的的顶点缓存*/
private FloatBuffer vertexB
/**绘制次序的缓存*/
private ShortBuffer drawOrderB
/**矩阵来变换纹理坐标,(具体含义下面再解释)*/
private float[] videoTextureT
/**当前的视频帧是否可以得到*/
private boolean frameAvailable =
private SurfaceTexture videoTexture;
// 从视频流捕获帧作为Opengl ES 的Texture
* @param texture 从TextureView获取到的SurfaceTexture,目的是为了配置EGL 的native window
public VideoTextureSurfaceRenderer(Context context, SurfaceTexture texture, int width, int height)
super(texture, width, height);
//先调用父类去做EGL初始化工作
this.context =
videoTextureTransform = new float[16];
对上面的代码再稍稍解释一下吧,在绘制之前,我们首先选定一块区域来让图像就在这块区域来绘制,则有如下定义:
private static float squareSize = 1.0f;
private static float squareCoords[] = {
-squareSize,
squareSize, 0.0f,
// top left
-squareSize, -squareSize, 0.0f,
// bottom left
squareSize, -squareSize, 0.0f,
// bottom right
squareSize,
squareSize, 0.0f
// top right
上幅图来解释解释:
当squareSize=1.0时,square的面积就是整个手机屏幕,若squareSize=0.5f则每个边长都为屏幕的一半。数组的坐标顺序为:左上-&左下-&右下-&右上。
然后接着定义一个纹理坐标数组:
private float textureCoords[] = {
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f
接着上图:
如上图,这就是2D OpenGL ES纹理坐标,它由四个列向量(s, t, 0, 1)组成,其中s, t ∈[0, 1]。不知道大家有没有发现,绘制区域(quareCoords[])的顶点数组坐标顺序跟纹理数组坐标的顺序都是从 左上方开始-&到右上方结束,为什么要这样做呢? 其实目的就是为了统一方向,因为我们知道手机屏幕的坐标是左上角为原点(0.0, 0.0),所以为了以后不必要的转换工作,最好将绘制的顺序的都统一起来。
然后则看看纹理的绘制次序drawOrder[] = {0,1,2, 0, 2, 3} ; 这又是个什么次序呢? 好,再来个图看看
其实,opengl es 在绘制一个多边形时,都是用一个基本的图元即三角形拼凑出来的,比如一个矩形它可以用(0-&1-&2)和(0-&2-&3)的次序,通过两个三角形给拼凑出来的,当然也可是其它的组合比如(1,3,2)(1,3,0)等等,总之得用两个三角形拼凑成一个矩形,不过建议还是都按找同一钟次序,比如都是按照顺时针或都按照逆时针来拼凑。
OK,接下来再贴出省略的代码:
* 重写父类方法,初始化组件
protected void initGLComponents()
setupVertexBuffer();
setupTexture();
loadShaders();
* 设置顶点缓存
private void setupVertexBuffer()
/** Draw Order buffer*/
ByteBuffer orderByteBuffer = ByteBuffer.allocateDirect(drawOrder. length * 2);
orderByteBuffer.order(ByteOrder.nativeOrder());
//Modifies this buffer's byte order
drawOrderBuffer = orderByteBuffer.asShortBuffer();
//创建此缓冲区的视图,作为一个short缓冲区.
drawOrderBuffer.put(drawOrder);
drawOrderBuffer.position(0); //下一个要被读或写的元素的索引,从0 开始
// Initialize the texture holder
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
ByteBuffer.allocateDirect(drawOrder. length * 2)表示的是直接从系统中分配大小为 (drawOrder. length * 2)的内存,2 代表一个short型占两个字节,(int占4个字节)。
/**接着初始化纹理*/
private void setupTexture()
ByteBuffer texturebb = ByteBuffer.allocateDirect(textureCoords.length * 4);
texturebb.order(ByteOrder.nativeOrder());
textureBuffer = texturebb.asFloatBuffer();
textureBuffer.put(textureCoords);
textureBuffer.position(0);
// 启用纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//生成纹理对象textures(用于存储纹理数据)
GLES20.glGenTextures(1, textures, 0);
// 将绑定纹理(texuture[0]表示指针指向纹理数据的初始位置)
GLES20.glBindTexture(GLES11Ext.GL_BLEND_EQUATION_RGB_OES, textures[0]);
videoTexture = new SurfaceTexture(textures[0]);
videoTexture.setOnFrameAvailableListener(this);
关于glBindTexure(int target, int texture) 中的参数target,表示指定这是一张什么类型的纹理,在此是GLES11Ext.GL_BLEND_EQUATION_RGB_OES,也可以是常用的2D纹理如GLES20.GL_TEXTURE_2D等;第二个参数texture,在程序第一次使用这个参数时,这个函数会创建一个新的对象,并把这个对象分配给它,之后这个texture就成了一个活动的纹理对象。如果texture=0 则OpenGL就停止使用纹理对象,并返回到初始的默认纹理。
/**加载顶点与片段着色器*/
private void loadShaders()
final String vertexShader = RawResourceReader.readTextFileFromRawResource(context, R.raw.vetext_sharder);
final String fragmentShader = RawResourceReader.readTextFileFromRawResource(context, R.raw.fragment_sharder);
final int vertexShaderHandle = pileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
final int fragmentShaderHandle = pileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
shaderProgram = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[]{"texture","vPosition","vTexCoordinate","textureTransform"});
关于可编程着色器在计算机图形领域本身就是个很大的主题,已超出了本文的范围。那么就稍稍解释下,OpenGL着色语言(OpenGL shading Language),它是一种编程语言,用于创建可编程的着色器。在opengl es 2.0以前使用的是一种“固定功能管线“,而2.0以后就是使用的着这钟可“编程的管线“,这种管线又分为顶点处理管线与片段处理管线(涉及到OpenGL的渲染管线的一些机制,大家自行的查吧)。我现在就说说这个GLSL着色器是怎么使用的吧:有关使用GLSL创建着色器流程如下图(参照OpenGL 编程指南):
shaderCreate
大家可以参照这幅图来看看程序中shader的使用。在本项目的res目录下新建一个raw文件夹,然后分别创建vertext_shader.glsl和fragment_sharder.glsl文件,代码如下:vertext_sharder:
attribute vec4 vP
//顶点着色器输入变量由attribute来声明
attribute vec4 vTexC
//uniform表示一个变量的值由应用程序在着色器执行之前指定,
//并且在图元处理过程中不会发生任何变化。mat4表示一个4x4矩阵
uniform mat4 textureT
varying vec2 v_TexC
//片段着色器输入变量用arying来声明
void main () {
v_TexCoordinate = (textureTransform * vTexCoordinate).
gl_Position = vP
fragment_shader
/**使用GL_OES_EGL_image_external扩展处理,来增强GLSL*/
#extension GL_OES_EGL_image_external : require
uniform samplerExternalOES //定义扩展的的纹理取样器amplerExternalOES
varying vec2 v_TexC
void main () {
vec4 color = texture2D(texture, v_TexCoordinate);
gl_FragColor =
着色器语言非常的类似C语言,也是从main函数开始执行的。其中的很多语法,变量等等,还是大家自行的查查,这不是几句能说明白的。着色器的创建及编译过程的代码都在项目里的一个util包下的三个工具类,RawRourceReader,ShaderHelper,TextureHelper类中,我就不再贴出来了,有兴趣大家可以fork或clone下来看看。
ok,终于初始化完了,太不容易了,冏。好吧,绘制工作开始跑起来。
protected boolean draw()
synchronized (this)
if (frameAvailable)
videoTexture .updateTexImage();
videoTexture .getTransformMatrix(videoTextureTransform);
frameAvailable =
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glViewport(0, 0, width, height);
this.drawTexture();
当视频的帧可得到时,调用videoTexture .updateTexImage()方法,这个方法的官网解释如下: &font color=#008000 &Update the texture image to the most recent frame from the image stream.
This may only be called while the OpenGL ES context that owns the texture is current on the calling thread. It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.&/font& 大意是,从的图像流中更新纹理图像到最近的帧中。这个函数仅仅当拥有这个纹理的Opengl ES上下文当前正处在绘制线程时被调用。它将隐式的绑定到这个扩展的GL_TEXTURE_EXTERNAL_OES 目标纹理(为什么上面的片段着色代码中要扩展这个OES,可能就是应为这个吧)。 而videoTexture .getTransformMatrix(videoTextureTransform)这又是什么意思呢?当对纹理用amplerExternalOES采样器采样时,应该首先使用getTransformMatrix(float[])查询得到的矩阵来变换纹理坐标,每次调用updateTexImage()的时候,可能会导致变换矩阵发生变化,因此在纹理图像更新时需要重新查询,该矩阵将传统的2D OpenGL ES纹理坐标列向量(s,t,0,1),其中s,t∈[0,1],变换为纹理中对应的采样位置。该变换补偿了图像流中任何可能导致与传统OpenGL ES纹理有差异的属性。例如,从图像的左下角开始采样,可以通过使用查询得到的矩阵来变换列向量(0,0,0,1),而从右上角采样可以通过变换(1,1,0,1)来得到。
关于 GLES20.glViewport(int x, int y, int width, int height) ,也是个比较重要的函数,这个函数大家网上查查。ok,接下来到了真正的绘制纹理的时候了。代码如下:
private void drawTexture() {
// Draw texture
GLES20.glUseProgram(shaderProgram); //绘制时使用着色程序
int textureParamHandle = GLES20.glGetUniformLocation(shaderProgram, "texture"); //返回一个于着色器程序中变量名为"texture"相关联的索引
int textureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinate");
int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
int textureTransformHandle = GLES20.glGetUniformLocation(shaderProgram, "textureTransform");
//在用VertexAttribArray前必须先激活它
GLES20.glEnableVertexAttribArray(positionHandle);
//指定positionHandle的数据值可以在什么地方访问。 vertexBuffer在内部(NDK)是个指针,指向数组的第一组值的内存
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glBindTexture(GLES20.GL_TEXTURE0, textures[0]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//指定一个当前的textureParamHandle对象为一个全局的uniform 变量
GLES20.glUniform1i(textureParamHandle, 0);
GLES20.glEnableVertexAttribArray(textureCoordinateHandle);
GLES20.glVertexAttribPointer(textureCoordinateHandle, 4, GLES20.GL_FLOAT, false, 0, textureBuffer);
GLES20.glUniformMatrix4fv(textureTransformHandle, 1, false, videoTextureTransform, 0);
//GLES20.GL_TRIANGLES(以无数小三角行的模式)去绘制出这个纹理图像
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawOrderBuffer);
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureCoordinateHandle);
好了所有代码就分析到这里了,前两篇链接, , 项目地址在
注: 当然了,其实也没必要配置EGL环境这么麻烦,android的GLSurfaceView就已经在底层就配置好了EGL环境,且自带绘制线程您当前位置: >
> Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView
Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView
来源:程序员人生&& 发布时间: 07:51:29 阅读次数:10336次
SurfaceView, GLSurfaceView, SurfaceTexture和TextureView是Android当中名字比较绕,关系又比较密切的几个类。本文基于Android 5.0(Lollipop)的代码理1下它们的基本原理,联系与区分。
SurfaceView从Android 1.0(API level 1)时就有 。它继承自类View,因此它本质上是1个View。但与普通View不同的是,它有自己的Surface。我们知道,1般的Activity包括的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有1个对应的WindowState。相应地,在SF中对应的Layer。而SurfaceView自带1个Surface,这个Surface在WMS中有自己对应的WindowState,在SF中也会有自己的Layer。以下图所示:
也就是说,虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的GL context。这对1些游戏、视频等性能相干的利用非常有益,由于它不会影响主线程对事件的响应。但它也有缺点,由于这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,1些View中的特性也没法使用。
GLSurfaceView从Android 1.5(API level 3)开始加入,作为SurfaceView的补充。它可以看做是SurfaceView的1种典型使用模式。在SurfaceView的基础上,它加入了EGL的管理,并自带了渲染线程。另外它定义了用户需要实现的Render接口,提供了用Strategy pattern更改具体Render行动的灵活性。作为GLSurfaceView的Client,只需要将实现了渲染函数的Renderer的实现类设置给GLSurfaceView便可。如:
public class TriangleActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
mGLView = new GLSurfaceView(this);
mGLView.setRenderer(new RendererImpl(this));相干类图以下。其中SurfaceView中的SurfaceHolder主要是提供了1坨操作Surface的接口。GLSurfaceView中的EglHelper和GLThread分别实现了上面提到的管理EGL环境和渲染线程的工作。GLSurfaceView的使用者需要实现Renderer接口。
SurfaceTexture从Android 3.0(API level 11)加入。和SurfaceView不同的是,它对图象流的处理其实不直接显示,而是转为GL外部纹理,因此可用于图象流数据的2次处理(如Camera滤镜,桌面殊效等)。比如Camera的预览数据,变成纹理后可以交给GLSurfaceView直接显示,也能够通过SurfaceTexture交给TextureView作为View
heirachy中的1个硬件加速层来显示。首先,SurfaceTexture从图象流(来自Camera预览,视频解码,GL绘制场景等)中取得帧数据,当调用updateTexImage()时,根据内容流中最近的图象更新SurfaceTexture对应的GL纹理对象,接下来,就能够像操作普通GL纹理1样操作它了。从下面的类图中可以看出,它核心管理着1个BufferQueue的Consumer和Producer两端。Producer端用于内容流的源输出数据,Consumer端用于拿GraphicBuffer并生成纹理。SurfaceTexture.OnFrameAvailableListener用于让SurfaceTexture的使用者知道有新数据到来。JNISurfaceTextureContext是OnFrameAvailableListener从Native到Java的JNI跳板。其中SurfaceTexture中的attachToGLContext()和detachToGLContext()可让多个GL
context同享同1个内容源。
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为中心的1个pipeline大体是这样的:
TextureView在4.0(API level 14)中引入。它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View
hierachy中的1个普通View,因此可以和其它普通View1样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。它显示的内容流数据可以来自App进程或是远端进程。从类图中可以看到,TextureView继承自View,它与其它的View1样在View hierachy中管理与绘制。TextureView重载了draw()方法,其中主要把SurfaceTexture中收到的图象数据作为纹理更新到对应的HardwareLayer中。SurfaceTexture.OnFrameAvailableListener用于通知TextureView内容流有新图象到来。SurfaceTextureListener接口用于让TextureView的使用者知道SurfaceTexture已准备好,这样就能够把SurfaceTexture交给相应的内容源。Surface为BufferQueue的Producer接口实现类,使生产者可以通过它的软件或硬件渲染接口为SurfaceTexture内部的BufferQueue提供graphic
下面以VideoDumpView.java(位于/frameworks/base/media/tests/MediaDump/src/com/android/mediadump/)为例分析下SurfaceTexture的使用。这个例子的效果是从MediaPlayer中拿到视频帧,然后显示在屏幕上,接着把屏幕上的内容dump到指定文件中。由于SurfaceTexture本身只产生纹理,所以这里还需要GLSurfaceView配合来做最后的渲染输出。
首先,VideoDumpView是GLSurfaceView的继承类。在构造函数VideoDumpView()中会创建VideoDumpRenderer,也就是GLSurfaceView.Renderer的实例,然后调setRenderer()将之设成GLSurfaceView的Renderer。
public VideoDumpView(Context context) {
mRenderer = new VideoDumpRenderer(context);
setRenderer(mRenderer);
随后,GLSurfaceView中的GLThread启动,创建EGL环境后回调VideoDumpRenderer中的onSurfaceCreated()。
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// Create our texture. This has to be done each time the surface is created.
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
mTextureID = textures[0];
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
Surface surface = new Surface(mSurface);
mMediaPlayer.setSurface(surface);
这里,首先通过GLES创建GL的外部纹理。外部纹理说明它的真正内容是放在ion分配出来的系统物理内存中,而不是GPU中,GPU中只是保护了其元数据。接着根据前面创建的GL纹理对象创建SurfaceTexture。流程以下:
SurfaceTexture的参数为GLES接口函数glGenTexture()得到的纹理对象id。在初始化函数SurfaceTexture_init()中,先创建GLConsumer和相应的BufferQueue,再将它们的指针通过JNI放到SurfaceTexture的Java层对象成员中。
230static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
jint texName, jboolean singleBufferMode, jobject weakThiz)
BufferQueue::createBufferQueue(&producer, &consumer);
sp&GLConsumer& surfaceT
if (isDetached) {
surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES,
true, true);
surfaceTexture = new GLConsumer(consumer, texName,
GL_TEXTURE_EXTERNAL_OES, true, true);
SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
SurfaceTexture_setProducer(env, thiz, producer);
sp&JNISurfaceTextureContext& ctx(new JNISurfaceTextureContext(env, weakThiz,
surfaceTexture-&setFrameAvailableListener(ctx);
SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
由于直接的Listener在Java层,而触发者在Native层,因此需要从Native层回调到Java层。这里通过JNISurfaceTextureContext当了跳板。JNISurfaceTextureContext的onFrameAvailable()起到了Native和Java的桥接作用:
180void JNISurfaceTextureContext::onFrameAvailable()
env-&CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
其中的fields.postEvent早在SurfaceTexture_classInit()中被初始化为SurfaceTexture的postEventFromNative()函数。这个函数往所在线程的消息队列中放入消息,异步调用VideoDumpRenderer的onFrameAvailable()函数,通知VideoDumpRenderer有新的数据到来。
回到onSurfaceCreated(),接下来创建供外部生产者使用的Surface类。Surface的构造函数之1带有参数SurfaceTexture。
public Surface(SurfaceTexture surfaceTexture) {
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
它实际上是把SurfaceTexture中创建的BufferQueue的Producer接口实现类拿出来后创建了相应的Surface类。
135static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
jobject surfaceTextureObj) {
sp&IGraphicBufferProducer& producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
sp&Surface& surface(new Surface(producer, true));
这样,Surface为BufferQueue的Producer端,SurfaceTexture中的GLConsumer为BufferQueue的Consumer端。当通过Surface绘制时,SurfaceTexture可以通过updateTexImage()来将绘制结果绑定到GL的纹理中。
回到onSurfaceCreated()函数,接下来调用setOnFrameAvailableListener()函数将VideoDumpRenderer(实现SurfaceTexture.OnFrameAvailableListener接口)作为SurfaceTexture的Listener,由于它要监听内容流上是不是有新数据。接着将SurfaceTexture传给MediaPlayer,由于这里MediaPlayer是生产者,SurfaceTexture是消费者。后者要接收前者输出的Video
frame。这样,就通过Observer pattern建立起了1条通知链:MediaPlayer -& SurfaceTexture -& VideDumpRenderer。在onFrameAvailable()回调函数中,将updateSurface标志设为true,表示有新的图象到来,需要更新Surface了。为毛不在这儿马上更新纹理呢,由于当前可能不在渲染线程。SurfaceTexture对象可以在任意线程被创建(回调也会在该线程被调用),但updateTexImage()只能在含有纹理对象的GL
context所在线程中被调用。因此1般情况下回调中不能直接调用updateTexImage()。
与此同时,GLSurfaceView中的GLThread也在运行,它会调用到VideoDumpRenderer的绘制函数onDrawFrame()。
public void onDrawFrame(GL10 glUnused) {
if (updateSurface) {
mSurface.updateTexImage();
mSurface.getTransformMatrix(mSTMatrix);
updateSurface =
// Activate the texture.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
// Draw a rectangle and render the video frame as a texture on it.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
DumpToFile(frameNumber);
这里,通过SurfaceTexture的updateTexImage()将内容流中的新图象转成GL中的纹理,再进行坐标转换。绑定刚生成的纹理,画到屏幕上。全部流程以下:
最后onDrawFrame()调用DumpToFile()将屏幕上的内容倒到文件中。在DumpToFile()中,先用glReadPixels()从屏幕中把像素数据存到Buffer中,然后用FileOutputStream输出到文件。
上面讲了SurfaceTexture,下面看看TextureView是如何工作的。还是从例子着手,Android的关于TextureView的官方文档(/reference/android/view/TextureView.html)给了1个简洁的例子LiveCameraActivity。它它可以将Camera中的内容放在View中进行显示。在onCreate()函数中首先创建TextureView,再将Activity(实现了TextureView.SurfaceTextureListener接口)传给TextureView,用于监听SurfaceTexture准备好的信号。
protected void onCreate(Bundle savedInstanceState) {
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
TextureView的构造函数其实不做主要的初始化工作。主要的初始化工作是在getHardwareLayer()中,而这个函数是在其基类View的draw()中调用。TextureView重载了这个函数:
HardwareLayer getHardwareLayer() {
mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer();
if (!mUpdateSurface) {
// Create a new SurfaceTexture for the layer.
mSurface = new SurfaceTexture(false);
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
nCreateNativeWindow(mSurface);
mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
if (mListener != null && !mUpdateSurface) {
mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
applyUpdate();
applyTransformMatrix();
由于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传给数据源模块,供数据源输出之用。如:
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mCamera = Camera.open();
mCamera.setPreviewTexture(surface);
mCamera.startPreview();
看1下setPreviewTexture()的实现,其中把SurfaceTexture中初始化时创建的GraphicBufferProducer拿出来传给Camera模块。
576static void android_hardware_Camera_setPreviewTexture(JNIEnv *env,
jobject thiz, jobject jSurfaceTexture)
producer = SurfaceTexture_getProducer(env, jSurfaceTexture);
if (camera-&setPreviewTarget(producer) != NO_ERROR) {
到这里,1切都初始化地差不多了。接下来当内容流有新图象可用,TextureView会被通知到(通过SurfaceTexture.OnFrameAvailableListener接口)。SurfaceTexture.OnFrameAvailableListener是SurfaceTexture有新内容来时的回调接口。TextureView中的mUpdateListener实现了该接口:
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
updateLayer();
invalidate();
可以看到其中会调用updateLayer()函数,然后通过invalidate()函数申请更新UI。updateLayer()会设置mUpdateLayer标志位。这样,当下次VSync到来时,Choreographer通知App通太重绘View hierachy。在UI重绘函数performTranversals()中,作为View hierachy的1份子,TextureView的draw()函数被调用,其中便会相继调用applyUpdate()和HardwareLayer的updateSurfaceTexture()函数。
public void updateSurfaceTexture() {
nUpdateSurfaceTexture(mFinalizer.get());
mRenderer.pushLayerUpdate(this);
updateSurfaceTexture()实际通过JNI调用到android_view_HardwareLayer_updateSurfaceTexture()函数。在其中会设置相应DeferredLayerUpdater的标志位mUpdateTexImage,它表示在渲染线程中需要更新该层的纹理。
前面提到,Android 5.0引入了渲染线程,它是1个更大的topic,超越本文范围,这里只说相干的部份。作为背景知识,下面只画出了相干的类。可以看到,ThreadedRenderer作为新的HardwareRenderer替换了Android 4.4中的Gl20Renderer。其中比较关键的是RenderProxy类,需要让渲染线程干活时就通过这个类往渲染线程发任务。RenderProxy中指向的RenderThread就是渲染线程的主体了,其中的threadLoop()函数是主循环,大多数时间它会poll在线程的Looper上等待,当有同步要求(或VSync信号)过来,它会被唤醒,然后处理TaskQueue中的任务。TaskQueue是RenderTask的队列,RenderTask代表1个渲染线程中的任务。如DrawFrameTask就是RenderTask的继承类之1,它主要用于渲染当前帧。而DrawFrameTask中的DeferredLayerUpdater集合就寄存着之前对硬件加速层的更新操作申请。
当主线程准备好渲染数据后,会以同步方式让渲染线程完成渲染工作。其中会先调用processLayerUpdate()更新所有硬件加速层中的属性,继而调用到DeferredLayerUpdater的apply()函数,其中检测到标志位mUpdateTexImage被置位,因而会调用doUpdateTexImage()真正更新GL纹理和转换坐标。
最后,总结下这几者的区分和联系。简单地说,SurfaceView是1个有自己Surface的View。它的渲染可以放在单独线程而不是主线程中。其缺点是不能做变形和动画。SurfaceTexture可以用作非直接输出的内容流,这样就提供2次处理的机会。与SurfaceView直接输出相比,这样会有若干帧的延迟。同时,由于它本身管理BufferQueue,因此内存消耗也会略微大1些。TextureView是1个可以把内容流作为外部纹理输出在上面的View。它本身需要是1个硬件加速层。事实上TextureView本身也包括了SurfaceTexture。它与SurfaceView+SurfaceTexture组合相比可以完成类似的功能(即把内容流上的图象转成纹理,然后输出)。区分在于TextureView是在View
hierachy中做绘制,因此1般它是在主线程上做的(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。而SurfaceView+SurfaceTexture在单独的Surface上做绘制,可以是用户提供的线程,而不是系统的主线程或是渲染线程。另外,与TextureView相比,它还有个好处是可以用Hardware overlay进行显示。
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
------分隔线----------------------------
------分隔线----------------------------
积分:4237}

我要回帖

更多关于 surfacetexture用法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信