摘要

  • RFC 2035 传输的不是逐帧完整 JPEG 文件。它省略很少变化或可按规则重建的表与标记结构,让每个分组携带八字节 RTP/JPEG 头,其中包括 Type、Q、宽高和 24 位 Fragment Offset。
  • 时间戳、序列号、片段偏移和 Marker 分别回答帧归属、分组次序、字节位置与声明结束点。组合使用可以发现缺口并处理乱序,却没有任何一个字段单独证明帧已齐全。
  • JPEG 重启标记可以清除熵解码状态;Type 4/5 进一步让重启区间与 RTP 分组对齐,为丢包后的局部解码提供边界。可局部开始不等于像素正确、播放及时、画面连续或体验达标。

如果第二个到达的分组声称自己的 Fragment Offset 是零,接收端不必因为到达顺序而把它放在第二段。它可以按偏移把扫描数据直接放回帧缓冲区开头。这个三字节字段的力量不在于描述网络走了哪条路,而在于把“何时到达”与“属于哪里”拆成两个问题。

1996 年 10 月发布的 RFC 2035 正是围绕这种拆分构造 RTP/JPEG。普通 JPEG 交换文件会携带标记段、量化表、帧尺寸、扫描描述等语法。实时视频中,一些描述长期不变,另一些可以由双方约定的规则推导。每帧重复发送它们会花掉带宽,却不增加新的图像样本。RFC 2035 因此让负载从熵编码扫描数据开始,只保留重启标记,把其余必要信息压进每个 RTP 分组前的 64 位 JPEG 头。

这不是简单“删头”。被删掉的语法原本承担解释作用;删掉以后,解释权转移到一套更薄、更严格的重建契约。接收端必须知道本分组属于哪一帧、扫描数据位于何处、采用何种采样与量化、尺寸是多少、帧在哪里结束,以及丢失前文后能否从某个点重新启动解码。

省略稳定描述,重复会变化的最小上下文

RFC 2035 限定于顺序 DCT、单扫描、交错图像,并采用少量 Type 组合。这一限制让能力较窄的硬件编解码器也能互通。它不是完整 JPEG 语法的 RTP 容器,更不是把磁盘上的 JPEG 文件机械切片。

每个 RTP/JPEG 头都包含 type-specific 字节、24 位 Fragment Offset、Type、Q、Width 和 Height。Type 规定采样和重启解释;Q 控制量化表的生成方式;宽高以八像素为单位。宽高和 Q 需要随包出现,因为发送端可能通过改变质量或分辨率来适配速率,接收端不能假定会话建立时的值永远不变。

Q 在 1 至 99 时,规范给出生成量化表的算法。Q 大于等于 100 时需要通过 RFC 2035 范围之外的会话机制动态提供表;零保留。这个边界很重要:规范告诉接收端在什么条件下能自行构造表,也明确没有些值本身并不足够。

因此,头字段是重建输入,不是图像真实性证明。一个分组准确声明宽、高、Q 和 Type,只能说明接收端应该按什么规则解释字节。它不能证明编码器真的按该表生成了扫描数据,不能证明字节未被破坏,更不能证明相机前曾有某个特定场景。

四个字段,不能压成一个“帧成功”

同一帧的所有分组共享 RTP 时间戳。序列号显示 RTP 分组流的次序并暴露丢失位置。Fragment Offset 表示本负载在 JPEG 扫描中的字节坐标。Marker 位声明本分组是该帧最后一个分组。

这四个字段看似都与“完整”有关,实际上证据对象不同。时间戳能分组,不能证明组内无缺。序列号能显示分组跳号,却不能直接说明扫描数据缺了哪一段。偏移能定位,不能认证内容。Marker 能声明终点,不能补齐此前的空洞。

组合起来,它们才形成可验证的重组路径。接收端可按 offset 直接复制数据,再用“上一段偏移加长度”与下一段偏移比较,识别缺口或重叠。乱序到达不再要求先排完整队列再复制。RFC 2035 也因此把分片责任放在 RTP,而不是交给 IP:一个 IP 片段丢失,整个 IP 数据报可能都无法向上交付;多个独立 RTP 分组则让仍然到达的数据保持可见,并让发送端更细地控制分组大小和发送速率。

帧尾丢失显示了证据边界的另一面。最后一个分组一旦丢失,Marker 也一起消失。接收端可能知道若干扫描范围已经到达,却缺少上一帧结束的声明。下一帧 offset 为零的分组提供新的起点;只有再观察到连续范围和该帧的 Marker,才能确认一个完整的后继帧。起点、连续性和终点缺一不可。

重启边界减少前置依赖,不会修复缺失画面

JPEG 熵编码具有状态:霍夫曼解码和 DC 预测常让后文依赖前文。重启标记会重置这些状态,让解码器可以从新的区间开始。RFC 2035 用不同 Type 区分这种能力。

Type 0/1 不使用重启标记;Type 2/3 可以有重启标记,却不要求 RTP 分组边界与重启区间对齐;Type 4/5 要求对齐,并通过类型专用的 restart count 表示区间序列中的开始、中间和结束位置。于是,某个对齐区间如果结构完整,接收端可能在前面数据缺失时仍从这里开始解码。

这里最容易产生过度承诺。“可以开始解码”并不等于“可以恢复整张图”。规范没有替丢失区域创造像素,没有保证完整区间恰好包含读者关心的内容,也没有保证分组赶上播放期限。局部解码是受结构条件约束的能力,不是丢包隐藏机制,更不是体验质量承诺。

还要守住版本边界。RFC 2435 后来废止 RFC 2035,当前 IANA 对静态负载类型 26 的引用也指向后继文档。它们说明现行标准记录已经变化,却不能把后继规范的可选头、重启信号改动或其他独立机制倒写进 RFC 2035。

从分组事件到观众体验,中间有多层证据

抓包可以证明哪些字节被观察到,哪些分组声称同一时间戳,序列如何推进,offset 覆盖哪些范围,Marker 是否出现,以及 Type、Q、尺寸和重启信息如何声明。重建器可以进一步记录生成了哪些 JPEG 语法。解码器还能记录接受、报错或输出哪些区域。

再往后则是不同事实:完整帧是否在期限前出现,播放队列是否采用它,渲染器是否显示它,画面是否连续,观众是否看见或理解内容。任何一层都不能靠前一层的一个绿色图标自动代替。

Lu Heng 所强调的 running-code 视角在这里非常具体:先保存实际发生的分组事件,再解释协议声明。时间戳、序列号、offset 与 Marker 应分开记录;原始数据和派生的“完整”判断也应分开保存。符号层可以帮助人决策,但不能覆盖底层事件留下的限制。

RFC 2035 的设计智慧来自一条很薄的共同层:省略可重建的稳定描述,重复真正会改变解释的最小上下文,并把分片位置保留在接收端可观察的位置。它的证据诚实也来自同一条边界。分组可以准确说出自己该放在哪里;“画面已经到达”仍需要逐层证明。

来源