前言

随着两客一危(旅游客运、包车客运、三类以上班线客运和危险货物运输)安全监管力度的不断加大,单纯的位置汇报(JT/T 808 协议)已经无法满足行业监管和企业主动安全防范(如 ADAS 驾驶辅助、DSM 疲劳驾驶监测)的需求。如何在定位车辆的同时,实时调取车载摄像头画面、进行历史视频回放以及下发语音对讲

为此,交通运输部推出了 JT/T 1078 协议(全称《道路运输车辆卫星定位系统视频通信协议》)。它是 JT/T 808 协议的音视频扩展版。

对于车联网开发者来说,JT/T 1078 引入了全新的流媒体处理架构,比 808 协议更为复杂。本文将为你全面剖析 JT/T 1078 的双链架构、控制信令、1078 特化的 RTP 报文结构,以及如何从零设计一个高性能的 1078 流媒体分发网关。


一、JT/T 1078 的“信令+媒体”双链架构

JT/T 1078 并不能脱离 JT/T 808 独立运行。它的核心思想是:“信令走 808 链路,媒体流走独立 1078 链路”

1
2
3
4
5
6
7
8
9
10
11
            +-----------------------------+
| 监控调度平台 |
+----+-------------------+----+
| |
808 信令控制链 1078 实时流媒体链
(TCP / 默认 808 通道) (TCP / UDP 专属流媒体端口)
| |
v v
+----+-------------------+----+
| 车载终端 (DVR) |
+-----------------------------+
  1. 信令控制面(利用 808 长连接)
    • 平台下发“点播视频”、“云台控制”、“历史视频检索”等指令时,这些指令被封装为特殊的 808 消息(如 0x9101 实时音视频请求),通过已有的 JT/T 808 TCP 长连接发送给车载终端。
  2. 媒体传输面(新建 1078 独立流通道)
    • 车载终端收到点播指令后,会根据指令中携带的 流媒体服务器 IP 和端口,主动建立一条全新的 TCP 或 UDP 通道,将实时采集到的摄像头音视频流打包成 1078 RTP 数据包推送给流媒体服务器(网关)。
    • 网关收到流后,进行协议解包与转封装,分发给 Web/App 前端播放。

二、核心控制信令解析

信令交互是 1078 视频点播的前提。以下是平台发起“实时视频索要”的经典时序流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sequenceDiagram
autonumber
participant Client as 监控前端 (Web/App)
participant Platform as 808 业务平台
participant Gateway as 1078 媒体网关
participant Terminal as 车载终端

Client->>Platform: 点击“播放视频”(如车牌:粤B12345, 通道:1)
Platform->>Gateway: 申请流媒体播放上下文,分配接收临时端口 (e.g. 10078)
Gateway-->>Platform: 返回流媒体接收端口及播放凭证
Platform->>Terminal: 下发 0x9101 实时音视频请求指令 (通过 808 链路) <br/> 包含:网关IP、端口(10078)、逻辑通道号(1)、数据类型(音视频/视频)、码流类型

Terminal->>Gateway: 建立媒体流 TCP 连接 (根据 0x9101 的 IP 和 10078 端口)
Terminal->>Gateway: 持续推送 1078 RTP 二进制音视频包

Gateway-->>Client: 转换为 HTTP-FLV / WebRTC 输出播放

1. 0x9101 实时音视频传输请求(平台下发)

这是最关键的指令,用来命令终端开始推流。它的消息体属性包含:

  • 服务器IP地址(终端要往哪里推流,支持 IP 字符串或域名)。
  • 服务器TCP端口 / UDP端口
  • 逻辑通道号:对应车辆的第几路摄像头(如 1:驾驶员,2:前路路况)。
  • 数据类型0:音视频1:视频2:双向对讲3:监听4:广播
  • 码流类型0:主码流(高清,用于存储和重点监控),1:子码流(流畅,省流量)。

2. 0x9102 音视频流控制(平台下发)

用于在播放中控制媒体流,比如:

  • 控制指令0:关闭当前通道的流1:切换为当前通道的子/主码流2:丢弃当前的 I 帧(快速降低延迟)3:关声音4:开声音

三、1078 特化的 RTP 报文结构

一旦建立了流媒体通道,车载终端就会以秒级数十包的频率推送音视频流。为了标识这些媒体帧,JT/T 1078 并没有直接采用标准的 RFC 3550 RTP 协议,而是定义了一个专有的 1078 头(1078 RTP Payload Header)

1. 数据包整体结构

每一个发送给网关的数据包都是由 1078 专属头 + 音视频负载 组成:

帧头标识 (4B) V/P/X/CC (1B) M/PT (1B) 包序号 (2B) 终端手机号 (6B) 逻辑通道号 (1B) 数据类型 (1B) 分包标记 (1B) 相对时间戳 (8B) 上一I帧间隔 (2B) 上一帧间隔 (2B) 负载长度 (2B) 媒体负载 (长度不定)
0x30 0x31 0x63 0x64 0x80 Payload Type Sequence BCD 手机号 Channel Data Type Sub-Flag Timestamp Last I-Frame Interval Last Frame Interval Length e.g. H.264 NALU

2. 核心字段深度解析

  • 帧头标识:固定 4 字节,为字符 "01cd" 的 ASCII 码:0x30 0x31 0x63 0x64。这用来在 TCP 流中作为包头边界进行切包判定。
  • V/P/X/CC (1字节):固定为 0x800x81,与标准 RTP 头部字段兼容。
  • M/PT (1字节)
    • M (bit 7):标志位,为 1 表示一帧视频的结束(最后一个分包)。
    • PT (bit 0-6):负载类型(Payload Type)。常见的有 98: H.26499: H.2658: G.711A26: G.72619: AAC 等。
  • 数据类型 (1字节)
    • 0x00:视频 I 帧(关键帧,包含完整的画面信息,必须由此帧开始渲染)。
    • 0x01:视频 P 帧(预测帧,只包含与前一帧的差异)。
    • 0x02:视频 B 帧(双向预测帧)。
    • 0x03:音频数据。
    • 0x04:透传数据。
  • 分包标记 (1字节)
    • 用来标记一帧大视频被拆分成多个网络包的情况。
    • 0000:第一包;0001:中间包;0010:最后一包;0011:独立包(不需要分包)。
  • 时间戳 (8字节):使用毫秒(ms)级的相对时间戳,用于音视频同步(AV Sync)和播放器缓存控制。

四、如何设计高性能 1078 流媒体分发网关?

1078 媒体网关是整个车载视频系统中最吃 CPU 和网络带宽的组件。一个优秀的 1078 流媒体服务器,其处理流程通常如下:

1
2
3
4
5
6
7
8
9
10
+------------+     +-------------------+     +---------------------+     +--------------+
| 车载终端 | --> | 1078 专属收流端口 | --> | 1078 协议解析与重组 | --> | H.264 裸流 |
| (DVR/1078) | | (Netty 异步接收) | | (还原 NALU 帧) | | & AAC 音频帧 |
+------------+ +-------------------+ +---------------------+ +-------+------+
|
v
+------------+ +-------------------+ +---------------------+ +-------+------+
| 网页播放端 | <-- | HTTP-FLV / WebRTC | <-- | 流媒体服务器封装器 | <-- | ZLMediaKit |
| (Web/App) | | (低延迟直播流分发) | | (RTMP / FLV 转码) | | 等流媒体引擎 |
+------------+ +-------------------+ +---------------------+ +--------------+

核心开发步骤

第一步:基于 Netty 监听并收流

因为 1078 媒体流是 TCP 二进制流,且包含边界魔数 "01cd"。我们使用自定义的 Netty 帧解码器(根据魔数定位和包头中 负载长度 字段动态切包):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class JT1078FrameDecoder extends LengthFieldBasedFrameDecoder {

public JT1078FrameDecoder() {
// 参数说明:
// maxFrameLength: 最大帧长度(如 65535)
// lengthFieldOffset: 长度字段偏离包头的字节数。
// 从 1078 头字节排布看,"负载长度" 字段在第 28 和 29 字节(索引28,长度2)
// lengthFieldLength: 长度字段本身的长度 (2字节)
// lengthAdjustment: 长度字段后的剩余数据长度调整,由于1078包头总长 30 字节,长度字段在 28-29,所以包头还剩 0 字节,调整为 0
// initialBytesToStrip: 剥离前面的字节,我们选择不剥离(设置为 0),保留完整 1078 头部用于解析元数据
super(65535, 28, 2, 0, 0);
}

@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 先利用魔数 "01cd" 进行对齐定位,防止因为网络抖动错位
if (in.readableBytes() < 4) return null;

in.markReaderIndex();
byte[] magic = new byte[4];
in.readBytes(magic);
if (magic[0] != 0x30 || magic[1] != 0x31 || magic[2] != 0x63 || magic[3] != 0x64) {
in.resetReaderIndex();
in.readByte(); // 错位了,丢弃 1 字节并继续寻找魔数
return null;
}

in.resetReaderIndex();
return super.decode(ctx, in); // 调用父类 LengthField 进行精确切包
}
}

第二步:提取 NALU 并进行帧重组

对于 H.264/H.265 编码,如果遇到“分包标记”为“第一包”或“中间包”,网关需要先开辟缓冲区,把后面的数据拼接在一起,直到收到“最后一包”或“独立包”,才把这一帧拼好的 NALU(需在帧前加上 0x00 0x00 0x00 0x01 起始码)送往推流器。

第三步:集成 ZLMediaKit / SRS 流媒体引擎实现转协议播放

自己写推流和分发协议(如 WebSockets-FLV、WebRTC)代价极大。最佳的工业实践是集成成熟的开源流媒体服务器

  • 方案:1078 网关解包还原出 H.264 裸流和音频帧后,通过本地 RTMP 推流(使用 Java-CV、FFmpeg 或 ZLMediaKit API)将流推送到流媒体引擎。
  • 分发:流媒体引擎自动生成 HTTP-FLVHLSWebRTC 等多种流,网页前端使用 flv.js 或标准的 WebRTC 播放器,延迟可控制在 1 秒以内!

五、1078 关键避坑与优化指南

  1. G.711 / G.726 音频转码
    • 车载摄像头音频很多采用 G.711AG.711UG.726。这些音频格式直接丢给浏览器播放是无声的,因为主流浏览器(HTML5 audio)只支持 AACOpus
    • 优化方案:在 1078 流媒体网关内部,通过 FFmpeg 库或 EasyAACEncoder 对音频帧进行重采样并实时转码为 AAC,再混流推给前端。
  2. 丢包导致的画面马赛克(针对 UDP 接入模式)
    • 如果终端以 UDP 协议上报视频,弱网下极易发生丢包。如果丢失了 I 帧或关键的 P 帧分包,会导致画面持续花屏。
    • 优化方案:网关检测到 Sequence 流水号不连续时,应立即向终端发送 0x9102 信令指令,要求终端“丢弃当前P帧,强制上报下一个I帧”以快速恢复画面清晰度。
  3. 假死连接断开
    • 车载终端处于移动状态,在通过隧道或山区时连接可能会假死挂起。1078 网关必须配置 Idle 检测,在 10 秒内未收到媒体包时应主动释放连接,避免内存泄露。