解决java+shell+ffmpeg视频切片卡死问题

一、写在前面

最近项目上有这么一个需求:

利用ffmpeg将用户上传的视频(MP4)转换为.ts文件后切片,再上传到minIO上。

因为之前都没使用过ffmpeg和minIO,所以哼哧哼哧百度了代码复制过来实现这个需求。

自测的时候,都是使用一些小的视频文件(没有超过10M的),所以功能很正常。进入到测试阶段后,发现稍微大点的文件(100M以上)就挂了。

查看日志,发现进入到java调用shell脚本,去进行.ts文件切片时就卡住了,等待一段时间后,前端就报错了。

于是乎,展开了这个问题的“漫长”调查之旅。

二、问题分析及排查

分析前,先将ffmpeg处理MP4的shell脚本里的命令分享一下

#!/bin/bash

# 执行两次命令,比一次命令效率高
# 转换为ts文件
ffmpeg -i $1.mp4 -c copy -bsf:v h264_mp4toannexb $1.ts
# 将ts切片
ffmpeg -i $1.ts -c copy -map 0 -f segment -segment_time 30 -segment_list $1.m3u8 $1_%05d.ts

# 删除mp4文件
rm -f $1.mp4
# 删除ts文件
rm -f $1.ts

参数说明:

$1:这个参数是java调用shell脚本时传进来的参数,是MP4在服务器上的路径。

其他ffmpeg的参数说明,可以去百度查找,这里就不做分享了。

1、首先怀疑的是切片命令是否有问题

之前的命令为:ffmpeg -i $1.ts -c copy -map 0 -f segment -segment_time 30 -segment_list $1.m3u8 $1_%05d.ts

修改后命令为:ffmpeg -i $1.ts -c copy -map 0 -f segment -segment_list $1.m3u8 -segment_time 30 $1_%05d.ts

区别就是将【-segment_list $1.m3u8】与【-segment_time 30】调换了位置,可问题未得到解决。

2、怀疑java调用shell脚本的代码是否有问题

照例,先将之前的代码分享一下

    /**
     * 运行shell并获得结果,注意:如果sh中含有awk,一定要按new String[]{"/bin/sh","-c",shStr}写,才可以获得流
     * 
     * @param shStr 需要执行的shell
     * @return
     */
    public List<String> runShell(String shStr) throws Exception {
        log.info("RemoteShellUtil runShell start");
        log.info("RemoteShellUtil runShell shStr=" + shStr);
        Process process = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", shStr}, null, null);
        process.waitFor();
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        List<String> result = new ArrayList<String>();
        String line;
        while ((line = br.readLine()) != null) {
            result.add(line);
        }
        log.info("RemoteShellUtil runShell end");
        return result;
    }

参数说明:

shStr:需要调用shell的命令及参数拼接字符串,比如【". ./videoFfmpegFormat.sh " + fullPath】,其中【fullPath】就是shell脚本中【$1】

修改后的代码

    /**
     * 执行shell脚本
     *
     * @author: caip
     * @date: 2021-05-23 09:11:40
     * @param shellPath shell脚本路径
     * @param params 参数数组
     * @return
     */
    public List<String> runShell(String shellPath, String... params) {
        log.info("RemoteShellUtil runShell start");
        List<String> strList = new ArrayList<String>();
        ProcessBuilder pb = new ProcessBuilder(params);
        pb.directory(new File(shellPath));
        String s = null;
        try {
            Process p = pb.start();
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((s = stdInput.readLine()) != null) {
                log.error(s);
                strList.add(s);
            }
            while ((s = stdError.readLine()) != null) {
                log.error(s);
                strList.add(s);
            }
            this.dealStream(p);
            p.waitFor();
        } catch (Exception e) {
            String msg = "shell脚本执行错误!" + e.getMessage();
            log.error(msg, e);
            strList.add(msg);
        }
        log.info("RemoteShellUtil runShell end");
        return strList;
    }

参数说明:

shellPath:shell脚本路径;

params:参数数组。这里包括了shell文件名,参数,比如:

String[] params = new String[] {"./videoFfmpegFormat.sh", fullPath};

虽然上面的代码,相比于最先的代码有了很大的修改,但其实问题仍然没有得到解决。

3、最终解决方案

    /**
     * 执行shell脚本
     *
     * @author: caip
     * @date: 2021-05-23 09:11:40
     * @param shellPath shell脚本路径
     * @param params 参数数组
     * @return
     */
    public List<String> runShell(String shellPath, String... params) {
        log.info("RemoteShellUtil runShell start");
        List<String> strList = new ArrayList<String>();
        ProcessBuilder pb = new ProcessBuilder(params);
        // 这是重中之重
        pb.redirectErrorStream(true);
        pb.directory(new File(shellPath));
        String s = null;
        try {
            Process p = pb.start();
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            while ((s = stdInput.readLine()) != null) {
                log.info(s);
                strList.add(s);
            }
            // this.dealStream(p);
            p.waitFor();
        } catch (Exception e) {
            String msg = "shell脚本执行错误!" + e.getMessage();
            log.error(msg, e);
            strList.add(msg);
        }
        log.info("RemoteShellUtil runShell end");
        return strList;
    }

上面代码里的【pb.redirectErrorStream(true);】是解决此篇文章问题的关键,这个方法的解释是这样的:

告诉此进程生成器是否合并标准错误和标准输出。如果此属性为true,则通过子进程所产生的任何错误输出随后由该对象的start()方法启动将与标准输出合并,这样既可以用Process.getInputStream()方法来读取。此使得更容易与对应的输出相关的错误消息。初始值是false。

意思就是,如果设置为true,那将在执行shell脚本时,无论正确的消息,还是错误的消息,都一起通过【p.getInputStream()】返回。

那么,一开始使用ffmpeg切片ts文件卡住(其实是阻塞)的问题,其实就是遇到错误消息时线程阻塞的问题。阻塞后,无法得到正常的返回(其实查看系统日志可以看到错误消息),才导致莫名其妙的不了了之。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
你可以通过以下步骤来实现Java与RTSP、FFmpeg、HTML和Nginx的结合来实现视频实时播放的监控系统: 1. 首先,你需要使用Java来创建一个监控系统的后端服务。你可以使用Java的网络编程库来监听RTSP流并将其解码。可以使用开源的库,例如JRTSP或者Xuggler来处理RTSP流,并将其转换为可供播放的视频流。 2. 接下来,你需要使用FFmpeg来处理视频流。FFmpeg是一个强大的多媒体处理工具,可以用于转码、解码、编码等操作。你可以使用FFmpeg来解码RTSP流,并将其转换为HTML5支持的视频格式,例如HLS(HTTP Live Streaming)或者MPEG-DASH(Dynamic Adaptive Streaming over HTTP)。 3. 在前端方面,你可以使用HTML和JavaScript来创建一个简单的视频播放器。你可以使用HTML5的<video>标签来嵌入视频,并使用JavaScript来控制视频的播放、暂停等操作。你可以使用一些开源的视频播放器库,例如video.js或者plyr来简化开发过程。 4. 最后,你可以使用Nginx作为反向代理服务器来提供视频流的分发和缓存功能。Nginx可以将视频流从后端服务器转发给前端浏览器,并且可以缓存视频文件以提高性能和可靠性。你可以配置Nginx来支持HLS或者MPEG-DASH协议,并且可以使用Nginx的HTTP模块来进行性能优化和安全加固。 综上所述,通过将Java、RTSP、FFmpeg、HTML和Nginx结合起来,你可以实现一个监控视频的实时播放系统。这个系统可以从RTSP流中提取视频数据,经过FFmpeg处理后,通过HTML和JavaScript在浏览器中进行播放,并且可以使用Nginx提供性能优化和缓存支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值