gfxinfo 显示 FPS = 60,UI 流畅度满分。但用户投诉:"视频播放时一顿一顿的"。
你查了半天帧耗时数据,全是正常帧。问题出在哪?
答案:视频帧和 UI 帧是两条完全独立的管线。gfxinfo 只统计 UI 帧,视频帧走的是 MediaCodec → Surface 的解码通道。你采的是 A 的数据,用户投诉的是 B 的问题。
一、视频帧 vs UI 帧:两条管线
Android 里有两种"帧",走不同的渲染通道,采集方式也完全不同:
css
┌─────────────────────────────────────────────────────┐
│ UI 帧管线 │
│ │
│ Choreographer → UI 线程 → RenderThread → Surface A │
│ │
│ 帧率上限 = 屏幕刷新率(60/90/120Hz) │
│ 数据源 = dumpsys gfxinfo │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 视频帧管线 │
│ │
│ 网络/文件 → 解封装 → MediaCodec 解码 → Surface B │
│ │
│ 帧率 = 视频源帧率(24/25/30/60fps) │
│ 数据源 = SurfaceFlinger(通用)/ media.codec(辅助) │
└─────────────────────────────────────────────────────┘
两个 Surface 最终都交给 SurfaceFlinger 合成到屏幕上。
↑
这是检测视频卡顿的关键切入点
| 维度 | UI 帧 | 视频帧 |
|---|---|---|
| 触发方式 | VSYNC 信号 + 界面变化 | 视频流的帧间隔 |
| 帧率上限 | 屏幕刷新率 | 视频编码帧率 |
| 渲染者 | App 的 RenderThread | MediaCodec 硬/软解码器 |
| 卡顿原因 | 布局复杂、主线程阻塞 | 解码慢、buffer 不足、网络卡、音画同步 |
| 采集工具 | dumpsys gfxinfo | SurfaceFlinger(最通用) |
| 采集时机 | 始终采集 | 仅在视频播放场景启用 |
二、残酷的现实:主流 App 几乎绕过了标准播放器 API
很多文章推荐用 dumpsys media.player 来检测视频卡顿。但在实际测试中,这个命令对主流视频 App 无效。
Android 标准路径:
markdown
App → android.media.MediaPlayer API → MediaPlayerService → 解码 → Surface
↑
dumpsys media.player 采数据的位置
主流 App 的实际路径:
css
抖音 → TTVideoEngine(字节自研引擎)→ MediaCodec → Surface
快手 → KSVideoPlayer(自研引擎) → MediaCodec → Surface
爱奇艺 → 自研播放器引擎 → MediaCodec → Surface
腾讯视频 → TXLiteAVSDK(自研引擎) → MediaCodec → Surface
B站 → IJKPlayer(FFmpeg 魔改) → MediaCodec / FFmpeg → Surface
优酷 → 阿里自研引擎 → MediaCodec → Surface
这些 App 都绕过了 android.media.MediaPlayer API ,dumpsys media.player 对它们无效。
但不管用什么播放器引擎,解码后的视频帧最终都必须提交给 SurfaceFlinger 合成上屏。SurfaceFlinger 是所有视频帧的必经之路:
markdown
任何播放器引擎
│
┌────┴────┐
│ 硬解码 │ 软解码
│MediaCodec│ FFmpeg
└────┬────┘ ┘
│ │
┌────┴──────┴────┐
│ Surface │ ← 所有视频帧必经之路
└────────┬───────┘
│
┌────────┴───────┐
│ SurfaceFlinger │ ← 在这里截获帧时间戳
└────────┬───────┘
│
屏幕
三、三种数据源:通用性对比
3.1 SurfaceFlinger --latency(主方案,通用)
原理:视频通过 SurfaceView 渲染时,SurfaceFlinger 记录每帧的上屏时间戳。通过分析相邻帧的时间差,可以检测丢帧和卡顿。
操作方法:
- 播放视频后,通过
dumpsys SurfaceFlinger --list查找目标 App 的 SurfaceView layer - 对该 layer 执行
dumpsys SurfaceFlinger --latency,获取最近 ~128 帧的时间戳 - 提取每帧的
actualPresentTime(第 2 列,实际上屏时间),过滤掉无效帧(0x7FFFFFFFFFFFFFFF表示未上屏) - 计算相邻帧时间差 → 得到帧间隔序列
- 从帧间隔序列推导所有视频性能指标
输出格式说明:
| 行 | 内容 | 说明 |
|---|---|---|
| 第 1 行 | 16666666 |
屏幕刷新周期(纳秒),16666666 = 60Hz |
| 后续每行 | 3 列纳秒时间戳 | 第 1 列:desiredPresentTime(期望上屏时间);第 2 列:actualPresentTime(实际上屏时间);第 3 列:frameReadyTime(帧就绪时间) |
一个关键限制------SurfaceView vs TextureView:
| 渲染方式 | SurfaceFlinger 能分离视频帧吗 | 使用场景 |
|---|---|---|
| SurfaceView | 能(独立 layer) | 大部分全屏视频播放 |
| TextureView | 不能(和 UI 共享 Surface) | 部分小窗播放、弹幕叠加 |
判断方法 :播放视频后,查看 dumpsys SurfaceFlinger --list 是否出现 SurfaceView[包名/...] 的 layer。有 → SurfaceView,没有 → TextureView。
3.2 dumpsys media.codec(辅助方案)
原理 :大部分 App 使用 MediaCodec API 硬件解码,该命令提供解码器级统计。
能提供什么:
| 信息 | 说明 |
|---|---|
| 解码器名称 | 判断硬解(OMX.qcom. / c2.qti.)还是软解(OMX.google. / c2.android.) |
| 丢弃帧数 | output buffers dropped(部分设备可用) |
| 平均/最大解码耗时 | 判断解码性能是否足够(部分设备可用) |
局限:纯 FFmpeg 软解码不走 MediaCodec,此时无数据。字段名和格式随 Android 版本差异大。
3.3 dumpsys media.player(有限场景)
适用范围极窄 ------只有使用 android.media.MediaPlayer 原生 API 的 App 才有数据。
| 播放器 | 是否有效 |
|---|---|
| 系统原生 MediaPlayer | 有效 |
| 自研 App(确认用原生 API) | 有效 |
| 抖音 / 快手 / 爱奇艺 / B站 | 无效 |
| IJKPlayer / ExoPlayer | 无效 |
3.4 三种方案总览
| 能力 | SurfaceFlinger | media.codec | media.player |
|---|---|---|---|
| 主流 App 通用性 | 全覆盖 | 大部分 | 极少数 |
| 帧间隔序列 | 有 | 无 | 无 |
| 丢帧数/丢帧率 | 有(从帧间隔推导) | 有 | 有 |
| 帧间隔 CV | 有 | 无 | 无 |
| 连续丢帧检测 | 有 | 无 | 无 |
| 视频实际帧率 | 有 | 间接 | 间接 |
| 解码耗时 | 无 | 有 | 无 |
| 解码方式(硬/软) | 无 | 有 | 无 |
| TextureView 场景 | 不可用 | 可用 | 看实现 |
推荐策略:SurfaceFlinger 为主 + media.codec 为辅。SurfaceFlinger 提供帧级时间戳分析,media.codec 提供解码性能数据,两者互补。
四、SurfaceFlinger 帧间隔分析方法
SurfaceFlinger 输出的原始数据是纳秒时间戳序列,核心分析方法是从时间戳 → 帧间隔 → 指标:
4.1 从时间戳到帧间隔
scss
SurfaceFlinger 输出的 actualPresentTime 序列(纳秒):
T1, T2, T3, T4, T5, ...
帧间隔序列(毫秒):
(T2−T1)/1000000, (T3−T2)/1000000, (T4−T3)/1000000, ...
以 30fps 视频为例(理想帧间隔 = 1000 / 30 = 33.3ms):
markdown
实际帧间隔序列(ms):
33.2, 33.4, 33.1, 33.5, 33.2, 66.8, 33.3, 33.4, 33.1, 33.5, ...
↑
66.8ms ≈ 2× 理想间隔 → 丢了 1 帧
4.2 丢帧判定规则
帧间隔 > 1.5× 理想间隔 → 判为丢帧
为什么是 1.5 倍而不是 1.0 倍?因为即使正常播放,帧间隔也有轻微波动。1.5 倍留出合理容差:
| 视频帧率 | 理想间隔 | 丢帧阈值(1.5×) | 含义 |
|---|---|---|---|
| 24fps | 41.7ms | 62.5ms | 间隔 > 62.5ms 视为丢帧 |
| 30fps | 33.3ms | 50.0ms | 间隔 > 50.0ms 视为丢帧 |
| 60fps | 16.7ms | 25.0ms | 间隔 > 25.0ms 视为丢帧 |
4.3 从帧间隔推导丢帧数量
scss
帧间隔 66.8ms,理想间隔 33.3ms
丢帧数 = round(66.8 / 33.3) − 1 = round(2.0) − 1 = 1 帧
帧间隔 133.6ms,理想间隔 33.3ms
丢帧数 = round(133.6 / 33.3) − 1 = round(4.0) − 1 = 3 帧
4.4 连续丢帧检测
丢帧率相同,但分布不同,体验天差地别:
css
场景 A:1000 帧中,10 个不同位置各丢 1 帧
丢帧率 = 1%,卡顿 10 次,每次 33ms
用户感受:轻微、分散,可能不注意
场景 B:1000 帧中,1 个位置连续丢 10 帧
丢帧率 = 1%,卡顿 1 次,持续 333ms
用户感受:明显冻帧,一定会投诉
方法 :遍历帧间隔序列,记录连续超阈值的最大长度。最大连续丢帧数直接决定用户感受到的最严重卡顿。
4.5 注意事项
- 过滤无效时间戳 :
0x7FFFFFFFFFFFFFFF(INT64_MAX)表示帧尚未上屏,必须过滤掉 - 过滤异常大间隔:帧间隔 > 5000ms 通常是暂停/切后台,不算卡顿
- 视频帧率自动检测:无需手动指定视频帧率。先采集一段数据,取帧间隔的中位数,然后匹配最近的标准帧率(24/25/30/48/50/60fps)作为 expected_fps。这样同一个工具可以自动适配不同帧率的视频源
- buffer 上限与去重 :SurfaceFlinger 最多保留 ~128 帧,且
--latency不会被 reset 清空 ------每次调用返回的是最近 128 帧的完整缓冲区。因此必须记录上一轮最后时间戳,过滤已处理的旧帧,否则同一帧会被重复计数。采集间隔建议:30fps 视频 ≤ 4 秒,60fps 视频 ≤ 2 秒 - 多 Layer 遍历:视频播放时可能存在多个 SurfaceView(如视频 + 弹幕),应遍历所有匹配包名的 layer,取帧数最多的作为视频指标
五、完整指标体系
本节定义 10 个视频质量指标。5.1 / 5.2 讲解各指标的定义和计算方式,所有判定阈值统一汇总在 5.3 的检查清单中(含行业标准对照)。
5.1 核心指标(SurfaceFlinger 可直接获取)
指标 1:视频丢帧率
erlang
丢帧率 = 帧间隔超阈值的帧数 / 总帧数 × 100%
指标 2:视频帧率达成率
yaml
实际帧率 = 1000 / 帧间隔均值(ms)
帧率达成率 = 实际帧率 / 源帧率 × 100%
指标 3:帧间隔 CV(变异系数)
ini
CV = 帧间隔标准差 / 帧间隔均值
和 UI 帧的 CV(#12 第五节)概念一致,但参考基准不同:
- UI 帧的理想间隔 = VSYNC 周期(屏幕刷新率决定)
- 视频帧的理想间隔 = 视频源帧间隔(编码帧率决定)
指标 4:最大连续丢帧数 & 最大卡顿时长
| 指标 | 计算方式 | 含义 |
|---|---|---|
| 最大连续丢帧数 | 连续超阈值帧间隔的最大长度 | 最严重一次冻帧持续几帧 |
| 最大卡顿时长 | 最大帧间隔值(ms) | 最长一次画面停顿多久 |
| 卡顿次数 | 超阈值帧间隔的总次数 | 发生了几次画面不连续 |
判定标准(完整四级阈值见 5.3 检查清单):
| 指标 | 优秀 | 良好 | 警告 | 严重 |
|---|---|---|---|---|
| 最大连续丢帧 | ≤ 1 帧 | ≤ 3 帧 | 4 ~ 6 帧 | > 6 帧 |
| 最大卡顿时长 | < 50ms | < 100ms | 100 ~ 200ms | > 200ms |
| 卡顿次数 / 分钟 | ≤ 1 | ≤ 3 | 4 ~ 6 | > 6 |
5.2 辅助指标
指标 5:首帧时间(First Frame Latency)
用户点击播放到第一帧画面渲染到屏幕的延迟。
采集方法:记录操作时间 T0,播放后轮询 SurfaceFlinger,第一个有效帧时间戳 T1 出现时,首帧时间 = T1 − T0。
指标 6:解码耗时(需 media.codec)
| 指标 | 阈值建议 | 说明 |
|---|---|---|
| 平均解码耗时 | < 视频帧间隔 × 50% | 30fps → 平均 < 16.7ms |
| P90 解码耗时 | < 视频帧间隔 × 80% | 30fps → P90 < 26.7ms |
| 最大解码耗时 | < 视频帧间隔 | 30fps → 最大 < 33.3ms |
从解码器名称判断解码方式:
| 前缀 | 解码方式 | 性能 |
|---|---|---|
OMX.qcom. / OMX.mtk. / c2.qti. |
硬件解码 | 快,功耗低 |
OMX.google. / c2.android. |
软件解码 | 慢,依赖 CPU |
指标 7:音画同步偏移(A/V Sync)
国际标准 ITU-R BT.1359 定义了音视频同步的可感知和可接受阈值。人耳对"声音先于画面"的容忍度远低于 "声音滞后于画面",因此标准阈值是不对称的:
| 偏移方向 | 可感知阈值 | 可接受阈值 | 说明 |
|---|---|---|---|
| 音频提前(声先于画) | +45ms | +90ms | 人眼更敏感,容忍度低 |
| 音频滞后(画先于声) | −125ms | −185ms | 容忍度相对高 |
简化参考(测试实用):偏移 ±20ms 以内无感知;±40ms 敏感用户可察觉;±80ms 普通用户注意到唇形对不上;> ±120ms 明显不同步。蓝牙耳机场景最容易出问题(见场景 5)。
指标 8:Buffer 健康度(网络视频)
| 指标 | 含义 | 数据来源 |
|---|---|---|
| Buffering 次数 | buffer 耗尽导致播放暂停的次数 | logcat 关键词:buffering / underrun / empty |
| Buffering 总时长 | 累计等待数据的时间 | logcat / 播放器回调 |
指标 9:Seek 延迟
| Seek 延迟 | 用户感知 |
|---|---|
| < 100ms | 即时响应 |
| 100~300ms | 轻微延迟 |
| > 300ms | 明显等待 |
5.3 统一判定标准 & 检查清单
所有指标的判定阈值汇总如下。指标 1~5 是必检项,来自 SurfaceFlinger,通用性高;指标 6~10 按场景选检。
核心指标(必检):
| # | 指标 | 数据源 | 优秀 | 良好 | 警告 | 严重(红线) |
|---|---|---|---|---|---|---|
| 1 | 视频丢帧率 | SurfaceFlinger | < 0.1% | 0.1% ~ 1% | 1% ~ 3% | > 3% |
| 2 | 帧率达成率 | SurfaceFlinger | ≥ 95% | 80% ~ 95% | 60% ~ 80% | < 60% |
| 3 | 帧间隔 CV | SurfaceFlinger | < 0.1 | 0.1 ~ 0.3 | 0.3 ~ 0.6 | > 0.6 |
| 4 | 最大连续丢帧 | SurfaceFlinger | ≤ 1 帧 | ≤ 3 帧 | 4 ~ 6 帧 | > 6 帧 |
| 5 | 最大卡顿时长 | SurfaceFlinger | < 50ms | 50 ~ 100ms | 100 ~ 200ms | > 200ms |
辅助指标(按场景选检):
| # | 指标 | 数据源 | 优秀 | 良好 | 警告 | 严重(红线) |
|---|---|---|---|---|---|---|
| 6 | 首帧时间 | SurfaceFlinger | < 200ms | 200 ~ 500ms | 500ms ~ 1s | > 1s |
| 7 | 解码耗时 | media.codec | < 帧间隔 × 50% | < 帧间隔 × 80% | < 帧间隔 | > 帧间隔 |
| 8 | A/V 同步偏移 | logcat / 播放器 | ±20ms | ±40ms | ±80ms | > ±120ms |
| 9 | Buffer 健康度 | logcat / 播放器 | 0 次暂停 | ≤ 1 次 | 2 ~ 3 次 | > 3 次 |
| 10 | Seek 延迟 | SurfaceFlinger | < 100ms | 100 ~ 200ms | 200 ~ 300ms | > 300ms |
5.4 行业标准对照
我们的阈值不是拍脑袋定的,参考了以下行业标准和工具:
| 标准 / 工具 | 领域 | 核心内容 | 和我们的关系 |
|---|---|---|---|
| PerfDog(腾讯) | 移动端性能 | 视频播放推荐 Jank = 0;Jank 判定采用"前 3 帧均值 × 2 + 固定阈值"双条件 | 丢帧率 < 0.1%(优秀)对应 PerfDog 的"接近零 Jank" |
| W3C VideoPlaybackQuality | Web 视频 | droppedVideoFrames / totalVideoFrames > 10% 触发质量降级 |
我们的红线(3%)严于 W3C 的 10%,面向高品质产品 |
| ITU-R BT.1359 | 音视频同步 | A/V 可感知:音频提前 +45ms / 滞后 −125ms;可接受:+90ms / −185ms | 指标 8 直接对齐该标准 |
| ITU-T P.1203 | 流媒体 QoE | 基于 MOS(1-5 分)的自适应流质量评估,综合卡顿、启动、质量波动 | 我们的指标覆盖了 P.1203 关注的核心维度 |
| Akamai QoE | 流媒体 | 每次 rebuffering 降低 14% 满意度;启动超 2s 用户开始流失 | 首帧 < 200ms + Buffer 0 次暂停与其一致 |
| ExoPlayer Analytics | Android 播放器 | 提供 maxConsecutiveDroppedFrames,可配置通知阈值,无固定标准 |
我们给出了具体的连续丢帧四级标准 |
一句话总结 :我们的阈值整体严于 W3C(10%)和 ExoPlayer(无固定标准),对齐 PerfDog 和 ITU-R 标准,面向高品质产品。帧间隔 CV 是我们的特色指标,PerfDog / Perfetto 均未提供。
六、数据源可用性验证流程
拿到任何 App,先做一轮数据源验证:
vbnet
Step 1:启动 App 并播放视频
Step 2:检查 SurfaceFlinger 是否可用
→ 执行 dumpsys SurfaceFlinger --list,搜索 SurfaceView
→ 有 SurfaceView layer → SurfaceFlinger 方案可用(最佳)
→ 无 SurfaceView layer → App 用 TextureView 渲染,降级到 Step 3
Step 3:检查 media.codec 是否可用
→ 执行 dumpsys media.codec,搜索 codec / drop / decode
→ 有解码器数据 → media.codec 可用
→ 无数据 → 纯软解码,回到 SurfaceFlinger(大部分场景仍可用)
Step 4:检查 media.player(大概率无效,但值得一试)
→ 执行 dumpsys media.player,搜索 numFrames
→ 有数据 → 额外数据源可用
→ 无数据 → 符合预期,主流 App 不走这条通道
实测兼容性(全屏 SurfaceView 播放):
| App | SurfaceFlinger | media.codec | media.player |
|---|---|---|---|
| 抖音 | 可用 | 可用 | 不可用 |
| 快手 | 可用 | 可用 | 不可用 |
| 爱奇艺 | 可用 | 可用 | 不可用 |
| 腾讯视频 | 可用 | 可用 | 不可用 |
| B站 | 可用 | 可用(硬解时) | 不可用 |
| 微信视频号 | 可用 | 可用 | 不可用 |
| 自研 App(MediaPlayer) | 可用 | 可用 | 可用 |
| 自研 App(ExoPlayer) | 可用 | 可用 | 不确定 |
七、常见视频卡顿场景 & 诊断方法
场景 1:硬解码器繁忙
现象:多路视频同时播放(如信息流),部分视频卡顿。
原因:手机硬件解码器数量有限(通常 4~8 路),超出后降级为软解码,CPU 负担加重。
硬解码:4.2ms/帧 → 远低于 33.3ms → 流畅
软解码:25.8ms/帧 → 接近 33.3ms → 容易掉帧
CPU 降频后的软解码:42ms/帧 → 超过 33.3ms → 必然掉帧
诊断 :通过 media.codec 查看解码器名称,OMX.google. / c2.android. 开头 = 软解。
场景 2:分辨率切换冻帧
现象:网络视频切换清晰度(360p → 1080p)时短暂冻帧。
原因:分辨率切换需要重建解码器,200~500ms 间隙。
SurfaceFlinger 表现:帧间隔序列中出现一个 200~500ms 的异常间隔,前后帧间隔正常。
场景 3:网络 buffering
现象:网络视频播放,帧率忽高忽低。
原因:网络带宽不足,解码器输入 buffer 空了。
| 视频规格 | 带宽需求参考 |
|---|---|
| 30fps 1080p H.264 | ≈ 5~8 Mbps |
| 30fps 4K H.265 | ≈ 15~25 Mbps |
| 60fps 1080p H.264 | ≈ 10~15 Mbps |
SurfaceFlinger 表现:帧间隔序列中出现间歇性的大间隔(数百毫秒),夹杂正常间隔。CV 飙高。
场景 4:CPU 降频导致软解码变慢
现象:长时间播放后越来越卡。
原因:设备发热后 CPU 降频,软解码器算力不足(参考 CPU 系列 #7)。
诊断:交叉分析 CPU 频率比(趋势下降)和 SurfaceFlinger 帧间隔 CV(趋势上升)。
场景 5:A/V 同步异常(蓝牙耳机)
现象:连接蓝牙耳机后,视频声音和画面对不上。
| 蓝牙编码 | 固有延迟 | 说明 |
|---|---|---|
| SBC | 170~270ms | 默认编码,延迟最高 |
| AAC | 120~200ms | iOS 常用,Android 上延迟偏高 |
| aptX | 100~250ms | 标准 aptX,中等延迟 |
| aptX Low Latency | < 40ms | 游戏/视频专用,需耳机支持 |
| LDAC | 200~250ms | 追求音质,延迟最高之一 |
| LC3(LE Audio) | 20~40ms | 新一代蓝牙低延迟标准 |
场景 6:帧率与刷新率不匹配(Judder)
现象 :24fps 电影在 60Hz 屏幕上有轻微"抖动感",但不丢帧。
原因:24 不能整除 60,每帧显示时长不均匀(3:2 pulldown)。
24fps 在 60Hz :每帧交替显示 3 或 2 个 VSYNC → 帧间隔交替 50ms/33ms → judder
24fps 在 120Hz:每帧固定显示 5 个 VSYNC → 帧间隔恒定 41.7ms → 无 judder
30fps 在 120Hz:每帧固定显示 4 个 VSYNC → 帧间隔恒定 33.3ms → 无 judder
SurfaceFlinger 表现:帧间隔交替出现两种值,丢帧率 = 0%,但 CV 偏高(> 0.2)。
识别方法:丢帧率正常 + CV 偏高 + 帧间隔呈规律性交替 → 大概率是 Judder,不是 bug。
八、与 UI 帧率的联动分析
视频播放和 UI 交互往往同时发生(边看视频边滑评论),需要同时采集两路数据。
联合诊断决策表:
| 症状 | UI 帧率 | 视频帧间隔 | CPU 频率比 | 可能原因 | 排查方向 |
|---|---|---|---|---|---|
| UI 卡,视频不卡 | 低 | CV 正常 | 正常 | UI 线程阻塞 | 帧耗时阶段拆解(#12) |
| UI 不卡,视频卡 | 正常 | CV 高 / 丢帧多 | 正常 | 解码器瓶颈 / buffer 不足 | media.codec 解码耗时 |
| 都卡 | 低 | CV 高 | 低 | CPU 降频,整机算力不足 | CPU 频率比趋势(#7) |
| 都卡 | 低 | CV 高 | 正常 | CPU 被占满 | top / CPU 核心利用率(#8) |
| 都不卡,但音画不同步 | 正常 | CV 正常 | 正常 | 蓝牙延迟 / 同步逻辑 bug | 蓝牙编码类型、播放器日志 |
| 都不卡,但画面抖 | 正常 | CV 偏高 | 正常 | 帧率/刷新率不整除 | 确认 Judder(场景 6) |
采集频率建议:
| 数据 | 采集方式 | 建议间隔 |
|---|---|---|
| UI 帧率 + 卡顿 | dumpsys gfxinfo | 1~2 秒 |
| 视频帧间隔 | SurfaceFlinger --latency | 2~4 秒 |
| 解码统计 | dumpsys media.codec | 5~10 秒 |
| CPU 频率/负载 | /proc/stat + cpufreq | 5~10 秒 |
九、不同场景的指标优先级
不同视频场景关注的指标不同:
| 场景 | 必检指标 | 辅助指标 | 可忽略 |
|---|---|---|---|
| 本地视频播放 | 丢帧率、CV、最大连续丢帧 | 解码耗时 | Buffer、Seek |
| 短视频(抖音/快手) | 首帧时间、Seek 延迟、丢帧率 | Buffer | A/V 同步 |
| 长视频(爱奇艺/腾讯) | 丢帧率、A/V 同步、CV | Buffer | Seek |
| 直播 | Buffer 健康度、丢帧率、首帧时间 | CV | Seek |
| 多路视频(信息流) | 丢帧率、解码方式(硬/软) | CPU 负载 | A/V 同步、Seek |
短视频 最核心的指标是首帧时间------用户刷视频时,每个视频都有一次首帧加载。首帧慢 = 每刷一个视频都卡一下。
小结
| 知识点 | 一句话 |
|---|---|
| 核心问题 | gfxinfo 只采 UI 帧,采不到视频帧------两条独立管线 |
| 通用方案 | SurfaceFlinger --latency 是唯一真正通用的视频卡顿检测方案 |
| 原理 | 不管什么播放器,视频帧最终都提交到 Surface → SurfaceFlinger 上屏 |
| SurfaceFlinger 限制 | TextureView 渲染场景无法分离视频帧,需降级 media.codec |
| media.player | 对抖音/快手/爱奇艺等主流 App 无效 |
| 分析方法 | 时间戳 → 帧间隔序列 → 丢帧/CV/连续丢帧/实际帧率 |
| 必检指标 | 丢帧率、帧率达成率、CV、最大连续丢帧、最大卡顿时长 |
| 判定标准 | 四级分档(优秀/良好/警告/严重),对齐 PerfDog、ITU-R BT.1359 等行业标准 |
| 和 UI 的关系 | 视频帧 + UI 帧 + CPU 数据三路联合诊断 |
下一篇我们把 FPS 系列所有指标整合到一起,给出完整的采集落地方案------从脚本架构到自动化报告。
系列目录
- 第 1~4 篇:内存泄漏检测 & 内存采集避坑
- 第 5~9 篇:CPU 采集系列(入门 → 避坑 → 降频 → 单核 → 落地)
- 第 10 篇:FPS 帧率采集入门
- 第 11 篇:FPS 采集的 8 个坑
- 第 12 篇:UI 卡顿量化------用数据回答"到底有多卡"
- 第 13 篇(本篇):视频播放卡顿检测
- 第 14 篇(下一篇):FPS 采集落地方案
我是测试工坊,专注 Android 系统级性能工程。 如果你也在做帧率相关的性能测试,欢迎评论区交流 👇 关注我,后续更新不迷路。