前言:最近需要把rtsp的视频流保存为MP4文件(就是录制直播流)。刚开始用的javacv的FFmpegFrameGrabber和FFmpegFrameRecorder,但是声音流和视频流无法调整,声音和视频一直对不上而且录制的视频也有问题每次录10秒保存的视频却有18秒。毕竟不是专业做音视频的,很多东西也不了解,也是一步步摸索。最后也是向位大佬询问,也给我了很好建议:1.是调用ffmpeg命令行完成转码。2.增加中间件srs,使用srs拉去rtsp流数据并录制为ts文件,再使用java调用ffmpeg将ts列表合成文件。大佬比较建议使用第一种方案,因为srs的Dvr录制不稳定。ffmpeg命令行稳定点。ok,下面就开始分享代码。
-
环境工具准备
1. ffmpeg的程序和一个稳定的rtsp流
这是我的ffmpeg的版本。
fmpeg version git-2019-10-11-71d9ae1 Copyright (c) 2000-2019 the FFmpeg developers built with gcc 9.2.1 (GCC) 20191010
-
工具类代码,说几个重点也是我踩了很久才出来的坑:
1.ffmpegPath一定要写绝对路径的地址(如 D:\ffmpeg\ffmpeg.exe).
2.command在add的时候一定是一个字符一个位置不能直接拼成一句,也不要在单独拼接空格。否则直接会抛出无效的指令
3.获取的输入流必须单独建立一个线程来进行结果的打印,如果直接转字节打印,结束方法就会无效。
4.输出流在次和dos发送指令时一定要刷新。import org.springframework.stereotype.Component; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * TODO: * * @Author: ZHANG * @create: 2021/8/27 16:11 */ @Component public class RtspToMP4 { public class In implements Runnable{ private InputStream inputStream; public In(InputStream inputStream) { this.inputStream = inputStream; } @Override public void run() { try { //转成字符输入流 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "gbk"); int len = -1; char[] c = new char[1024]; //读取进程输入流中的内容 while ((len = inputStreamReader.read(c)) != -1) { String s = new String(c, 0, len); System.out.print(s); } }catch (Exception e) { e.printStackTrace(); } } } public Process StartRecord(String ffmpegPath,String streamUrl, String FilePath){ ProcessBuilder processBuilder = new ProcessBuilder(); //定义命令内容 List<String> command = new ArrayList<>(); command.add(ffmpegPath); command.add("-rtsp_transport"); command.add("tcp"); command.add("-y"); command.add("-i"); command.add(streamUrl); command.add("-c"); command.add("copy"); command.add("-f"); command.add("mp4"); command.add(FilePath); processBuilder.command(command); System.out.println("脚本:" + command.toString()); //将标准输入流和错误输入流合并,通过标准输入流读取信息 processBuilder.redirectErrorStream(true); try { //启动进程 Process process = processBuilder.start(); System.out.println("开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()))); //获取输入流 InputStream inputStream = process.getInputStream(); Thread inThread = new Thread(new In(inputStream)); inThread.start(); return process; } catch (Exception e) { e.printStackTrace(); } return null; } public boolean stopRecord(Process process) { try { OutputStream os = process.getOutputStream(); os.write("q".getBytes()); // 一定要刷新 os.flush(); os.close(); } catch (Exception err) { err.printStackTrace(); return false; } return true; } }
-
在贴一下调用的代码
import com.beiLinCourt.common.result.Result; import com.beiLinCourt.common.task.RtspToMP4; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * TODO: * * @Author: ZHANG * @create: 2021/8/27 16:16 */ @Api(tags = "测试") @RestController @RequestMapping("/test") public class TestController { @Autowired private RtspToMP4 rtspToMP4; private Map<Integer,Process> map=new HashMap<>(); @ApiOperation(value = "开始录制") @GetMapping(value = "/Start") public Result<String> Start(Integer id,String FileName) { String ffmpegPath="D:\ffmpeg\ffmpeg.exe"; String streamUrl="rtsp://192.168.0.168/0"; String FilePath="E:\\data\\MP4\\"+FileName; Process process = rtspToMP4.StartRecord(ffmpegPath, streamUrl, FilePath); if(null!=process){ map.put(id,process); return Result.success(); } return Result.failed(); } @ApiOperation(value = "结束录制") @GetMapping(value = "/stop") public Result<String> stop(Integer id) { if(map.containsKey(id)){ Process process = map.get(id); if(null!=process){ rtspToMP4.stopRecord(process); return Result.success(); } } return Result.failed(); } }
-
启动项目,进入swagger测试
控制台日志
在调用结束接口
在看一下录制的视频文件
视频时间也对,声音的输出也正常。暂时也没有发现其他的什么Bug。 -
最后也是感谢大佬,给我提供了一个很好的思路。最后有什么错误的地方请指出,大家一起讨论,一起进步。