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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/eguid_1/article/details/51777716

本项目已在github上维护,具体源码请到github上查看:https://github.com/eguid/FFmpegCommandHandler4java

一、环境搭建

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>

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏新智元

GitHub榜首:最强目标检测平台Detectron2 ,基于PyTorch完全重构

虽然在某些特定的场景下计算机可以比人类更快、更精准的识别出目标,但实际上,由于各类物体在不同的观测角度、不同的光照成像、遮挡等因素的干扰,计算机视觉的图像识别迄...

3K50
来自专栏Java架构沉思录

Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!

本文主要介绍 Spring Cloud Config 基本概念、实践过的配置及遇到的问题进行剖析。关于如何启动运行配置中心可以参考官方 Demo。

9420
来自专栏Python无止境

Python 命令行之旅:深入 argparse(二)

在上一篇“深入 argparse (一)”的文章中,我们深入了解了 argparse 的包括参数动作和参数类别在内的基本功能,具备了编写一个简单命令行程序的能力...

7920
来自专栏mall学习教程

Spring Cloud Config:外部集中化配置管理

Spring Cloud Config 分为服务端和客户端两个部分。服务端被称为分布式配置中心,它是个独立的应用,可以从配置仓库获取配置信息并提供给客户端使用。...

7820
来自专栏Python无止境

Python 命令行之旅:使用 argparse 实现 git 命令

在前面三篇介绍 argparse 的文章中,我们全面了解了 argparse 的能力,相信不少小伙伴们都已经摩拳擦掌,想要打造一个属于自己的命令行工具。

8420
来自专栏古时的风筝

无意间做了个 web 版的 JVM 监控端(前后端分离 React+Spring Boot)

之前写了JConsole、VisualVM 依赖的 JMX 技术,然后放出了一个用纯 JMX 实现的 web 版本的 JConsole 的截图,今天源码来了。

10120
来自专栏c#Winform自定义控件系列

(八十五)c#Winform自定义控件-引用区块

GitHub:https://github.com/kwwwvagaa/NetWinformControl

8510
来自专栏Seebug漏洞平台

从 Masscan, Zmap 源码分析到开发实践

Zmap和Masscan都是号称能够快速扫描互联网的扫描器,十一因为无聊,看了下它们的代码实现,发现它们能够快速扫描,原理其实很简单,就是实现两种程序,一个发送...

14730
来自专栏FreeBuf

如何在Windows和Linux上搜索可利用的二进制文件或exe文件

Gtfo这款工具采用Python3开发,在Gtfo的帮助下,广大研究人员可以直接在命令行终端窗口中搜索GTFOBins和LOLBAS代码文件。

11130
来自专栏大数据成神之路

HBase调优|HBase + G1GC 性能调优

目前小米已经在线上开始大规模使用G1垃圾回收算法,在论坛中也看到一些朋友在讨论使用G1碰到的各种各样的问题,这里打算写一篇文章记录下调G1的一些经验。 先传送门...

18720

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励