Android显示系统(13)- 向SurfaceFlinger提交Buffer

Android显示系统(01)- 架构分析
Android显示系统(02)- OpenGL ES - 概述
Android显示系统(03)- OpenGL ES - GLSurfaceView的使用
Android显示系统(04)- OpenGL ES - Shader绘制三角形
Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)
Android显示系统(06)- OpenGL ES - VBO和EBO和VAO
Android显示系统(07)- OpenGL ES - 纹理Texture
Android显示系统(08)- OpenGL ES - 图片拉伸
Android显示系统(09)- SurfaceFlinger的使用
Android显示系统(10)- SurfaceFlinger内部结构
Android显示系统(11)- 向SurfaceFlinger申请Surface
Android显示系统(12)- 向SurfaceFlinger申请Buffer
Android显示系统(13)- 向SurfaceFlinger提交Buffer

一、前言:

前面获取了Surface,并且为Surface申请了Buffer,然后通过EGL的往这Buffer里头渲染数据,完成之后,我们就需要去提交这个Buffer。

二、回顾流程图:

在这里插入图片描述

  1. 首先,SF会创建一个Client代表着App;
  2. App调用createSurface 得到一个SurfaceControl,同时,SF会创建一个Layer,代表APP端的SurfaceControl
  3. 同时,SF端的Layer会有生产者和消费者,两者都有成员变量mCore(代表BufferQueueCore)和mSlots数组,而App端会有一个生产者代理,代表SF那边的Layer中的生产者。(中间通过binder通信)
  4. App端的SurfaceControl可以通过getSurface获得Surface,Surface当中也有mSlots和生产者代理。
  5. App端想要往Surface里面填充数据,首先得通过lock申请buffer,通过dequeueBuffer返回一个buffer,如果SF侧分配的buffer已经用完了,就通过Gralloc模块向匿名内存Ashmem申请一个buffer,并且填入自己的mSlots当中,同时返回给APP,需要重新分配Buffer,APP侧会重新调用requestBuffer让SF侧重新关联这个Buffer,然后进行mmap,并且将fd返回给APP;
  6. App收到binder过来的fd之后(其实binder转换成fd’了),进行mmap(fd')得到虚拟地址vaddr,然后填入自己的mSlots当中;
  7. 最后App填充数据到vaddr当中之后,通过unlockAndPost提交给SF。

三、unlockAndPost:

3.1、总体思路:

  • Surface->lock被调用获取Buffer之后,生产者的dequeueBuffer调用。
  • 获得buffer之后,App侧通过Surface->unlockAndPost提交填充数据的Buffer,也就是调用queueBuffer。
  • 那么queueBuffer调用之后,主要做两件事入队列,并且通知消费者取数据,通知顺序是:
    • 通知Layer的消费者;
    • 通知SurfaceFlinger;

3.2、代码走读:

入口在这儿:

status_t Surface::unlockAndPost()
{if (mLockedBuffer == nullptr) {ALOGE("Surface::unlockAndPost failed, no locked buffer");return INVALID_OPERATION;}int fd = -1;// 解锁当前被锁的缓冲区(异步)status_t err = mLockedBuffer->unlockAsync(&fd);ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);// 提交缓冲区err = queueBuffer(mLockedBuffer.get(), fd);ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",mLockedBuffer->handle, strerror(-err));mPostedBuffer = mLockedBuffer;mLockedBuffer = nullptr;return err;
}

3.3、Layer的消费者:

1)为Layer创建消费者:

看代码之前,我们先看下这个Layer第一次被引用时候:

void BufferQueueLayer::onFirstRef() {BufferLayer::onFirstRef();// Creates a custom BufferQueue for SurfaceFlingerConsumer to usesp<IGraphicBufferProducer> producer;sp<IGraphicBufferConsumer> consumer;// 创建BufferQueue的时候,里面会创建生产者和消费者BufferQueue::createBufferQueue(&producer, &consumer, true);// 将消费者做一次封装mProducer = new MonitoredProducer(producer, mFlinger, this);{// Grab the SF state lock during this since it's the only safe way to access RenderEngine// 同样,将生产者做一次封装Mutex::Autolock lock(mFlinger->mStateLock);mConsumer =new BufferLayerConsumer(consumer, mFlinger->getRenderEngine(), mTextureName, this);}// 。。。
}

看在这里面创建了生产者和消费者。我们看下这个消费者BufferLayerConsumer

BufferLayerConsumer::BufferLayerConsumer(const sp<IGraphicBufferConsumer>& bq,renderengine::RenderEngine& engine, uint32_t tex,Layer* layer): ConsumerBase(bq, false), // 调用了父类构造函数//。。。mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) {// 。。。
}

发现构造函数中首先调用了父类构造函数:

ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :mAbandoned(false),mConsumer(bufferQueue),mPrevFinalReleaseFence(Fence::NO_FENCE) {// Choose a name using the PID and a process-unique ID.mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());// Note that we can't create an sp<...>(this) in a ctor that will not keep a// reference once the ctor ends, as that would cause the refcount of 'this'// dropping to 0 at the end of the ctor.  Since all we need is a wp<...>// that's what we create.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);}
}

构造函数主要完成以下工作:

  1. 接受一个指向 IGraphicBufferConsumer 类型的缓冲队列接口和一个控制标志(是否由应用程序控制)。
  2. 初始化成员变量,例如放弃状态、消费者句柄等。
  3. 创建并绑定 ConsumerListener,监听缓冲区的操作事件。
  4. 通过 BufferQueueconsumerConnect 方法,将当前消费端与缓冲队列连接。
  5. 设置消费端名称,以便开发时调试和跟踪。

进去看看consumerConnect:

// 代码路径:native\libs\gui\include\gui\BufferQueueConsumer.hvirtual status_t consumerConnect(const sp<IConsumerListener>& consumer,bool controlledByApp) {return connect(consumer, controlledByApp);}

调用这个connect就将consumer保存到Layer当中了:

status_t BufferQueueConsumer::connect(const sp<IConsumerListener>& consumerListener, bool controlledByApp) {ATRACE_CALL();if (consumerListener == nullptr) {BQ_LOGE("connect: consumerListener may not be NULL");return BAD_VALUE;}BQ_LOGV("connect: controlledByApp=%s",controlledByApp ? "true" : "false");std::lock_guard<std::mutex> lock(mCore->mMutex);if (mCore->mIsAbandoned) {BQ_LOGE("connect: BufferQueue has been abandoned");return NO_INIT;}// 将Consumer赋值给mCoremCore->mConsumerListener = consumerListener;mCore->mConsumerControlledByApp = controlledByApp;return NO_ERROR;
}

其中sp<BufferQueueCore> mCore;

2)给Consumer设置监听者:

void BufferQueueLayer::onFirstRef() {// ...// 给Consumer设置一个ListenermConsumer->setContentsChangedListener(this);mConsumer->setName(mName);// ...
}

稍微进去看看:

void BufferLayerConsumer::setContentsChangedListener(const wp<ContentsChangedListener>& listener) {setFrameAvailableListener(listener);Mutex::Autolock lock(mMutex);mContentsChangedListener = listener;
}

这个会走到父类:

void ConsumerBase::setFrameAvailableListener(const wp<FrameAvailableListener>& listener) {CB_LOGV("setFrameAvailableListener");Mutex::Autolock lock(mFrameAvailableMutex);mFrameAvailableListener = listener;
}

发现Listener最终保存到父类的成员变量mFrameAvailableListener以及BufferLayerConsumermContentsChangedListener,以后Consumer有变化,就通过这个通知给监听者。

3.3、queueBuffer

入口函数:

int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {// ...nsecs_t now = systemTime();// 调用生产者代理的queueBuffer,导致Binder调用status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);mLastQueueDuration = systemTime() - now;if (err != OK)  {ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);}// ... 
}

调用代理对象的函数:

然后就是调用了QueueBuffer的生产者代理提交buffer。中间一堆binder调用我们省略了,直接看子类BufferQueueProducer

status_t BufferQueueProducer::queueBuffer(int slot,const QueueBufferInput &input, QueueBufferOutput *output) { BufferItem item;// 。。。// 取出要提交的那一项,用取出的项构造一个BufferItem对象;item.mAcquireCalled = mSlots[slot].mAcquireCalled;item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;item.mCrop = crop;item.mTransform = transform &~static_cast<uint32_t>(NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);item.mTransformToDisplayInverse =(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;item.mScalingMode = static_cast<uint32_t>(scalingMode);item.mTimestamp = requestedPresentTimestamp;item.mIsAutoTimestamp = isAutoTimestamp;item.mDataSpace = dataSpace;item.mHdrMetadata = hdrMetadata;item.mFrameNumber = currentFrameNumber;item.mSlot = slot;item.mFence = acquireFence;item.mFenceTime = acquireFenceTime;item.mIsDroppable = mCore->mAsyncMode ||(mConsumerIsSurfaceFlinger && mCore->mQueueBufferCanDrop) ||(mCore->mLegacyBufferDrop && mCore->mQueueBufferCanDrop) ||(mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);item.mSurfaceDamage = surfaceDamage;item.mQueuedBuffer = true;item.mAutoRefresh = mCore->mSharedBufferMode && mCore->mAutoRefresh;item.mApi = mCore->mConnectedApi;// 。。。output->bufferReplaced = false;if (mCore->mQueue.empty()) {// When the queue is empty, we can ignore mDequeueBufferCannotBlock// and simply queue this buffermCore->mQueue.push_back(item);frameAvailableListener = mCore->mConsumerListener;} else {// ...mCore->mQueue.push_back(item);frameAvailableListener = mCore->mConsumerListener;}
}

其实不管队列空不空,最终都会push_back进去;

同时,记录了一下listener,这个listener前面已经分析过了,是一个proxy,也就是BufferQueue::ProxyConsumerListener对象。

通知观察者:

status_t BufferQueueProducer::queueBuffer(int slot,const QueueBufferInput &input, QueueBufferOutput *output) { // ...{ // scope for the lockstd::unique_lock<std::mutex> lock(mCallbackMutex);while (callbackTicket != mCurrentCallbackTicket) {mCallbackCondition.wait(lock);}// 通知监听者,有数据了if (frameAvailableListener != nullptr) {frameAvailableListener->onFrameAvailable(item);} else if (frameReplacedListener != nullptr) {frameReplacedListener->onFrameReplaced(item);}// ...}// ...
}

就是通知监听者有数据了,这个监听者是谁呢?上面我们说了,是Queue的消费者,以及SurfaceFlinger。所以,我们到了消费者的代理类:

void BufferQueue::ProxyConsumerListener::onFrameAvailable(const BufferItem& item) {sp<ConsumerListener> listener(mConsumerListener.promote());if (listener != nullptr) {listener->onFrameAvailable(item);}
}

这个listener是ConsumerBase,于是我们看看消费者的onFrameAvailable函数:

void ConsumerBase::onFrameAvailable(const BufferItem& item) {CB_LOGV("onFrameAvailable");sp<FrameAvailableListener> listener;{ // scope for the lockMutex::Autolock lock(mFrameAvailableMutex);// 通过调用 promote() 方法,将弱引用 mFrameAvailableListener 提升为强引用 listenerlistener = mFrameAvailableListener.promote();}if (listener != nullptr) {CB_LOGV("actually calling onFrameAvailable");listener->onFrameAvailable(item);}
}

当然,mFrameAvailableListener 这个就是Layer:

void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {// ...// If this layer is orphaned, then we run a fake vsync pulse so that// dequeueBuffer doesn't block indefinitely.if (isRemovedFromCurrentState()) {fakeVsync();} else {// 通知Layer已经更新mFlinger->signalLayerUpdate();}mConsumer->onBufferAvailable(item);
}

通过SurfaceFlinger来通知Layer已经更新:

// 通知Layer已经更新
void SurfaceFlinger::signalLayerUpdate() {mScheduler->resetIdleTimer();mEventQueue->invalidate();
}

这个invalidate() 会导致另外一个很重要的线程被唤醒,后面文章再分析。

四、总结:

Buffer被填充完渲染数据之后,通过Binder告诉SF端对应的消费者,后续通知流程:生产者->进入了消费者->Layer->SurfaceFlinger。就进入了视频显示的主流程,具体如何显示,且听下回分解。

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

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

相关文章

【kubernetes】资源管理方式

目录 1. 说明2. 命令式对象管理3. 命令式对象配置4. 声明式对象配置5. 三种方式的对比 1. 说明 1.在Kubernetes&#xff08;k8s&#xff09;中&#xff0c;资源管理是一个核心功能&#xff0c;它允许用户通过操作资源来管理Kubernetes集群。2.Kubernetes将所有的内容都抽象为资…

【git、gerrit】特性分支合入主分支方法 git rebase 、git cherry-pick、git merge

文章目录 1. 场景描述1.1 分支状态 2. 推荐的操作方式方法 1&#xff1a;git merge&#xff08;保留分支结构&#xff09;方法 2&#xff1a;git rebase&#xff08;线性合并提交历史&#xff09;直接在master分支执行git merge br_feature&#xff0c;再 执行 git pull --reba…

211-基于FMC的1路1.5G ADC 1路 2.5G DAC子卡

一、板卡概述 FMC-1AD-1DA-1SYNC是我司自主研发的一款1路1G AD采集、1路2.5G DA回放的FMC、1路AD同步信号子卡。板卡采用标准FMC子卡架构&#xff0c;可方便地与其他FMC板卡实现高速互联&#xff0c;可广泛用于高频模拟信号采集等领域。 二、功能介绍 2.1 原理框图 2.2 硬件…

实操给自助触摸一体机接入大模型语音交互

本文以CSK6 大模型开发板串口触摸屏为例&#xff0c;实操讲解触摸一体机怎样快速增加大模型语音交互功能&#xff0c;使用户能够通过语音在一体机上查询信息、获取智能回答及实现更多互动功能等。 在本文方案中通过CSK6大模型语音开发板采集用户语音&#xff0c;将语音数据传输…

DocFlow票据AI自动化处理工具:出色的文档解析+抽取能力,提升企业文档数字化管理效能

目录 财务应付 金融信贷业务 近期&#xff0c;DocFlow票据自动化产品正式上线。DocFlow是一款票据AI自动化处理工具&#xff0c;支持不同版式单据智能分类扩展&#xff0c;可选功能插件配置流程&#xff0c;满足多样业务场景。 随着全球化与信息化进程&#xff0c;企业的文件…

英伟达垄断?中国芯片如何破局?

近期&#xff0c;全球AI芯片巨头英伟达被曝遭遇中国反垄断调查&#xff0c;引发行业广泛关注。 【图片来源于网络&#xff0c;侵删】 众所周知&#xff0c;在人工智能的浪潮中&#xff0c;英伟达无疑是全球AI算力市场的领头羊。在芯片领域&#xff0c;尤其是 GPU 市场&#xff…

用户体验测试与专项测试常用工具

用户体验&#xff08;User Experience, UX&#xff09;测试关注的是用户在使用软件产品时的主观感受。这包括用户界面的易用性、交互设计的友好性以及整体的满意度。UX测试不仅能够帮助开发人员发现产品中的潜在问题&#xff0c;还能为企业提供改善产品体验的建议。 功能亮点 …

40 基于单片机的温湿度检测判断系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用dht11温湿度传感器检测温湿度&#xff0c; 通过lcd1602显示屏各个参数&#xff0c;四个按键分别可以增加温湿度的阈值&#xff0c; 如果超过阈值&#xff0c;则…

基于Matlab实现三维地球模型(源码)

利用MATLAB强大的图形处理能力和数学计算功能构建的可视化应用。这个模型允许用户在三维空间中观察地球&#xff0c;并且能够动态地旋转地球模型&#xff0c;同时还可以模拟卫星在其周围的运动轨迹&#xff0c;为学习地球科学、天文学以及航天工程等领域提供了一个直观的教学工…

JavaSe部分总结

我们先来了解一下Java语言,JavaSE是Java编程语言的标准版,主要是来学习Java的基本语法,书写方式,以及一些简单的逻辑循环和判断,包括一些关键字,特殊类(抽象类),特殊的方法(static修饰的方法,final修饰的方法)等等,最重要的是Java语言是比较C语言和C语言是比较简单的,Java是面向…

适用于 Windows 的 Podman

适用于 Windows 的 Podman 虽然“容器是 Linux”&#xff0c;但 Podman 也可以在 Mac 和 Windows 上运行&#xff0c;它提供原生 CLI 并嵌入来宾 Linux 系统来启动您的容器。此 guest 称为 Podman 计算机&#xff0c;并使用命令进行管理。在 Windows 上&#xff0c;每台 Podma…

Linux中 vim 常用命令大全详细讲解

文章目录 前言一、Vim 基本操作 &#x1f579;️1.1 打开或创建1.2 退出编辑1.3 模式切换 二、Vim 光标移动命令 ↕️2.1 基本移动2.2 行内移动2.3. 单词移动2.4. 页面移动2.5. 行跳转 三、Vim 文本编辑命令 &#x1f4cb;3.1 插入和删除3.2 复制、剪切与粘贴3.3 替换与修改 四…

如何借助5G网关实现油罐车安全在线监测

油罐车是常见的特种运输车辆&#xff0c;用以运送各种汽油、柴油、原油等油品&#xff0c;运输危险系数大&#xff0c;而且由于油罐车需要经常行驶在城区道路&#xff0c;为城市各个加油站点、企业工厂运输补充所需油料&#xff0c;因此也是危化品运输车辆的重点监测和管控对象…

联想至像M3070DW打印机连接手机方法

首先&#xff0c;按打印机上的“功能”键&#xff0c;上翻页翻到第4项“网络”按“OK”键进入&#xff1b; 点进去之后&#xff0c;再按下翻页翻到第3项“安装向导”&#xff0c;按“OK”键进入&#xff1b; 然后&#xff0c;选择我们要连接的WiFi并输入WiFi密码&#xff0c; 输…

双亲委派机制是Java类加载器的一种工作模式

双亲委派机制是Java类加载器的一种工作模式&#xff0c;确保了类加载的一致性和安全性。以下是对双亲委派机制的详细解析&#xff1a; 一、定义与工作原理 双亲委派机制&#xff08;Parent Delegation Model&#xff09;要求除了顶层的启动类加载器外&#xff0c;其余的类加载…

Qt 使用modbus协议

Qt 框架下 使用modbus协议 一&#xff0c;使用Qt原生的 QModbusClient &#xff0c;比如QModbusTcpClient 1&#xff0c;因为modbus的读写 需要在同一个线程中&#xff0c;所以需要在主线程中利用moveToThread的方式&#xff0c;将业务逻辑封装到 子线程中。 2&#xff0c;m…

C语言中怎样将NULL定义为空指针?空指针的具体值其实为0哈,即地址值为0的指针为空指针。

用下面这条命令即可&#xff1a; #define NULL (void *)0下面是对这句代码的解释&#xff1a; #define NULL (void *)01. #define 的作用 #define 是一个预处理指令&#xff0c;用于定义宏。宏替换发生在编译前的预处理阶段&#xff0c;所有出现 NULL 的地方都会被替换为 (v…

Linux中的cp命令:使用、原理与源码分析

在Linux系统中&#xff0c;cp命令是最常用的命令之一&#xff0c;用于复制文件或目录。无论是日常的文件管理&#xff0c;还是系统维护&#xff0c;cp命令都扮演着重要的角色。本文将深入探讨cp命令的使用方法、工作原理&#xff0c;并从源码层面分析其实现细节。 1. cp命令的…

oracle client linux服务器安装教程

p13390677_112040_Linux-x86-64_4of7.zip 安装前&#xff0c;确认/etc/hosts文件已配置正确 cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.2…

云和恩墨 zCloud 与华为云 GaussDB 完成兼容性互认证

近日&#xff0c;云和恩墨&#xff08;北京&#xff09;信息技术有限公司&#xff08;以下简称&#xff1a;云和恩墨&#xff09;的多元数据库智能管理平台 zCloud 与华为云计算技术有限公司&#xff08;以下简称&#xff1a;华为云&#xff09;的 GaussDB 数据库完成了兼容性互…