前言
2019年的上半年开始做1078的音视频开发工作,并开源了jtt1078-video-server这个项目,在9月份创建了QQ群,认识了很多这个行业的开发人员,服务器端开发的、车载终端开发的,认识到了很多有趣的人,也大大的开拓了我在音视频方面的知识面。
JT/1078协议是用于营运车辆车载终端做音视频传输的一个协议,交通部对于很多车辆是要求强制安装的,并且实时的音视频传输协议必须是JT/1078协议,而这个协议起草得相当的差劲,文档里好几处错误、设计有缺陷、厂家不按套路来,并且在音视频传输的细节里,有很多不明确不详细的地方,对于初次接触音视频开发的人来说,无从下手、完全摸不着头脑是很正常的事,开发进度一拖再拖,领导给的脸色也是越来越差,真是太蛋疼了。。。
闲话不多说,JT/1078协议是对808协议的扩展,就是在它的基础上,增加了几个用于音视频传输控制的指令,以及几个报警信息,所以如果之前就已经有808协议服务器的,直接在它的基础上,增加1078协议的几个指令的传输控制就可以了。
角色
在整个音视频的传输与播放的环节里,是以下几个方面配合一起完成的。
- 用户前端,比如浏览器网页或是手机APP。
- WEB服务器,比如后端的J2EE WEB项目,承接用户端的操作,并与1078指令服务器打交道,是一个中间桥梁。
- 1078指令服务器,在808的基础上,增加了对1078协议指令支撑的服务器端。
- 流媒体服务器,当车载终端传输音视频数据上来时,需要完成对协议的解析,以及流媒体的转播等。
- 车载终端,安装在车辆上,连接到我们的1078指令服务器上来,将车辆的一些实时数据传输到服务器端,并且能够响应服务器的下行指令。
一般流程
- 用户前端选择车辆、以及要调阅的视频通道,向后端的WEB服务器发起调阅请求。
- WEB服务器通知1078指令服务器,向目标车载终端的连接通道上,发送9101指令,通知目标车载终端开始传输音视频。
- 车载终端在接收到9101指令后,通过指令消息体里所包含的流媒体服务器的IP与端口,建立一个新的连接,开始传输音视频。
- 流媒体服务器将收到车载终端发过来的音视频数据包,完成解析并且通过各种流转换,重编码、封装成可供前端播放的音视频流。
- 用户前端开始播放流媒体服务器所提供的音视频流。
实时音视频
下发9101指令
1078指令服务器负责下发9101指令来通知车载终端开始音视频传输,由协议定义的消息体包含以下内容:
起始字节 | 字段 | 数据类型 | 描述及要求 |
---|---|---|---|
0 | 流媒体服务器IP地址长度 | BYTE | 长度n |
1 | 流媒体服务器IP地址 | STRING | 流媒体服务器IP地址,如1.2.3.4,字节内容就是31 2E 32 2E 33 2E 34 |
1+n | 流媒体服务器监听端口号(TCP) | WORD | 如端口号1078,值为04 36 |
3+n | 流媒体服务器监听端口号(UDP) | WORD | 如果是TCP协议的,这里可填写为00 00 |
5+n | 逻辑通道号 | BYTE | 按照JT/T 1078-2016中的表2字义,可见文章末尾的逻辑通道号对照表 |
6+n | 数据类型 | BYTE | 0:音视频,1:视频,2:双向对讲,3:监听,4:中心广播,5:透传 |
7+n | 码流类型 | BYTE | 0:主码流,1:子码流 |
数据类型说明
- 音视频:表示车载终端发上来的消息包里即有音频数据又有视频数据。
- 视频:表示只会发送视频类数据包。
- 双向对讲:在这个模式下,连接为双向通道,车载终端会发送音频消息包上来,流媒体服务器也可以主动下发音频消息包下去,终端会播放出来。
- 监听:没有用过,应该是车载终端只发送音频,但不接收流媒体服务器下行的数据包。
- 中心广播:不知道做什么用的。
- 透传:1078协议不止是用于音视频传输,还可以用于第三方设备的数据采集传输,就比如车辆上安装了一个陀螺仪,可以使用1078协议传输到服务器端来。
码流类型说明
- 主码流:一般为704x576的分辨率,传输数据量较大。
- 子码流:一般为352x288的分辨率,传输的数据量很小,大概是1分钟1M字节的数据量。
音视频流媒体服务器
流媒体服务器是我们这个协议中的主体,是完成对音视频传输的数据对接,到转码、封装为新形式音视频,以提供给用户前端进行播放的关键服务器,涉及到了大量的音视频编解码、分布式架构、各种网络协议方面的知识。
在这里,我们主要要完成对RTP消息包的解析,从中分离出音频或视频的数据体,并且完成解码或重装封装的工作,如果可能,还需要提供音视频流的订阅,以供给用户前端来播放。
RTP消息包
1078在定义音视频传输消息包时,借鉴了RTP协议,但又不是完全一致,而且里面还有很多不清楚以及有错误的地方,这里是重中之重。协议文档里的定义如下:
起始字节 | 字段 | 数据类型 | 描述及要求 |
---|---|---|---|
0 | 帧头标识 | DWORD | 固定为0x30 0x31 0x63 0x64,ASCII码为01cd |
4 | V | 2 BITS | 固定为2 |
P | 1 BIT | 固定为0 | |
X | 1 BIT | RTP头是否需要扩展位,固定为0 | |
CC | 4 BITS | 固定为1 | |
5 | M | 1 BIT | 标志位,确定是否完整数据帧的边界,因为数据体的最大长度是950字节,而一个视频I帧通道要远远超过950字节,所以视频的一个帧通常会分包 |
PT | 7 BITS | 负载类型,原文档里的这里的参考表是错误的,实际上是参考文档的表12,此文章的附录里也有 | |
6 | 包序号 | WORD | 初始为0,每发送一个RTP数据包,序列号加1 |
8 | SIM卡号 | BCD[6] | 终端设备SIM卡号 |
14 | 逻辑通道号 | BYTE | 按照JT/T 1076-2016中的表2 |
15 | 数据类型 | 4 BITS | 0000:数据I祯,0001:视频P帧,0010:视频B帧,0011:音频帧,0100:透传数据 |
分包处理标记 | 4 BITS | 0000:原子包,不可拆分等,0001:分包处理时的第一个包,0010:分包处理时的最后一个包,0011:分包处理时的中间包 | |
16 | 时间戳 | BYTE[8] | 标识此RTP数据包当前祯的相对时间,单位毫秒(ms)。当数据类型为0100时,则没有该字段,RTP协议里规定这个数字是以任意值开始,然后按毫秒的时间间隔递增即可,千万不要认为它是常规的时间戳的定义,目前有碰到个别厂家提供的时间戳有问题,一直不变。 |
24 | Last I Frame Interval | WORD | 该祯与上一个关键祯之间的时间间隔,单位毫秒(ms),当数据类型为非视频祯时,则没有该字段 |
26 | Last Frame Interval | WORD | 该祯与上一个关键祯之间的时间时间,单位毫秒(ms),当数据类型为非视频祯时,则没有该字段 |
28 | 数据体长度 | WORD | 后续数据体长度,不含此字段 |
30 | 数据体 | BYTE[n] | 音视频数据或透传数据,长度不超过950 byte |
我做了一个对于此消息类型的解析工具,见:https://www.hentai.org.cn/format/
RTP协议的定义是有缺陷的,因为实时音视频和历史音视频传输时,使用的RTP消息包格式完全一致,而每个连接通道我们只能够使用消息体里的SIM卡号和逻辑通道号作为标识,导致我们无法正确的区分现在连接到流媒体服务器上来的到底是实时还是回放,所以一般做法是,实时音视频和回放这里,分开两个不同的服务器端口来提供服务。
另外,视频编码一般,或者说几乎所有的终端使用的都是H.264编码,好像有群友碰到过AVS编码的。而音频编码最常用的就是G.711A、G.711U、ADPCMA以及G.726这四种,比较头疼的是,终端经常搞鬼,有时间消息体里传递过来的音频编码与实际的不相符,对于这种情况,最好是在服务器端做配置设置,不以终端的标识为准,以服务器端配置的来。四种常见的音频解码,我提供了acodec音频编解码库项目,大家可以参考一下(目前只支持G711A、G711U、ADPCM的解码)。
还有一点,使用了海思音频芯片的终端(比如锐明的终端),音频数据体通常会带上海思头,它的形式如00 01 XX 00
,XX表示后续字节数的一半,如果碰到符合这个规则的前四个字节,直接去掉就可以了。
音视频编码与封装
在这一步里,我们需要完成对已经读到的音频或视频的解码封装的工作,车载终端传输过来的音频或视频,用户前端的浏览器或APP不一定能够直接播放,通常需要封装为其它形式。最佳的办法是提供RTMP+HTTP-FLV,这样的话,网页端可以使用flv.js进行播放(摆脱对flash的依赖),而APP端或小程序等,可以播放RTMP流媒体。最简单的办法是,使用ffmpeg将视频直接推流到RTMP服务器,这里推荐nginx-http-flv-module这个流媒体插件,ffmpeg完成将视频推送,而身为RTMP服务器的nginx-http-flv-module除了接收RTMP推送,同时还能够将RTMP流转为HTTP-FLV流,同时兼顾两种形态的流媒体。
而音频就比较麻烦了,ffmpeg虽然可以将音频和视频整合推流(参考FFMPEG入门与集成开发指南),但是里面有太多不可控制的因素了,我项目里的multimedia分支就做了这个尝试,但是效果很糟糕,而有的群友就很成功,不推荐这个办法。如果需要音频,建议默认只开启视频,需要音频时,再发送指令开始传输音频再传输到用户前端进行播放。
音视频流的订阅
实时音视频流是可以共享的,也就是说,对于同一辆辆的同一个通道的音视频,是可以有多个用户同时播放观看的,而历史音视频不行,必须是独占的。如果在流媒体服务器上直接提供音视频的话,则需要控制好这一点,而如果使用了比如nginx所提供的RTMP服务器,就不需要另外考虑,流媒体天生就是一对多的。
历史音视频回放
因为RTP消息包结构只有SIM卡号和通道号可以作为通道标识,而无法区分是实时音视频还是历史音视频,所以通常我们另外开启一个TCP端口监听来为历史回放提供支持。
在官方文档的0x9201指令消息体里,文档里有错误,快进或快退倍数重复了两遍,忽略掉这个小错误,只写一次即可,切记切记,其它的流程里,就与实时音视频一样的处理就可以了,里面有一点要注意的是,历史音视频的传输速度会以终端的最大带宽进行传输数据,比较典型的结果就是,如果车载终端目前已经传输了5分钟的数据量了,而用户前端只播放到30秒,那此时对车载终端下发回放控制指令(如快进、暂停、seek定位等)可能会很久以后才会响应到,最好就是在发送播放控制指令后,清空流媒体的缓冲数据,或是干脆重建流媒体,或是控制服务器端的接收速率,让它能够尽快的响应过来。
双向对讲
双向对讲模式下,车载终端与流媒体建立的连接通道将是一个双向通道,终端将采集上来的音频编码后发送到服务器端,服务器端也可以通过这个连接通道发送音频数据下去,终端将完成播放。
首先,下发的消息包,结构也是按照上面的RTP消息包结构来的,需要注意以下事项:
- SIM卡号不能错
- 通道号,大坑,建议先按协议标准定义的36测试一下,不行的话,就从1开始,一直试到255为止。
- 时间戳不能错,要按照实际的时间间隔来递增,建议直接从0开始。
- 音频编码,终端是什么编码,服务器下发的时候,就应该是什么编码,不对等的音频编码没有测试过。
- 发送的速率不要太快,如果一个消息包的音频数据时长为20毫秒,那发送下一个消息包也应该在20毫秒以后,终端通常会全速播放,不一定会按时间戳来慢慢播放。
- 如果发上来的音频数据里有海思头,那下发的音频数据体也应该加上海思头。
- 终端音频的采样率都是8000,不用考虑其它的采样率。
语音对讲比实时和历史音视频的坑要大得多,一定要注意多测试,并且控制测试的粒度,不要把用户前端的录音到传输到流媒体服务器等整个链路一起来测试,应该直接准备一个PCM数据文件,直接进行分包下发测试,听到终端的声音后再考虑完整的流程应该是怎么样去规划。
音视频上传
协议里要求的历史音视频上传的接收端,必须是一个FTP服务器,在架设FTP服务器时,注意PASV被动模式的端口问题,FTP服务器通常使用21号端口提供指令服务,同时还要提供一个端口范围段,来提供数据交换服务。
协议里的0x9206指令消息体里,文件上传路径是一个关键性的字段,应该把这个字段与用户请求关联起来,建议使用 /{UUID}/ 这样的值(这个路径是相对于FTP根目录的一个路径,终端会通过协议自动创建),当车载终端上传完成后,它还会再次主动上报上传结果的,这个时候我们就可以知道应该去FTP的哪个目录下去读取媒体文件了。这里还要注意一件事,终端上传到 /{UUID}/ 目录下的文件可是千奇百怪的,什么样的命名都有,什么编码的都有,而且通常是好几个文件,建议直接取大小最大的那个文件就好,而上传的视频文件一般有h264裸流和mp4这两种,建议在上传完成后,使用ffmpeg转码统一为mp4后再提供给用户。
如果要求提供上传进度功能,可以参考我的FTP代理服务器项目:ftp-proxy-server
前端播放
上面建议使用nginx-http-flv-module,它可以支持RTMP的推流,同时可以提供RTMP流媒体、以及HTTP-FLV的支持,在网页上,可以使用flv.js来播放,在移动APP端就可以使用RTMP来播放,相信各位可以很容易找到支持RTMP协议的播放器,这里就不细说了。
其它问题
关于延迟
- 累积延迟:flv.js有累积延迟问题,它在播放的过程中,会越来越落后于视频流的进度,这里应该定期的清空MSE的缓冲,在我的开源项目的multimedia.html里有示例可以参考。
- 视频延迟:如果用户前端的首屏时间为1秒,而视频画面上看到的时间OSD文字为几秒或几十秒之前,这是终端的锅。首屏时间有多长,视频延迟就应该有多长,这才是正常的,因为我们的流媒体无法创建出视频出来。
flv.js的并发
chrome浏览器对于同一个域名的并发连接是6路,而HTTP-FLV是基于HTTP协议的长连接,一般不注意的话,很容易误以为流媒体服务器有什么并发上的性能问题,其实这是浏览器的锅,这里建议使用多域名或是多端口号来避开浏览器对单域名的并发数的限速。
写在最后
本文档写了差不多两天,断断续续的,很多地方也还没有写清楚,还只是把常见的一些问题和简单的流程写了一下,还有很多实现上的细节需要了解的,可以进群交流,群里各种语言的开发者都有。
附录
JT 1076-2016营运车辆车载视频终端音视频通道定义表
通道编号 | 通道类型 | 监控区域 |
---|---|---|
1 | 音视频/视频 | 驾驶员 |
2 | 音视频/视频 | 车辆正前方 |
3 | 音视频/视频 | 车前门 |
4 | 音视频/视频 | 车厢前部 |
5 | 音视频/视频 | 车厢后部 |
6 | 音视频/视频 | 车后门 |
7 | 音视频/视频 | 行李舱 |
8 | 音视频/视频 | 车辆左侧 |
9 | 音视频/视频 | 车辆右侧 |
10 | 音视频/视频 | 车辆正后方 |
11 | 音视频/视频 | 车厢中部 |
12 | 音视频/视频 | 车中门 |
13 | 音视频/视频 | 驾驶席车门 |
14~32 | 预留 | 预留 |
33 | 音频 | 驾驶员 |
36 | 音频 | 车厢前部 |
37 | 音频 | 车厢后部 |
注意:实际上车辆在安装摄像头的时候,并不会遵照通道号的要求,很多都是乱七八糟的接,线只要接上就行了,所以通道号的对照表没有什么用。另外,最重要的一点,如果要做双向对讲,是需要在消息体内声明逻辑通道号的,这里我建议先按协议要求的试一试,如果不行就从1试到255,因为有家厂家我记得使用的驾驶员音频通道号是99。
音视频编码类型定义表
编码 | 名称 | 备注 |
---|---|---|
0 | 保留 | -- |
1 | G.721 | 音频 |
2 | G.722 | 音频 |
3 | G.723 | 音频 |
4 | G.728 | 音频 |
5 | G.729 | 音频 |
6 | G.711A | 音频 |
7 | G.711U | 音频 |
8 | G.726 | 音频 |
9 | G.729A | 音频 |
10 | DVI4_3 | 音频 |
11 | DVI4_4 | 音频 |
12 | DVI4_8K | 音频 |
13 | DVI4_16K | 音频 |
14 | LPC | 音频 |
15 | S16BE_STEREO | 音频 |
16 | S16BE_MONO | 音频 |
17 | MPEGAUDIO | 音频 |
18 | LPCM | 音频 |
19 | AAC | 音频 |
20 | WMA9STD | 音频 |
21 | HEAAC | 音频 |
22 | PCM_VOICE | 音频 |
23 | PCM_AUDIO | 音频 |
24 | AACLC | 音频 |
25 | MP3 | 音频 |
26 | ADPCMA | 音频 |
27 | MP4AUDIO | 音频 |
28 | AMR | 音频 |
29~90 | 保留 | 音频 |
91 | 透传 | 系统 |
92~97 | 保留 | 视频 |
98 | H.264 | 视频 |
99 | H.265 | 视频 |
100 | AVS | 视频 |
101 | SVAC | 视频 |
102~110 | 保留 | |
111~127 | 自定义 |
1078指令服务器发送完历史视频请求 在流媒体服务器那边直接断开连接是为啥
nice
学习
牛逼大牛逼
牛逼的人总是干牛逼的事,在下佩服
有木有好心人创建QQ群或者微信群
群满了,加不了
大佬,你好,请问jtt1078-video-server是充当流媒体服务器角色,还是指令服务器角色
牛逼,慢慢学习下
分析很透彻,写的很规范
大佬牛逼
“负载类型,原文档里的这里的参考表是错误的,实际上是参考文档的表12”,这个描述真是解决了一个大疑惑
给力
提交留言: