搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)
- 欢迎大家积极开心的加入讨论群
群号:371249677 (点击这里进群)
一、环境搭建
1、安装ffmpeg
下载对应系统的ffmpeg安装包,个人采用windows平台进行开发,所以安装了windows版本(各平台ffmpeg命令都是一样的,无须纠结)
2、ffmpeg的命令
这里不在详述,在这里会用简单的命令即可,后面我会写篇专门介绍ffmpeg的命令的文章
二、使用Java实现ffmpeg的命令调用的接口化可管理
1、java解析ffmpeg命令解析及动态实现
这是rtmp直播流服务器的发布地址:rtmp://192.168.30.21/live/
如果新发布一个视频,可以增加一个应用名,比如
rtmp://192.168.30.21/live/test2
或者
rtmp://192.168.30.21/live/DahuaCamera
注释写的很全,这里就不在做过多叙述
- <span style="font-size:18px;"> /**
- * 通过解析参数生成可执行的命令行字符串;
- * name:应用名;input:接收地址;output:推送地址;fmt:视频格式;fps:视频帧率;rs:视频分辨率;disableAudio:是否开启音频
- *
- * @param paramMap
- * @return 命令行字符串
- */
- protected String getComm4Map(Map<String, Object> paramMap)
- {
- // -i:输入流地址或者文件绝对地址
- StringBuilder comm = new StringBuilder("ffmpeg -i ");
- // 是否有必输项:输入地址,输出地址,应用名
- if (paramMap.containsKey("input") && paramMap.containsKey("output")
- && paramMap.containsKey("name"))
- {
- comm.append(paramMap.get("input")).append(" ");
- // -f :转换格式,默认flv
- comm.append(" -f ").append(paramMap.containsKey("fmt") ? paramMap.get("fmt") : "flv").append(" ");
- // -r :帧率,默认25
- comm.append("-r ").append(paramMap.containsKey("fps") ? paramMap.get("fps") : "30").append(" ");
- // -s 分辨率 默认是原分辨率
- comm.append("-s ").append(paramMap.containsKey("rs") ? paramMap.get("rs") : "").append(" ");
- // -an 禁用音频
- comm.append("-an ").append(paramMap.containsKey("disableAudio") && ((Boolean)paramMap.get("disableAudio")) ? "-an" : "").append(" ");
- // 输出地址
- comm.append(paramMap.get("output"));
- //发布的应用名
- comm.append(paramMap.get("name"));
- //一个视频源,可以有多个输出,第二个输出为拷贝源视频输出,不改变视频的各项参数
- comm.append(" ").append(" -vcodec copy -f flv -an rtmp://192.168.30.21/live/test2");
- System.out.println(comm.toString());
- return comm.toString();
- }
- else
- {
- throw new RuntimeException("输入流地址不能为空!");
- }
- }</span>
2、执行ffmpmeg命令
2.1、上一步已经可以动态的创建ffmpeg的命令了,这一步我们要让命令执行
- <span style="font-size:18px;"> final Process proc = Runtime.getRuntime().exec(comm);
- System.out.println("执行命令----start commond");
- OutHandler errorGobbler = new OutHandler(proc.getErrorStream(), "Error");
- OutHandler outputGobbler = new OutHandler(proc.getInputStream(), "Info");
- errorGobbler.start();
- outputGobbler.start();</span>
2.2、在执行ffmpeg命令时必须开启两个输出线程(上面代码中的OutHandler类)
OutHandler类实现了Thread接口,并且重写了注销该线程的方法(用于关闭该线程)
具体实现是这样的:
- <span style="font-size:18px;">import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- /**
- * 用于输出命令行主进程的消息线程(必须开启,否则命令行主进程无法正常执行) 重要:该类重写了destroy方法,用于安全的关闭该线程
- *
- * @author eguid
- * @see OutHandler
- * @since jdk1.7
- */
- public class OutHandler extends Thread
- {
- // 控制线程状态
- volatile boolean status = true;
- BufferedReader br = null;
- String type = null;
- public OutHandler(InputStream is, String type)
- {
- br = new BufferedReader(new InputStreamReader(is));
- this.type = type;
- }
- /**
- * 重写线程销毁方法,安全的关闭线程
- */
- @Override
- public void destroy()
- {
- status = false;
- }
- /**
- * 执行输出线程
- */
- @Override
- public void run()
- {
- String msg = null;
- try
- {
- while (status)
- {
- if ((msg = br.readLine()) != null)
- {
- System.out.println(type + "消息:" + msg);
- }
- }
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- }
- </span>
2.3、实现统一关闭命令行主进程和关联的两个输出线程
现在命令行已经可以执行了,但是却没法关闭它和关联的两个输出线程,怎么办?
我们在上面代码中已经重写了输出线程的注销方法,只要能够得到这两个线程我们就能关闭它们;
命令行主进程也是同样,只需要获得该进程Process即可使用destroy()方法进行关闭。
2.3.1、实现(把主进程Process和两个OutHandler返回给上一级,让上一级统一存放并管理他们):
- <span style="font-size:18px;"> public ConcurrentMap<String, Object> push(Map<String, Object> paramMap)
- throws IOException
- {
- // 从map里面取数据,组装成命令
- String comm = getComm4Map(paramMap);
- ConcurrentMap<String, Object> resultMap = null;
- // 执行命令行
- final Process proc = Runtime.getRuntime().exec(comm);
- System.out.println("执行命令----start commond");
- OutHandler errorGobbler = new OutHandler(proc.getErrorStream(), "Error");
- OutHandler outputGobbler = new OutHandler(proc.getInputStream(), "Info");
- errorGobbler.start();
- outputGobbler.start();
- // 返回参数
- resultMap = new ConcurrentHashMap<String, Object>();
- resultMap.put("info", outputGobbler);
- resultMap.put("error", errorGobbler);
- resultMap.put("process", proc);
- return resultMap;
- }</span>
2.3.2、父级这样实现关闭主进程和两个输出线程(必须先关闭两个输出线程):
- <span style="font-size:18px;"> public void removePush(String pushId)
- {
- if (hd.isHave(pushId))
- {
- ConcurrentMap<String, Object> map = hd.get(pushId);
- //关闭两个线程
- ((OutHandler)map.get("error")).destroy();
- ((OutHandler)map.get("info")).destroy();
- System.out.println("停止命令-----end commond");
- //关闭命令主进程
- ((Process)map.get("process")).destroy();
- hd.delete(pushId);
- }
- }</span>
2.3.3、简单使用map存放Process和OutHandler
- <span style="font-size:18px;"> private static ConcurrentMap<String, ConcurrentMap<String, Object>> handlerMap = new ConcurrentHashMap<String, ConcurrentMap<String, Object>>(20);
- </span>
到这里,我们就可以做到动态创建、运行并关闭ffmpeg命令的功能
简单测试一下能不能正常发布视频流到rtmp直播流服务器
- <span style="font-size:18px;">//name:应用名;input:接收地址;output:推送地址;fmt:视频格式;fps:视频帧率;rs:视频分辨率;disableAudio:是否开启音频
- PushManager pusher = new PushManagerImpl();
- Map map=new HashMap();
- map.put("name", "test3");
- map.put("input", "rtsp://admin:[email protected]:37779/cam/realmonitor?channel=1&subtype=0");
- map.put("output", "rtmp://192.168.30.21/live/");
- map.put("fmt", "flv");
- map.put("fps", "25");
- map.put("rs", "640x360");
- map.put("disableAudio", true);
- //推送后会获得该处理器的id,通过该id可关闭推送流
- String id = pusher.push(map);
- Thread.sleep(100000);
- //关闭推送流
- pusher.removePush(id);</span>
版权声明:做好自己!--eguid温馨提示:本博客所有原创文章均采用知识共享署名-相同方式共享 3.0 中国大陆许可协议进行许可。如有转载请注明出处和作者名!
- 本文已收录于以下专栏:
- 实时流媒体技术