这里继续交代背景啊。
首先我们前端推拉rtmp流,服务端用nginx+rtmp服务器,粗略的实现视频直播已经完成的。然后毕竟最终目标是多人视频,所以这个实测1人直播几个人同时看倒是没问题。甚至两个人推拉实现两个人视频通话也是没问题。
但是!!!!问题来了,超过三个人也就是当四个人一起视频就出问题了,卡不卡的不说,实现的逻辑是每个人推自己的视频流,同时拉另外三个人的视频流。咱也不知道是服务器不行还是咋的,反正每个人拉其余三个人的视频就会出现某个视频是拉不出来的,也就是黑屏状态。
这个和我们老板九个人视频聊天的想法差的有点远,所以只能从头更改策略了啊。然后我们领导发话了,rtsp延迟低,虽然会丢帧但是小问题啊,所以换吧。
于是乎辛辛苦苦查阅rtsp,找了个已有的框架easyDarwin,按照官网的教程下载了个包,直接双击.exe文件启动(我是windows系统),然后服务端就这么搭起来了啊。
搭起服务端第一件事就是测试,所以我先用ffmpeg随便推了个视频试了下,没问题的啊。
这简单的三步操作确定了我平台搭建完毕。然后本来我以为这就是结束了,谁想到意外一个接一个来。
首先我们前端是app,而且不是原生写的,用的uniapp、所以不支持推rtsp流。也就是只能退rtmp流啊。所以第一个问题:这里需要前端推流到rtmp服务器,然后我这边从rtmp服务器上获取这个流再转流成rtsp然后再推到easyDarwin上(我也不知道为什么要这么sb的操作,只能说领导最大,说什么是什么)
然后开始转流吧,我当时做rtmp就涉及到了这个问题。用的javacv的工具包,我记得是可以实现的,于是乎百度。。百度,,,百度!!!
说真的,一点不夸张,百度前十页的差不多都看了,人家都是rtsp转rtmp,像我们这样反过来的一个demo都没有。简直气笑了。
然后也试图用差不多的原理来实现,但是rtmp转rtmp实现了,rtsp转rtmp实现了。rtmp转rtsp就是一直报错。现在回想起来也不确定是FFmpegFrameGrabber不支持还是哪里细节处理有问题,反正就是没实现用工具类直接转流。
emmmmm...一条路不通换下一个,再查查有没有别的办法。
最后发现一个不错的思路,直接用命令行总不会出问题了吧?所以又用了java中process进程类,直接到ffmpeg的控制台去打命令行。下面是实现代码:
public class RTSPTool extends Thread {
String line = "";
private String params;
private Process process;
// ffmpeg位置,最好写在配置文件中
String ffmpegPath = "D:/demo/ffmpeg/bin/";
public RTSPTool(String params) {
super();
this.params = params;
}
@Override
public void run() {
try {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// cmd命令拼接,注意命令中存在空格
String command = ffmpegPath; // ffmpeg位置
command += "ffmpeg"; // ffmpeg开头,-re代表按照帧率发送,在推流时必须有
command += " -i rtmp://192.168.10.189:1935/live/" + params;
// command += " -threads 2 -rtsp_transport tcp -vcodec libx264 -max_muxing_queue_size 9999 -s 320x240";
command += " -threads 2 -vcodec libx264 -max_muxing_queue_size 9999 -s 320x240";
command += " -f rtsp rtsp://192.168.10.189/" + params; // 指定推送服务器,-f:指定格式
System.out.println("ffmpeg推流命令:" + command);
// 运行cmd命令,获取其进程
process = Runtime.getRuntime().exec(command);
// 输出ffmpeg推流日志
BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while((line = br.readLine()) != null) {
System.out.println("视频推流信息[" + line + "]");
}
if(process!=null)process.destroy();
System.err.println("<<<<<<<<<<<<<<<<<<<<销毁" + params + "的转流进程!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先一点一点说:
这个command的命令是一点点完善的,获取rtmp流,然后转化成rtsp流,中间threads 2 -vcodec libx264 -max_muxing_queue_size 9999 -s 320x240这一排的参数,都是一点点在百度找到的。如果不加libx264编码,前端拉倒流无法解析。不弱不加容量9999,会转流一会儿自动失败,失败原因管道破裂。如果不加threads 2 会报什么2 faram的错,反正简单的几个命令是一次次尝试,搜索出来的结果。
然后确定命令是对的,于是放心大胆的跑起来了,联调。
结果发现第一次跑就出了问题,没有正常一帧一帧转码,而且只有一个开头就卡住了,天地良心,这两周是我第一次接触视频流之类的,一脸懵逼的去百度,去群里问人,中间多波折不说,最后得出结论,不继续往下走可能是因为视频源(rtmp拉流)有问题,emmmm..再专门测试一下发现确实,大多数这个时候vlc拉流也拉不到。
咱也不知道是因为推流需要时间,还是网速问题,反正大多数都说卡在这是视频源的问题,但是视频源有啥问题咱也不知道,所以这也不好解决。
最坑的是这样不属于报错,所以上面代码中的输出控制台打印语句的 while((line = br.readLine()) != null) 是一直阻塞的,因为他还等着控制台往里面写东西呢,所以这个程序也就卡死在这了,所以就没有所以了,程序上说这个线程就此死着了,不可能结束了,系统上说这个子进程也是一直后台就这么着了,最主要的是转流这个行为也就彻彻底底的失败了。(哪怕后来rtmp可以拉倒流了这个进程也不会继续走了)
最特么神奇的一点是这一块的内容百度都几乎没有结果!可能我们这么沙雕处理的没有吧。然后搜索类似的,发散思维,最后又是我们领导拍板:用线程监控(这之前还有个问题,uniapp不支持netty-socketio)!然后我说好,线程监控,怎么判断没正常转流呢?控制台输出语句不刷新了。于是乎又开始写进程。隔五s获取一下line的值。如果五秒不变说明卡死了,那么就销毁之前的进程,重新再开一个(目前能想到的唯一办法)。苦哈哈的把监控线程加好了,如下代码:
public class RTSPTool extends Thread {
String line = "";
private String params;
private Process process;
// ffmpeg位置,最好写在配置文件中
String ffmpegPath = "D:/demo/ffmpeg/bin/";
public RTSPTool(String params) {
super();
this.params = params;
}
@Override
public void run() {
try {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// cmd命令拼接,注意命令中存在空格
String command = ffmpegPath; // ffmpeg位置
command += "ffmpeg"; // ffmpeg开头,-re代表按照帧率发送,在推流时必须有
command += " -i rtmp://192.168.10.201:1935/live/" + params;
// command += " -threads 2 -rtsp_transport tcp -vcodec libx264 -max_muxing_queue_size 9999 -s 320x240";
command += " -threads 2 -vcodec libx264 -max_muxing_queue_size 9999 -s 320x240";
command += " -f rtsp rtsp://192.168.10.201/" + params; // 指定推送服务器,-f:指定格式
System.out.println("ffmpeg推流命令:" + command);
// 运行cmd命令,获取其进程
process = Runtime.getRuntime().exec(command);
// 输出ffmpeg推流日志
BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
new Thread(new Runnable() {
@Override
public void run() {
//这个控制台没被注销则检查。
while(true) {
String string = line;
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
}
//10s一判断
if(line.equals(string)){
process.destroy();
if(!LiveApplication.urls.contains(params)) {
System.out.println(params+"已退出登录!转流结束!");
break;
}
System.err.println(params+"转流失败,重新尝试转流!");
new RTSPTool(params).start();
break;
}
}
}
}).start();
while((line = br.readLine()) != null) {
System.out.println("视频推流信息[" + line + "]");
}
if(process!=null)process.destroy();
System.err.println("<<<<<<<<<<<<<<<<<<<<销毁" + params + "的转流进程!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
中间涉及到的业务逻辑很少,因为本来就是demo。反正就是控制台不继续转流则判断是用户退出了还是出错了!如果是用户退出了则结束这个进程。如果是转流失败了则结束这个进程并且重试。
至此,我这里能保证的就是最终能转流。但是中间时间不定。但是我目前能做的也就这样了。
你以为我们这个demo就这么结束了?不不不,下面的问题就是我这边实现了最终转流成功,但是前端是在用户加入房间就开始拉流的,这里有个问题,如果前端开始拉流的时候不知道有没有流,这个时候去拉,如果没流的话,哪怕后来有流了也不会再拉一遍的。其实和vlc一个道理,这里理解不了的建议去实际操作一下啊。
反正目前项目是卡在这里了,说实话我觉得rtsp实现九个人视频聊天不卡也是不太可能啊~反正不管怎么样我暂时就记录到这里了。
这篇笔记其实我吐槽比较多,但是知识点也不少啊,毕竟都是血泪的教训,如果稍微帮到你了记得点个喜欢点个关注,这方面的知识这几天狂补,如果有想交流或者指教的亲可以留言或者私信啊。顺便祝大家工作顺顺利利,生活健健康康!如果事件有后续我会续写的啊~