spring boot 实现rtsp视频推流

1、搭建rtsp服务(docker方式)

复制代码
docker pull aler9/rtsp-simple-server

docker run -d --restart=always \
  --name rtsp-server \
  -p 8554:8554 \
  aler9/rtsp-simple-server

2、maven依赖

复制代码
<dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>7.1-1.5.11</version>
        </dependency>

3、新建RtspStreamer服务类

复制代码
package com.example.superior_conjuncte_iot_rtspvideo.system_config.config;


import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.Base64;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;




@Component
@Slf4j
public class RtspStreamer {
    static {
        FFmpegLogCallback.set();
        // 强制加载 FFmpeg 核心库
        Loader.load(org.bytedeco.ffmpeg.global.avutil.class);
        Loader.load(org.bytedeco.ffmpeg.global.avcodec.class);
        Loader.load(org.bytedeco.ffmpeg.global.avformat.class);
    }

    private FFmpegFrameRecorder recorder;
    private ExecutorService executor = Executors.newSingleThreadExecutor();
    private final Java2DFrameConverter converter = new Java2DFrameConverter();





    /**
     * 启动 RTSP 流服务
     * @param rtspUrl 例如: "rtsp://localhost:8554/live"
     * @param width 视频宽度
     * @param height 视频高度
     */
    public void startStream(String rtspUrl, int width, int height) {
        try {

            recorder = new FFmpegFrameRecorder(rtspUrl, width, height);

            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setFormat("rtsp");

            recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            recorder.setFrameRate(30);

            recorder.setVideoBitrate(2_000_000);                 // 码率设为 2Mbps

            recorder.setVideoOption("preset", "medium");
            recorder.setVideoOption("tune", "zerolatency");
            recorder.setVideoOption("g", "30");

            recorder.start();


        } catch (Exception e) {
            log.error("启动 RTSP 失败"+e.getMessage());
            throw new RuntimeException("启动 RTSP 失败", e);
        }
    }

    /**
     * 推送视频帧
     * @param image 视频帧图像
     */
    public void pushFrame(BufferedImage image) {
        executor.submit(() -> {
            try {
                //转换格式
                BufferedImage bgrImage = convertToBGR(image);
                Frame frame = converter.getFrame(bgrImage);
                recorder.record(frame);
            } catch (Exception e) {
                log.error("推送视频帧失败"+e.getMessage());
                e.printStackTrace();
            }
        });

    }

    //转换格式,很关键
    private BufferedImage convertToBGR(BufferedImage image) {
        BufferedImage bgrImage = new BufferedImage(
                image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR
        );
        bgrImage.getGraphics().drawImage(image, 0, 0, null);
        return bgrImage;
    }

    @PreDestroy
    public void stopStream() {
        if (recorder != null) {
            try {
                recorder.stop();
                recorder.release();
            } catch (Exception e) {
                log.error("停止 RTSP 失败"+e.getMessage());
            }
        }
        executor.shutdown();
    }

    //字符串视频流解码
    public BufferedImage getBufferedImage(String base64Image){
        try {
            byte[] imageBytes = Base64.getDecoder().decode(base64Image);
            BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
            return image;
        }catch (Exception ex){
            log.error("视频解码错误");
            return null;
        }
    }
}

4、测试代码

复制代码
package com.example.superior_conjuncte_iot_web.tj.project_video.service.impl;

import com.example.superior_conjuncte_iot_web.system_config.videoConfig.RtspStreamer;
import com.example.superior_conjuncte_iot_web.tj.project_video.service.IVideoRtspService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.awt.image.BufferedImage;

@Service
@Slf4j
public class VideoRtspServiceImpl implements IVideoRtspService, ApplicationRunner {

    @Resource
    @Lazy
    private RtspStreamer rtspStreamer;

    @Override
    public void run(ApplicationArguments args)  {
        rtspStreamer.startStream("rtsp://localhost:8554/live", 640, 480);
        // 模拟生成视频帧(例如从摄像头或文件读取)
        Thread thread=new Thread(() -> {
            while (true) {
                BufferedImage image = generateTestFrame(); // 生成测试帧
                rtspStreamer.pushFrame(image);
                try {
                    Thread.sleep(33); // 约30帧/秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();

    }

    // 生成测试帧(渐变颜色)
    private BufferedImage generateTestFrame() {
        BufferedImage img = new BufferedImage(640, 480, BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g2d = img.createGraphics();
        g2d.setColor(new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)));
        g2d.fillRect(0, 0, 640, 480);
        g2d.dispose();
        return img;
    }
}

5、用VLC工具测试

测试地址:rtsp://localhost:8554/live,其中localhost:8554为rtsp服务器的地址

相关推荐
桦说编程15 分钟前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研18 分钟前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi41 分钟前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
一只爱撸猫的程序猿1 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋1 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国2 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy2 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack2 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9653 小时前
pip install 已经不再安全
后端
寻月隐君3 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github