台式电脑

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可将此缓冲区作为外部纹理使用。

谈一谈Android上的SurfaceTexture

基本流程如下:

通过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(this));}

framework层会调用到nativeInit方法,最终调用到SurfaceTexture_init方法。

//frameworks\base\core\jni\SurfaceTexture.cpp//texName为应用创建texture名//weakThiz为SurfaceTexture对象弱引用staticvoidSurfaceTexture_init(JNIEnv*env,jobjectthiz,jbooleanisDetached,jinttexName,jbooleansingleBufferMode,jobjectweakThiz){spproducer;spconsumer;//创建IGraphicBufferProducer及IGraphicBufferConsumerBufferQueue::createBufferQueue(&producer,&consumer);if(singleBufferMode){consumer->setMaxBufferCount(1);}spsurfaceTexture;//isDetached为falseif(isDetached){....}else{//将consumer和texName封装为GLConsumer类对象surfaceTexturesurfaceTexture=newGLConsumer(consumer,texName,GL_TEXTURE_EXTERNAL_OES,true,!singleBufferMode);}....//设置surfaceTexture名字surfaceTexture->setName(String8::format("SurfaceTexture-%d-%d-%d",(isDetached?0:texName),getpid(),createProcessUniqueId()));//Ifthecurrentcontextisprotected,informtheproducer.consumer->setConsumerIsProtected(isProtectedContext());//将surfaceTexture保存到env中SurfaceTexture_setSurfaceTexture(env,thiz,surfaceTexture);//将producer保存到env中SurfaceTexture_setProducer(env,thiz,producer);jclassclazz=env->GetObjectClass(thiz);//JNISurfaceTextureContext继承了GLConsumer::FrameAvailableListenerspctx(newJNISurfaceTextureContext(env,weakThiz,clazz));//surfaceTexture设置帧回调对象ctx,//收到帧数据是会触发ctx->onFrameAvailable方法surfaceTexture->setFrameAvailableListener(ctx);SurfaceTexture_setFrameAvailableListener(env,thiz,ctx);}

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&graphicBuffer,constRect&crop){EGLClientBuffercbuf=static_cast(graphicBuffer->getNativeBuffer());constboolcreateProtectedImage=(graphicBuffer->getUsage()&GRALLOC_USAGE_PROTECTED)&&hasEglProtectedContent();EGLintattrs[]={EGL_IMAGE_PRESERVED_KHR,EGL_TRUE,EGL_IMAGE_CROP_LEFT_ANDROID,crop.left,EGL_IMAGE_CROP_TOP_ANDROID,crop.top,EGL_IMAGE_CROP_RIGHT_ANDROID,crop.right,EGL_IMAGE_CROP_BOTTOM_ANDROID,crop.bottom,createProtectedImage?EGL_PROTECTED_CONTENT_EXT:EGL_NONE,createProtectedImage?EGL_TRUE:EGL_NONE,EGL_NONE,};.....eglInitialize(dpy,0,0);//调用eglCreateImageKHR创建EGLImageKHR对象EGLImageKHRimage=eglCreateImageKHR(dpy,EGL_NO_CONTEXT,EGL_NATIVE_BUFFER_ANDROID,cbuf,attrs);...returnimage;}

关键语句是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(mEglImage));}

小结一下,整个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的创建函数原型是:

surface电脑怎么样程序员(谈一谈Android上的SurfaceTexture)

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与共享内存的应用,敬请期待。

相关新闻

返回顶部