同步操作将从 matrixy/jtt1078-video-server 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
基于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 |
经过阅读文档,以及测试,有如下问题或发现:
xx.h264
,可以使用PotPlayer
来播放。一般来说,视频的推流首先想到的是使用ffmpeg
,如果要集成ffmpeg
的sdk或是使用javacv
、jcodec
等视频编解码类的库,开发与学习维护的成本很高。在这里我们直接创建ffmpeg
子进程,通过它的参数设定,让ffmpeg
子进程直接从stdin中读取数据进行转码推流,通过这种方法,可以很好的达到推流的目的,并且不需要整合其它任何第三方的sdk,如果有后续的进一步的扩展需求(比如添加OSD字幕),可以直接通过修改创建子进程时的命令行来达到目的,非常的简单与易维护。
ffmpeg的输入端,可以是stdin、网络流、文件(图片或视频)等几乎所有常见的数据来源,也可以是同时多个输入,功能非常强大,我之前一直使用的是FIFO命名管道,现在可以抛弃对FIFO命名管道的依赖了,并且可以真正的跨平台运行了。
ffmpeg
的机器一台nginx-rtmp-module
或nginx-http-flv-module
(推荐)的Nginx
服务器,用于实时视频的转播PotPlayer
,可用于播放实时转播的RTMP
视频src/main/java/cn.org.hentai.jtt1078.test.VideoPushTest.java
),以及一个数据文件(src/main/resources/tcpdump.bin
),数据文件是通过工具采集的一段几分钟时长的车载终端发送上来的原始消息包,测试程序可以持续不断的、慢慢的发送数据文件里的内容,用来模拟车载终端发送视频流的过程。ffmpeg
的完整路径,替换掉app.properties
配置项。rtmp.format
已经设置为正确的RTMP地址格式。cn.org.hentai.jtt1078.app.VideoServerApp
,或对项目进行打包,执行mvn package
,执行java -jar jtt1078-video-server-1.0-SNAPSHOT.jar
来启动服务器端。VideoPushTest.java
,开始模拟车载终端的视频推送。start streaming to rtmp://....
的字样,后面即为实际推送的RTMP地址。PotPlayer
,按下Ctrl+U
,输入上面输出的RTMP地址,稍等片刻就能看到转播出来的视频了。Aliplayer
,效果很不理想,可以尝试使用video.js
。nginx-http-flv-module
+flv.js
来做网页播放,效果很不错,延迟低,不依赖于flash,值得推荐。ffmpeg
子进程合并音轨到视频流中去(我瞎想的)。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.out
(Java)就是stdout
,相应的System.in
就是我们的stdin
。基本上,我们就是需要借用编程语言,来启动一个子进程,让ffmpeg
能够从stdin
里读取数据,并且完成编码与推流的过程。
首先,我们先来进一步的了解一下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 -i - -c copy -f flv rtmp://1.2.3.4/ccav/test
来创建一个子进程,-i -
表示ffmpeg将从stdin
里读取数据,而它的输出将是一个RTMP URL,也就是说,它将从stdin
里读取到的视频数据,经过编码后发送到了RTMP服务器上去,然后我们的其它的应用程序就能够通过这个RTMP URL来播放这个实时的音视频了。我们需要做的事情如下:
Process process = exec('ffmpeg -i -c copy -f flv rtmp://1.2.3.4/ccav/test');
,通过命令行,创建子进程OutputStream stdin = process.getOutputStream();
,得到子进程的stdin所对应的输出流stdin.write(...)
,每读取到一个RTP消息包,把数据体部分写到子进程的stdin,如此往复,就能够完成视频的编码与推流了。。。待补充
QQ群:808432702
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。