rtsp 转 httpflv
1060人浏览 / 0人评论
参考
https://blog.csdn.net/Candyz7/article/details/126741970
https://tools.jbritian.com/link/2
nginx
下载带 nginx-flv-module 的 nginx
https://gitee.com/isyuesen/nginx-flv-file
或者
https://pan.jbritian.com/share/764e22d2fa2e4f6c95ae2be571f42b97
启动
nginx.exe
java 权限认证
stausCode 如果不等于200会拒绝I/O。
@RestController
@RequestMapping("/")
public class RSTPController {
@PostMapping("/auth")
public void getVideo(@RequestParam("token") String token, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
if (token.equals("tokenValue")) {
httpServletResponse.setStatus(200);
} else {
// 拒绝服务
httpServletResponse.setStatus(500);
}
}
}
ffmepg
下载
https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z
执行 ffmepg 命令
ffmpeg -re -rtsp_transport tcp -i "rtsp://171.8.64.184:9100/dss/monitor/param/cameraid=1000016%2430%26substream=1?token=4084&channelId=1000016$1$0$30&trackId=0" -f flv -vcodec h264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 640*360 -q 10 "rtmp://127.0.0.1:1935/liveapp/test?token=tokenValue"
采用 java 代码去执行 ffmepg 命令
依赖:
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.2</version>
</dependency>
代码:
public class App {
public static void main( String[] args ) throws IOException, InterruptedException {
String name = "test";
// rtsp地址
String rtspDir = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
// rtmp地址
String rtmpDir = "rtmp://192.168.0.140:1935/liveapp/" + name + "?token=tokenValue";
String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
ProcessBuilder pb = new ProcessBuilder(ffmpeg,
"-re",
"-rtsp_transport",
"tcp",
"-i",
rtspDir,
"-f",
"flv",
"-vcodec",
"h264",
"-vprofile",
"baseline",
"-acodec",
"aac",
"-ar",
"44100",
"-strict",
"-2",
"-ac",
"1",
"-f",
"flv",
"-s",
"640*360",
"-q",
"10",
rtmpDir
);
pb.inheritIO().start().waitFor();
}
}
测试
播放地址
http://127.0.0.1:18080/live?port=1935&app=liveapp&stream=test&token=tokenValue
前端使用flv.js播放
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>播放http-flv</title>
</head>
<body>
<video id="videoElement" muted="muted"></video>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
<script>
if (flvjs.isSupported()) {
const videoElement = document.getElementById('videoElement');
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://127.0.0.1:18080/live?port=1935&app=liveapp&stream=test&token=tokenValue'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>
</body>
</html>
实例
项目结构
项目文件
pom.xml
<properties>
<java.version>8</java.version>
<skipTests>true</skipTests>
<javacv.version>1.5.2</javacv.version>
<system.windowsx64>windows-x86_64</system.windowsx64>
</properties>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.2</version>
</dependency>
<!-- javacv+javacpp核心库-->
<!--<dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>javacv</artifactId>-->
<!-- <version>1.5.2</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>javacpp</artifactId>-->
<!-- <version>1.5.2</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>opencv</artifactId>-->
<!-- <version>4.1.2-1.5.2</version>-->
<!-- <classifier>windows-x86_64</classifier>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>ffmpeg</artifactId>-->
<!-- <version>4.2.1-1.5.2</version>-->
<!-- <classifier>windows-x86_64</classifier>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>ffmpeg</artifactId>-->
<!-- <version>4.2.1-1.5.2</version>-->
<!-- <classifier>linux-x86_64</classifier>-->
<!--</dependency>-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8888
spring:
freemarker:
template-loader-path: classpath:/templates/
suffix: .ftl
charset: utf-8
cache: false
expose-request-attributes: true
expose-session-attributes: true
StaticCache.java
import java.util.HashMap;
import java.util.Map;
public class StaticCache {
private static Map<String, Object> cache = new HashMap<>();
public static void put(String key, Object value) {
cache.put(key, value);
}
public static Object get(String key) {
return cache.get(key);
}
public static void delete(String key) {
cache.remove(key);
}
public static boolean containsKey(String key) {
return cache.containsKey(key);
}
}
IndexController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
/**
* @Author FengHao
* @Date 2023/9/18
* @Description:
**/
@Controller
public class IndexController {
@RequestMapping(value = {"", "/"})
public String index(Model model) throws IOException {
//Path path = Paths.get("rtspUrls.txt");
Path path = Paths.get("D:\\project_temp\\rtspUrls.txt");
byte[] data = Files.readAllBytes(path);
String result = new String(data, StandardCharsets.UTF_8);
result = result.replaceAll("\r\n", "");
result = result.replaceAll(" ", "");
String[] split = result.split(",");
model.addAttribute("rtspUrls", Arrays.asList(split));
return "index";
}
}
TestController.java
import org.apache.commons.lang.StringEscapeUtils;
import org.bytedeco.javacpp.Loader;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author FengHao
* @Date 2023/9/18
* @Description:
**/
@RestController
@RequestMapping("/rstp")
public class TestController {
private static final String tokenValue = "123";
@PostMapping("/auth")
public void getVideo(@RequestParam("token") String token, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
if (token.equals(tokenValue)) {
httpServletResponse.setStatus(200);
} else {
// 拒绝服务
httpServletResponse.setStatus(500);
}
}
@PostMapping("/play")
public String play(@RequestParam("rtspDir") String rtspDir) throws IOException, InterruptedException {
rtspDir = StringEscapeUtils.unescapeHtml(rtspDir);
String ip = getIp(rtspDir);
String name = ip.replace(".", "");
// rtmp地址
String rtmpDir = "rtmp://127.0.0.1:1935/liveapp/" + name + "?token=" + tokenValue;
String key = ip + "playUrl";
String playUrl = "http://127.0.0.1:18080/live?port=1935&app=liveapp&stream=" + name + "&token=" + tokenValue;
boolean containsKey = StaticCache.containsKey(key);
if (!containsKey) {
System.err.println("//////////////////////////// " + ip + " start ////////////////////////////////");
String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
ProcessBuilder pb = new ProcessBuilder(ffmpeg,
"-re",
"-rtsp_transport",
"tcp",
"-i",
rtspDir,
"-threads",
"5",
"-preset",
"ultrafast",
"-max_delay",
"100000",
"-f",
"flv",
"-vcodec",
"h264",
"-vprofile",
"baseline",
"-acodec",
"aac",
"-ar",
"44100",
"-strict",
"-2",
"-ac",
"1",
"-f",
"flv",
"-s",
"640*360",
"-q",
"10",
rtmpDir
).inheritIO();
Process process = pb.start();
//process.waitFor();
StaticCache.put(key, playUrl);
StaticCache.put(ip, process);
} else {
playUrl = StaticCache.get(key).toString();
}
return playUrl;
}
@GetMapping("/stop")
public void stop(@RequestParam("rtspDir") String rtspDir) {
String ip = getIp(rtspDir);
boolean containsKey = StaticCache.containsKey(ip);
if (containsKey) {
System.err.println("//////////////////////////// " + ip + " stop ////////////////////////////////");
Process process = (Process) StaticCache.get(ip);
process.destroy();
String key = ip + "playUrl";
StaticCache.delete(key);
StaticCache.delete(ip);
}
}
public String getIp(String url) {
String regex = "\\d+\\.\\d+\\.\\d+\\.\\d+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
return matcher.group();
}
return null;
}
}
index.ftl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>rtsp转flv测试</title>
<link rel="stylesheet" href="/bootstrap-4.6.1/css/bootstrap.min.css">
<script src="/jquery/jquery.js"></script>
<script src="/bootstrap-4.6.1/js/bootstrap.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
<style>
ul {
list-style: none;
}
ul li {
margin-bottom: 5px;
border: 1px solid gray;
width: 800px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body style="text-align:center;">
<div style="display: inline-block;">
<h1>rtsp转flv测试</h1>
<ul>
<#list rtspUrls as url>
<li>${url}</li>
</#list>
</ul>
<div class="modal fade" id="playVideo" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" style="max-width: max-content;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">实时预览</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" style="display: flex;justify-content: center;align-items: center;">
<video id="videoElement" muted="muted"></video>
</div>
</div>
</div>
</div>
</div>
<script>
let rtspDir;
let flvPlayer;
$("li").click(function () {
rtspDir = $(this).html();
$.ajax({
type: "POST",
url: "/rstp/play",
data: {"rtspDir": rtspDir},
datatype: "text",
success: function (data) {
const videoElement = document.getElementById('videoElement');
flvPlayer = flvjs.createPlayer({
type: 'flv',
url: data,
enableWorker: true, //浏览器端开启flv.js的worker,多进程运行flv.js
isLive: true, //直播模式
hasAudio: false, //关闭音频
hasVideo: true,
stashInitialSize: 128,
enableStashBuffer: true //播放flv时,设置是否启用播放缓存,只在直播起作用。
}, {
enableStashBuffer: false, //禁用IO存储缓存区
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
},
error: function (e) {
console.log("error")
}
})
})
$(".close").click(function () {
if (rtspDir !== undefined && rtspDir !== "undefined") {
$.ajax({
type: "GET",
url: "/rstp/stop",
data: {"rtspDir": rtspDir},
datatype: "text",
success: function (data) {
console.log("关闭")
},
error: function (e) {
console.log(e)
}
});
flvPlayer.pause(); //停止播放
flvPlayer.unload(); //停止加载
// flvPlayer.detachMediaElement(); //销毁实例
// flvPlayer.destroy();
// flvPlayer = null;
}
})
</script>
</body>
</html>
rtspUrls.txt
rtsp://admin:123456@172.16.10.61:554/cam/realmonitor?channel=1&subtype=0&proto=Private3,
rtsp://admin:123456@172.16.10.62:554/cam/realmonitor?channel=1&subtype=0&proto=Private3,
rtsp://admin:123456@172.16.10.62:554/cam/realmonitor?channel=1&subtype=0&proto=Private3,
rtsp://admin:123456@172.16.10.63:554/cam/realmonitor?channel=1&subtype=0&proto=Private3,
rtsp://admin:123456@172.16.10.64:554/cam/realmonitor?channel=1&subtype=0&proto=Private3
查看 nginx 媒体流状态
http://localhost:18080/stat
全部评论