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

来源:互联网 发布:关80端口 编辑:程序博客网 时间:2024/06/16 13:04

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

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


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

    版权声明:做好自己!--eguid温馨提示:本博客所有原创文章均采用知识共享署名-相同方式共享 3.0 中国大陆许可协议进行许可。如有转载请注明出处和作者名!
    • 本文已收录于以下专栏:
    • 实时流媒体技术
    阅读全文
    0 0