自适应码率算法(ABR)的云游戏专项演进:当缓冲池变成敌人 Adaptive Bitrate in Cloud Gaming
直播 ABR 想的是「别卡住」,云游戏 ABR 想的是「别迟到」——一字之差,整条控制回路得重写。
为什么 ABR 是云游戏体验的「最后一道闸门」
先说个真事。前几年我帮一个团队调串流,测试同学跑过来说:「20 兆档位画面挺好啊,就是暗的地方像糊了一层油。」我第一反应是编码器量化参数给高了,闷头改了两天,啥也没改出来。后来把整段会话的码率曲线拉出来一看,病根根本不在编码器——那哥们儿在家用 WiFi,他媳妇一开视频会议,带宽掉到 12 兆,我们的自适应逻辑磨蹭了整整四秒才开始降码率,降完又立刻反弹,来回横跳。玩家看到的不是「糊」,是「一会儿清楚一会儿糊」。
说句实话,这比一直糊难受十倍。人眼对画质的时间稳定性极其敏感,一个稳定偏软的画面,比一个忽清忽糊的画面让人舒服得多。这个坑我踩过,后面还会细说。
那次之后我记住一件事:云游戏的自适应码率,跟你刷视频、看直播的自适应码率,名字一样,骨子里是两套东西。视频网站的 ABR 是个「播放平滑器」,它的最高使命是别卡住,宁可用 480p 也要让你看完。云游戏的 ABR 是个「帧调度器」,它的最高使命是让每一帧在玩家察觉到之前抵达屏幕——至于这一帧是清晰还是糊,那是第二位的。
这篇文章我想把这件事拆开讲:为什么直播那套 ABR 搬不过来、控制回路的时标该怎么选、控制信号从哪来、目标函数怎么定。中间会拿 Stadia、GeForce NOW、Parsec、Xbox Cloud Gaming 这几个真实平台开刀,讲讲它们各自的选择和代价。看完你至少能干一件事:调自家串流的时候,知道该往哪儿看,而不是像我当年一样瞎改编码参数。
第 1 层:先认清三笔账,直播的 ABR 搬不过来
很多人做云游戏 ABR 的第一反应是「找个成熟的开源方案改改」。能改,但改之前你得先接受三个断层,这三个断层决定了你不可能照抄。
第一个断层:缓冲区没了。直播和点播的 ABR 之所以敢做,是因为客户端有个缓冲池——通常是 5 到 30 秒。码率估高了、链路抖了,只要缓冲不空,播放就不停,你还有十几秒去纠正错误。这个缓冲池是 ABR 的安全垫。云游戏呢?缓冲直接等于延迟。你在客户端攒两秒缓冲,玩家的摇杆输入就要晚两秒生效,格斗游戏直接判死刑,射击游戏也活不过一局。所以云游戏客户端可用的缓冲通常只有几十毫秒,一两帧的量级。安全垫被抽走之后,ABR 的每一次误判都是当场兑现的,没有「待会儿再补」这回事。
第二个断层:编码器的账完全不一样。传统流媒体是一次编码、切成多档、存起来、CDN 分发。多一个档位,边际成本几乎为零,所以大家敢堆六档八档,ABR 想切哪档切哪档。云游戏不是这个结构。每个玩家一条独立的编码会话,跑在他独占的那块 GPU 切片上。你要给他备三个档位,就得跑三路编码——三份算力、三份显存带宽、三份会话配额。消费级显卡的硬件编码器会话数还有硬限制,数据中心卡虽然放开了,但成本照样实打实。所以云游戏的 ABR 基本不能「切档」,只能「调参」:改目标码率、改量化步长、改输出分辨率、改帧率,全都在同一路编码器里完成。这个约束是一切的起点,我见过太多方案在设计阶段就把这件事忘了,后面推倒重来。
第三个断层:内容是玩家自己「演」的。直播画面变化由主播决定,你至少能做统计预测:「这类场景平均需要多少码率」。云游戏画面变化由玩家的手决定。他一甩鼠标转身,画面里八成像素全变了,这一瞬间恰好是带宽需求最高的时候,也恰好是他最不能容忍糊的时候——他正盯着那个方向找敌人呢。ABR 要么提前预判,要么就只能事后补救。
不过这里有个反直觉的好消息:云游戏的服务端其实握着「提前量」。渲染完到编码完到发出去,中间有几十毫秒,而且服务端天然知道这一帧有多复杂——渲染统计、运动幅度、是不是场景切换,这些信息直播 ABR 根本拿不到。会不会用这个作弊器,就是分水岭。
拿 Stadia 当反面教材看最清楚。它的公开带宽建议是 720p 约 10 兆、1080p 约 20 兆、4K 约 35 兆,走 VP9。这套阶梯本身没问题,问题出在它的策略极度保守:宁可画质平庸,绝不让你卡。于是《赛博朋克 2077》夜之城这种大面积暗场加高频霓虹的场景,20 兆根本喂不饱,暗部出现明显色带,远景糊成一团。当时社区里最狠的一句吐槽是「Stadia 的 4K 看起来像 1080p 拉上来的」。这话不冤,但也不全怪 VP9,是码率预算和保守爬升策略一起造成的。更关键的是,Stadia 没给高带宽用户留一个「我就是要画质」的出口——码率上限锁死,玩家想自己加钱都不行。这就把一个技术取舍变成了产品缺陷。
第 2 层:控制回路的时标——GOP 级还是帧级,答案比你想的复杂
确定了「只能调参不能切档」,下一个问题就是多久调一次。行业里大致三档:
| 决策粒度 | 典型周期 | 能做什么 | 代价 |
|---|---|---|---|
| GOP 级 | 1 到 2 秒 | 改目标码率、强制插入关键帧重同步 | 反应慢;插关键帧等于往最脆弱的链路上砸一记重拳 |
| 帧级 | 16 到 33 毫秒 | 逐帧调整量化步长和目标码率 | 量化步长抖动导致画质「呼吸」;和质量控制回路打架 |
| 帧内切片级 | 帧内分块 | 不同区域分配不同质量 | 解决不了链路突然变窄的问题,属于另一条战线 |
GOP 级的好处是稳。一个两秒的图组里,决策次数少,不会来回横跳。坏处是慢——链路在第 0.3 秒崩了,你要么等到第 2 秒,要么插一个关键帧。关键帧的数据量通常是普通帧的三到十倍,插进去的那一瞬间等于给已经拥堵的链路再来一脚,很容易把「有点卡」变成「彻底卡死」。这是很多团队的血泪教训。
帧级的诱惑在于反应快。听起来很美,做起来有三个陷阱,我逐个说。
陷阱一是码率控制模型失配。硬件编码器内部有个虚拟缓冲的概念,它靠这个缓冲来保证输出码率不会突刺。你每帧都改目标值,这个虚拟缓冲的水位就会剧烈起伏,实际吐出来的码率跟你要的对不上。你以为你在精确控制,其实你在给编码器添乱。
陷阱二是画质「呼吸」。这是我最想强调的一条。人眼对画质的时间变化极其敏感。我们做过一次内部实验:把决策周期从 1 秒压到 100 毫秒,客观指标涨了 1.5 分左右,结果主观评分反而掉了。为什么?因为玩家看到了「闪烁」——这一帧清晰,下一帧糊,再下一帧又清晰。它像有人在你的屏幕上不停拧焦距旋钮,比一直糊难受得多。后来我们加了强制平滑,画质变化率做了限幅,主观分才回来。
陷阱三是和底层拥塞控制打架。ABR 是一条控制回路,底层的拥塞控制是另一条,两条回路的时标一旦接近,就会互相激励,产生振荡。你降码率,链路空了,拥塞控制以为带宽变好了,猛发一波,链路又堵了,你的 ABR 再降……这个死循环在真实网络里非常常见。破解办法只有一个:让两条回路的时标拉开,至少差一个数量级。
所以真正的结论不是「帧级比 GOP 级好」,而是:决策周期必须和你能拿到的有效信号匹配。你的带宽估计 200 毫秒才更新一次,你却每 16 毫秒做一次决策,那不叫灵敏,那叫瞎猜。我见过太多团队在这上面自嗨。
Parsec 在这条路上走得最远,也最能说明取舍。它客户端每收到一帧就回一个确认,服务端根据确认的到达节奏和排队情况来调码率——信号是真的,不是估算的。这让它在局域网和同城场景里口碑极好,延迟低到让人误以为是本地。代价也很明显:收敛依赖往返时延,跨洲链路上一来一回三百毫秒,它的调整就会明显滞后;丢包突发的时候又容易过度保守,画质掉下去爬不回来。你在论坛上能看到两种截然相反的评价,说它「神了」的和说它「糊成狗」的,八成是网络位置不同。这不是玄学,是设计决定的。
第 3 层:控制信号——从「带宽有多少」到「这一帧来不来得及」
直播 ABR 的经典三件套是:吞吐量估计、缓冲水位、丢包重传。云游戏这套不够用,因为它的核心问题变了——不是「管道有多粗」,而是「这一帧还剩多少时间」。
我给你列一下真正该采的信号:
| 信号 | 采集位置 | 噪声水平 | 拿来干什么 |
|---|---|---|---|
| 发送队列长度 | 服务端 | 低 | 判断是不是自己把链路撑爆了 |
| 帧截止时间余量 | 服务端 | 低 | 紧急程度的直接度量 |
| 帧确认到达时间 | 客户端回执 | 中 | 真实端到端表现,比任何估算都准 |
| 渲染帧耗时 | 服务端 | 低 | 预示下一帧复杂度,是唯一的「前瞻」信号 |
| 编码耗时 | 服务端 | 低 | 判断瓶颈在编码还是在网络 |
| 往返时延与抖动 | 两端 | 高 | 决定你能多激进 |
| 客户端解码与呈现延迟 | 客户端 | 中 | 端到端预算里最容易被漏掉的一段 |
把这些信号拼起来,你会得到一个和传统 ABR 完全不同的核心指标:按时到达率。定义很朴素——在一个统计窗口里,在截止时间之前到达并被解码呈现的帧数,除以总帧数。这个数字比「平均码率」有用一百倍,因为它直接对应玩家的感受。平均码率 18 兆、按时到达率 92%,和平均码率 12 兆、按时到达率 99%,后者体验好得多,但你看报表的时候很可能被前者骗了。
围绕这个指标,有个必须做的决策:一帧超时了,等还是丢?
这事儿不能一刀切,得按品类配。格斗、音游、竞技射击这类,宁可丢帧也不能延迟呈现——因为一帧迟到的画面是「错误的过去」,玩家看到的是敌人 80 毫秒前的位置,他会以为自己的枪打偏了。开放世界、回合制、剧情向反过来,多等 30 毫秒保住这一帧的完整性更划算,画面撕裂或者缺一块反而更出戏。这条策略在配置里通常叫「超时帧处理」,默认值给「丢」,然后按品类覆盖。
Xbox Cloud Gaming 是个很有意思的样本。它长期停留在 1080p,码率上限保守,画面偏软是被普遍吐槽的点。但你要说移动网络下的「可玩性」,它的口碑反而不差。原因就在这儿:它把按时到达率做得比较扎实,宁可画面软一点也不让你操作打滑。这说明一件事——ABR 的目标函数要是选错了,参数调得再精细都是白搭。画质是可以商量的,手感不能。
还有一个容易被忽视的细节:服务端的渲染帧耗时是唯一的「前瞻」信号。这一帧渲染花了 8 毫秒,下一帧大概率也复杂,你可以提前把码率预算提上去,等链路真的堵了再降就晚了。这是云游戏独有的作弊器——直播 ABR 永远只能看后视镜,你能看前挡风玻璃。用不用,是水平问题。
第 4 层:目标函数——纯带宽驱动和感知驱动的分野
纯带宽驱动的逻辑特别直白:链路能跑多少,我就填多少,填满为止。这套思路在视频网站上行得通,因为视频内容的码率需求相对平滑。放到游戏上就会出问题,因为游戏画面的「质量价值分布」极其不均匀。
具体不均匀在哪?
暗部。恐怖游戏、太空题材,城市夜景都是重灾区——大面积低亮度渐变,压缩稍微一狠就出色带,像糊了一层油。而且暗部恰恰是玩家最专注的地方,因为你在大面积黑里找东西。
快速平移。玩家一转身,整屏运动,运动补偿基本失效,码率需求瞬间翻倍。而这个瞬间恰好是他最需要看清的时候。
界面文字。细笔画字体,糊一点就不可读。界面又是玩家高频注视的区域,一块看不清的任务提示能把人逼疯。
粒子与烟雾。高频细节,编码器最恨这类内容,同样码率下画质掉得最快。
所以同样 20 兆,给《文明》绰绰有余,给《赛博朋克》就是不够。如果 ABR 能识别内容类型,就能做预算再分配——把不重要的帧省下来的比特,挪给重要的帧。
落地手段大概三类。第一类是场景切换检测:检测到切换,主动小幅降分辨率保帧率,切完再爬回来,玩家基本感知不到。第二类是内容分类:按运动量、暗部占比、界面占比给每帧打标签,动态调整量化偏移,暗部帧给更低的量化步长,运动帧适当放宽。第三类是感知质量驱动:用轻量模型估计当前帧的感知得分,把它当目标函数。这条路最诱人,也最容易翻车——画质评估模型在游戏内容上系统性偏高(这个话题在[10] 那篇会展开),而且算力开销不小,实际只能在服务端抽样跑,不能每帧跑。
真正要做降档的时候,先动谁?这张表我建议你贴在显示器边上:
| 降档手段 | 带宽节省 | 感知伤害 | 恢复速度 | 我的建议 |
|---|---|---|---|---|
| 小幅降分辨率(1080p 到 900p) | 中 | 低,多数玩家看不出 | 快 | 首选,但注意界面会糊 |
| 提高量化步长(降码率) | 高 | 中,暗部先崩 | 快 | 次选,配合暗部保护 |
| 降帧率(60 到 30) | 高 | 极高,手感直接变质 | 中 | 尽量别动,动了就要背锅 |
| 丢帧 | 极高 | 看品类,竞技类反而可接受 | 快 | 保底手段,按品类开 |
GeForce NOW 这几年的演进特别值得琢磨。它早期是让用户手动选码率档位,后来加了自适应,2023 年推出旗舰档位,把码率上限推得很高,同时支持高刷新率,还上了新一代编码格式。这套动作背后的逻辑是:ABR 不只是技术问题,还是产品分层问题。同一个算法,在 20 兆和 75 兆下的行为完全不同,与其绞尽脑汁在低带宽里榨质量,不如把「画质预算」直接做成商品,让愿意付钱的用户跳出这场博弈。这招很聪明,因为它承认了一个事实——ABR 永远是在确定性和质量之间妥协,而总有一部分用户愿意用钱买确定性。
说到这儿我想插一句个人判断:很多人把 ABR 当成一个「算法问题」,我觉得它更像个「产品问题」。你得先想清楚,你的用户在什么网络下玩、玩什么品类、愿意为画质付多少钱,然后才轮到调参数。顺序反了,做出来的东西指标很漂亮,玩家还是不买账。
可落地的参数配置表与建议区间
下面这张表是我这些年攒下来的经验值,不是标准答案,但能让你少走弯路。拿去做起点,然后按你的品类和用户网络分布去调。
| 参数 | 含义 | 建议区间 | 说明 |
|---|---|---|---|
| 决策周期 | 两次码率决策的最小间隔 | 100 到 500 毫秒 | 必须显著长于带宽估计的更新周期 |
| 爬升速率 | 每个周期允许上调的幅度 | 5% 到 10% | 慢升,避免把链路顶爆 |
| 下降速率 | 每个周期允许下调的幅度 | 20% 到 30% | 快降,一般是爬升速度的三倍 |
| 变化率限幅 | 相邻帧量化步长的最大差值 | 2 到 4 | 防画质「呼吸」,主观分救星 |
| 编码缓冲深度 | 编码器内部虚拟缓冲对应时长 | 200 到 500 毫秒 | 太浅会突刺,太深反应迟钝 |
| 降分辨率触发 | 连续几个周期带宽不足目标的百分比 | 连续 3 个周期低于 85% | 加连续条件是为了防误判 |
| 回升高分辨率 | 带宽持续富余多久才爬回去 | 2 秒以上高于 120% | 滞回区间,防来回横跳 |
| 超时帧阈值 | 帧龄超过多少判定作废 | 1.5 倍帧间隔 | 按品类覆盖,竞技类可收紧 |
| 暗部质量偏移 | 低亮度帧相对平均的量化偏移 | -2 到 -4 | 数值越负质量越高,按内容调 |
| 界面保护偏移 | 界面区域量化偏移 | -2 到 -6 | 如果做区域编码才有意义 |
补两条不成文的规矩。一是所有参数都要能按品类覆盖。格斗游戏和开放世界的配置不可能是同一套,别偷懒做全局配置。二是每个决策都要留日志。我见过太多团队,ABR 出了问题只能靠猜,因为没有记录每一帧的决策依据。这活儿不难,但上线前没人愿意做,出事的时候就知道疼了。
初级速览与进阶深入
初级用户,你只需要记住三条:
第一条,快降慢升。往下降的速度至少是往上升的三倍。链路的坏消息来得快,好消息不一定可信。
第二条,决策别比信号快。你的带宽估计多久更新一次,决策周期就不该短于它。做不到就老老实实做慢回路。
第三条,看板上别放平均码率,放按时到达率。这个指标一换,你团队关注的焦点会立刻变化——从「我们给了多少比特」变成「玩家按时看到了多少帧」。
进阶用户,往下挖三层:
第一层是分层控制。外层慢回路一到两秒定「预算」,内层快回路一百毫秒做「微调」。这样两条回路的时标天然拉开,不会打架,也不会因为抖动而误判。
第二层是可观测性。每一帧记三样东西:决策依据(当时的带宽估计、队列长度、渲染耗时)、决策结果(目标码率、实际输出码率)、玩家侧表现(到达时间、有没有作废)。出问题的时候,这三样能让你十分钟定位,而不是两天。
第三层是测试方法。别拿平均值做 A/B 测试的判据,要看分布——尤其是网络条件差的那 10% 用户的分布。云游戏的口碑往往是被这小部分人决定的,因为他们在论坛上发帖最勤快。
几个我见过太多人踩的坑
坑一:把「码率越高画质越好」当成公理。码率是手段不是目标。同样 20 兆,新一代编码格式可能直接把老格式按在地上摩擦。你死磕码率数字,不如先看看编码器换没换。
坑二:拿平均码率当考核指标。玩家感受到的不是平均值,是方差。一条 20 兆但每三秒抖一下的链路,体验远不如一条稳定的 14 兆。
坑三:把 ABR 全放客户端。客户端只知道「我收到了什么」,不知道「服务端本来有什么」。服务端的渲染复杂度、队列状态、编码耗时,这些信息不对称不解决,算法再聪明也是瞎子摸象。
坑四:忽视暗部。暗部是最容易暴露压缩问题的地方,也是最容易被工程师忽略的地方——因为测试用的截图往往是明亮的游戏开场。
坑五:忘记 ABR 和拥塞控制在打架。这是最隐蔽的一个。两条回路的时标接近就会振荡,而且振荡起来看起来像「网络问题」,你会花很长时间查错方向。这个坑的详细版本在[11] 那篇讲传输协议改造的时候会展开。
FAQ
问:为什么云游戏不能像视频网站那样准备多档,让客户端随便切?
因为成本结构不一样。视频网站一次编码、多档存储、CDN 分发,多一档几乎不花钱。云游戏每个玩家独占一路编码,多一档就多跑一路编码器,是实打实的算力和显存带宽开销。所以云游戏只能在同一路编码器里调参数,不能切档。
问:帧级自适应是不是一定比 GOP 级好?
不是。帧级反应快,但会带来三个问题:编码器码率控制模型失配、画质「呼吸」、和拥塞控制回路打架。正确的做法是让决策周期匹配你的信号更新频率,再加上变化率限幅。我自己的经验是,大部分场景下「慢回路定预算、快回路做微调」比纯帧级更稳。
问:能不能直接把画质评估模型当目标函数?
方向对,但不能直接用。画质评估模型在游戏内容上有系统性偏差,尤其是快速运动和粒子特效场景,而且算力开销不允许每帧跑。现实做法是服务端抽样跑,用它的结果去校准一套轻量的启发式规则,而不是把它当成实时目标。
问:小团队做云游戏,ABR 最少要做哪几件事?
三件。第一,把按时到达率做出来,这是你的眼睛。第二,快降慢升加变化率限幅,这是你的手。第三,每帧决策留日志,这是你的记忆。这三件事做完,剩下的都是锦上添花。
问:ABR 真的需要 AI 吗?
现阶段我觉得不必要。规则引擎在大部分场景下够用,而且可解释、好调试。AI 的价值在于处理规则覆盖不到的长尾场景,但前提是你有足够的数据和可观测性——这两样东西没有,上什么模型都是玄学。先把基本功做扎实,比追热点重要得多。