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

news2024/12/25 23:39:57

文章目录

    • 引言
    • 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() {
    @Override
    public 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.java
    public 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 的 updateTexImage
    status_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 {
        // 传入了 消费者  consumer
        surfaceTexture = 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 中 mSurfaceTexture
    SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
    // 通过 fields.producer 保存 producer 到 android.graphics.SurfaceTexture 中 mProducer
    SurfaceTexture_setProducer(env, thiz, producer);


    sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz, clazz));
    // 回调 设置到 ConsumerBase 中
    surfaceTexture->setFrameAvailableListener(ctx);
    // 通过 fields.frameAvailableListener 保存 ctx 到 android.graphics.SurfaceTexture 中 mFrameAvailableListener
    SurfaceTexture_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 中 mFrameAvailableListener
    SurfaceTexture::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::onFrameAvailable
        listener->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);

    // 释放之前的buffer
    err = 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.coloradmin.cn/o/1428778.html

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

相关文章

2024数模美赛C题F题完整代码结果展示

C&#xff1a;Momentum in Tennis 实在精力有限&#xff0c;完整讲解可以移步去看我的讲解视频啦&#xff1a; 美赛C题每一问代码结果讲解及进度说明 F&#xff1a;Reducing lllegal Wildlife Trade 实在精力有限&#xff0c;完整讲解可以移步去看我的讲解视频啦&#xff1a; ​…

近期CCF系列会议截稿时间

专属领域会议订阅 关注{晓理紫}&#xff0c;每日更新会议信息&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新会议信息。 CSFW (CCF B) IEEE Computer Security Foun…

【gcc】webrtc发送侧计算 丢包率

大神的分析 : 提到: 每当收到cc-feedback或者收到RR-report的时候就能统计出丢包率,在cc-controller中就会调用SendSideBandwidthEstimation::UpdatePacketsLost()去更新丢包率,同时进行码率预估 G:\CDN\rtcCli\m98\src\modules\congestion_controller\goog_cc\send_side_b…

假期无忧!这个微信自动回复工具助您高效管理与客户互动

在繁忙的现代生活中&#xff0c;尤其是在假期期间&#xff0c;处理大量微信消息可能会成为一项艰巨的任务。今天就给大家分享一个能够让微信自动回复消息的工具&#xff0c;帮助你解放双手&#xff0c;让你的假期无忧&#xff01; 通过微信管理系统&#xff0c;您可以同时登录…

152基于matlab的GUI滚动轴承特征频率计算

基于matlab的GUI滚动轴承特征频率计算&#xff0c;输入轴承参数&#xff0c;包括转速&#xff0c;节圆直径、滚子直径、滚子数、接触角&#xff0c;就可得滚动特征频率结果&#xff0c;程序已调通&#xff0c;可直接运行。 152 matlab 轴承特征频率 GUI (xiaohongshu.com)

uni-app 经验分享,从入门到离职(三)——关于 uni-app 生命周期快速了解上手

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;什么是生命周期&#x1f9e9;应用生命周期&#x1f4cc; 关于 App.vue/App.uvue &#x1f9e9;页面生命周期&#x1f4cc;关于 onShow 与 onLoad 的区别 &#x1f9e9;组件生命周期 &#x1f4dd;最后 &#x1f4cb;前言 这…

C# SSH.NET 长命令及时返回

在SSH中执行长时间的命令&#xff0c;SSH.NET及时在文本框中返回连续显示结果。 c# - Execute long time command in SSH.NET and display the results continuously in TextBox - Stack Overflow 博主管理了一个服务器集群&#xff0c;准备上自动巡检工具&#xff0c;测试在…

clickhouse如何清除多个分区数据 alter table drop partition操作

官网drop partition操作 官网链接&#xff1a;https://clickhouse.com/docs/zh/sql-reference/statements/alter/partition#drop-partitionpart 官网上之有清除单个分区的例子&#xff0c;并没有对清除多个分区的场景进行描述&#xff0c;之前清除分区时也是按照官网例子进行…

python爬虫4

#1.练习 # &#xff08;1&#xff09; 获取网页的源码 # &#xff08;2&#xff09; 解析 解析的服务器响应的文件 etree.HTML # (3) 打印 import urllib.request urlhttps://www.baidu.com/ headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit…

2024年美赛B题潜水器定位和搜救建模代码和完整论文文档

目前已完成2024年美赛B题潜水器定位和搜救的建模代码和论文编写&#xff0c;部分文章内容和代码如下&#xff1a; 摘要 在海洋探险和搜救领域&#xff0c;潜水器的定位和搜救任务具有重要意义。本文旨在开发一系列模型来预测潜水器位置、分析不确定性、确定信息传递策略、建议…

关于配置系统环境变量 点击确定就显示,此环境变量过大2047

使用了网络上的所有办法,均无效 最终解决办法:把系统path环境变量里的变量环境删掉一些之后,成功加入! 原因就是path里面的内容太多了导致的,删掉一些变量就好了&#xff01;

3D打印、自动升降超静电机驱动方案TMC2209

TMC2209步进电机驱动芯片介绍 TMC2209是一款用于两相步进电机的超静音电机驱动IC。Trinamic的精密StealthChop波器确保了无噪音运行、最大效率和最佳电机转矩。它的快速电流调节和与SpreadCycle的可选组合允许高度动态运动&#xff0c;同时为无传感器归位添加了StallGuard4。集…

qt -chart控件设计器可拖拉

qt -chart控件设计器可拖拉 一、演示效果二、安装过程三、核心程序四、程序链接 一、演示效果 二、安装过程 三、核心程序 #include <QtGui> #include <QColor>#include <cstdlib> #include <cassert> #include <numeric>#include <chartwor…

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

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

缓存框架jetcache

在实际应用中&#xff0c;并不是单一的使用本地缓存或者redis&#xff0c;更多是组合使用来满足不同的业务场景。 jetcache组件实现了优雅的组合本地缓存和远程缓存。 支持多种缓存类型&#xff1a;本地缓存、分布式缓存、多级缓存。 官网地址&#xff1a;https://github.com…

解决Docker打包Eureka注册中心,其他服务无法注册问题

​前言 本文主要是介绍利用docker打包Eureka注册中心&#xff0c;并且发布镜像到服务器&#xff0c;遇到的一个比较坑的问题。主要是服务镜像部署完毕之后&#xff0c;docker容器都能启动&#xff0c;并且也能访问&#xff0c;但是其他服务就是无法注册到注册中心。排除问题&a…

【Java程序设计】【C00239】基于Springboot的漫画之家管理系统(有论文)

基于Springboot的漫画之家管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的漫画之家系统 本系统分为系统功能模块、管理员功能模块以及用户功能模块。 系统功能模块&#xff1a;在系统首页可以查看首页&a…

云原生业务全流程DevOps配置预研与实践

背景 我在一个二线城市&#xff08;山东济南&#xff09;&#xff0c;相对与北上广深杭这些IT业发达的城市来说&#xff0c;济南IT业对于业内新技术的接受度是有点慢的&#xff0c;国内很多一线大厂早先几年前就开始实践使用的技术&#xff0c;我们这边也是近两年才开始慢慢兴…

wangEditor v4的简单使用

当前文档是 wangEditor v4 版本的。 wangEditor v5 已经正式发布&#xff0c;可参考文档。 v5 发布之后&#xff0c;v4 将不再开发新功能。 介绍 English documentation wangEditor4 —— 轻量级 web 富文本编辑器&#xff0c;配置方便&#xff0c;使用简单。 官网&#…

2023 OpenHarmony 年度运营报告

汇聚 70 家企业 6700名贡献者力量&#xff0c; OpenHarmony 已成为下一代智能终端操作系统根社区&#xff1b; 我们在成长,OpenHarmony 项目群成员单位增至 35 家&#xff1b; 2023 年持续迭代更新 6 个版本及 OpenHarmony4.0 重点特性简介……