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">&times;</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

全部评论