通过java使用ffmpeg录制rtmp视频流,文件无法读取


前段时间项目有一个录制rtmp流生成文件的需求,使用ffmpeg软件录制输出本地文件,再使用ffprobe读取视频文件信息保存到数据库。
当时遇到一个问题,部分文件录制完后,数据库中没有保存文件信息。经过日志分析,这部分文件在使用ffprobe读取时,抛出了Invalid data found when processing input。老司机一看就知道,八成是读取文件信息的时候,写入还没有完成。程序在读取文件时,确定录制是已经停止的,这种小问题,加个延时应该就解决了。于是在调用读取的地方,添加了一个定时。调用停止录制2000毫秒后再读取硬盘上的文件信息。

//发现录制应该结束
Process cmd = FFmpegProcessUtil.getFFmpegProcessUtil().getRecord(streamName);
cmd.destroy();
vertx.setTimer(2000,id->{
    FFmpegProbeResult probe = ShellUtil.probe(config.getString("ffprobe_path"), filePath);
    //保存文件信息
});

然而周冬雨眉头一皱,发现事情并不简单。【此处应有图,原谅我图床还没选好😂😂😂】
前两天部门一小哥说录制文件找不到。分析日志仍然是Invalid data found when processing input。感觉我之前的想法确实有点简单了。
而且小哥分析日志后,发现读取错误的文件其实是有共同点的。
这个record程序的流程如下。
使用一个定时器,每秒钟扫描nginx-rtmp端口。发现有数据流就加载到缓存中,提供外部接口,由上层业务系统调用接口控制录制的开始和关闭。ffmpeg录制结束有两个节点。

  1. 上层业务系统调用接口停止录制。
  2. 定时器扫描到nginx端口不存在这个数据流,停止录制。

小哥发现:所有读取错误的文件,均是第二种方式停止的录制。
经测试。在使用如下命令转存文件时,及时输入端数据已经终止,ffmpeg进程仍然存在。
ffmpeg -i rtmp://<ip>/live/streamName -f mp4 test.mp4
本地调试代码后发现当定时器检测到nginx端口的流已经不存在时,调用Process.destroy()方法并不能立即终止进程。

vertx.setPeriodic(1000 * 1, new RefreshStreamHandler());
RefreshStreamHandler.java
public void handle(Long event) { //其它未失效数据处理 loseMapKey.forEach(streamName -> { //其它处理 if(lostMap.get(streamName).getLcFile() != null){ logger.info("发现流结束时仍然有录制"); Process cmd = FFmpegProcessUtil.getFFmpegProcessUtil().getRecord(streamName); cmd.destroy(); FFmpegProbeResult probe = ShellUtil.probe(config.getString("ffprobe_path"), filePath); //保存文件信息 } //其它处理 }

当我在logger.info行添加断点调试时发现就算destroy()执行成功后。ffmpeg进程仍然存在,直到整个handle处理完成之后ffmpeg才消失。
于是对代码做了如下调整。

vertx.setPeriodic(1000 * 1, new RefreshStreamHandler());
vertx.setPeriodic(1000 * 1, new FileHandler());
public void handle(Long event) {
    //其它未失效数据处理
    loseMapKey.forEach(streamName -> {
        //其它处理
        if(lostMap.get(streamName).getLcFile() != null){
            logger.info("发现流结束时仍然有录制");
            Process cmd = FFmpegProcessUtil.getFFmpegProcessUtil().getRecord(streamName);
            cmd.destroy();
            recordList.add(filePath)
        }
        //其它处理
}
public void handle(Long event) { getNext(); } private void getNext() { if (recordList.size() > 0) { insertFile(recordList.get(0)); recordList.remove(0); getNext(); } }

使用另一个定时器去查找文件,由于两个定时器同时执行,当扫描端口的定时器查询到流结束时将流标识添加到list中,本次同时执行的文件扫描定时器因为已经执行完成,所以只能在下个周期处理当前周期添加到list中的任务。本地测试后放到服务器,检验OK。

代码表面的问题已经解决,但是更深入的原理的仍然困扰着我。rtmp源地址没有断的情况下,使用destroy()方法后立即查询文件详情为什么没有问题?ffmpeg为何会在handle()方法执行完成后就会停止?Runtimer执行shell的时候,Process和进程之间的关系等等。。。

要学的东西还有很多。留个坑,以后一定会补上后面三个问题


文章作者: 鱍鱍
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 鱍鱍 !
 上一篇
What is Vert.x What is Vert.x
两年前在公司开始负责一个底层api的项目。包含功能很简单,对接底层系统进行封装,提供一些功能实现的封装,对外提供http restful接口。主要要求是支持高并发和高稳定性。最开始的选型是用一些轻量级的http server框架。然后集成s
下一篇 
My first one My first one
2018-01-20 鱍鱍
  目录