1 Star 0 Fork 257

廖书浩 / jtt1078-video-server

forked from matrixy / jtt1078-video-server 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

目录

  1. 简介说明
  2. 协议所定义的实时码流数据报文格式
  3. 准备工具
  4. 测试步骤
  5. 常见问题
  6. 其它语言实现
  7. 交流讨论

jtt1078-video-server

基于JT/T 1078协议实现的视频转播服务器,当车机服务器端主动下发音视频实时传输控制消息(0x9101)后,车载终端连接到此服务器后,发送指定摄像头所采集的视频流,此服务器部分负责将视频流推送到RTMP服务器端,完成转播的流程。

协议所定义的实时码流数据报文格式

起始字节 字段 数据类型 描述及要求
0 帧头标识 DWORD 固定为0x30 0x31 0x63 0x64
4 V 2 BITS 固定为2
P 1 BIT 固定为0
X 1 BIT RTP头是否需要扩展位,固定为0
CC 4 BITS 固定为1
5 M 1 BIT 标志位,确定是否完整数据帧的边界
PT 7 BITS 负载类型,见表19
6 包序号 WORD 初始为0,每发送一个RTP数据包,序列号加1
8 SIM卡号 BCD[6] 终端设备SIM卡号
14 逻辑通道号 BYTE 按照JT/T 1076-2016中的表2
15 数据类型 4 BITS 0000:数据I祯等
分包处理标记 4 BITS 0000:原子包,不可拆分等
16 时间戳 BYTE[8] 标识此RTP数据包当前祯的相对时间,单位毫秒(ms)。当数据类型为0100时,则没有该字段
24 Last I Frame Interval WORD 该祯与上一个关键祯之间的时间间隔,单位毫秒(ms),当数据类型为非视频祯时,则没有该字段
26 Last Frame Interval WORD 该祯与上一个关键祯之间的时间时间,单位毫秒(ms),当数据类型为非视频祯时,则没有该字段
28 数据体长度 WORD 后续数据体长度,不含此字段
30 数据体 BYTE[n] 音视频数据或透传数据,长度不超过950 byte

经过阅读文档,以及测试,有如下问题或发现:

  1. 视频流发送是不需要回应的,车载终端将一直持续不断的发送。
  2. 1078协议借鉴了RTP协议,但不是真正的RTP协议。
  3. 数据包的第30字节(非视频类的消息包位置不一样)起为视频数据体,可以将每个消息包的这个部分保存到文件中,比如xx.h264,可以使用PotPlayer来播放。

一般来说,视频的推流首先想到的是使用ffmpeg,如果要集成ffmpeg的sdk或是使用javacvjcodec等视频编解码类的库,开发与学习维护的成本很高。在这里我们直接创建ffmpeg子进程,通过它的参数设定,让ffmpeg子进程直接从stdin中读取数据进行转码推流,通过这种方法,可以很好的达到推流的目的,并且不需要整合其它任何第三方的sdk,如果有后续的进一步的扩展需求(比如添加OSD字幕),可以直接通过修改创建子进程时的命令行来达到目的,非常的简单与易维护。

ffmpeg的输入端,可以是stdin、网络流、文件(图片或视频)等几乎所有常见的数据来源,也可以是同时多个输入,功能非常强大,我之前一直使用的是FIFO命名管道,现在可以抛弃对FIFO命名管道的依赖了,并且可以真正的跨平台运行了。

准备工具

  1. 安装了ffmpeg的机器一台
  2. 安装了nginx-rtmp-modulenginx-http-flv-module(推荐)的Nginx服务器,用于实时视频的转播
  3. PotPlayer,可用于播放实时转播的RTMP视频
  4. 项目里准备了一个测试程序(src/main/java/cn.org.hentai.jtt1078.test.VideoPushTest.java),以及一个数据文件(src/main/resources/tcpdump.bin),数据文件是通过工具采集的一段几分钟时长的车载终端发送上来的原始消息包,测试程序可以持续不断的、慢慢的发送数据文件里的内容,用来模拟车载终端发送视频流的过程。

测试步骤

  1. 配置好服务器端,确定ffmpeg的完整路径,替换掉app.properties配置项。
  2. 确保nginx服务器已经启动,同时配置文件里的rtmp.format已经设置为正确的RTMP地址格式。
  3. 直接在IDE里运行cn.org.hentai.jtt1078.app.VideoServerApp,或对项目进行打包,执行mvn package,执行java -jar jtt1078-video-server-1.0-SNAPSHOT.jar来启动服务器端。
  4. 运行VideoPushTest.java,开始模拟车载终端的视频推送。
  5. 服务器端的日志里将会输出start streaming to rtmp://....的字样,后面即为实际推送的RTMP地址。
  6. 打开PotPlayer,按下Ctrl+U,输入上面输出的RTMP地址,稍等片刻就能看到转播出来的视频了。

常见问题

  1. HLS、HTTP-FLV、RTMP等实时视频都可以通过ffmpeg推流,在RTMP服务器端配置来实现。
  2. 网页端的RTMP播放,之前使用Aliplayer,效果很不理想,可以尝试使用video.js
  3. 测试使用nginx-http-flv-module+flv.js来做网页播放,效果很不错,延迟低,不依赖于flash,值得推荐。
  4. 音频不关注也没有测试,如果有很强的音视频同步传输的需求的话,可以考虑修改程序,将RTP消息包里的音频单独拆分出来,处理后也通过ffmpeg子进程合并音轨到视频流中去(我瞎想的)。
  5. RTMP测试工具:live_test.swf,具有对RTMP流媒体延迟与缓冲测试等功能,RTMP测试必备工具。
  6. no such publisher报错,通常是由于ffmpeg路径配置错误或是rtmp推流失败而导致子进程提前退出而引起的,需要确定ffmpeg路径配置及rtmp服务器是否正确配置。

其它语言实现

目前,QQ群里已经有网友完成C++C#Python等语言的实现、而且也有完成了语音对讲等,基于ffmpeg子进程的实现简便易行,对音视频的知识要求已经降低到了很低很低的门槛了,值得推荐。在做其它语言的实现前,我们需要了解一个重要的知识点:stdio标准输入输出,一个应用程序在启动时,可以有两种方式传入数据:命令行参数stdin标准输入流,命令行参数是广为人知的内容了,比如当我们使用ping www.baidu.com时,www.baidu.com就是我们交给ping这个命令的运行时参数。而stdin标准输入流其实也经常使用到,只是一般而言,我们通常不会去关注它的实现,比如当我们使用ls|grep *.txt这个命令行,用于列出所有文件,然后过滤只留下*.txt时,这就已经使用到了管道stdin的功能了,|竖线为管道命令,用于把前一个命令行的stdout输出内容转为后一个命令的stdin输入,在这个例子里,grep命令就是从stdin里读取数据的,而这个数据就是来自于前一个ls命令的输出。这就是管道stdio的关系。而我们平常所使用的System.outJava)就是stdout,相应的System.in就是我们的stdin。基本上,我们就是需要借用编程语言,来启动一个子进程,让ffmpeg能够从stdin里读取数据,并且完成编码与推流的过程。

stdio标准输入输出

首先,我们先来进一步的了解一下stdio。在这里我们使用C语言作为本节的范例。如果我们熟悉了解Linux,应当知道Linux几乎所有的命令行,一个命令行完成一个工作,彼此之间通过管道命令进行联结,所以通常我们可以通过各种拼接组合,来完成很多很复杂的工作,非常的方便,在这里我们也是使用C语言这个老前辈语言,来描述一下stdio的使用,毕竟绝大部分的编码语言的入口方法是完全贴合于Unix设计哲学的。

stdio是三个IO流的统称,每个进程在启动时,都会打开这三个流,它们分别是:stdin标准输入流、stdout标准输出流、stderr标准错误流。

#include <stdio.h>

int main(int argc, char** argv)
{
    char data[1024];
    memset(data, 0, 1024);			// 初始化data数组
    
    fgets(data, 1024, stdin);		// 从stdin里读取一行内容,最多1024字节
    
    fputc('>', stdout);				// 向stdout里写一个字符:>,这里等同于printf(">");
    fputs(data, stdout);			// 将stdin里读取到的全部输出到stdout里去
    
    return 0;						// 返回0表示成功,其它非0表示错误码
}

测试一下:

# 编绎
gcc test.c -o test

# 测试一下
echo abc | ./test

# 控制台上将输出">abc",如果没有管道后面的内容,将只输出"abc",在这里我们的管道负责把前一个的输出交给我们的test应用程序的stdin,我们自己的应用程序里可以随意的处理stdin读取到的内容

主进程与子进程的交互

当我们在我们自己的应用程序里,启动一个子进程时,通常编程语言本身也会提供对于子进程的stdio的读写的API,各位请自行搜索“编码语言 子进程 stdin”等关键词,了解自己所使用的编程语言的相关API。

以Java为例,Process类为子进程运行时交互的API,它由Runtime.getRuntime().exec()方法来启动子进程从而获得Process子进程实例的引用,它提供了如下几个方法,用于与子进程的交互:

方法原型 返回值说明 说明
int waitFor() 子进程的返回码值,同exitValue() 等待子进程退出,如果子进程一直运行,此方法将一直阻塞
int exitValue() 0表示成功退出,非0表示错误码 获取子进程的返回码值,也就是main方法里return的整型值
InputStream getInputStream() 子进程的stdout 子进程的标准输出流,主进程用来读取子进程的输出
InputStream getErrorStream() 子进程的stderr 子进程的错误输出流,主进程用来读取子进程的错误输出
OutputStream getOutputStream() 子进程的stdin 子进程的标准输入流,主进程用来写入到子进程的标准输入

这是用于子进程运行时的关键方法,相信其它编程语言一样都有提供,请自行搜索查阅相关的文档。

ffmpeg子进程

我们在主应用程序里,通过运行ffmpeg -i - -c copy -f flv rtmp://1.2.3.4/ccav/test来创建一个子进程,-i -表示ffmpeg将从stdin里读取数据,而它的输出将是一个RTMP URL,也就是说,它将从stdin里读取到的视频数据,经过编码后发送到了RTMP服务器上去,然后我们的其它的应用程序就能够通过这个RTMP URL来播放这个实时的音视频了。我们需要做的事情如下:

  1. Process process = exec('ffmpeg -i -c copy -f flv rtmp://1.2.3.4/ccav/test');,通过命令行,创建子进程
  2. OutputStream stdin = process.getOutputStream();,得到子进程的stdin所对应的输出流
  3. stdin.write(...),每读取到一个RTP消息包,把数据体部分写到子进程的stdin,如此往复,就能够完成视频的编码与推流了。。。

线程管理

待补充

交流讨论

QQ群:808432702

空文件

简介

其于JT/T 1078标准实现的视频转播服务器 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/lgoodbook/jtt1078-video-server.git
git@gitee.com:lgoodbook/jtt1078-video-server.git
lgoodbook
jtt1078-video-server
jtt1078-video-server
master

搜索帮助