搭建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

注释写的很全,这里就不在做过多叙述

[java] view plain copy
  1. <span style="font-size:18px;">  /** 
  2.      * 通过解析参数生成可执行的命令行字符串; 
  3.      * name:应用名;input:接收地址;output:推送地址;fmt:视频格式;fps:视频帧率;rs:视频分辨率;disableAudio:是否开启音频 
  4.      *  
  5.      * @param paramMap 
  6.      * @return 命令行字符串 
  7.      */  
  8.     protected String getComm4Map(Map<String, Object> paramMap)  
  9.     {  
  10.         // -i:输入流地址或者文件绝对地址  
  11.         StringBuilder comm = new StringBuilder("ffmpeg -i ");  
  12.         // 是否有必输项:输入地址,输出地址,应用名  
  13.         if (paramMap.containsKey("input") && paramMap.containsKey("output")  
  14.             && paramMap.containsKey("name"))  
  15.         {  
  16.             comm.append(paramMap.get("input")).append(" ");  
  17.             // -f :转换格式,默认flv  
  18.             comm.append(" -f ").append(paramMap.containsKey("fmt") ? paramMap.get("fmt") : "flv").append(" ");  
  19.             // -r :帧率,默认25  
  20.             comm.append("-r ").append(paramMap.containsKey("fps") ? paramMap.get("fps") : "30").append(" ");  
  21.             // -s 分辨率 默认是原分辨率  
  22.             comm.append("-s ").append(paramMap.containsKey("rs") ? paramMap.get("rs") : "").append(" ");  
  23.             // -an 禁用音频   
  24.             comm.append("-an ").append(paramMap.containsKey("disableAudio") && ((Boolean)paramMap.get("disableAudio")) ? "-an" : "").append(" ");  
  25.             // 输出地址  
  26.             comm.append(paramMap.get("output"));  
  27.             //发布的应用名  
  28.             comm.append(paramMap.get("name"));  
  29.             //一个视频源,可以有多个输出,第二个输出为拷贝源视频输出,不改变视频的各项参数  
  30.             comm.append(" ").append(" -vcodec copy -f flv -an rtmp://192.168.30.21/live/test2");  
  31.             System.out.println(comm.toString());  
  32.             return comm.toString();  
  33.         }  
  34.         else  
  35.         {  
  36.             throw new RuntimeException("输入流地址不能为空!");  
  37.         }  
  38.   
  39.     }</span>  

2、执行ffmpmeg命令

2.1、上一步已经可以动态的创建ffmpeg的命令了,这一步我们要让命令执行


[java] view plain copy
  1. <span style="font-size:18px;">  final Process proc = Runtime.getRuntime().exec(comm);  
  2.         System.out.println("执行命令----start commond");  
  3.         OutHandler errorGobbler = new OutHandler(proc.getErrorStream(), "Error");  
  4.         OutHandler outputGobbler = new OutHandler(proc.getInputStream(), "Info");  
  5.   
  6.         errorGobbler.start();  
  7.         outputGobbler.start();</span>  

2.2、在执行ffmpeg命令时必须开启两个输出线程(上面代码中的OutHandler类)

OutHandler类实现了Thread接口,并且重写了注销该线程的方法(用于关闭该线程)

具体实现是这样的:

[java] view plain copy
  1. <span style="font-size:18px;">import java.io.BufferedReader;  
  2. import java.io.IOException;  
  3. import java.io.InputStream;  
  4. import java.io.InputStreamReader;  
  5.   
  6. /** 
  7.  * 用于输出命令行主进程的消息线程(必须开启,否则命令行主进程无法正常执行) 重要:该类重写了destroy方法,用于安全的关闭该线程 
  8.  *  
  9.  * @author eguid 
  10.  * @see OutHandler 
  11.  * @since jdk1.7 
  12.  */  
  13.   
  14. public class OutHandler extends Thread  
  15. {  
  16.     // 控制线程状态  
  17.     volatile boolean status = true;  
  18.   
  19.     BufferedReader br = null;  
  20.   
  21.     String type = null;  
  22.   
  23.     public OutHandler(InputStream is, String type)  
  24.     {  
  25.         br = new BufferedReader(new InputStreamReader(is));  
  26.         this.type = type;  
  27.     }  
  28.   
  29.     /** 
  30.      * 重写线程销毁方法,安全的关闭线程 
  31.      */  
  32.     @Override  
  33.     public void destroy()  
  34.     {  
  35.         status = false;  
  36.     }  
  37.   
  38.     /** 
  39.      * 执行输出线程 
  40.      */  
  41.     @Override  
  42.     public void run()  
  43.     {  
  44.         String msg = null;  
  45.         try  
  46.         {  
  47.             while (status)  
  48.             {  
  49.   
  50.                 if ((msg = br.readLine()) != null)  
  51.                 {  
  52.                     System.out.println(type + "消息:" + msg);  
  53.                 }  
  54.             }  
  55.         }  
  56.         catch (IOException e)  
  57.         {  
  58.             e.printStackTrace();  
  59.         }  
  60.     }  
  61.   
  62. }  
  63. </span>  

2.3、实现统一关闭命令行主进程和关联的两个输出线程

现在命令行已经可以执行了,但是却没法关闭它和关联的两个输出线程,怎么办?

我们在上面代码中已经重写了输出线程的注销方法,只要能够得到这两个线程我们就能关闭它们;

命令行主进程也是同样,只需要获得该进程Process即可使用destroy()方法进行关闭。

2.3.1、实现(把主进程Process和两个OutHandler返回给上一级,让上一级统一存放并管理他们):

[java] view plain copy
  1. <span style="font-size:18px;"public ConcurrentMap<String, Object> push(Map<String, Object> paramMap)  
  2.         throws IOException  
  3.     {  
  4.         // 从map里面取数据,组装成命令  
  5.         String comm = getComm4Map(paramMap);  
  6.         ConcurrentMap<String, Object> resultMap = null;  
  7.         // 执行命令行  
  8.         final Process proc = Runtime.getRuntime().exec(comm);  
  9.         System.out.println("执行命令----start commond");  
  10.         OutHandler errorGobbler = new OutHandler(proc.getErrorStream(), "Error");  
  11.         OutHandler outputGobbler = new OutHandler(proc.getInputStream(), "Info");  
  12.   
  13.         errorGobbler.start();  
  14.         outputGobbler.start();  
  15.         // 返回参数  
  16.         resultMap = new ConcurrentHashMap<String, Object>();  
  17.         resultMap.put("info", outputGobbler);  
  18.         resultMap.put("error", errorGobbler);  
  19.         resultMap.put("process", proc);  
  20.         return resultMap;  
  21.     }</span>  

2.3.2、父级这样实现关闭主进程和两个输出线程(必须先关闭两个输出线程):

[java] view plain copy
  1. <span style="font-size:18px;"public void removePush(String pushId)  
  2.     {  
  3.         if (hd.isHave(pushId))  
  4.         {  
  5.             ConcurrentMap<String, Object> map = hd.get(pushId);  
  6.             //关闭两个线程  
  7.             ((OutHandler)map.get("error")).destroy();  
  8.             ((OutHandler)map.get("info")).destroy();  
  9.               
  10.             System.out.println("停止命令-----end commond");  
  11.             //关闭命令主进程  
  12.             ((Process)map.get("process")).destroy();  
  13.             hd.delete(pushId);  
  14.         }  
  15.     }</span>  

2.3.3、简单使用map存放Process和OutHandler

[java] view plain copy
  1. <span style="font-size:18px;"private static ConcurrentMap<String, ConcurrentMap<String, Object>> handlerMap = new ConcurrentHashMap<String, ConcurrentMap<String, Object>>(20);  
  2. </span>  

到这里,我们就可以做到动态创建、运行并关闭ffmpeg命令的功能

简单测试一下能不能正常发布视频流到rtmp直播流服务器

[java] view plain copy
  1. <span style="font-size:18px;">//name:应用名;input:接收地址;output:推送地址;fmt:视频格式;fps:视频帧率;rs:视频分辨率;disableAudio:是否开启音频  
  2.         PushManager pusher = new PushManagerImpl();  
  3.         Map map=new HashMap();  
  4.         map.put("name""test3");  
  5.         map.put("input""rtsp://admin:[email protected]:37779/cam/realmonitor?channel=1&subtype=0");  
  6.         map.put("output""rtmp://192.168.30.21/live/");  
  7.         map.put("fmt""flv");  
  8.         map.put("fps""25");  
  9.         map.put("rs""640x360");  
  10.         map.put("disableAudio"true);  
  11.         //推送后会获得该处理器的id,通过该id可关闭推送流  
  12.         String id = pusher.push(map);  
  13.         Thread.sleep(100000);  
  14.         //关闭推送流  
  15.         pusher.removePush(id);</span>  


通过输出线程输出的消息可以看到直播流发布成功了
搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)

版权声明:做好自己!--eguid温馨提示:本博客所有原创文章均采用知识共享署名-相同方式共享 3.0 *许可协议进行许可。如有转载请注明出处和作者名!