Android Graphics 显示系统 - 监测、计算FPS的工具及设计分析

news2024/11/17 2:41:46

“ 在Android图像显示相关的开发、调试、测试过程中,如何能有效地评估画面的流畅度及监测、计算图层渲染显示的实时FPS呢?本篇文章将会提供一种实用、灵巧的思路。

01

设计初衷

面对开发测试中遇到的卡顿掉帧问题,如何在复现卡顿的过程中持续监控FPS 和丢帧情况?特别是视频播放的场景,掉帧与FPS不稳定会带来糟糕的用户体验。因此需要有一款小工具可以实时监测图层渲染、显示的FPS变化,以便于判断是否FPS不稳定或掉帧。

02

设计原理

参考了网上现有的 FPS 计算方式原理,一定程度上满足自己的预期需求,但要么自己设计脚本计算逻辑处理,要么就要修改到源码。

方式1. 写作FPS计算脚本

基于dumpsys SurfaceFlinger --latency Layer-name或dumpsys gfxinfo获取信息,然后设计计算逻辑。

方式2. 修改SF源码呼叫computeFps

修改SurfaceFlinger的逻辑,利用原生computeFps方法来计算指定图层的FPS.

float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds)

那有没有其它方式呢?

当然!

我们留意到在SurfaceFlinger的源码中有一个FpsReporter,里面有提供注册监听器的接口,看起来可以利用!

class FpsReporter : public IBinder::DeathRecipient {
public:
    FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
                std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
    ....
    // Registers an Fps listener that listens to fps updates for the provided layer
    void addListener(const sp<gui::IFpsListener>& listener, int32_t taskId);
    // Deregisters an Fps listener
    void removeListener(const sp<gui::IFpsListener>& listener);
    ....
}

再接着看下IFpsListener的定义,顾名思义,当fps变化时就会回调到

/frameworks/native/libs/gui/aidl/android/gui/IFpsListener.aidl
oneway interface IFpsListener {

    // Reports the most recent recorded fps for the tree rooted at this layer
    void onFpsReported(float fps);
}

那用户层有无提供设置的接口呢?

先看SurfaceComposerClient中的定义,确实也存在add/remove方法

/frameworks/native/libs/gui/include/gui/SurfaceComposerClient.h
static status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener);
static status_t removeFpsListener(const sp<gui::IFpsListener>& listener);

那设计原理有了:设计一个C++小工具,注册fps listener,就可以监测特定task的fps了。

03

设计实现

源码下载

废话不说直接开源给大家,如果觉得有用 期待您点赞

yrzroger/FpsMonitor: 监测Android平台实时刷新率FPS (github.com)

使用说明

  1. 下载main branch,基于Android 14平台开发,代码放到android源码目录下

  2. 执行mm编译获得可执行档 FpsMonitor

  3. 方式测试设备 adb push FpsMonitor /data/local/tmp/

  4. 执行 adb shell am stack list 获取要监测的应用的 taskId

  5. adb shell /data/local/tmp/FpsMonitor -t taskId 运行程序

  6. 输入‘q’ 或者 Ctrl+C 退出监测

图片

效果展示

在console log中打印出实时的FPS 

图片

在屏幕左上角展示当前的屏幕的刷新率和Task图层渲染显示的帧率

图片

04

设计分析

  • 自己实现 IFpsListener,在onFpsReported中做想做的事情

struct TaskFpsCallback : public gui::BnFpsListener {
binder::Status onFpsReported(float fps) override {
    // 收到FPS,do what you want
}
}
  • 注册监听器到SurfaceFlinger

    sp<TaskFpsCallback> callback = new TaskFpsCallback();
    if (SurfaceComposerClient::addFpsListener(mTaskId, callback) != OK) {
        ALOGD("addFpsListener error!");
        return -1;
    }
  • UI显示

RefreshRateOverlay类中封装实现UI显示的逻辑,这部分源自SurfaceFlinger中的逻辑,本质也是创建Native Surface,然后使用Skia在Surface上绘制数字,不细讲。

05

再探FpsReporter::dispatchLayerFps

大家要留意一点,注册的IFpsListener是和Task绑定的,而一个Task通常会关联多个图层,因为有Child Layer,所以任何一个图层的变化都是导致FPS变化。

因此:

我们的小工具有一个局限,那就是跟踪单一图层的FPS会受到场景限制,比如视频播放时,如果视频图层上有其他UI变化(隶属同一个Task),那计算出的FPS就不仅仅是解码视频显示的FPS了

SurfaceFlinger中合成阶段会去调用FpsReporter::dispatchLayerFps

void SurfaceFlinger::postComposition(nsecs_t callTime) {
    ....
        Mutex::Autolock lock(mStateLock);
        if (mFpsReporter) {
            mFpsReporter->dispatchLayerFps();
        }
    ....
}

dispatchLayerFps会去遍历所有layers,找到和task对应的layer及child layers,然后呼叫computeFps计算,最终回调listener->onFpsReported

void FpsReporter::dispatchLayerFps() {
    const auto now = mClock->now();
    if (now - mLastDispatch < kMinDispatchDuration) {
        return;
    }

    std::vector<TrackedListener> localListeners;
    {
        std::scoped_lock lock(mMutex);
        if (mListeners.empty()) {
            return;
        }

        std::transform(mListeners.begin(), mListeners.end(), std::back_inserter(localListeners),
                       [](const std::pair<wp<IBinder>, TrackedListener>& entry) {
                           return entry.second;
                       });
    }

    std::unordered_set<int32_t> seenTasks;
    std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;

    mFlinger.mCurrentState.traverse([&](Layer* layer) {
        auto& currentState = layer->getDrawingState();
        if (currentState.metadata.has(gui::METADATA_TASK_ID)) {
            int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0);
            if (seenTasks.count(taskId) == 0) {
                // localListeners is expected to be tiny
                for (TrackedListener& listener : localListeners) {
                    if (listener.taskId == taskId) {
                        seenTasks.insert(taskId);
                        listenersAndLayersToReport.push_back(
                                {listener, sp<Layer>::fromExisting(layer)});
                        break;
                    }
                }
            }
        }
    });

    for (const auto& [listener, layer] : listenersAndLayersToReport) {
        std::unordered_set<int32_t> layerIds;

        layer->traverse(LayerVector::StateSet::Current,
                        [&](Layer* layer) { layerIds.insert(layer->getSequence()); });

        listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));
    }

    mLastDispatch = now;
}

06

拓展

是否可以设置APP实现FPS监测呢?

在WMS中,也是有提供registerTaskFpsCallback的接口,可以在app层面使用监测FPS,本质原理是一样的,只是通过JNI呼叫到了SurfaceComposerClient::addFpsListener。

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
    @Override
    @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)
    public void registerTaskFpsCallback(@IntRange(from = 0) int taskId,
            ITaskFpsCallback callback) {
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)
                != PackageManager.PERMISSION_GRANTED) {
            final int pid = Binder.getCallingPid();
            throw new SecurityException("Access denied to process: " + pid
                    + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
        }

        if (mRoot.anyTaskForId(taskId) == null) {
            throw new IllegalArgumentException("no task with taskId: " + taskId);
        }

        mTaskFpsCallbackController.registerListener(taskId, callback);
    }

    @Override
    @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)
    public void unregisterTaskFpsCallback(ITaskFpsCallback callback) {
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)
                != PackageManager.PERMISSION_GRANTED) {
            final int pid = Binder.getCallingPid();
            throw new SecurityException("Access denied to process: " + pid
                    + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
        }

        mTaskFpsCallbackController.unregisterListener(callback);
    }

如何监测单一图层的FPS,避免上面提到的局限呢?

可以考虑采用文章开头提到的方式1 、 方式2。或者对SurfaceFlinger动动手脚。


本文基于 Android U 源码解析,请结合完整源码阅读!


关注公众号,优质内容

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

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

相关文章

黑马的ES课程中的不足

在我自己做项目使用ES的时候&#xff0c;发现了黑马没教的方法&#xff0c;以及一些它项目的小问题 搜索时的匹配方法 这个boolQuery().should 我的项目是通过文章的标题title和内容content来进行搜索 但是黑马它的项目只用了must 如果我们的title和content都用must&#x…

QCustomPlot+ vs2022+ qt

零、printSupport 步骤一&#xff1a;下载QCustomPlot 访问QCustomPlot的官网 QCustomPlot 下载最新版本的源代码。 步骤二&#xff1a;配置项目 创建新的Qt项目&#xff1a; 打开VS2022&#xff0c;创建一个新的Qt Widgets Application项目。 将QCustomPlot源代码添加到项目…

C语言编程与进阶

1.0 C语言关键字 1-1C语言关键字-CSDN博客文章浏览阅读831次&#xff0c;点赞13次&#xff0c;收藏24次。define使用define定义常量return 0;使用define定义宏// define 定义宏&#xff0c;名字是ADD(x,y),x y 是宏的参数int a 10;int b 20;return 0;宏定义的本质是替换&am…

JavaEE——计算机工作原理

冯诺依曼体系&#xff08;VonNeumannArchitecture&#xff09; 现代计算机&#xff0c;大多遵守冯诺依曼体系结构 CPU中央处理器&#xff1a;进行算术运算与逻辑判断 存储器&#xff1a;分为外存和内存&#xff0c;用于存储数据&#xff08;使用二进制存储&#xff09; 输入…

百日筑基第十二天-入门Elasticsearch

百日筑基第十二天-入门Elasticsearch Elasticsearch 是什么 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。 安装 Elasticsearch 下载&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch Elasticsearch 是免安装的&#xff0c;只需要把 zip…

绝了,华为伸缩摄像头如何突破影像边界?

自华为Pura70 Ultra超聚光伸缩镜头诞生以来&#xff0c;备受大家的关注&#xff0c;听说这颗镜头打破了传统手机的摄像头体积与镜头的设计&#xff0c;为我们带来了不一样的拍照体验。 智能手机飞速发展的今天&#xff0c;影像功能已经成为我们衡量一款手机性能的重要指标。想…

【Qt5.12.9】程序无法显示照片问题(已解决)

问题记录&#xff1a;Qt5.12.9下无法显示照片 我的工程名为03_qpainter&#xff0c;照片cd.png存放在工程目录下的image文件夹中。 /03_qpainter/image/cd.png 因为这是正点原子Linux下Qt书籍中的例程&#xff0c;在通过学习其配套的例程中的项目&#xff0c;发现我的项目少…

Python的招聘数据分析与可视化管理系统-计算机毕业设计源码55218

摘要 随着互联网的迅速发展&#xff0c;招聘数据在规模和复杂性上呈现爆炸式增长&#xff0c;对数据的深入分析和有效可视化成为招聘决策和招聘管理的重要手段。本论文旨在构建一个基于Python的招聘数据分析与可视化管理系统。 该平台以主流招聘平台为数据源&#xff0c;利用Py…

昇思25天学习打卡营第1天|初识MindSpore

# 打卡 day1 目录 # 打卡 day1 初识MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思 MindSpore 优势|特点 昇思 MindSpore 不足 官方生态学习地址 初识MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思MindSpore 是全场景深度学习架构&#xff0c;为开发者提供了全…

Ubuntu固定虚拟机的ip地址

1、由于虚拟机网络是桥接&#xff0c;所以ip地址会不停地变化&#xff0c;接下来我们就讲述ip如何固定 2、如果apt安装时报错W: Target CNF (multiverse/cnf/Commands-all) is configured multiple times in /etc/apt/sources.list:10&#xff0c; 检查 /etc/apt/sources.list…

计算机组成原理--概述

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

AI Earth应用—— 在线使用sentinel数据VV和VH波段进行水体提取分析(昆明抚仙湖、滇池为例)

AI Earth 本文的主要目的就是对水体进行提取,这里,具体的操作步骤很简单基本上是通过,首页的数据检索,选择需要研究的区域,然后选择工具箱种的水体提取分析即可,剩下的就交给阿里云去处理,结果如下: 这是我所选取的一景影像: 详情 卫星: Sentinel-1 级别: 1 …

Redis IO多路复用

0、前言 本文所有代码可见 > 【gitee code demo】 本文涉及的主题&#xff1a; 1、BIO、NIO的业务实践和缺陷 2、Redis IO多路复用&#xff1a;redis快的主要原因 3、epoll 架构 部分图片 via 【epoll 原理分析】 1、BIO单线程版 1.1 业务代码 client client代码相同…

Proxmox VE 8虚拟机直通USB磁盘

作者&#xff1a;田逸&#xff08;fromyz&#xff09; 今天有个兄弟发消息&#xff0c;咨询怎么让插在服务器上的U盾被Proxmox VE上的虚拟机识别。在很久很久以前&#xff0c;我尝试过在Proxmox VE 5以前的版本创建windows虚拟机&#xff0c;并把插在Proxmox VE宿主机上的银行U…

Vue3基础(二)

一、搭建工程(vite) ## 1.创建命令 npm create vuelatest## 2.具体配置 ## 配置项目名称 √ Project name: vue3_test ## 是否添加TypeScript支持 √ Add TypeScript? Yes ## 是否添加JSX支持 √ Add JSX Support? No ## 是否添加路由环境 √ Add Vue Router for Single P…

【matlab】智能优化算法——基准测试函数

智能优化算法的基准测试函数是用于评估和优化算法性能的一组标准问题。这些测试函数模拟了真实世界优化问题的不同方面&#xff0c;包括局部最小值、全局最优解、高维度、非线性、不连续等复杂性。以下是对智能优化算法基准测试函数的详细归纳&#xff1a; 测试函数的分类&…

使用nohup和CUDA_VISIBLE_DEVICES进行GPU训练的教程

文章目录 1. 在单个GPU上训练模型1.1 使用nohup命令运行Python脚本1.2 查看运行中的进程1.3 查看输出日志 2. 在多个GPU上训练模型2.1 启动第一个程序&#xff0c;指定使用第0号GPU2.2 启动第二个程序&#xff0c;指定使用第1号GPU2.3 查看运行中的进程2.4 查看输出日志 3. 总结…

【不容错过】可灵AI重磅更新:画质升级,运镜控制,首尾帧自定义,还有30万创作激励奖金!

还记得最近在各大平台肆虐的老照片变成视频吗&#xff0c;就是用快手的可灵AI做的&#xff0c;今天可灵又迎来了一次重大更新。 「电脑端上线了」 之前一直用其他工具生的图片还需要保存到手机上&#xff0c;再用可灵来生成视频&#xff0c;很多人都能感受到手机操作不太方便&…

【ARMv8/v9 GIC 系列 5.6 -- GIC 超优先级中断详细介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 Interrupt superpriority超优先级中断的特性和应用Physical interface interrupt signalsPhysical Group 1 Non-NMI for Current Security StatePhysical Group 1 for Other Security State, or a Group 0 Non-NMIPhysical Group 1 …

上海外贸建站公司wordpress模板推荐

Sora索啦高端制造业wordpress主题 红色高端制造业wordpress主题&#xff0c;适合外贸企业出海建独立站的wordpress模板。 https://www.jianzhanpress.com/?p5885 Yamal外贸独立站wordpress主题 绿色的亚马尔Yamal外贸独立站wordpress模板&#xff0c;适用于外贸公司建独立站…