Java 实现Rtsp 转rtmp,hls,flv

服务支撑:FFmpeg + srs(流媒体服务器)

整个流程是 FFmpeg 收流转码 推 rtmp 到流媒体服务 流媒体服务再 分发流到公网

搭建流媒体服务:

  1. SRS (Simple Realtime Server) | SRS (本例子使用的是SrS 安装使用docker )

2.GitHub - ZLMediaKit/ZLMediaKit: WebRTC/RTSP/RTMP/HTTP/HLS/HTTP-FLV/WebSocket-FLV/HTTP-TS/HTTP-fMP4/WebSocket-TS/WebSocket-fMP4/GB28181/SRT server and client framework based on C++11

3.nginx实现 (自己百度)

  1. 其他的还有收费的那种

2.服务器安装FFmpeg yum 可以安装

java 服务实现调用ffmpeg

1.ProcessManager 用于执行指令以及 关闭这个流等操作

复制代码
package io.renren.common.live;

import cn.hutool.core.thread.ThreadUtil;
import org.springframework.beans.factory.DisposableBean;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

/**
 * @author chenkang
 * @date 2023年8月3日09:43:21
 */
public class ProcessManager implements DisposableBean {

    private Map<String, WeakReference<Process>> processMap=new HashMap<>();

    /**
     * 启动一个进程
     * @param processName 进程名称key
     * @param command 执行指令
     */
    public void startProcess(String processName, String command) {
        ThreadUtil.execAsync(() -> {
            try {
                ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
                Process process = processBuilder.start();
                processMap.put(processName, new WeakReference<>(process));
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(processName + ": " + line);
                }
                int exitCode = process.waitFor();
                System.out.println(processName + ": Process execution completed with exit code: " + exitCode);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     *  销毁
     * @param processName key
     */
    public void terminateProcess(String processName) {
        WeakReference<Process> weakRef = processMap.get(processName);
        if (weakRef != null) {
            Process process = weakRef.get();
            if (process != null) {
                process.destroy();
            }
            processMap.remove(processName);
        }
    }



    private void terminateAllProcesses() {
        for (WeakReference<Process> weakRef : processMap.values()) {
            Process process = weakRef.get();
            if (process != null) {
                process.destroy();
            }
        }
    }

    @Override
    public void destroy() throws Exception {
        this.terminateAllProcesses();
        this.processMap.clear();
    }
}

对接的是大华的摄像头

/**

* 开始推流

* 备注:现在客户的设备是NVR NVR 下大概有54个摄像头 公网映射rtsp 554 端口 根据channel 开区分是那个摄像头

* 必须条件:1.服务端要安装好 ffmpeg 2.要搭建一个流媒体服务器 这个使用的是 srs

* 流程:用户端想要查看某个摄像头->查询到设备信息获取到摄像头的channel 这个是提前维护好的

* ->拿到channel走如下方法 调用FFmpeg 执行转码推流指令(客户的摄像头是h265)-》rtsp流会被转码 重新设定分辨率 码率转h264 并把转码流推向流媒体服务器 srs

* ->客户想看的时候就 拉取 流媒体服务端的rtmp流 完成播放

*

* 其他:

* 1.用户再播放的时候 要先确定这个摄像头有没有别的人在观看 观看了就不在执行了 (1.可以调用srs 接口查询流是不是存在 这个比较稳妥 2.或者是 processManager 判断是否在推了)

* 2.有时候用户强制关闭客户端 无法感知用户不在观看了,这面还要 定时的去调用srs 接口查询闲置的流及时的给关闭 同时也要 把服务的process 给主动关闭不然一直推

* 这个要先去关闭process再调用接口关闭srs服务的流

*

*

* http://127.0.0.1:1985/api/v1/streams 查询服务端所有流

*{

* "code": 0,

* "server": "vid-f1gt8j3",

* "streams": [

* {

* "id": "vid-143p019",

* //streamName

* "name": "3",

* "vhost": "vid-5847096",

* "app": "live",

* "live_ms": 1691039306435,

* //客户端数量 这个要注意 默认就有1 个客户端是推流

* "clients": 1,

* "frames": 0,

* "send_bytes": 0,

* "recv_bytes": 1068,

* "kbps": {

* "recv_30s": 0,

* "send_30s": 0

* },

* "publish": {

* //是否正在推流 有时候服务端流停推了 但是还有客户端在看 这个流还能查到 但是 active 为false

* "active": false,

* "cid": ""

* },

* "video": null,

* "audio": null

* }

* ]

* }

*

*

*

* http://127.0.0.1:1985/api/v1/clients

*

*{

* "code": 0,

* "server": "vid-f1gt8j3",

* "clients": [

* {

* "id": "868249e9",

* "vhost": "vid-5847096",

* "stream": "vid-778ujy0",

* "ip": "172.17.0.1",

* "pageUrl": "",

* "swfUrl": "",

* "tcUrl": "rtmp://127.0.0.1:1935/live",

* "url": "/live/9",

* //类型是 fmle-publish 推流 删除掉这个推流就会停止

* //类型是 rtmp-play 拉流 删除掉这个拉流就会停止

* //剔除方法 Method DELETE api /api/v1/clients/{id} 停止推流/踢掉用户端

* "type": "fmle-publish",

* "publish": true,

* "alive": 16.18,

* "kbps": {

* "recv_30s": 0,

* "send_30s": 0

* }

* }

* ]

* }

*

*

* @param channel

*/

复制代码
    @GetMapping("/start")
    @ResponseBody
    public void start(@RequestParam(defaultValue = "1") String channel){
        RtspUrlBuilder builder = new RtspUrlBuilder();
        RtmpUrlBuilder rtmpUrlBuilder = new RtmpUrlBuilder();
        //构建 rtsp 这个是客户的nvr rtsp 地址 只有channel 是灵活的 他们是64路 现在接了50多摄像头对应50 多路channel
        String rstp = builder.setUsername("admin")
                .setPassword("xx")
                .setIpAddress("xx")
                .setChannel(channel)
                .build();
        //这个是流媒体服务器的rtmp 推流地址
        String rtmp = rtmpUrlBuilder.setApplication("live").setStreamName(channel).build();
        final String vcodec="libx264";
        String camera1=String.format(RTSP_RTMP, rstp,vcodec,rtmp);
        //TODO 判断是否已经再推了 推就直接返回拉流地址
        processManager.startProcess(channel,camera1);
        //拉流地址和推流地址是一至的 除非 java 服务和srs 在一台服务器 那么  推流地址 rtmp ip为127.0.0.1 拉流 rtmp ip 为公网
        // 就是java通过ffmpeg 收流转发到本地 rtmp srs分发流 到公网去
        System.out.println("拉流地址:"+rtmp);
    }

    @GetMapping("/end")
    @ResponseBody
    public void end(String channel){
        processManager.terminateProcess(channel);
    }

两个辅助类:

复制代码
package io.renren.common.live;

/**
 * @author chenkang
 * @date 2023-8-3 12:27
 */
public class RtspUrlBuilder {
    private String username;
    private String password;
    private String ipAddress;
    private int port;
    private String channel;
    private int subtype;

    public RtspUrlBuilder() {
        // 默认端口为554
        this.port = 554;
        // 默认子类型为0
        this.subtype = 0;
    }

    public RtspUrlBuilder setUsername(String username) {
        this.username = username;
        return this;
    }

    public RtspUrlBuilder setPassword(String password) {
        this.password = password;
        return this;
    }

    public RtspUrlBuilder setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
        return this;
    }

    public RtspUrlBuilder setPort(int port) {
        this.port = port;
        return this;
    }

    public RtspUrlBuilder setChannel(String channel) {
        this.channel = channel;
        return this;
    }

    public RtspUrlBuilder setSubtype(int subtype) {
        this.subtype = subtype;
        return this;
    }

    public String build() {
        return "rtsp://" + username + ":" + password + "@" + ipAddress + ":" + port +
                "/cam/realmonitor?channel=" + channel + "&subtype=" + subtype;
    }
}

package io.renren.common.live;

/**
 * @author chenkang
 * @date 2023-8-3 12:30
 */
public class RtmpUrlBuilder {
    private String ipAddress;
    private int port;
    private String application;
    private String streamName;

    public RtmpUrlBuilder() {
        // 默认IP地址为127.0.0.1
        this.ipAddress = "127.0.0.1";
        // 默认端口为1935
        this.port = 1935;
    }

    public RtmpUrlBuilder setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
        return this;
    }

    public RtmpUrlBuilder setPort(int port) {
        this.port = port;
        return this;
    }

    public RtmpUrlBuilder setApplication(String application) {
        this.application = application;
        return this;
    }

    public RtmpUrlBuilder setStreamName(String streamName) {
        this.streamName = streamName;
        return this;
    }

    public String build() {
        return  "rtmp://" + ipAddress + ":" + port + "/" + application + "/" + streamName;
    }
}
相关推荐
Pniubi1 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko71 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师1 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.2 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
秋饼2 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
gCode Teacher 格码致知3 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas
发量惊人的中年网工3 小时前
分公司访问总部ERP慢怎么办?SD-WAN组网的排查与验收要点
服务器·开发语言·php
下页、再停留3 小时前
【C#桌面客户端系列学习-15】生成可视化图表之折线图、柱状图、饼状图
开发语言·c#
ShineWinsu3 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说4 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json