Salsify — 让编码器和传输层一起商量怎么发视频
待复核Salsify 是一个把视频编码器和网络传输协议紧密绑在一起的实时视频系统。日常类比:传统视频通话像一个流水线——厨师(编码器)做好菜放到传送带(传输层),传送带不管菜多大多重,照搬不误。如果传送带突然变慢,菜堆在中间卡住,用户看到的画面就卡了。
Salsify 的做法是让厨师和传送带实时商量:传送带说”我这一秒只能送 50KB”,厨师就立刻切出一盘刚好 50KB 的菜。传送带快了,厨师就做大份精致菜。两边每一帧都协商,而不是各干各的。
技术上,Salsify 每一帧都根据当前网络带宽估计,动态选择编码质量,使得每一帧刚好填满当前可用带宽,既不浪费也不超发。实现里把端到端延迟目标上界设在约 100ms,在波动链路上相对 Skype、Hangouts、FaceTime、WebRTC 显著更低。
打个更具体的比方:你在地铁上视频通话,信号忽强忽弱。传统方案像一辆只会匀速开的大卡车——信号弱了它还在狂塞数据,结果堵在隧道口。Salsify 像一辆灵活的自行车,随时变速,信号弱就少踩两脚,信号好了立刻加速。它追求的不是”平均最快”,而是”永远不堵”。
- 传统实时视频(WebRTC / Skype)编码器和传输层是”分层解耦”的,编码器不知道网络能送多少,传输层不知道视频能压多小。两层信息不对称导致排队→延迟→卡顿恶性循环
- Salsify 证明了打破分层可以把延迟降一个数量级(从 300-500ms 到 < 100ms),改变了学术界和工业界对”分层一定好”的默认假设
- 它是后来 Puffer(同一组作者的大规模视频实验平台)的核心思想来源,影响了 WebRTC 拥塞控制的后续改进方向
- 论文的实验方法也很有启发:用 mahimahi 回放 LTE/UMTS 等蜂窝 trace 做端到端对比,相对 Skype/FaceTime/Hangouts/WebRTC 平均约 4.6× 更低的 p95 延迟,SSIM 约高 2.1 dB
- 对系统设计的启示超越了视频领域:任何”生产者-消费者”模型里,如果消费者容量实时波动,让生产者感知消费者状态再决定产出量,都能大幅减少排队
Salsify 的关键设计可以拆成三块:
-
功能性编解码器(functional codec):编码器是纯函数——输入一帧图像 + 上一帧状态 + 目标字节数,输出刚好那么大的压缩帧。不同于 H.264/VP8 那种”编码器内部有复杂状态机”,Salsify 的编码器把状态完全暴露给外层,允许外层随时回滚、重试、选择不同质量。具体实现上,他们 fork 了 libvpx(VP8 编码库),把内部的参考帧缓冲区变成可序列化的对象,外层可以随时 clone 一份状态出来。
-
传输感知调度器(transport-aware scheduler):每一帧到来时,调度器查询当前网络容量(能发多少字节),然后告诉编码器”这一帧压到 X 字节”。如果编码器压出来超了,就降质量重编;如果还有余量,就提质量再编。一帧一决策,粒度极细。调度器的核心算法很简单:贪心地选择”不超过可用字节数的最高质量”。
-
自研传输协议:Salsify 没用 TCP 也没用标准 UDP+FEC,而是自己写了一个极简传输层。核心特征是:每个包独立解码(不依赖前面的包)、丢包不重传(过期帧没有价值)、带宽估计反馈环路延迟极低。协议跑在 UDP 上,每个数据包只包含一帧(或一帧的一部分),接收端收到就解码显示,不等缺失的包。
三块合在一起,视频编码和网络传输变成了一个紧耦合的反馈系统,每 16ms(一帧间隔)循环一次。
补充一个关键细节:传统系统里,编码器产出的帧大小不可控(你设了目标码率,但实际每帧大小波动很大)。Salsify 通过 functional codec 的”试编码”能力,保证产出的帧大小精确匹配网络容量——误差在一个 MTU(约 1500 字节)以内。这种精确匹配是低延迟的根本原因。
案例 1:对比 Skype 的排队地狱
Section titled “案例 1:对比 Skype 的排队地狱”假设网络带宽突然从 2Mbps 跌到 500Kbps:
Skype/Hangouts 的反应: 编码器还在按 2Mbps 产出数据 → 发送缓冲区堆积 → 排队延迟从 50ms 涨到 800ms → 用户感受到”冻屏”→ 等 2-3 秒后编码器才收到 RTCP 反馈降码率 → 这 2-3 秒的数据全是废物但已经排在缓冲区里了
Salsify 的反应: 下一帧(16ms 后)调度器就发现带宽跌了 → 告诉编码器”这帧只能 8KB”→ 编码器立刻产出 8KB 的低质量帧 → 用户看到画质下降但完全不卡 → 没有任何缓冲区堆积
案例 2:functional codec 的”时光倒流”
Section titled “案例 2:functional codec 的”时光倒流””传统编码器一旦编出一帧,内部参考帧状态就更新了,无法回退。Salsify 的 functional codec 把状态存为不可变快照:
state_0 → encode(frame_1, target=50KB) → state_1a (高质量)state_0 → encode(frame_1, target=20KB) → state_1b (低质量)调度器可以同时尝试两种质量,选一个最合适的发出去。另一个分支直接丢弃。这种”投机编码”在传统编码器里做不到。
案例 3:带宽估计的反馈环路
Section titled “案例 3:带宽估计的反馈环路”Salsify 不靠 包大小/RTT 估带宽,而是看接收端包到达间隔,再按目标延迟上界算下一帧能发多大。教学简化伪代码:
on_ack(ack): # 接收端回报的平均包到达间隔(越小 = 链路越快) tau = ack.mean_interarrival_time in_flight = last_sent_idx - last_acked_idx # d ≈ 100ms:希望端到端排队不超过这个上界 max_frame_size = MTU * (d / tau - in_flight) notify_scheduler(max(0, max_frame_size))调度器拿到 max_frame_size 后,立刻告诉编码器下一帧的目标大小。反馈从”发包→收 ACK→更新估计→通知编码器”通常一个 RTT(约 20-50ms),远快于传统 ABR 的”观察→平滑→决策”(常 1-2 秒)。
注意:估计偏保守——宁可少估也不多估。多估 = 帧太大 = 排队涨延迟;少估最多画质差一帧。这种”延迟优先于画质”贯穿整个设计。
-
功能性编解码器性能开销大:每帧可能编码 2-3 次才选出最优质量。Salsify 用的是修改版 VP8,编码速度刚好够 720p@30fps,但换成 H.265 级别的编码器就撑不住了。这限制了它的实际部署场景。想用更新的编解码器(AV1 等),需要等编码器本身支持”部分编码 + 快速回滚”的 API。
-
带宽估计延迟 vs 真实网络波动:估计依赖 ACK / 到达间隔反馈,突发丢包或 Wi-Fi 跳频时会滞后 1-2 帧。论文评测是 mahimahi 回放蜂窝 trace(不是裸有线限速),对 5G 毫米波那种几十毫秒内 Gbps→几十 Mbps 的跳变仍可能更吃力。
-
不兼容标准硬件编解码器:手机、浏览器里的视频编码都走硬件加速(GPU/专用芯片),这些芯片不暴露内部状态,无法做”functional codec”。所以 Salsify 至今难以在移动端落地。要让这个思路工业化,需要芯片厂商在硬件编码器 API 里加入”状态快照/恢复”能力——目前没有厂商这么做。
-
多人会议场景失效:Salsify 是点对点设计,每条链路独立调度。一旦变成多人视频(SFU 架构),一份编码流要发给多个不同带宽的接收者,“一帧一质量”的设计就不够用了,需要 SVC 分层编码或 simulcast。Zoom / Google Meet 用的都是 SFU + simulcast 方案,思路与 Salsify 完全不同。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 点对点实时视频通话(1 对 1),对延迟极敏感(远程手术、实时协作、远程操控)
- 网络带宽剧烈波动的环境(移动网络切换、拥塞频繁、卫星链路)
- 研究原型:验证”编码-传输联合优化”思想的学术实验
- 对画质 vs 延迟需要精细权衡的专业场景(如远程面试平台、在线教育白板)
不适用:
- 多人视频会议(需要 SFU + simulcast/SVC,一份流发多个接收者)
- 需要硬件加速的移动端/嵌入式设备(functional codec 要求软件编码,手机芯片不支持)
- 直播/点播(延迟要求宽松,传统 ABR 自适应码率就够了,不需要帧级决策)
- 需要标准兼容的场景(WebRTC 标准栈已经深度集成 H.264/VP8/VP9/AV1,替换成本高)
- 超高分辨率场景(4K@60fps 下,软件编码器跑不动多次试编码)
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”Salsify 出自 Stanford 的 Keith Winstein 实验室。Winstein 之前的代表作是 Mosh(Mobile Shell),一个替代 SSH 的工具,核心思想也是”不依赖 TCP 顺序交付,自己在应用层做状态同步”。从 Mosh 到 Salsify,你能看到同一个执念:TCP 的可靠有序交付对交互式应用是负担,不如自己管。
论文第一作者 Sadjad Fouladi 后来做了 gg(一个 serverless 编译系统)和 ExCamera(用 Lambda 并行编码视频),都是”把传统整体式系统拆成细粒度、无状态的函数调用”。functional codec 的思路并非灵光一现,而是这个组持续追求的设计哲学。
一个有趣的细节:Salsify 的名字来源于一种植物(婆罗门参),作者的几个项目都用植物命名。论文发表在 NSDI 2018(当年 Best Paper 是 NetChain,不是这篇),社区认可的是”打破编码-传输分层”这个看似违反直觉但实验效果惊人的设计。
读完这篇论文,最大的收获不是某个具体算法,而是一种系统设计思维:
- 分层不是免费的:网络协议栈追求”层间解耦”,但解耦的代价是信息传递延迟。对实时视频这种”每毫秒都在变”的场景,紧耦合反而更优
- 一帧一决策 > 一秒一决策:传统 ABR 每 1-2 秒调整一次码率,Salsify 每 16ms 调整一次。粒度细一个数量级,延迟降一个数量级
- functional / 不可变状态思想在系统设计中的力量:把编码器状态做成不可变快照,就能回滚、分支、投机——这和函数式编程里”纯函数 + 不可变数据”一脉相承
- 工程约束决定学术思想能走多远:思想正确但依赖软件编码、不兼容硬件加速,导致工业落地困难。好论文也需要考虑生态兼容性
- 度量指标的选择:Salsify 用 SSIM(结构相似度)而非 PSNR 来评估画质,因为 SSIM 更接近人眼感知。选对评估指标,才能证明”低延迟不是靠牺牲画质换来的”
- 论文 PDF:Salsify NSDI 2018
- 同组后续工作 Puffer:puffer.stanford.edu(大规模验证”联合优化”在真实用户上的效果)
- Keith Winstein 的 Mosh 项目:mosh.org(理解 Salsify 的思想根源——用户态状态同步替代 TCP)
- Google BBR 拥塞控制:另一个”打破传统分层假设”的网络协议改进
- ExCamera(同组 NSDI 2017):用 AWS Lambda 并行编码视频,展示了 functional codec 思想的另一个应用
- VP8 编码器源码(libvpx):了解 Salsify 基于什么做的 functional codec 改造
- bbr-2017 —— 同样打破传统拥塞控制分层的 Google 方案,但只管传输不管编码
- quic —— Google 的用户态传输协议,和 Salsify 一样绕过内核 TCP 栈追求低延迟
- tcp —— Salsify 明确拒绝使用 TCP,因为 TCP 的重传和排队对实时视频是灾难
- rtp-rfc-1889 —— 传统实时视频的传输协议(RTP),Salsify 用自研协议替代了它
- tcp-vegas-1995 —— 基于延迟的拥塞控制先驱,Salsify 的带宽估计思路与之相关
- saltzer-1984-e2e —— 端到端论证:Salsify 是”把功能放在端上”思想的极致体现
- cerf-kahn-1974 —— TCP/IP 分层架构的诞生,Salsify 挑战的正是这种经典分层思路
思考题:如果未来硬件编码器支持”状态快照”API,Salsify 的思想能否直接用于 4K 实时视频?瓶颈会转移到哪里?(提示:考虑显示延迟、采集延迟、以及 GPU 编码 pipeline 的延迟。)
进一步思考:在多人会议场景下,如何把 Salsify 的”帧级决策”思想与 SVC 分层编码结合?每层的目标字节数能否独立由各接收者的链路容量决定?