安卓SurfaceTexture中updateTexImage使用及源码分析

文章目录

    • 引言
    • updateTexImage 简单使用
    • SurfaceTexture 初始化相关源码分析
    • Surface 绘制流程源码分析
    • createBufferQueue 源码分析
    • SurfaceTexture 之 updateTexImage 源码分析
    • 结尾

本文首发地址 https://h89.cn/archives/140.html
最新更新地址 https://gitee.com/chenjim/chenjimblog

引言

在Android系统中,SurfaceTexture 是一个特殊的类,它将来自硬件纹理缓冲区(如相机预览流或视频解码输出)的图像数据转换为 OpenGL ES 可以直接使用的纹理。updateTexImage() 方法是 SurfaceTexture 类的核心方法之一,此方法的主要作用是从 SurfaceTexture 内部持有的图像缓冲区中取出最新一帧,并将其内容复制到与 SurfaceTexture 关联的 OpenGL 纹理上。这对于实时图形渲染、视频播放以及从相机捕获并实时处理图像等场景至关重要。

updateTexImage 简单使用

下面是一个简化的使用 SurfaceTexture 与 GLSurfaceView 实现渲染的基本流程:

// 初始化 SurfaceTexture
SurfaceTexture surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture st) {// 新帧到达时,通知主线程进行渲染// 在这里可以调用 requestRender() 或者通过 Handler 发送消息}
});// 在GL线程中初始化纹理并绑定
fun initTexture(){int[] textureId = new int[1];GLES20.glGenTextures(1, textureId, 0);GLES20 glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId[0]);// 设置纹理参数GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);// 将SurfaceTexture关联到OpenGL纹理surfaceTexture.attachToGLContext(textureId[0]);
}// 在onDrawFrame(GL10 gl)方法中更新和渲染纹理
@Override
public void onDrawFrame(GL10 unused) {// 检查是否有新帧可用if (surfaceTexture.isFrameAvailable()) {// 更新纹理数据surfaceTexture.updateTexImage();// 获取变换矩阵float[] transformMatrix = new float[16];surfaceTexture.getTransformMatrix(transformMatrix);// 使用该矩阵进行纹理坐标变换后渲染// ...}
}
// 注意:实际应用中需要确保在正确的线程上调用 updateTexImage() 和其他OpenGL函数

SurfaceTexture 初始化相关源码分析

android.graphics.SurfaceTexture 初始化

    // frameworks.base\graphics\java\android\graphics\SurfaceTexture.javapublic SurfaceTexture(int texName, boolean singleBufferMode) {mCreatorLooper = Looper.myLooper();mIsSingleBuffered = singleBufferMode;nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));}public void updateTexImage() {nativeUpdateTexImage();}

android.graphics.SurfaceTexture JNI实现

// frameworks\base\core\jni\android_graphics_SurfaceTexture.cpp
static const JNINativeMethod gSurfaceTextureMethods[] = {{"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init},{"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage},
};static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz) {// 最终调用了 C++ 对象 SurfaceTexture 的 updateTexImagestatus_t err = surfaceTexture->updateTexImage();
}static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,jint texName, jboolean singleBufferMode, jobject weakThiz)
{sp<IGraphicBufferProducer> producer;sp<IGraphicBufferConsumer> consumer;// 创建 生产者和消费者, createBufferQueue 详细源码见后文。BufferQueue::createBufferQueue(&producer, &consumer);if (singleBufferMode) {consumer->setMaxBufferCount(1);}sp<SurfaceTexture> surfaceTexture;if (isDetached) {// 一般情况只有 singleBufferMode 时,isDetached 为真surfaceTexture = new SurfaceTexture(consumer, GL_TEXTURE_EXTERNAL_OES,true, !singleBufferMode);} else {// 传入了 消费者  consumersurfaceTexture = new SurfaceTexture(consumer, texName,GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);}// 设置名称 surfaceTexture->setName(String8::format("SurfaceTexture-%d-%d-%d",(isDetached ? 0 : texName),getpid(),createProcessUniqueId()));// 检查当前的 EGL 上下文是否受保护。consumer->setConsumerIsProtected(isProtectedContext());// 通过 fields.surfaceTexture 保存 surfaceTexture 到 android.graphics.SurfaceTexture 中 mSurfaceTextureSurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);// 通过 fields.producer 保存 producer 到 android.graphics.SurfaceTexture 中 mProducerSurfaceTexture_setProducer(env, thiz, producer);sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz, clazz));// 回调 设置到 ConsumerBase 中surfaceTexture->setFrameAvailableListener(ctx);// 通过 fields.frameAvailableListener 保存 ctx 到 android.graphics.SurfaceTexture 中 mFrameAvailableListenerSurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
}void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */)
{// 回调到 android.graphics.SurfaceTexture 中 postEventFromNative// 进而 可以 通过 android.graphics.SurfaceTexture.setOnFrameAvailableListener 通知到应用env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
}

SurfaceTexture 继承自 ConsumerBase, setFrameAvailableListener 如下

class ANDROID_API SurfaceTexture : public ConsumerBase {}
void ConsumerBase::setFrameAvailableListener(const wp<FrameAvailableListener>& listener) {Mutex::Autolock lock(mFrameAvailableMutex);mFrameAvailableListener = listener;
}

SurfaceTexture_setFrameAvailableListener 如下

static void SurfaceTexture_setFrameAvailableListener(JNIEnv* env,jobject thiz, sp<SurfaceTexture::FrameAvailableListener> listener)
{// 拿到 android.graphics.SurfaceTexture 中 mFrameAvailableListenerSurfaceTexture::FrameAvailableListener* const p =(SurfaceTexture::FrameAvailableListener*)env->GetLongField(thiz, fields.frameAvailableListener);...// 更新到 android.graphics.SurfaceTexture env->SetLongField(thiz, fields.frameAvailableListener, (jlong)listener.get());
}

谁会调用 JNISurfaceTextureContext::onFrameAvailable 呢

void ConsumerBase::onFrameAvailable(const BufferItem& item) {sp<FrameAvailableListener> listener;{Mutex::Autolock lock(mFrameAvailableMutex);listener = mFrameAvailableListener.promote();}if (listener != nullptr) {// 也就是调用 JNISurfaceTextureContext::onFrameAvailablelistener->onFrameAvailable(item);}
}

谁会调用 ConsumerBase::onFrameAvailable 呢

// frameworks\native\libs\nativedisplay\surfacetexture\SurfaceTexture.cpp
SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,uint32_t texTarget, bool useFenceSync, bool isControlledByApp): ConsumerBase(bq, isControlledByApp)...
// SurfaceTexture 继承自 ConsumerBase,最终由代理通知 FrameAvailable 
// frameworks\native\libs\gui\ConsumerBase.cpp
ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :mAbandoned(false),mConsumer(bufferQueue),mPrevFinalReleaseFence(Fence::NO_FENCE) {mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());// 我们不能在构造函数中创建一个不会在构造函数结束后保持引用的sp<...>(this),// 因为这会导致this的引用计数在构造函数结束时变为0。// 由于我们所需要的只是一个wp<...>,所以我们创建了它。wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);sp<IConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);// 创建一个消费者代理,并连接到代理 status_t err = mConsumer->consumerConnect(proxy, controlledByApp);if (err != NO_ERROR) {CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",strerror(-err), err);} else {mConsumer->setConsumerName(mName);}
}

本小节主要分析了 SurfaceTexture 初始化 和 onFrameAvailable 回调源码。
下小节主要分析 消费者 的 onFrameAvailable 在哪被调用的。

Surface 绘制流程源码分析

先看一张图,后面对图进行解释

原图来自 Android绘制机制以及Surface家族源码全解析

  • 首先我们从 lockCanvas 这个入口开始调用链是:lockCanvas——>nativeLockCanvas——>Surface::lock——>Surface::dequeueBuffer,这里最终会使用我们在 Surface 创建的时候得到的 BufferQueueProducer(IGraphicBufferProducer) 来想 SF (SurfaceFlinger) 请求一块空白的图像内存。得到了图像内存之后,将内存传入 new SkBitmap.cpp 中创建对应对象,一共后面使用 Skia 库绘制图像。这里的 SkBitmap.cpp 会被交给 SkCanvas.cpp 而 SkCanvas.cpp 对象就是 Canvas.java 在 c++ 层的操作对象。
  • 上面我们通过 lockCanvas 获取到了一个 Canvas 对象。当我们调用 Canvas 的各种 api 的时候其实最终会代用到 c++ 层的 Skia 库,通过 cpu 对图像内存进行绘制。
  • 当我们绘制完之后就可以调用 unlockCanvasAndPost 来通知 SF 合成图像,调用链是:unlockCanvasAndPost——>nativeUnlockCanvasAndPost——>Surface::queueBuffer,与 lockCanvas 中相反这里是最终是通过 BufferQueueProducer::queueBuffer 将图像内存放回到队列中,除此之外这里还调用 IConsumerListener::onFrameAvailable 来通知 SF 进程来刷新图像,调用链是:onFrameAvailable——>SF.signalLayerUpdate——>MQ.invalidate——>MQ.Handler.dispatchInvalidate——>MQ.Handler.handleMessage——>onMessageReceived:因为 SF 进程采用的也是事件驱动模型,所以这里和 ui thread 类似也是通过 looper + 事件 的形式触发 SurfaceFinger 对图像的刷新的。注意:这里的 IConsumerListener 是在 createNormalLayer 的时候创建的 Layer.cpp。
    这里没有贴出详细源码,可以依据名称,查看对应源码。
    到这里我们已经清晰了上一下小结中 onFrameAvailable 是从哪里回调来的。

createBufferQueue 源码分析

createBufferQueue 的实现:

// frameworks/native/libs/gui/BufferQueue.cpp
// 创建 BufferQueue 进而初始化 producer 和 consumer
// camera如有帧数据来时,会调用dequeBuffer取一个buffer塞数据,塞好后再queueBuffer将其丢回到BufferQueue,然后通知consume
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,sp<IGraphicBufferConsumer>* outConsumer,bool consumerIsSurfaceFlinger) {sp<BufferQueueCore> core(new BufferQueueCore());sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger));sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));*outProducer = producer;*outConsumer = consumer;
}

  • 最左边表示原始图像流的生成方式:Video(本地/网络视频流)、Camera(摄像头拍摄的视频流)、Software/Hardware Render(使用 Skia/GL 绘制的图像流)。
  • 最左边的原始图像流可以被交给 Surface,而 Surface 是 BQ 的生产者,GLConsumer(java层的体现就是 ST) 是 BQ 的消费者。
  • GLConsumer 拿到了 Surface 的原始图像流,可以通过 GL 转化为 texture。最终以合理的方式消耗掉。
  • 消耗 GLConsumer 中的 texture 的方式就是使用 SurfaceView、TextureView 等方式或者显示在屏幕上或者用于其他地方。

SurfaceTexture 之 updateTexImage 源码分析

通过前面初始化源码分析,我们知道会调用到 SurfaceTexture.cpp 的 updateTexImage

// frameworks\native\libs\nativedisplay\surfacetexture\SurfaceTexture.cpp
status_t SurfaceTexture::updateTexImage() {Mutex::Autolock lock(mMutex);return mEGLConsumer.updateTexImage(*this);
}
//frameworks/native/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
status_t EGLConsumer::updateTexImage(SurfaceTexture& st) {// 检查EGL状态status_t err = checkAndUpdateEglStateLocked(st);BufferItem item;// 调用 status_t SurfaceTexture::acquireBufferLocked(*) 获取 BufferQueue 头部最新的图像内存err = st.acquireBufferLocked(&item, 0);// 释放之前的buffererr = updateAndReleaseLocked(item, nullptr, st);// 绑定新的 Buffer 到 GL texture,然后等待下一帧return bindTextureImageLocked(st);
}
status_t SurfaceTexture::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, uint64_t maxFrameNumber) {status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber);return NO_ERROR;
}
status_t ConsumerBase::acquireBufferLocked(BufferItem *item,nsecs_t presentWhen, uint64_t maxFrameNumber) {...status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);...return OK;
}
status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,nsecs_t expectedPresent, uint64_t maxFrameNumber) {.....}

结尾

到此我们分析了 SurfaceTexture 整个显示流程,可以用下图概括:

原图及本文部分内容来自 Android绘制机制以及Surface家族源码全解析 , 感谢 !!
希望本文对你有帮助,欢迎留言讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/664992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中国象棋基础

帅&#xff08;将&#xff09;的运用原则&#xff1a; &#xff08;1&#xff09;帅走直线&#xff0c;前进后退均可 &#xff08;2&#xff09;一次只能走一格 &#xff08;3&#xff09;活动范围在“九宫”之内 &#xff08;4&#xff09;可行处可吃敌子 &#xff08;5&…

React实例之完善布局菜单(一)

今天我们来用所学的知识来做一个布局菜单的组件, 针对这个组件我之前写过一个教程 React之布局菜单-CSDN博客&#xff0c;那个呢比较基础&#xff0c;这节课算是对那个教程的一个扩展和补充。这个实例讲完&#xff0c;这个系列就算告一段落了。先看效果 这个教程要求对React知识…

算法学习——华为机考题库6(HJ36 - HJ40)

算法学习——华为机考题库6&#xff08;HJ36 - HJ40&#xff09; HJ36 字符串加密 描述 有一种技巧可以对数据进行加密&#xff0c;它使用一个单词作为它的密匙。下面是它的工作原理&#xff1a;首先&#xff0c;选择一个单词作为密匙&#xff0c;如TRAILBLAZERS。如果单词中…

DDD技术方案落地实践

DDD技术方案落地实践 引言 从接触领域驱动设计的初学阶段&#xff0c;到实现一个旧系统改造到DDD模型&#xff0c;再到按DDD规范落地的3个的项目。对于领域驱动模型设计研发&#xff0c;从开始的各种疑惑到吸收各种先进的理念&#xff0c;目前在技术实施这一块已经基本比较成…

Postgres与DynamoDB:选择哪个数据库

启动新项目时需要做出的决定之一是使用哪个数据库。如果您使用的是Django这样的包含电池的框架&#xff0c;那么没有理由再三考虑。选择一个受支持的数据库引擎&#xff0c;就可以了。另一方面&#xff0c;如果你使用像FastAPI或Flask这样的微框架&#xff0c;你需要自己做出这…

JVM系列——垃圾收集器Parrlel Scavenge、CMS、G1常用参数和使用场景

背景 当前在Java领域&#xff0c;JDK 8版本仍然享有广泛的使用&#xff0c;它支持了Parallel Scavenge、CMS和G1这几种垃圾收集器。因此&#xff0c;为了在业务应用中更加高效地进行开发和性能调优&#xff0c;我们需要对这些垃圾收集器的工作原理和特性有一个全面的理解和认识…

【MySQL】双写、重做日志对宕机时脏页数据落盘的作用的疑问及浅析

众所周知&#xff0c;双写机制、重做日志文件是mysql的InnoDB引擎的几个重要特性之二。其中两者的作用都是什么&#xff0c;很多文章都有分析&#xff0c;如&#xff0c;双写机制&#xff08;Double Write&#xff09;是mysql在crash后恢复的机制&#xff0c;而重做日志文件&am…

【复现】大华 DSS 数字监控系统 任意文件读取漏洞_38

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 大华DSS是大华的大型监控管理应用平台&#xff0c;支持几乎所有涉及监控等方面的操作&#xff0c;支持多级跨平台联网等操作。 可…

2024年适合进入股市吗?北京想开股票账户找哪家证券公司交易佣金费用最低?

股市规则是指股票市场中的一系列规则和制度&#xff0c;用于监管和管理股票交易。以下是一些常见的股市规则&#xff1a; 证券法律法规&#xff1a;股市规则的基础是国家的证券法律法规&#xff0c;包括证券法、公司法等&#xff0c;用于规范股票发行、交易和上市等方面的法律规…

重生奇迹MU套装怎么配

汉斯的皮套装&#xff1a;冰之指环,皮护腿,皮盔,皮护手,皮靴,皮铠,流星槌 汉斯的青铜套装&#xff1a;青铜护腿,青铜靴,青铜铠 汉斯的翡翠套装&#xff1a;雷之项链,翡翠护腿,翡翠盔,翡翠铠,远古之盾 汉斯的黄金套装&#xff1a;火之项链,黄金护腿,黄金护手,黄金靴,黄金铠 …

代码随想录算法训练营DAY11 | 栈与队列 (2)

一、LeetCode 20 有效的括号 题目链接&#xff1a;20.有效的括号https://leetcode.cn/problems/valid-parentheses/ 思路&#xff1a;遇到左括号直接进栈&#xff1b;遇到右括号判断站顶是否有匹配的括号&#xff0c;没有就返回flase&#xff0c;有就将栈顶元素出栈&#xff1…

FANUC机器人示教器的菜单变成了图标,如何改成列表的形式?

FANUC机器人示教器的菜单变成了图标&#xff0c;如何改成列表的形式&#xff1f; 如下图所示&#xff0c;开机后按下MENU菜单键时&#xff0c;发现原来的列表形式变成了菜单图标的形式&#xff0c;同时在按F1-F5键时&#xff0c;提示&#xff1a;HMI模式-键不可用&#xff0c; …

向日葵案例解析:无外网接入,医疗设备如何进行远程售后运维

随着医学科学以及生物工程技术的高速发展&#xff0c;医院对于高端医疗设备如MR、CT、B超等高科技成像设备和放射治疗设备的需求激增。医学影像检查作为一种重要的手段&#xff0c;在许多疾病确诊过程中发挥着至关重要的作用。检查结果正确与否&#xff0c;直接影响临床医生对疾…

JS 引导动画

前言 引导动画是程序在某一时刻播放的动画&#xff0c;通常用于向用户介绍程序的功能和特点。 实现效果 实现方式 引导动画的实现方式有很多种&#xff0c;这里我使用的是 CSS 的 clip-path 属性。 技术选型 这里我为什么要选择 clip-path 属性而不是 mask 属性呢&#xf…

阿狸与小兔子的奇幻之旅

在很久很久以前&#xff0c;有一个遥远的国度&#xff0c;这个国度里生活着各种各样的动物&#xff0c;它们和谐共处&#xff0c;幸福快乐。在这个国度里&#xff0c;有一只聪明伶俐的小狐狸&#xff0c;名叫阿狸。 一天&#xff0c;阿狸在森林里散步时&#xff0c;遇到了一只正…

关于网络面试题汇总

什么是TCP/IP五层模型&#xff1f;它们的作用是啥&#xff1f;基于TCP/IP实现的应用&#xff08;层协议&#xff09;有哪些&#xff1f; TCP/IP五层模型&#xff0c;从上向下分别是&#xff1a; 应用层&#xff1a;应用程序本身&#xff0c;应用层的作用是负责应用程序之间的…

数据结构篇-05:哈希表解决字母异位词分组

本文对应力扣高频100 ——49、字母异位词分组 哈希表最大的特点就是它可以把搜索元素的时间复杂度降到O(1)。这一题就是要我们找到 “字母异位词” 并把它们放在一起。 “字母异位词”就是同一个单词中字母的不同组合形式。判断“字母异位词”有两个视角&#xff1a;1、所含字…

全面认识DOS系统

目录 一、DOS系统的功能 1.执行命令和程序&#xff08;处理器管理&#xff09; 2.内存管理 3.设备管理 4.文件管理 5.作业管理 二、文件与目录 三、文件类型与属性 1.系统属性&#xff08;S&#xff09; 2.隐含属性&#xff08;H&#xff09; 3.只读属性&#xff08…

初谈类和对象

文章目录 前言类的引入类的定义类的两种定义方式成员命名规则 类的访问限定符及封装访问限定符面试题封装 类的作用域类的实例化类对象模型计算类对象的大小 this指针this指针特性 前言 C语言是面向过程的&#xff0c;关注点是过程&#xff1b;而C面向的是对象&#xff0c;关注…

mysql-FIND_IN_SET查询优化

优化前 SELECTuser_id,user_name,real_name,PASSWORD,real_org_id,real_org_name,real_dept_id,real_dept_name, STATUS FROMsys_user WHEREis_del 0 AND find_in_set( lilong, login_user_account ) 优化后 SELECTuser_id,user_name,real_name,PASSWORD,real_org_id,real…