云手机中的交互时延组成和验证

34 阅读29分钟

背景

市面的云手机中,我们一个交互动作到画面出现变化展现到我们的眼前,这个过程中产生的时延,一般称为交互时延,我们在业务场景中经常关注这个时延数据,这个时延一般会直接让用户感觉到操作是否跟手,是一个非常重要的指标数据。同理,这个指标在云游戏云电脑的性能指标中同样的重要性,本篇文章会讨论这个时延的测试和组成。

初步分析

我们先讲做一些初始的分析。按照经验,我们可以将时延大致分为三部分:客户端上行时延、云测时延、客户端下行时延,我们先梳理一部分我们目前所知部分时延我们画一个时序图。

sequenceDiagram
    participant User as 👤 用户
    participant Client as 📱 客户端设备
    participant Network as 🌐 网络
    participant Cloud as ☁️ 云手机服务器
    
    rect rgb(255, 240, 240)
        Note over User,Cloud: 📤 上行链路 (触控 → 云手机)
        User->>Client: 触摸屏幕
        Note right of User: 🕐 触控采样延迟<br/>
        Client->>Network: 发送触控指令
        Note right of Client: 🌐 网络延迟(上行)<br/>
        Network->>Cloud: 接收指令
        Note right of Network: ⚙️ 指令处理延迟<br/>
    end
    
    rect rgb(240, 255, 240)
        Note over Cloud: 🖥️ 云手机处理
        Cloud->>Cloud: 渲染画面
        Note right of Cloud: 🎨 渲染延迟<br/>
        Cloud->>Cloud: 视频编码
        Note right of Cloud: 📦 编码延迟<br/>
        Cloud->>Cloud: 帧等待/打包
        Note right of Cloud: ⏳ 帧等待延迟<br/>
    end
    
    rect rgb(240, 240, 255)
        Note over Cloud,User: 📥 下行链路 (视频流 → 显示)
        Cloud->>Network: 发送视频流
        Note right of Cloud: 🌐 网络延迟(下行)<br/>
        Network->>Client: 接收数据
        Client->>Client: 视频解码
        Note right of Client: 🔓 解码延迟<br/>
        Client->>User: 显示画面
        Note right of Client: 🖼️ 屏幕显示延迟<br/>
    end

这里有几个概念可能需要拓展来讲一下

  1. 在主流设备中(非游戏模式下),触控采样延迟,一般是120HZ-240HZ。采样率越高,每秒采集触控信号次数越多,理论延迟越低。120Hz≈8.3ms 周期,240Hz≈4.2ms 周期。听起来很低是吧,但这个是电信号,信号传输到系统还会有时延。
  2. 不同设备的系统时钟是不同的,所以无法直接通过简单的打点来直接得到精准的时延,但是如果用一侧的时钟作为标准,发送一个包,再回来一个包,这个就可以很精准的得到网络时延的数据。这个是现在普遍的测试网络时延的方法。但是要测试交互时延还是不够的
  3. 屏幕显示是具有延迟的,假设屏幕画面帧率是60fps刷新率,也就是大约16.67ms进行一次刷新,这里有一个比较难以注意的点是,屏幕的画面是从上到下扫描出来的,并不是一下子展现出来,也就是说画面更新的时候,是从顶部到顶部更新的,这里面会存在时延差异

接下来我们做一些实际测试,来进一步分析

测试环境说明

测试方法:小米14pro,使用480fps拍摄模式,可以将时延误差控制在2.08ms 测试手机:oppo findx3,开始120fps高刷,可以减少送显时延,减少误差

这里补一下我这边用到云手机的其他环境变量:

云机环境:虚拟安卓子系统 使用技术:WebRTC,H264/H265,ffmpege 容器:APP, WebView两个环境

测试方案:FFPlay,命令:ffplay -vf "transpose=1,showinfo" 文件名 -y 360 -x 640 -sync video 后,按s键逐帧分析。(按p可恢复播放)

测试物理机采集 -> 反馈时延

需要注意的是,我们通过高速摄像机拍摄到的时延是手动计算的,这个过程在测试阶段是可接受的,但是在商用的业务场景,我们还是需要对交互时延数据进行程序化的计算。这里就存在一个问题,我们的采集触控时延是不可计算的,因为他有一部分硬件的干扰,比如触控IC,内核中断,输入系统这部分的过程他是程序不可知的。所以一般来说我们会通过在实验室环境进行一次批量测试,然后取一个平均值,然后在后面的时延计算组成中将这个值作为一个相对固定值。

这里我们采集的数据是测试物理机采集到屏幕出现白点作为我们的初步时延方案,我们称这个阶段的时延为T1,。该阶段发现不同刷新率的手机的T1具有不同的值

480fps拍摄模式下, oppo findx3的120fps高刷模式, 240fps的触控采样率, 平均T1: 50ms, 如果在60FPS的时候平均T1是70ms。 这里为什么会存在差异,这里的T1为什么会相差20ms. 其实按照我的时延理解,理论来讲应该是屏幕刷新时延和触控采样导致的,120HZ的屏幕固定等两个刷新间隔时延是8.33ms,60HZ的屏幕两个固定刷新间隔是16.67ms, 两者之间的时延固定相差8.33ms了,此外还存在一个指令的等待Vsync时延。 这个时延我们如果取平均值半个刷新间隔,120HZ是4.17ms,60HZ是8.33ms。两者之间刚好就相差了4.17ms。 时延的相加4.17 + 8.33 = 12.50ms,还差7.5ms,为什么还存在一个时延,要这个是安卓的刷新机制引起的,我们后面会讨论这个问题。

概念说明

因为时延一般我们讨论都是毫秒级的单位,为了测试时延的精度,我们一般会引入一台外部的高速摄像机来作为云手机的时延拍摄设备。拍摄的案例是:

手指接触屏幕Time0 ---> 云机出现画面响应Time1

这个时候我们将 Time1 - Time0 就是我们说的完整的端到端时延了(e2e)。虽然这也能满足我们对时延的部分了解,但是我们如果想优化这个时延,我们有必要进行科学的时延分析,才有可能知道具体如何优化。

1、计算时延

计算时延在过往我们的计算方式是将上面的时延进行分段统计,然后进行求和。在过往的经验中,客户端T1是无法通过程序获取的,我们往往取经验值50ms。网络时延RTT、客户端解码时延、帧接受时延我们可以通过WebRTC获取的到, 编码时延我们可以通过系统获取。云机是60fps的,所以渲染时延过往我们取两个渲染帧来作为估值经验值,也就是33ms.

过往的时延计算公式是:

// 这个认识是存在部分错误和不足的
计算时延 = T1 + RTT + 云机渲染时延 + 编码时延 + 发送等待时延 + 帧接受耗时 + 解码时延 + 抖动时延

2、观测时延

我们在进行测试的时候,会先在云机安装一个响应APP,然后打开物理设备和云手机机的开发者工具,将触控指针打开。使用外部的高速摄像机,拍摄我们通过物理设备点击云机APP这个过程,然后记录各个响应点的时间,最终将他们的事件的时间的差值来作为我们的测试时延,有的时候也会叫做观测时延,同个意思。下面我们会使用观测时延来作为我们通过摄像头观察到时延差值的名称。

3、帧率和屏幕刷新率的差别

这是两个不同的概念

帧率:表示GPU 在1s中内可以渲染多少帧到buffer中,单位是fps,这里要理解的是帧率是一个动态的,比如我们平时说的60fps,只是1s内最多可以渲染60帧,假如我们屏幕是静止的,则GPU此时就没有任何操作,帧率就为0.

屏幕刷新率:屏幕在1s内去buffer中取数据的次数,单位为HZ,常见屏幕刷新率为60HZ。和帧率不一样,屏幕刷新率是一个固定值和硬件参数有关。

最终分析和结论

我们先将得到的结论贴出来。我们经过一系列的测试,最终将云机的时延分为三大部分:网络时延、云机侧时延,端侧时延

  • 网络时延通过WebRTC的指标RTT得到
  • 端侧的时延H5和APP会有所不同

APP的端侧时延由触控指令处理、组包时延、jitter时延、解码时延、OpenGL ES时延, SurfaceFlinger时延,扫描时延

H5的程序因为运行在WebView上,所以WebView导致的性能差异会产生额外的时延,额外包含指令透传时延、Webview内部合成和光栅化的时延

  • 云机侧时延目前是通过在端侧打印每一帧的RTP时间戳,然后通过云机日志对比最近的一个触控事件接收时间,来计算实际的云侧时延

我们在推导得到了计算公式

计算时延(去掉T1) = 网络时延 + 云机侧经过时延 + 端侧时延

向上取整(计算时延) = 误差取整(观测时延) // 前面的向上取整的意义是存在帧缓冲,如果错过了Vsync必须等待下一个帧的刷新间隔画面才能显示

APP时延组成

whiteboard_exported_image.png

里面参与交互时延组成的部分包括:

  1. 指令扫描
  2. 硬件传递电信号
  3. 指令处理
  4. 网络上行
  5. 云侧指令处理
  6. 云侧APPP处理输入
  7. 云侧surfaceFlinger
  8. 画面提交编码器编码
  9. WebRTC帧等待、FrameBrocast
  10. 网络下行
  11. 接收RTP包等待,组包
  12. 解码器解码YUV
  13. OpenGL ES渲染,提交管线
  14. 端侧SurfaceFlinger合成
  15. 屏幕扫描

H5时延组成

whiteboard_exported_image (1).png

踩坑点

1、存在组包时延频繁统计的情况

组包的时延其实只有当1个帧由多个包存在的时候才存在,在WebRTC的统计参数里面这个指标刚开始被统计上了,会导致计算时延每次都算上平均组包时延,但其实除了I帧其他的画面不一定含有组包时延。

组包时延是通过公式 totalAssemblyTime/framesAssembledFromMultiplePackets_in_ms 计算的,这个其实会有异常情况,P帧视频帧其实是不一定会分包上传的,在一些情况下,P帧就不会分包,一个RTP包就可以直接传输了,当前只有I帧是比较大的,确定一定要分片上传,下方的图片可以看到

framesAssembledFromMultiplePackets的数量是和I帧的数量是相同的,所以我们存在这个统计结果的问题,除了I帧,其他的数据是不需要计算这个组包时延的。那么我们就要和上次的统计结果做出一轮比较。但是我们还是很难判断统计结果过程中,上一轮的画面是不是I帧,需不需要算入统计结果,所以我们要考虑延长一下I帧的间隔来做测试。

当前云测SDK的GOP设置有一个30~3000的有效值,不能设置超过这个的值,当前是120, 也就是2s一个关键帧,我们将他设置成3000. 也就是50s一个关键帧,这足够测试了。

2、桌面程序帧率不稳定

一开始为了避免第三方程序(王者荣耀)的影响,我在桌面先进行了多轮的物理机响应到云手机响应的时延测试,但在进行时延统计的时候发现如果在桌面程序放置一个时钟悬浮窗,会导致桌面程序帧率降至10fps。导致了时延很混乱,波动很大,后面为了保持帧率的稳定,继续使用了王者荣耀来做持续测试。这里也推断出来了云机的帧率会对时延造成直接影响

image.png

3、连上了弱网的路由器

这个是由于这个手机之前用来测试过弱网,导致大量丢包,导致时延出现波动,换成正常的网络就好了,不过我们在我们的统计界面加上弱网信息的打印,下次方便排查问题

4、测试时延的数据没有纠正

测试时延没有和物理设备的刷新间隔呈现倍数关系。我们计算测试时延,其实是都是刷新帧的时间间隔在做分析,同一个屏幕触控点的响应,应该和屏幕刷新率呈现倍数关系。

这个比较重要,我们取几个测试数据来作为分析,来展示的他们的关系

因为我们的测试的这一台设备是120fps设备,刷新的间隔是8.33ms,高速摄像头的拍摄间隔是:2.083ms,我们来看看这几行数据

image.png

第一行数据:

测试时延是72.83ms(上面的表格取的第一个值)。刷新的间隔是8.33ms。那么我们求余72.83 % 8.33 = 6.19ms, 这样我们认为他这个余数不对,我们认为这个是我们认为统计的时候应该是少计算了一个拍摄帧2.083ms。那么正确的数据计算应该是(72.83 + 2.08)% 8.33 = 8.269999999999996。这个余数基本等于一帧的时间间隔。那么这个调整后的值才是正确的

第二行数据:

时延是56.21ms(上面的表格取的第二个值)。刷新的间隔是8.33ms。那么我们求余56.21 % 8.33 = 6.74789916ms,这样我们认为他这个余数不对,我们认为这个是我们认为统计的时候应该是少计算了一帧。那么正确的数据计算应该是(56.21 + 2.08)% 8.33 = 0.58ms。 这个偏差按照我们理论才是正确的。说明我们人为的统计存在了异常的偏差。

第三行数据:

时延是64.52ms(上面表格取第三个值)。刷新的间隔是8.33ms。那么我们求余64.52 % 8.33 = 6.2099999999999955ms。和上面一样,这个余数明显也存在问题。我们对64.52按照上面的相似的做一下处理,(64.52+2.08)% 8.33 = 7.99519808ms。这个余数也接近一帧的间隔。可以作为正确值

那么我们上面的时延的纠正方法,换算成计算公式应该是:

#方式一
纠正观测时延 = 四舍五入(观测时延 / 物理设备刷新间隔) * 物理设备刷新间隔

#方式二
屏幕刷新间隔倍数 = 观测时延 / 物理设备刷新间隔 + ((观测时延 % 物理设备刷新间隔) < 物理设备刷新间隔的一半 ? 0 : 1) 
屏幕刷新时延 = 屏幕刷新间隔倍数 * 物理设备刷新间隔时延

这样我们就可以得到怎么纠正我们测试时延的公式了。并且我们也能将我们的时延从具体的时延指标转向帧数分析的维度上,我们下面会通过时间轴和画面刷新来作为分析重点

5、纠正后的测试时延数据比可知的计算总时延还小

因为T1的时延不好统计,所以一开始我的计算时延和测试时延都是去除了T1的影响后讨论的。我们的计算时延只能统计可知能统计的数据。经过上面的踩坑和我抛弃了一些过往的固定经验值,在这里我计算的时延已经调整为

计算时延 = 网络时延 + 编码时延 + 帧等待 + 解码时延 + 抖动时延

按照上面的计算时延,其实这只是我们可以通过程序可以统计的到指标,但未知的时延我们知道还有云测指令处理时延,云测渲染时延、云测采集时延(包含间隔等待&采集本身),端侧送显时延。

但同题目一样,我们获取的到计算时延在一些场景已经和纠正后的测试时延基本是一致,甚至还比测试数据大,这是怎么回事?

经过一些资料的查询和思考,我们得了解到了系统的屏幕刷新时延

6、安卓系统屏幕刷新机制V1版本

PS: 这个是官方的2012图,但现在是错误的,估计是刷新机制中间更新的,等下我们会更新我们的认识。

现在安卓系统是3缓冲刷新。当Vsync信号接收的时候,CPU开始工作,制造缓冲区,但是这个缓冲区,理想情况下其实是要下一个Vsync信号的时候才会开始展示在屏幕画面上, 糟糕的情况下,可能就要多等一个frame后才有机会出现在屏幕上。这会导致一个什么问题呢?

image.png

用上面的图片来展示,这是一个理想的情况,就是双缓冲已经满足了情况,这种情况我们的手机触控屏幕,到屏幕展示出我们的触控指针,这个中间一定会经过一个完整的帧间隔。我们算触控的时间距离下一个sync信号平均为0.5个帧间隔。指针出现的位置在屏幕中间的话,因为屏幕是逐行向下刷新的,出现的时间我们也算0.5个帧间隔。这样我们的指针触控到指针出现在屏幕上,实际已经过去2个帧间隔了。换成120屏幕刷新率的屏幕,这个时间间隔是 16.67ms。这样,我们在使用云机触控的时候,其实在屏幕出现指针的时候,指针的事件大概率已经通过网络发送出去了,如果RTT时延短一点,可能触控事件已经到达云机了。在我们过往的经验中,这些时延都被含在了T1内,我们的云机画面刷新其实也有这个这个固定帧时延的问题,但因为大家在同一个时间轴,如果T1包含了,那么大家的时间轴可以同步减去这个固定帧时延。

不过上面我们讲到上面的安卓刷新机制认识是有不足的

7、安卓刷新机制理解的纠正V2版本

本来是想找资料看Vsync的信号和触控事件的延时如何在程序计算,但是查阅相关谷歌的文档,发现前面对于Vsync信号的理解有个大问题(浪费了不少时间,有的时候还是多看官方的文档)。

Vsync的信号接收的时候,SurfaceFlinger和APP处理输入并生成帧是不是相连执行的,而是刚好错开一个Vsycn信号来执行的。这样我们上面理解的Vsync的图就需要更换理解了

image.png

初步理论分析

我们在测试时延里面得到了真正的时延其实是和物理设备刷新率是呈现倍数关系的,那么我们按照上面的理解可以得到他们的关系图

image.png

在这个画面中时延的组成有

  1. T1的误差时延
  2. 网络上行时延和下行时延RTT
  3. 云机的屏幕刷新时延(0~16.33ms范围, 同样,我们看到云机的响应, 假设不算其他的时延,当时看到的那一刻其实可能指令已经接收了一段时间了)
  4. 云机的采集时间轴和刷新时间轴产生的时间差(画面刷新的那一刻到采集频率到达这一帧的时间差)
  5. 采集本身的时间(这个未知,我们先忽略)
  6. 编码的时间(这个为79ms)+ 等待时延(1~2ms)
  7. JitterBuffer缓冲时延(现在有playout 设置为0的RTP拓展和去掉了SR包,网络好的情况下这个值看统计在1ms以下,APP也对JitterBuffer做了处理,可以忽略不计)
  8. 解码时延 (oppo findx3: 7~8ms)
  9. 送显时延(测试的时候直接采集屏幕顶部的响应动作,去掉这个数据的影响)

在这个阶段,一我还没认识到解码的时延和APP的计算过程是两个独立的线程,并没有交集的,实际是各算各的,最后通过SurfaceFlinger合成画面的。在这一节分析的时候我把解码的时延包含在了APP的计算内,所以会看到忽略了解码时延。二还没有认识到APP的计算之后并不是紧接SurfaceFlinger的流程的,而是还隔着一个Vsync信号。

计算方案的优化

上面中,我们依然存在了很多的范围取值,这种模糊的情况是我们想要避免的。经过思考之后,想将云机部分的时延单独来作为一项来作为数值统计,这样可以避免太多干扰的变量。也就是下图的部分的总的时延

image.png

怎么得到这个时延?这个时延在理论上应该是云测的云机接收到第一个触控事件的时间到云侧的发生变化画面被编码之后,得到发送的最后一个帧的RTP包的发送时间之差。那我们就可以先在云测打印所有的触控事件的时间,和所有RTP包的发送时间,这样他的日志文件我们就可以作为一个参考值,端侧在渲染画面的时候,每一帧的画面组成的所有的RTP包的时间戳是一样的,这样,我们就可以在渲染这个画面的时候打印他的RTP的时间戳,然后对应起来

上图中的两个时间差T2-T1就是云机的云侧时延。1759030764576840 - 1759030764528203 = 48637us = 48.6ms。说明这个触控事件在云机上经过了48.6ms之后才从云机发送出去最后一个RTP包。

这样我们得到了云侧的时延,我们就可以忽略上面公式内的云机指令处理时延、云侧应用计算时延、云侧的SurfaceFlinger、云侧的采集-渲染-编码-帧等待时延。这些在云机内发生的内容,可以直接用这个时延来作为代替来优化上面的公式

验证

触控事件到Vsync信号时延验证

上文我们了解到Vsync信号是屏幕给安卓系统分发的信号,那触控事件的时间到Vsync信号的时间我们使用程序可以这样子统计

物理机
    fun onTouchEvent() {
        touchTimeNanos = SystemClock.elapsedRealtimeNanos()
        choreographer.postFrameCallback { frameTimeNanos ->
            val timeDiffMicros = (frameTimeNanos - touchTimeNanos) / 1000 // 转换为微秒
            val timeDiffMillis = timeDiffMicros / 1000.0 // 转换为毫秒
            
            maxDelayMillis = maxOf(maxDelayMillis, timeDiffMillis)
            minDelayMillis = minOf(minDelayMillis, timeDiffMillis)
            
            _text.postValue("触摸到Vsync信号的时间差:\n当前: $timeDiffMillis 毫秒\n最大: $maxDelayMillis 毫秒\n最小: $minDelayMillis 毫秒")
        }
    }

我们看一下测试的结果如何

这里和我们上面推断一样,触控事件的到最近的一个Vsync信号的时延是取决于屏幕的刷新间隔,这里按照上面的测试值我们可以取【0.5ms~屏幕刷新间隔】。这部分验证通过,不过这个方案只有APP可以采用,H5没有相关接口可以获取这个值。

云机

云机 的触控事件接收到Vsync信号的逻辑应该也可以如此计算。我们将云机使用这个应用测试一下

image.png 可以看到触控事件到Vsync信号的时延范围确实在 【0.5~16】ms。

需要注意的是,文档有相关提示Vsync接收延时

本身程序的Vsync信号接收有一定延时,所以这个0.5ms的值理论可以忽略,我们实际的指针事件到Vsync信号的时间间隔就是【0~屏幕刷新间隔】

这个方案目前满足测试需求,但是如果要满足实际的程序需要可能依然存在问题。原因存在于

如果设备存在睡眠的行为之后,得到的时间会偏小,暂时不关注这个问题,不阻塞我们当前的场景

APP-UI时延验证

上面的demo中,我们通过安卓的性能分析工具分析了一下,他们的APP-UI计算绘制时间可以直接看到

这个值在这个应用基本都在2~3ms, 因为云手机安卓的代码我这边暂时没有这个环境,安卓的同事可以帮忙看一下这个值的平均值是多少,如果在8ms以内基本可以在计算的时候直接使用物理手机的刷新间隔来作为他上位替代。

SurfaceFlinger时延验证

物理机

所以我们无法通过程序获取,但是系统底层提供一些相关工具供我们查看他们的指标。

安卓有提供dumpsys工具供我们查看时延

Android Studio也有性能面板工具供我们查看Vsync的间隔

60HZ的时候符合我们的推论,但120HZ的情况出现了变化,当我们拉流60fps的云机画面并保持不动的时候,他的Vsync信号还是16ms的间隔。如下图所示

当我滑动界面的时候,Vsync信号就会变成8ms的间隔

暂时怀疑,高刷的场景下存在画面帧率动态调整,在这个场景下,Vsync会根据动态的内容(视频流)决定Vsync的信号。如果加入认为的滑动,因为画面有物理设备的内容参与,所以Vsync信号会根据极限值来调整。

云机

监控提示的时延是16ms。

这边通过对云机ADB地址进行性能监控发现云机的合成实际时间是比较慢的,接近700ms了,这是一台外部环境的云机,我自己操作的时候也确实很卡,这个时间也可能假的,做出两种假设

1、云机本身卡,SF合成就是慢的

2、统计数据可能存在一些问题

集成验证

安卓的代码不是很熟悉,我这边直接集成WebView的页面在上方的demoAPP里面。

但是问题出现了,当在页面加载WebView的时候,发现触控事件到下一个Vsync信号的值竟然是一个负值。并且经过测试发现是webview加载的问题,意思就是,当页面不加载webview的时候,这里不会出现的负值。

上面的代码中,捕获的逻辑是按顺序执行的,什么情况下会导致这种问题呢?这时候就可以合理怀疑因为加载了WebView中的云机画面,APP的计算可能会存在时延超时,导致UI主线程阻塞。我们抓一下物理机的性能报告

可以看到确实存在了不少的jank frame,我们取一帧来看一下详情

这里可以看到应用的计算线程超时了,超过了Vsync的deadline线, 怀疑这种情况会导致负数

并且安卓的控制面板也看到相应的Vsync在时间上面产生的间隔,证明我们的刷新理论没有问题,并且如果发生jank的情况我们取一个样本来看,是经过39ms

样本一和样本二的采样是不同的,他们相差8ms是通过指标我们发现部分的帧他能在SF合成之后就马上绘制,部分的情况是要等下一个画面绘制完了之后才能绘制。

这里代表的是流程是Vsync信号之后,APP主线程开始计算并绘制 -> 渲染线程接受绘制数据,使用OpenGL之类的工具 库执行渲染,并将渲染之后的数据提交给BufferQueue供SF进行合成-> SF合成 -> SF合成之后-> 提交显示器进行显示。

我们看一些真实的细节

  1. 帧的渲染工作确实是由Vsync信号之后才开始的

  2. 120HZ刷新率下单个帧的UI计算时延普遍大于Vsync时间(8.33ms)

  3. 在120HZ刷新率下,APP的主UI线程计算工作之后马上就接上了SF的流程了,这个流程和我们上面的理解有一些出入

  4. SF合成的时间普遍大于一个Vsync间隔

  5. 屏幕保持的时延普遍是16ms,部分是8.33ms,这个猜测是因为下一个帧还没通过SF合成,画面只能继续重画上一帧

  6. 屏幕的刷新没有跟随Vsync信号。而是紧接着SF的流程直接执行了

上面看到的现象和我们上面的理论产生冲突,我们理想情况下,APP的计算占用一个刷新间隔,SF占用一个刷新间隔,屏幕的实际展示从上到下也占用一个刷新间隔,所以我们理论是三个Vsync信号之后,我们就能看到想要看到的画面了。但是在高刷的情况下,Vsync间隔缩短,上面的APP的计算和SF都超时了,所以最后我们看到他接近4个刷新间隔才将画面展示出来。

按60HZ刷新率进行Vsync信号的处理的情况如何?我们取一个60HZ的样本来观察一下有什么不同

973 - 927 = 46ms。这一帧的画面从Vsync开始计算到Frame展示,一共过了46ms, 和120HZ刷新率的时延相差7ms。

在这几份60HZ的样本中,就很符合我们对安卓刷新机制的理解,让我们也归纳他们的细节。

  1. 画面一样也是从Async信号开始,

  2. 60HZ刷新率下单个帧的UI计算时延普遍小于画面刷新间隔(16.67ms)

  3. 单个帧的计算时延经过对比,发现和120HZ一样,说明这个时间是固定的,不受画面刷新率的影响

  4. 总的时延如果120HZ刷新率的情况下,如果送显时延理想,他的端侧时延应该是31ms。60HZ是49ms,相差18ms

在这个60HZ的情况下,我们看到了,画面的延时平均都是49ms。大概是三个帧的刷新时延,Vsync也对的上。得出一个初步的结论,120HZ的画面刷新延时并不完全遵循安卓的刷新机制,在Vsync的阶段只有APP计算跟从Vsync,SF是直接跟在APP计算后面的,显示的刷新也是跟在SF完成后面,60HZ的画面延时是遵循安卓的系统刷新机制的,APP的 UI 计算,SF的合成,屏幕的刷新,都跟在Vsync信号后面。由于120HZ的刷新的时候,时延的屏幕刷新是不稳定的,后续我们采用60HZ进行测试

目前评估在云机阶段存在了一段不小的时延变量,想到了一个方案。**我们的触控指令到达云机的时间我们是能确定的。然后画面编译之后得到画面每一个都有一个rtp的时间戳,如果能在端侧展示画面的的触控的时间和每个画面的rtp的时间戳,那么触控指令在云测经历时间我们就能得到了。我们就能确定云测触控到画面被监控到究竟经过了多长时间**

附录

120HZ-T1测试结果

image.png

60HZ-T1测试结果

image.png

![](p0-xtjj-private.juejin.cn/tos-cn-i-73…

RTP拓展尝试(已放弃)

使用上面代码之后,我们能得知每一帧画面对应的rtp时间戳,我们尝试在rtp的拓展中加入一个拓展头:

webrtc.googlesource.com/src/+/refs/…~~

这个拓展会携带1个时间戳,表明数据包从发送系统的出发时间(或尽可能接近该时间)。我们取一个rtp包的时间戳来作为分析

这个时间戳365d1c代表的意思是云测发送的时间戳,他的规则是

这里代表的是三个字节的十六进制表示。换算成具体的时间是:

二进制(0011 0110 0101 1101 0001 1100 => 秒数(0011 01 小数(100101110100011100) => 13.5909271240234375(秒)

但是这个时间被精简过了,不确定哪里出问题了,暂时忽略

屏幕扫描的证据

使用480fps的采样高速摄像机拍摄画面的时候,发现了120刷新率的屏幕画面的在相邻不到4ms间隔出现了画面刷新,这个经过调查,是因为屏幕刷新的 “扫描过程”。一帧画面不是突然出现,然后在刷新率间隔到达之后直接覆盖在上一个画面上的,屏幕不是按照这种理解刷新的。多数 LCD/LED 屏幕采用逐行扫描刷新(从顶部到底部依次更新像素)的。

这是四张相邻2.08ms的连续画面,虽然他们的画面信息不同,看起来好像是动态的画面变化,但他其实是一帧画面在中间刷新态的不同阶段,从顶部到底部刷新,第一张可以理解为画面刷新到1/4, 第二张画面刷新到1/2, 第三张画面3/4。 这里留意细节,可以看到王者荣耀的那个攻击范围是一个圆圈,但是在第二张的时候只有上面的部分被绘制,在这张图片的时候圆圈才被完整绘制,第四张图片攻击键才被触发。那这样我们可以理解这四张图片其实都是一张图片,在第四张图片的时候我们才完整看到这张图片。

ffplay 存在的问题

ffplay播放存在一个音画时钟同步的问题,默认以音频作为主时钟,我们需要添加一个参数 -sync video 来让他指定video为主时钟,避免我们在时延分析的时候受到音频的影响

WebRTC 专栏

WebRTC SDP 概念和解释 H264与VP8两种编码在WebRTC中时延对比 WebRTC系列 WebGL 绘制YUV 画面 # WebRTC-H265软编码WASM 编码入口文件解析

......