搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)

时间:2023-01-11 12:22:26

    欢迎大家积极开心的加入讨论群

一、环境搭建

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:admin@192.168.2.236: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>

通过输出线程输出的消息可以看到直播流发布成功了

搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)