Android SurfaceTexture说明
what
SurfaceTexture用来捕获视频流中的图像帧,视频流可以是相机预览或者是视频解码数据。
里边承接图像数据的也是GraphicBuffer, GLConsumer 作为其BufferQueue的消费方,取得数据后可以通过eglImage挂到opengles texture 里边对其做纹理采样。
how
就用法来讲,主要就是有数据后BufferQueue Producer在queueBuffer时 回调应用的onFrameAvailable(),应用自己决定是否、何时调用updateTetxtureImage()去做纹理绑定、采样
BufferQueue:
native层,在SurfaceTexture native对象 创建前创建了生产者和消费者;
消费者作为其构造函数传入给SurfaceTexture;
生产者作为全局变量保存,同时也保存在SurfaceTexture java 对象的mProducer里边 ;
GraphicBuffer的获取
生产方:通过 SurfaceTexture_getProducer()获取到BufferQueueProducer ;
消费方:SurfaceTexture 在UpdateTexImage()时去acquireBuffer
相关技术点
SurfaceView
what
只拥有BufferQueue的生产端,消费端为SurfaceFlinger,渲染后必须上屏。
TextureView
what
由于其里边拥有SurfaceTexture, 可用于离屏渲染,拥有BufferQueue的生产端和消费端,
生产端可为camera、MediaCodec、任意render 模块, 可以跨进程(底层是GraphicBuffer嘛),消费端为SurfaceTexture GLConsumer, 可对图像做二次处理。
YUV
why
一般情况下SurfaceTexture承载的数据都是YUV类型(来自mediacodec、camera),有必要简单说下。
what
一种图像数据编码格式,常用的为YUV420,俗称I420,
各格式说明:
4:4 ,完全取样
2:2 ,2:1水平取样,垂直完全采样
2:0 ,2:1水平取样,垂直2:1采样
1:1 ,4:1水平取样,垂直完全采样
格式 packed,planar、semi-planar
packed:不带后缀,如YUV420,数据以逐像素为单位排列,如
planar:带后缀P,如YUV420P,Y、U、V 分开存储,如
semi-planar:带后缀SP,如YUV420SP,UV交叉存储,与Y分开存储,如
这边可能要注意,排列可能为YYY.....UVUV, 或YYY...VUVU,即UV的第一个值到底是U还是V,
前者准确点叫YUV,后者叫YVU,又如 NV12、NV21 哈哈哈是不是有点迷糊了😜,
rgb与yuv的转换
一般可以用gpu,做个纹理采样去转,若gpu不支持硬件转换,需自己写shader;
有的gpu硬件支持直接转,如高通平台adreno,SurfaceTexture.updateTexImage()把YUV 图像数据挂上后,shader里边通过samplerExternal2D 采出来就是RGB数据。
android 系统 + 高通平台,如何获取不同YUV数据类型的GraphicBuffer大小,
接口:hardware/qcom/display/gralloc/gr_utils.cpp:: GetBufferSizeAndDimensions()
对于抓帧工具而言,将GraphicBuffer lock后,获取size,buffer里边分为Y、UV数据块,若是ubwc类型,头部还带meta数据,分块读取保存比较麻烦,由于这些分块数据在内存里边连续存储,所以可以直接整大块读取保存。
//notes: 该功能只有vendor才有能力开发,因为GraphicBuffer接口不对开发者开放,开放的AHardwareBuffer 相关的接口也起不了什么作用。
参考 kernel/msm-4.19/include/uapi/media/msm_media_info.h
EGLImage
what
EGLImageKHR是EGL定义的一种专门用于共享2D图像数据的扩展格式,它可以在EGL的各种client api之间共享gpu侧数据(如OpenGL,OpenVG),也可称之为direct texture(Using direct textures on Android), 当我们需要对一块GraphicBuffer数据做纹理采样就可以使用EGLImage,所以其底层数据载体当然为GraphicBuffer。
对于Android系统,既然共享,又用了GraphicBuffer,那么就是不仅可以在不同线程间共享,还可以跨进程共享。
适用场景 : 需要经常更新纹理数据的场合,比如逐帧更新;
对camera、mediacode的输出做采样处理;
跨进程、跨线程共享纹理;
数据类型: 由于做为扩展给gpu厂家去自行支持, EGLImage里边可以存的数据其格式可以是opengles标准不支持的, 具体看厂家自己去实现,理论上可以存各种数据, 但一般在高通平台主要也就RGB和YUV,再加上是否带UBWC压缩。
why
性能优化,能跨进程、跨线程在gpu 侧直接共享纹理数据,减少拷贝次数;
在跨进程、跨线程共享texture上统一定义了标准接口与用法。
how
底层通过GraphicBuffer 实现(基于EGL_ANDROID_image_native_buffer 扩展),
所以原理上其实EGLImage只是一种接口封装,对GraphicBuffer的封装,将其封装成texture,
eglCreateImageKHR()glBindTexture()glEGLImageTargetTexture2DOES()//glDrawXXX()eglDestroyImageKHR()
关于EGL_ANDROID_image_native_buffer 扩展
说明:
This extension enables using an Android window buffer (struct
ANativeWindowBuffer) as an EGLImage source.
该扩展使得可以将ANativeWindowBuffer 作为EGLImage的输入源
限制 :
If
is EGL_NATIVE_BUFFER_ANDROID, must be a valid display,
must be EGL_NO_CONTEXT, must be a pointer to a valid ANativeWindowBuffer object (cast into the type EGLClientBuffer), and
attributes other than EGL_IMAGE_PRESERVED_KHR are ignored."
即 对于EGLImageKHR eglCreateImageKHR( EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
dpy必须合法,上下文必须为EGL_NO_CONTEXT,
buffer输入源必须为ANativeWindowBuffer,GraphicBuffer通过getNativeBuffer()类型转换而来。
引用自: frameworks/native/opengl/specs/EGL_ANDROID_image_native_buffer.txt
bind texture
可以通过glEGLImageTargetTexture2DOES() 与GL_TEXTURE_2D、GL_TEXTURE_EXTERNAL_OES绑定,
eglImage和eglImageKHR
带KHR表示khronos 加的扩展,整体用法在Android上应该都差不多,一般都用带KHR后缀的,
具体细节倒没理得那么清😂。
EGLImage和EGLSurface
相同点: 底层都是GraphicBuffer,都是可以作为RenderTarget或texture使用;
不同点:创建的接口不同,eglCreateWindowSurface()、eglCreatePixmapSurface()、eglCreatePbufferSurface()、eglCreatePbufferFromClientBuffer(),eglCreateImageKHR()
用法不同,EGLImage上下文无关,可以跨进程、线程共享、
EGLSurface 上下文绑定。
GraphicBuffer 与eglImage 关系
等我看看freedreno代码先。。。
进程间共享图像数据方式
GPU侧:GraphicBuffer + EGLImage + oes texture;
CPU侧:共享内存;
共进程,不同opengl es线程共享纹理方式
(1)share context后,共享texture;
(2)EGLImage;
GL_TEXTURE_EXTERNAL_OES
what
俗称oes texture, 专门为EGLImage而加的texute类型,对于装有YUV数据的EGLImage,EGLImage一般通过挂到GL_TEXTURE_EXTERNAL_OES去做处理;
shader里边的sampler类型: samplerExternalOES;
如果是采样YUV数据类型的纹理,会自动转成RGB(GPU厂商需要实现这个功能),
Sampling an external texture will return an RGBA vector in the same colorspace as the source image. If the source image is stored in YUV (or some other basis) then the YUV values will be transformed to RGB values.
why
为何不直接挂到GL_TEXTURE_2D处理呢? 为什么要加上这个扩展?
这块理的不是特别清楚,相关资料都没讲得那么清,以下是一些整理+自己理解
来自:frameworks/base/libs/hwui/surfacetexture/SurfaceTexture.h
即,除非需要从fbo拷贝数据到该texture挂的eglimage,才会将该eglImage挂到GL_TEXTURE_2D上,
而咱们一般都是对这块eglimage做采样嘛,所以要挂到GL_TEXTURE_EXTERNAL_OES上。
(感觉有点牵强,可以先接着往下看)
(1)外部纹理,即gpu侧图像数据来自其他上下文,对其采样时,目前的GL_TEXTURE2D不能挂,需要用eglimage挂到GL_TEXTURE_EXTERNAL_OES,
(2)硬件对YUV转RGB的支持
对于装YUV数据格式的EGLImage,一般GPU也不支持硬件YUV转RGB,若直接挂到GL_TEXTURE_2D做采样,需要自己在shader做RGB转换;
TEXTURE_EXTERNAL_OES 作为扩展,当GPU支持硬件YUV转RGB时,就会通过支持该扩展来表明。
how
怎么把gpu这个功能用上呢?
使用TEXTURE_EXTERNAL_OES,把EGLImage挂上去。
eglCreateImageKHR()glBindTexture(TEXTURE_EXTERNAL_OES, textureId)//eglImage 装YUV格式数据glEGLImageTargetTexture2DOES(TEXTURE_EXTERNAL_OES, eglImage)//glDrawXXX()eglDestroyImageKHR()
通过samplerExternalOES类型的采样器,从YUV数据类型的TEXTURE_EXTERNAL_OES里边采样出来的数据,就是RGB,gpu做了转换,无需shader去转,eg:
//声明使用该扩展 "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + //指定sampler类型 "uniform samplerExternalOES tex_sampler_0;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + "}\n";
跨线程使用
准确的说是跨拥有不同opengles context的线程
attachToGLContext()detachToGLContext()//通过这2个接口跨线context
代码分析
主要代码流程
SurfaceTexture.java -->base/core/jni/android/graphics/SurfaceTexture.cpp-->base/libs/hwui/surfacetexture/SurfaceTexture.cpp--> EGLConsumer.cpp --> opengl es
SurfaceTexture.java:
主要是做类的作用、用法说明,对每个接口做了详细的说明,没什么逻辑,都是直接转调native 接口,
base/core/jni/android/graphics/SurfaceTexture.cpp:
实现SurfaceTexture.jave 的jni接口,里边没什么具体逻辑, 主要是承接 SurfaceTexture.jave 和base/libs/hwui/surfacetexture/SurfaceTexture.cpp
base/libs/hwui/surfacetexture/SurfaceTexture.cpp:
对SurfaceTexture.java的实现,主要是GraphicBuffer 的处理
EGLConsumer.cpp :
opengl es接口的封装,主要做了 基于acquireBuffer()出来的GraphicBuffer创建eglImageKHR, 将其挂到指定的oes texture 上
时序图
注册SurfaceTexture.OnFrameAvailableListener:
生产者生产完一块GraphicBuffer 数据后回调onFrameAvailable() 流程:
UpdateTextureImage(),消费者消费该GrapicBuffer,将其挂到opengles texture做纹理采样流程:
参考
https://blog.csdn.net/xkuzhang/article/details/115423061
https://paaatrick.com/2020-01-26-yuv-pixel-formats/
https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_image_base.txt
https://registry.khronos.org/OpenGL/extensions/OES/OES_EGL_image_external.txt
http://www.khronos.org/registry/gles/extensions/OES/OES_EGL_image_external.txt
来源地址:https://blog.csdn.net/goodnight1994/article/details/129379585
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341