master
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
48dc8fd65b |
fix(preview): 中转模式在有 RTT 的链路上频繁缓冲——旁路挪到解析点、掉队原地续播、缓冲分低延迟 2 s / 流畅 5 s 两档 (#1716)
* fix(preview): stream-gears FLV 在解析点旁路,不等写盘循环整 GOP 落盘 httpflv 的写盘循环把 tag 攒在 flv_tags_cache 里,等下一个关键帧到来才把整个 GOP 一起写出——预览的旁路点原来就挂在这个写出循环里,于是预览端每个 GOP 间隔(B 站 常见 2–4 s)收到一次整 GOP 的突发、其余时间一个字节都不来,且整体落后一个 GOP。 播放器拿到的缓冲恰好在下一个突发到达时耗尽,零余量:同机 0 ms 下勉强连上,链路 上稍有 RTT / 抖动就 waiting;GOP 长于 mpegts.js 的追帧阈值时更是每个 GOP 都跳一次 再停一次(实测 GOP 4.2 s 的房间同机也有 85% 时间处于 waiting)。 把旁路挪到 tag 解析完、进缓存之前:预览按 tag 实时收到,与 mesio 路径(在条目进 管线前 tee)一致。写盘循环一个字节不变;旁路仍只是 push,不 await、不返回错误。 实测(arrival-probe,40 s):同一斗鱼房间直连 CDN 到达连续(最大静默 0.24 s), 中转修前每 2.0 s 一个 ~940 KB 突发、20 个静默间隙全部 > 1 s。 Refs #1712 #1713 * feat(preview): 快照保留最近 6 s 的 GOP,新订阅者起播就有几秒缓冲 原来快照只含「当前 GOP」:新订阅者从零到一个 GOP 之间的缓冲起步,之后到达 = 消耗, 缓冲永远这么薄,链路上几十到几百毫秒的抖动就 waiting。浏览器直连 CDN 时 CDN 会先 给几秒的 GOP 缓存,中转得给同样的深度。 PreviewSink 里多一个已完成 GOP 的队列(按关键帧到达时刻计,保留 SNAPSHOT_WINDOW = 6 s 以内的),快照 = 文件头 + 序列头 + 队列里的 GOP + 当前 GOP,实际深度在 6 s 到 6 s + 一个 GOP 之间。字节上限 MAX_SNAPSHOT_BYTES = 16 MB(6 s × 20 Mbps),超过先丢最旧的 GOP;单个 GOP 超过 MAX_GOP_BYTES 作废时历史一并放掉(不再连续);序列头内容变了或 fMP4 换了 init 时历史也放掉(旧 GOP 与新参数不配),内容相同的重发(stream-gears 分段时重放 onMetaData / 序列头)不影响。热路径只多几次 usize 比较与一次 VecDeque push,仍不 await、不返回错误;无订阅者时同样保留(第一个订阅者也要立刻有缓冲), 每路多占约 6 s 的码率(1080p 2–5 MB)。 `with_snapshot_window(Duration::ZERO)` 给只关心分块判定的测试用。 Refs #1712 #1713 * fix(preview): 客户端掉队时从最近关键帧原地续播,不再断开重连 广播缓冲被覆盖(Lagged)时原来直接结束响应,浏览器看到「连接已断开,2 秒后重连」 的加载态,再花一次 HTTP 往返 + 等下一个关键帧才恢复;带宽长期偏紧的链路上就是 反复「触发加载」。 现在 hub 多一个 `resubscribe(previous, timeout)`:复用原订阅的连接许可(不占第二 个名额),向写入端再要一份从最近关键帧起的快照与新接收端;响应在同一条连接里续 发这份快照——FLV 去掉文件头(`Subscription::snapshot_after_header`,解码器不能中途 再收到文件头;序列头照给,掉队期间可能换过),fMP4 的 init segment 照发(MSE 接受 中途再来一份)。mpegts.js 的 remuxer 会把批次之间的时间戳跳变收平,画面表现为跳 过一段而不是转圈;一次响应最多 MAX_RESYNCS_PER_RESPONSE = 20 次,再多说明客户端 长期跟不上,结束响应交给播放器。每次重对齐 warn 日志带 skipped 与累计次数。 Refs #1712 #1713 * perf(preview): 客户端追赶积压时把广播缓冲里攒着的分块合并写出 实时转发每收到一个分块就写一个 chunked 帧(FLV 一个 tag 几百字节到几十 KB)。 客户端跟得上时缓冲里本来就只有一个,无从合并;追赶积压(起播快照刚发完、链路 抖动之后)时缓冲里攒着几十上百个,逐个写出是几十上百次 socket 写与 chunked 帧。 `coalesce` 在 `recv()` 到手后用 `try_recv` 把已经在缓冲里的后续分块合并成一个 响应块(上限 COALESCE_BYTES = 64 KB),缓冲空着就原样透传不复制,不引入任何等待; 合并途中撞上 Lagged 时把跳过数交回主循环按掉队处理,不吞掉。 Refs #1712 #1713 * fix(ui): 中转模式的播放器维持约 5 s 缓冲,用倍速而不是跳变追帧 mpegts.js 原来对中转、直连一律 liveBufferLatencyMaxLatency 3 s / MinRemain 0.5 s: 缓冲一超过 3 s 就跳到末尾前 0.5 s,等于把服务端刚给的几秒快照直接扔掉,之后只剩 0.5 s 余量,链路一抖就 waiting;GOP 长于 3 s 的流更是每个 GOP 跳一次再停一次。 按取流方式分两套参数(`Players` 新 prop `transport`,LivePreviewPlayer 按实际 source 传): - relay:liveSync 倍速追帧——落后 > 9 s 用 1.1× 追到 5 s 附近,≤ 5 s 恢复 1×; 只在落后 > 20 s(长时间暂停后)才直接跳到末尾前 5 s。与服务端 6 s 的快照窗口配合, 播放器起播就有约 5 s 缓冲并一直保持。 - direct:沿用原参数(CDN 自带 GOP 缓存,ForgQi 实测异地直连不卡,不动)。 fMP4 的 MediaSource 播放器(只在中转路径上)同样改成:起播对齐到末尾前 5 s、 落后 > 9 s 用 1.1× 追、> 20 s 才跳;另加空洞跳过——服务端掉队重对齐后分片时间戳 前跳,缓冲区出现空洞,停在空洞前沿时跳到下一段起点,不再原地转圈。 Refs #1712 #1713 * style(preview): cargo fmt 与 clippy(len_zero) * feat(preview): /live 支持 snapshot_ms,起播快照按播放器要的缓冲深度回溯 新订阅者拿到多深的快照,播放器就有多深的起播缓冲,也就是多长的中转延迟。之前一律给整个 6 s 保留窗口,只适合「流畅」档;低延迟档要 2 s 就只该拿 2 s,而不是拿 6–10 s 再用 1.1 倍速追 40 s。 - PreviewHub::subscribe_with_depth(depth):从当前 GOP 往前取,取到第一个关键帧到达时刻早于 「现在 − depth」的 GOP 为止(含),起播缓冲落在 depth 到 depth + 一个 GOP 之间;Some(ZERO) 只给当前 GOP,None 给整个窗口。Subscription 记住 depth,Lagged 重新对齐沿用。 - GET /v1/streamers/{id}/live?snapshot_ms=2000;不带参数行为不变。「开始直播预览」日志多一个 snapshot_ms 字段。 - 保留窗口 SNAPSHOT_WINDOW 仍是 6 s,服务端内存不变;热路径只在有订阅请求时多一次 VecDeque::make_contiguous(历史只有几个 GOP)。 - 单测 +2:深度切分的边界(零 / 跨线 / 大于全部历史 / 空历史)、带深度订阅与重对齐沿用深度。 Refs #1713 #1716 * feat(ui): 中转预览分「低延迟 / 流畅」两档,默认低延迟 2 s,卡了自动升档 ForgQi 在 #1713 下问「中转有办法不延迟 5 s 也不卡顿吗」。主因(整 GOP 成坨下发)修掉后 5 s 是 过度配置:同一条模拟链路(60 ms RTT ± 16 ms、丢包重传停顿、20 Mbps)上四路真实房间扫 1 / 1.5 / 2 / 3 / 5 s 目标缓冲各播 180 s,三路 FLV 从 1 s 起就 0 次 waiting;只有 HLS 来的 fMP4 要盖住一个分片(B 站 1–3 s),3 s 起 0 次、4 s 余量最低约 1–2 s。 - app/lib/live-buffer.ts:缓冲控制统一到一处(原来 mpegts.js 的 liveSync 管 FLV、fMP4 播放器 自己一套 chase):起播跳到「末尾 − target」,快照落地那几秒里超了再对齐(慢链路上几 MB 要 几秒才到齐,每对齐一次多看 2 s、最多 15 s),稳态超 max 倍速 / 超 hard 跳 / 跳过重对齐留下的 空洞,稳态里缓冲耗尽一次报 onStall。档位:low = FLV 2 s / fMP4·TS 4 s,smooth = 5 s (目标与原 0d41140 相同,倍速阈值 9 → 7)。mpegts.js 在中转模式下关掉自己的追帧,直连一行未动。 - /live 请求带 snapshot_ms = 目标缓冲,起播就是目标深度,不再从 6–10 s 追回来。 - 低延迟档在稳态里卡一次 ≥ 1 s 或 60 s 内两次,本次播放升到流畅档重连(更深的快照起播), 角标「卡顿,已切到流畅档」;换房间或改偏好即复位。 - 弹层标题栏与监视器工具栏各一个「低延迟 / 流畅」开关,记 localStorage biliup.preview.latency, 默认低延迟;直连模式下不显示。 实测(同一模拟链路,180 s,hub 最新时间戳 → 已渲染):低延迟档三路 FLV 延迟中位 2.1–2.4 s、 fMP4 2.8–3.7 s,四路 0 次 waiting;流畅档 5.1 s。每 18 s 停 2.5 s 的链路上低延迟档卡两次后 自动升档,之后 130 s 0 次。 Refs #1713 #1716 |