你写了个脚本,用来在多个视频来源里挑"码率最高"的那条线路。逻辑看上去无懈可击:拿一个分片文件,量一下它的大小和时长,算出码率,挑最高的。但跑出来的结果很奇怪——脚本选中的"最好线路",实际播放时画质明显不如其他几条。
现象
- 脚本按"单分片估算码率"选了最高的一条;
- 这条线路实际观感却是最差的之一;
- 换几个不同的影片重复测试,结论依然不稳定,时好时坏。
根因
根因是单点采样不具备代表性。
视频的码率不是恒定的,它是可变码率(VBR):画面激烈、动作多、细节密集的片段,编码器会分配更多数据;画面平静(比如一段静止的对话镜头)的片段,占用的数据则少得多。
如果你恰好采到的是一段激烈片段,算出来的码率会显著偏高;采到一段平静片段,则会显著偏低。用一个点去代表整条线路的平均水平,误差可能大到把"中等线路"算成"最好线路"、"最好线路"算成"中等线路"——排序整个乱掉。
这也解释了为什么结论"时好时坏":每次采到哪种类型的片段是随机的,所以脚本的结论也跟着随机。这种不稳定,正是"采样不具代表性"的典型表现。
解决
把"单点采样"改成"多点采样取中位数":
import statistics
def estimate_bitrate(line, sample_count=3):
samples = []
segments = pick_segments(line, n=sample_count) # 尽量分散在不同位置
for seg in segments:
size_bytes = fetch_size(seg.url)
seconds = seg.duration
samples.append(size_bytes * 8 / seconds) # bps
return statistics.median(samples) # 取中位数,不取均值
三个要点:
1. 采 3 个点,位置分散。 不要都从开头采——片头往往是静止标题。尽量一头、中间、一尾各取一个,覆盖不同类型的片段。
2. 取中位数而不是平均值。 这是关键。中位数天然抵抗离群值:哪怕有一个点是极端激烈片段(码率异常高)或异常平静片段(异常低),中位数也不会被它拉偏。用平均值的话,一个离群点就足以改变排序结果。
3. 成本可以接受。 采 3 个点意味着 3 次网络请求,在"选源"这个环节完全值得。但要注意别采太多——每个点都要真实下载一小段数据,点多了会明显拖慢整个选源流程。3 到 5 个是常见区间。
延伸与预防
这条坑的通用教训是:用采样估计整体时,至少要防住"采样点不具代表性"。
采样估算是很常用的手法——网络测速、磁盘测速、接口耗时统计、抽样质检,本质上都是"用一部分代表全部"。它们有一个共同的失效模式:你以为自己采的是"典型样本",实际采到的是"特殊样本"。
几条通用的防身规则:
- 样本要分散。 别都从开头采、别都在同一时段采、别都在同一个位置采。如果数据本身有"位置/时间"上的模式(片头平静、收盘前波动大、早高峰慢),采样就要跨过这个模式去取点。
- 用抗离群的统计量。 中位数、截尾均值比算术平均更稳。同时报"样本的离散度"(比如最大最小值、四分位距),如果几个采样点之间差得离谱,那就是在提醒你"这个估计不可靠",该采更多点了。
- 采样点的粒度要和结论匹配。 你要的是"整条线路的平均码率",就不能用一个分片代表;你要的是"整部影片的表现",就不能只用一段代表。先想清楚结论对应的时间/空间范围,再决定采多少、采在哪。
- 结论不稳定,先怀疑采样。 如果你反复测同一个东西,结论却来回摆动,这通常不是"数据源不稳定",而是"你的采样方式放大了随机性"。把采样点固定下来、把统计量换成中位数,摆动往往会消失。