surface电脑怎么样程序员(谈一谈Android上的SurfaceTexture)
导言:这篇文章介绍了SurfaceTexture的用法以及原理,对于常见用法不做过多描述,而重点介绍了内部实现以及EGLImage,包括实现共享纹理的两种方式。
摘要:
1什么是SurfaceTexture2SurfaceTexture的常见应用-相机与视频解码3SurfaceTexture的内部实现-EGLImageKHR3.1SurfaceTexture是如何创建的3.2updateTexImage是如何将数据更新到纹理的4EGLImageKHR4.1使用权限问题4.2EGLImageKHR的重要性质5共享纹理的两种实现5.1ShareContext5.2EGLImageKHR6结语
1什么是SurfaceTexture
SurfaceTexture是Android上做渲染的核心组件,它是Surface和OpenGLES纹理的组合,用于提供输出到GLES纹理的Surface。从安卓渲染系统上来说,SurfaceTexture是一个BufferQueue的消费者,当生产方将新的缓冲区排入队列时,onFrameAvailable()回调会通知应用。然后,应用调用updateTexImage(),这会释放先前占有的缓冲区,从队列中获取新缓冲区并执行EGL调用,从而使GLES可将此缓冲区作为外部纹理使用。
基本流程如下:
通过Camera、VideoDecoder、OpenGL生成图像流。图像流通过Surface入队到BufferQueue,并通知到GLConsumer。GLConsumer从BufferQueue获取图像流GraphicBuffer,并转换为EXTERNALOES纹理。得到OES纹理后,用户方就可以将其转换成普通的纹理,然后应用特效或者上屏。2SurfaceTexture的常见应用-相机与视频解码
SurfaceTexture的最常见应用场景是作为相机或者视频解码器的输出,这种应用场景非常常见,这里就不做详细描述了,以相机为例:
//SurfaceTexture配合GLSurfaceView实现渲染的关键代码//初始化surfacetexturefuninitSurfaceTexture(textureCallback:(surfaceTexture:SurfaceTexture)->Unit){valargs=IntArray(1)GLES20.glGenTextures(args.size,args,0)surfaceTexName=args[0]internalSurfaceTexture=SurfaceTexture(surfaceTexName)textureCallback(internalSurfaceTexture)}//收到OnFrameAvailableListener回调时,请求刷新GLSurfaceViewcameraSurfaceTexture.initSurfaceTexture{it.setOnFrameAvailableListener{requestRender()}cameraSurfaceTextureListener?.onSurfaceReady(cameraSurfaceTexture)}//设置相机previewtexturecamera.setPreviewTexture(surfaceTexture)//在gl线程更新texturefunupdateTexImage(){internalSurfaceTexture.updateTexImage()internalSurfaceTexture.getTransformMatrix(transformMatrix)}3SurfaceTexture的内部实现-EGLImageKHR
SurfaceTexture使用时,最主要的两个方法:
SurfaceTexture(inttexName)//创建SurfaceTexturevoidupdateTexImage()//将当前图片流更新到纹理3.1SurfaceTexture是如何创建的
首先需要自己创建一个纹理ID,传递给SurfaceTexture,比如
//创建纹理idint[]tex=newint[1];GLES20.glGenTextures(1,tex,0);//创建SurfaceTexture并传入tex[0]mSurfaceTexture=newSurfaceTexture(tex[0]);
Framework层的SurfaceTexture创建代码如下
//frameworks\base\graphics\java\android\graphics//构造函数publicSurfaceTexture(inttexName){this(texName,false);}//构造函数//singleBufferMode是否是单buffer,默认为falsepublicSurfaceTexture(inttexName,booleansingleBufferMode){mCreatorLooper=Looper.myLooper();mIsSingleBuffered=singleBufferMode;//native方法nativeInitnativeInit(false,texName,singleBufferMode,newWeakReference
framework层会调用到nativeInit方法,最终调用到SurfaceTexture_init方法。
//frameworks\base\core\jni\SurfaceTexture.cpp//texName为应用创建texture名//weakThiz为SurfaceTexture对象弱引用staticvoidSurfaceTexture_init(JNIEnv*env,jobjectthiz,jbooleanisDetached,jinttexName,jbooleansingleBufferMode,jobjectweakThiz){sp
SurfaceTexture初始化后,向GLConsumer设置了JNISurfaceTextureContext监听器,该监听器会回调到Java层SurfaceTexture.postEventFromNative方法,进一步回调到注册到SurfaceTexture中的OnFrameAvailableListener监听器,用于通知业务层有新的GraphicBuffer入队了。这时候业务层就可以调用updateTexImage将GraphicBuffer更新到纹理。
3.2updateTexImage是如何将数据更新到纹理的
按文档所说,OnFrameAvailableListener.onFrameAvailable回调可以发生在任意线程,所以不能在回调中直接调用updateTexImage,而是必须切换到OpenGL线程调用updateTexImage,那么内部是怎么实现的呢?
应用层的updateTexImage会最终调用到native层的GLConsumer::updateTexImage()方法。
//frameworks\native\libs\gui\GLConsumer.cppstatus_tGLConsumer::updateTexImage(){....//mEglContext为eglGetCurrentContext//MakesuretheEGLstateisthesameasinpreviouscalls.status_terr=checkAndUpdateEglStateLocked();....BufferItemitem;//Acquirethenextbuffer.//Inasynchronousmodethelistisguaranteedtobeonebuffer//deep,whileinsynchronousmodeweusetheoldestbuffer.err=acquireBufferLocked(&item,0);.....//UpdatetheCurrentGLConsumerstate.//Releasethepreviousbuffer.err=updateAndReleaseLocked(item);.....//BindthenewbuffertotheGLtexture,andwaituntilit'sready.returnbindTextureImageLocked();}
可以看到获取帧数据是acquireBufferLocked方法。
//frameworks\native\libs\gui\GLConsumer.cppstatus_tGLConsumer::acquireBufferLocked(BufferItem*item,nsecs_tpresentWhen,uint64_tmaxFrameNumber){//获取Consumer当前的显示内容BufferItem//既获取相机预览帧数据status_terr=ConsumerBase::acquireBufferLocked(item,presentWhen,maxFrameNumber);...//Ifitem->mGraphicBufferisnotnull,thisbufferhasnotbeenacquired//before,soanypriorEglImagecreatedisusingastalebuffer.This//replacesanyoldEglImagewithanewone(usingthenewbuffer).if(item->mGraphicBuffer!=NULL){intslot=item->mSlot;//由获取的item->mGraphicBuffer生成一个EglImage,并赋值给mEglSlots[slot].mEglImagemEglSlots[slot].mEglImage=newEglImage(item->mGraphicBuffer);}returnNO_ERROR;}
可以看到,SurfaceTexture最终会把GraphicBuffer创建一个EglImage对象,这个对象就保存了帧数据。
那么接下来的bindTextureImageLocked是如何将帧数据绑定到纹理上的呢?
status_tGLConsumer::bindTextureImageLocked(){....GLenumerror;....//mTexTarget为应用创建的GL_TEXTURE_EXTERNAL_OES型textureglBindTexture(mTexTarget,mTexName);...//由mGraphicBuffer生成EGLImageKHRstatus_terr=mCurrentTextureImage->createIfNeeded(mEglDisplay,mCurrentCrop);.....//将mCurrentTextureImage内容绑定到mTexTarget上mCurrentTextureImage->bindToTextureTarget(mTexTarget);.....//Waitforthenewbuffertobeready.returndoGLFenceWaitLocked();}
再看下createIfNeeded方法,它会最终调用到GLConsumer::EglImage::createImage
EGLImageKHRGLConsumer::EglImage::createImage(EGLDisplaydpy,constsp
关键语句是eglCreateImageKHR,可以看到,这里创建了EGLImageKHR对象。现在已经生成了EGLImageKHR图像,接下来分析下EGLImageKHR图像是如何绑定的GL_TEXTURE_EXTERNAL_OES纹理的。
//frameworks\native\libs\gui\GLConsumer.cppvoidGLConsumer::EglImage::bindToTextureTarget(uint32_ttexTarget){//更新纹理texTarget的数据为mEglImage//相当于glTexImage2D或者glTexSubImage2DglEGLImageTargetTexture2DOES(texTarget,static_cast
小结一下,整个updateTexImage方法大致分为这几步:
生成纹理内容EGLImageKHR对象,通过ConsumerBase::acquireBufferLocked获取当前的显示内容GraphicBuffer(比如相机帧数据),然后由此GraphicBuffer生成一个EGLImageKHR图像。将EGLImageKHR图像通过glEGLImageTargetTexture2DOES绑定到GL_TEXTURE_EXTERNAL_OES型纹理上。4EGLImageKHR
通过上面分析可以看到,SurfaceTexture的内部,主要是通过EGLImageKHR实现的,那么EGLImageKHR有什么作用呢?EGLImageKHR是EGL定义的一种专门用于共享2D图像数据的扩展格式,它可以在EGL的各种clientapi之间共享数据(如OpenGL,OpenVG),它的本意是共享2D图像数据,但是并没有明确限定共享数据的格式以及共享的目的。EGLImageKHR的创建函数原型是:
EGLImageKHReglCreateImageKHR(EGLDisplaydpy,EGLContextctx,EGLenumtarget,EGLClientBufferbuffer,constEGLint*attrib_list)
在Android系统中专门定义了一个称为EGL_NATIVE_BUFFER_ANDROID的Target,支持通ANativeWindowBuffer创建EGLImage对象,而Buffer则对应创建EGLImage对象时使用数据。
而在Android上定义的EGLClientBuffer是在native中定义的GraphicBuffer类,综上,EGLImageKHR的基本使用流程如下:
#defineEGL_NATIVE_BUFFER_ANDROID0x3140#defineEGL_IMAGE_PRESERVED_KHR0x30D2GraphicBuffer*buffer=newGraphicBuffer(1024,1024,PIXEL_FORMAT_RGB_565,GraphicBuffer::USAGE_SW_WRITE_OFTEN|GraphicBuffer::USAGE_HW_TEXTURE);unsignedchar*bits=NULL;buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,(void**)&bits);//Writebitmapdatainto'bits'herebuffer->unlock();//CreatetheEGLImageKHRfromthenativebufferEGLinteglImgAttrs[]={EGL_IMAGE_PRESERVED_KHR,EGL_TRUE,EGL_NONE,EGL_NONE};EGLImageKHRimg=eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY),EGL_NO_CONTEXT,EGL_NATIVE_BUFFER_ANDROID,(EGLClientBuffer)buffer->getNativeBuffer(),eglImgAttrs);//CreateGLtexture,bindtoGL_TEXTURE_2D,etc.//AttachtheEGLImagetowhatevertextureisboundtoGL_TEXTURE_2DglEGLImageTargetTexture2DOES(GL_TEXTURE_2D,img);
如果EGLClientBuffer的数据是YUV格式的,还可以使用纹理Target为:GL_TEXTURE_EXTERNAL_OES,该Target也是主要用于从EGLImage中产生纹理的情景。
4.1使用权限问题
AndroidNDK没有暴露GraphicBuffer相关接口,因此如果直接使用需要自行下载Android源码,编译并打包成动态库,需要注意的是,在API26之后,android已经禁止了私有nativeapi的调用。不过在API26以后,AndroidNDK提供了HardwareBufferAPIs类,来实现这样的功能,这个后续文章会详细说明,欢迎订阅。虽然大部分情况下我们直接使用SurfaceTexture即可。
4.2EGLImageKHR的重要性质
EGLImageKHR其设计目的就是为了共享2D纹理数据的,因此驱动程序在底层实现的时候,往往实现了CPU与GPU对同一资源的访问,这样就可以做到无需拷贝的数据共享,降低功耗与提高性能。
在Android平台上,了解这点是非常重要的。而iOS平台由于使用了EAGL而不是EGL,因此并不会使用EGLImage,但也有自己的数据映射的方式。
5共享纹理的两种实现
从上面可以知道,实现共享纹理,就可以有两种实现方式:
一种是EGL的ShareContext机制一种是共享内存,这里就是EGLImageKHR5.1ShareContext
EGL的ShareContext是常见的共享上下文的方式(iOS平台的EAGL叫ShareGroup)。
/**share_context:SpecifiesanotherEGLrenderingcontextwithwhichtosharedata,asdefinedbytheclientAPIcorrespondingtothecontexts.Dataisalsosharedwithallothercontextswithwhichshare_contextsharesdata.EGL_NO_CONTEXTindicatesthatnosharingistotakeplace.**/EGLContexteglCreateContext(EGLDisplaydisplay,EGLConfigconfig,EGLContextshare_context,EGLintconst*attrib_list)
当share_context参数传入另一个EGL的context时,这两个EGLContext就可以共享纹理以及VBO等。
需要注意的是containerobjects不能被共享,比如:
FramebufferobjectsVertexarrayobjectsTransformfeedbackobjectsProgrampipelineobjects5.2EGLImageKHR
这实际上是一种共享内存的方式,以实现共享纹理,最简单就是直接使用SurfaceTexture,这里不再详细说明。当然也可以使用HardwareBuffer与EGLImageKHR来实现。
6结语
这篇文章主要介绍了SurfaceTexture的原理,下篇文章将主要介绍SurfaceTexture与共享内存的应用,敬请期待。