zoukankan      html  css  js  c++  java
  • 搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)

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

    一、环境搭建

    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>


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

  • 相关阅读:
    34. 在排序数组中查找元素的第一个和最后一个位置
    153. 寻找旋转排序数组中的最小值
    278. 第一个错误的版本
    540. 有序数组中的单一元素
    744. 寻找比目标字母大的最小字母
    69. x 的平方根
    763. 划分字母区间
    53. 最大子序和
    665. 非递减数列
    Zabbix探索:Agent配置中Hostname错误引起的Agent.Ping报错
  • 原文地址:https://www.cnblogs.com/eguid/p/6821611.html
Copyright © 2011-2022 走看看