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


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

posted @ 2016-06-28 17:50  eguid  阅读(2211)  评论(0编辑  收藏  举报