2.4 学生拉流与弱网体验优化

拉流的本质:订阅远端流并跟着网络调档

学生端的核心动作只有一个:订阅老师的流并播放。但学生端的环境千差万别——家里 WiFi、卧室 4G、学校机房、晚高峰挤宽带——拉流优化的本质,是让码率和分辨率跟着网络走,而不是选定一个档位扛到底。这一节把档位、降级顺序和指标定义讲清楚,最后附一个真实的调优故事。

顺带说一句心法:学生端的优化永远比老师端难,因为老师端的环境你能培训、能换设备,学生端的设备你摸不着、网络你换不了。所以学生端的一切设计原则都是:少假设、多兜底、把选择权留给用户。这一节后面所有决策都围绕这三句话展开。

码率分辨率档位表

我们最终给学生端设了六档,自动切换加手动锁定(学生可以强制选流畅省流量,也可以强制超清不将就):

档位 分辨率 视频码率 适合网络
流畅 320×240 约 200kbps 4G 弱信号
标清 640×360 约 500kbps 4G 满格
高清 960×540 约 800kbps 家用宽带
超清 1280×720 约 1200kbps 独立带宽
课件优先 1280×720 低帧率 约 300kbps 屏幕共享场景
纯音频 无视频 约 48kbps 极差网络兜底

表里的码率是经验值,实际按画面内容浮动:老师坐在镜头前讲课,背景基本不动,实际码率比峰值低不少;屏幕共享时文字边缘多,码率反而需要拉高一点才不糊。平台底层会做自适应,我们要做的是把档位边界定清楚,而不是替平台操心每一帧。

档位表里特意留出锁定能力,是被投诉教育出来的。有一批学生用流量上课,自动选了超清,一节课烧掉几个 G 的流量,家长心疼话费来质问。我们加了手动锁定:选了流畅就按流畅走,自动升档不会越过用户的选择,同时在角落提示当前处于省流量模式。说到底,技术上的最优解和用户心里的最优解经常不是同一个,把选择权交还给学生,这比任何算法都更少挨骂。

课件优先这一档是编程课特有的:屏幕共享画面里文字多、动态少,帧率砍到 10 帧学生也看不出区别,但码率省下来一大截,弱网体验直接拉高。这一档是我们观察学生实际观看时长分布之后加的,最开始只有前四档,弱网学生全部掉到纯音频,课上一个代码字也看不见,家长投诉说孩子听了半天天书。

弱网降级策略表:网络差时先降什么

降级顺序是这一节的灵魂。我们定下的原则是:降级必须逐级、可逆、先降视频后降音频:

网络状态 第一步 第二步 最后兜底
轻微波动 不降,观察回调 暂无 暂无
明显卡顿 关掉学生视频画面 帧率减半 降一档分辨率
严重卡顿 只保留老师视频 音频降码率 切纯音频
断线重连 停在最后一帧 提示重连中 音频优先恢复

两个关键点值得一说。一是降级要可逆:网络恢复之后自动升档,但不能抖动,我们加了 30 秒的冷却时间,否则网络一波动档位来回切,画面忽大忽小比卡顿更难受,学生还会误以为电脑坏了。二是兜底永远有:就算 4G 只剩一格,纯音频 48kbps 也要保证学生听见老师在讲什么。这两条原则是上线一个季度之后逐步沉淀下来的,早期版本只有自动降级没有回升,被家长吐槽过。

档位设计的三个误区

档位表定下来之前,我们改过好几版,走过三个典型误区,值得提前避开。

第一个误区是盲目追高清。最初我把默认档位设成超清,觉得画面清楚才是好课,结果晚高峰家庭宽带扛不住,学生大面积卡顿投诉。教训是:默认档位要按最差常见网络而不是最好网络来定,愿意看高清的学生会自己调上去,被迫看卡顿的学生只会投诉。

第二个误区是档位之间跨度太大。从超清直接掉纯音频,中间没有过渡,感知上是断崖。加了课件优先和标清两档之后,降级路径变得平缓,学生对降级的容忍度明显提高——同样是降级,从超清掉到标清几乎没人抱怨,从超清掉到纯音频就有一堆问题。

第三个误区是只降不升。早期版本降下去就固定住了,网络恢复了也不升档,学生整节课都在看模糊画面。加了回升逻辑和冷却时间之后,弱网时段的观看体验才真正闭环。监控数据里有个有意思的现象:加回升之后,纯音频观看时长反而下降了,因为学生知道画面迟早会回来,就不急着切走。

音视频优先级:保音频优先

为什么所有降级路径都把音频放在最后?因为直播课堂的信息密度主要在声音。学生看不见代码可以课后看录播,听不见老师讲话这节课就算废了。我们的实现里,音频流独立于视频流做质量保障,视频卡住的时候音频仍然在推进,学生哪怕对着一张静止画面也能跟上思路。

这个原则还有一个副产品:家长接送孩子在路上,只用音频也能听课,等于多出一种使用场景。后台数据显示我们平台纯音频播放时长占总时长的百分之四,这个数字一开始让我惊讶,后来想想很合理——通勤场景本来就是学习的高峰之一。

卡顿率指标定义表

优化弱网之前先定义清楚指标,不然改了也不知道改没改好:

指标 定义 我们的目标
卡顿率 卡顿时长占总观看时长的比例 低于 5%
首帧时间 进房到看到画面的间隔 低于 2 秒
丢包率 传输过程中丢失的包占比 低于 3%
拉流延迟 老师说话到学生听见的间隔 低于 1 秒

这四个指标我们接进云监控做了每周报表,具体怎么配告警在第五篇讲。有了指标之后,优化才有方向:首帧时间高就预热连接,卡顿率高就调降级阈值,延迟大就查 CDN 链路。拍脑袋调参数的时代过去了。云监控的通用用法见云监控文档。

指标还有个用法容易被忽略:按课程维度横向对比。我们每周拉一次各门课的卡顿率排行,排在最后的两门课一定有特殊原因——要么是老师设备老旧,要么是学生群体网络环境整体偏差。有一次发现某门课卡顿率奇高,查下来是上课时间卡在晚高峰,机构把那门课顺延半小时之后,卡顿率直接回到平均水平。数据的价值不仅在定位技术问题,还在验证运营决策,这算是个意外收获。

一个真实的调优故事:家里 4G 也能听课

上线第二个月,收到一个学生家长反馈:家里宽带坏了,孩子用手机 4G 热点上课,前几分钟一卡一卡,后半节课突然顺畅了。我们很感兴趣,因为后半节课变顺不是网络变好了,而是我们的降级起作用了——先从高清降到标清,再在连续卡顿后切到课件优先档。家长不知道这些,只觉得又能上课了。

但这个故事有个不完美的开头:前几分钟的卡顿,是因为首帧时间太长。当时的实现是进房之后才开始建连接,从 4G 拿到第一帧画面要 4 秒多,学生盯着加载圈,老师已经开始讲第一个知识点,这段内容课后只能靠录播补。我们的优化是预连接——学生点进课程页就开始建立传输通道,真正进房时连接已经热好了,首帧时间压到 1.5 秒以内。另外一个改动是把 4G 场景的初始档位默认设为标清而不是高清,先稳再升,比从高清往下掉体验好得多,学生的感知是全程流畅而不是先卡后顺。这之后我们没有再收到 4G 听课的投诉。移动端站点部署相关的经验,可以参考轻量建站实战。

这次调优之后我们把弱网场景做成了上线前的固定验收项:准备一台开了限速的手机热点(限制到几百 kbps),用学生账号完整听完一节课,全程记录卡顿次数和降级轨迹。限速听歌谁都会,但只有完整听完一节课,才知道降级路径在真实课堂节奏下走得对不对——光是启动时看一眼首帧时间,是验不出后半段问题的。

拉流渲染的标准步骤

学生端拉流表面上是播放一个视频,拆开来看分四步:订阅远端流、拿到流对象、挂到页面播放容器、随档位变化调整渲染参数。最容易出问题的是第三步:容器必须先于流准备好,否则流到了没有去处,会被静默丢弃,表现为进房成功却死活不出画面。我们的固定写法是页面先渲染好容器占位,进房事件来了再订阅,订阅回调里把流挂进去,这套顺序跑了两学期没再出过黑屏。

档位切换时渲染参数会跟着变(清晰度变化时画面会有一瞬间的重载),这部分交给 SDK 内部处理,我们要做的是别在切换瞬间做任何销毁容器的操作——曾有人在切换时顺手清容器,结果画面再也回不来。管住手,比什么都强。

卡顿现场排查表

学生端还有些与网络无关的常见故障,一并列在这里。客服同学反馈的高频问题里,有一大半能对照着这张表直接给答案,不用每次都找技术介入:

故障现象 先看哪个指标 一句话处置
有声音没画面 拉流码率 码率为零即降级兜底,等恢复或手动调高
画面有人声对不上 卡顿率与延迟 缓存堆积,刷新页面重进
一直转圈不进房 首帧时间 换网络或重新登录换签名
声音忽大忽小 采集电平 关掉浏览器自动增益选项
画面定住不动 推流端帧率 推流端问题,提示老师检查设备
整体卡顿但指标正常 本地 CPU 占用 关掉多余标签页,降一档省性能

最后一行的处理最能体现客服和技术的配合:客服看到画面定格,先去看推流监控数据,确认是老师端问题之后再联系,而不是让学生反复刷新页面——学生刷新一百次也没用,因为问题根本不在他那里。

延伸阅读