安卓帧率获取

背景

  性能优化,经常用到一些指标,诸如帧率、功耗等。对于普通app来讲, 之前一直使用gfxinfo指令获取丢帧率。但是这个指令无法获取游戏的帧率,查阅资料,发现SurfaceFlinger可以获取游戏帧率。

帧率获取原理

获取当前focused layer

指令 :adb shell 'dumpsys SurfaceFlinger | grep -i Explicit -A 30'
这一步看到focused layer name可能会不全,有省略号。可使用下一步指令查看完整layer name。focused的图层会有*号标志。示例如下:

java 复制代码
PS D:\work> adb shell 'dumpsys SurfaceFlinger | grep -i Explicit -A 30'
           Z |  Window Type |  Layer Class | Comp Type |  Transform |   Disp Frame (LTRB) |          Source Crop (LTRB) |     Frame Rate (Explicit) (Seamlessness) [Focused]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 SurfaceView[com.netease.l22.nearme.g[...]tyPlugin.AndroidPlugin](BLAST)#14220
           9 |            0 |            2 |     DEVICE |          0 |    0    0 1080 2376 |    0.0    0.0 1080.0 2376.0 |                                              [*]
获取SurfaceFlinger的layer列表

指令 :adb shell 'dumpsys SurfaceFlinger --list | grep xxx'
经过上一步获取到的focused layer name部分字符串xxx过来匹配,过滤掉不相关的数据。
3.

获取具体图层的帧信息

指令 :adb shell 'dumpsys SurfaceFlinger --latency <layer_name>'
terminal输出格式大致如下:
共有128行。第一行是刷新率。剩余的数据127行,分为3列,分别是:
desiredPresentTime:应用期望提交的时间
actualPresentTime:实际提交的时间
frameReadyTime:帧准备好的时间
每个数字,代表一个帧的时间戳,单位是ns。计算帧率,使用第二列数据,通过数时间戳,可以确定1秒有多少帧。

java 复制代码
> adb shell dumpsys SurfaceFlinger --latency "xxxxx"
16666666
59069638658663  59069678041684  59069654158298
59069653090955  59069695022100  59069670894236
59069671034444  59069711403455  59069687949861
59069688421840  59069728057361  59069704415121
59069705420850  59069744773350  59069720767830
59069719818975  59069761378975  59069737416007
59069736702673  59069778060955  59069754568663
59069753361528  59069794716007  59069770761632
59069768766371  59069811380486  59069787649600
......

帧率获取脚本

  通过上述原理,将计算逻辑封装成python脚本。计算原理如下:

python 复制代码
import subprocess
import time
from threading import Thread

nanoseconds_per_second = 1e9


class SurfaceFlingerFPS():

    def __init__(self, view, ip):
        self.view = view
        self.ip = ip
        self.refresh_period, self.base_timestamp, self.timestamps = self.__init_frame_data__(self.view)
        self.recent_timestamps = self.timestamps[-2]
        self.fps = 0

    def __init_frame_data__(self, view):
        # print('__init_frame_data__()')
        out = ''
        try:
            out = subprocess.check_output(['adb', '-s', self.ip, 'shell', 'dumpsys', 'SurfaceFlinger', '--latency-clear', view])
        except subprocess.CalledProcessError as e:
            print(e.output)
        out = out.decode('utf-8')
        if out.strip() != '':
            raise RuntimeError("Not supported.")
            time.sleep(0.1)
        (refresh_period, timestamps) = self.__frame_data__(view)
        base_timestamp = 0
        base_index = 0
        for timestamp in timestamps:
            if timestamp != 0:
                base_timestamp = timestamp
                break
            base_index += 1
        if base_timestamp == 0:
            raise RuntimeError("Initial frame collect failed")
        # print('refresh_period={} base_timestamp={}\ntimestamps=\n{}'.format(refresh_period, base_timestamp, str(timestamps[base_index:])))
        return (refresh_period, base_timestamp, timestamps[base_index:])

    def __frame_data__(self, view):
        out = subprocess.check_output(['adb', '-s', self.ip, 'shell', 'dumpsys', 'SurfaceFlinger', '--latency', view])
        out = out.decode('utf-8')
        results = out.splitlines()

        refresh_period = int(results[0]) / nanoseconds_per_second
        timestamps = []
        for line in results[1:]:
            fields = line.split()
            if len(fields) != 3:
                continue
            (start, submitting, submitted) = map(int, fields)
            if submitting == 0:
                continue

            timestamp = submitting / nanoseconds_per_second
            timestamps.append(timestamp)
        return (refresh_period, timestamps)

    def collect_frame_data(self, view):
        if view is None:
            raise RuntimeError("Fail to get current SurfaceFlinger view")

        self.refresh_period, self.timestamps = self.__frame_data__(view)
        # print("\ncollect_frame_data()\ntimestamps=\n" + str(self.timestamps))
        time.sleep(1)
        self.refresh_period, tss = self.__frame_data__(view)
        # print("tss=\n" + str(tss))
        self.last_index = 0

        if self.timestamps:
            self.recent_timestamp = self.timestamps[-2]
            if self.recent_timestamp not in tss:
                self.recent_timestamp = self.timestamps[-3]
            self.last_index = tss.index(self.recent_timestamp)

        self.timestamps = self.timestamps[:-2] + tss[self.last_index:]
        # time.sleep(1)

        ajusted_timestamps = []
        for seconds in self.timestamps[:]:
            seconds -= self.base_timestamp
            if seconds > 1e6:  # too large, just ignore
                continue
            ajusted_timestamps.append(seconds)

        # print('ajusted_timestamps=\n' + str(ajusted_timestamps))
        from_time = ajusted_timestamps[-1] - 1.0

        fps_count = 0
        for seconds in ajusted_timestamps:
            if seconds > from_time:
                fps_count += 1
        self.fps = fps_count

    def start(self):
        th = Thread(target=self.collect_frame_data, args=(self.view,))
        th.start()

    def getFPS(self):
        self.collect_frame_data(self.view)
        return self.fps
相关推荐
念何架构之路27 分钟前
zap采样器与性能优化内幕
算法·性能优化·哈希算法
惊讶的猫37 分钟前
工具选对之后还不够:商城 Agent 的参数与权限治理
开发语言·python
天赐范式1 小时前
天赐范式第186天:让方差开始自洽——Bulmer效应的闭式验证
python·数字生命·天赐范式·动态运行时·遗传方差·bulmer效应
泡茶喝茶写代码1 小时前
A股量化数据工程:从 REST 接口到策略信号(第 8 篇):成长能力因子:营收与利润增速
java·python·股票数据api·股票数据api接口·股票量化数据api·股票量化数据接口·股票数据api数据
三少爷的鞋1 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Είναι η κοπέλα2 小时前
显存计算与模型选择:你的显卡能跑多大的模型
人工智能·pytorch·python·开源·conda
ROSF68682 小时前
2026 仿石漆乳液供应商:市场布局与行业发展态势梳理
大数据·人工智能·python
青少儿编程课堂2 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
hhzz2 小时前
【YOLO 入门到精通 04】理解任务与模型家族:7 大 CV 任务与 YOLO26 选型指南
人工智能·python·深度学习·yolo·计算机视觉
Escalating_xu2 小时前
【Python】使用库:标准库、第三方库、pip、项目实战与程序打包
java·python·pip