前言:最近需要把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;<span class="token keyword">public</span> <span class="token class-name">In</span><span class="token punctuation">(</span><span class="token class-name">InputStream</span> inputStream<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">this</span><span class="token punctuation">.</span>inputStream <span class="token operator">=</span> inputStream<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">//转成字符输入流</span> <span class="token class-name">InputStreamReader</span> inputStreamReader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InputStreamReader</span><span class="token punctuation">(</span>inputStream<span class="token punctuation">,</span> <span class="token string">"gbk"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> len <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> c <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">//读取进程输入流中的内容</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>len <span class="token operator">=</span> inputStreamReader<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">String</span> s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
-
在贴一下调用的代码
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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
-
-
启动项目,进入swagger测试
控制台日志
在调用结束接口
在看一下录制的视频文件
视频时间也对,声音的输出也正常。暂时也没有发现其他的什么Bug。 -
最后也是感谢大佬,给我提供了一个很好的思路。最后有什么错误的地方请指出,大家一起讨论,一起进步。
</article>