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服务器的地址

相关推荐
我是一颗柠檬5 分钟前
【JDK8新特性】函数式接口Day2
java·开发语言·后端·intellij-idea
Trouvaille ~6 分钟前
【Redis篇】Redis 安装与启动:快速搭建一个 Redis 环境
数据库·redis·后端·ubuntu·缓存·环境搭建·安装教程
Mahir088 分钟前
Spring Boot 自动装配深度解密:从原理到自定义 Starter 实战
java·spring boot·后端·自动装配·自定义starter·大厂面试题
java1234_小锋13 分钟前
Spring Boot 的嵌入式服务器(如 Tomcat)是如何启动的?如何替换为 Jetty 或 Undertow?
服务器·spring boot·tomcat
K姐研究社9 小时前
怎么用AI制作电商口播视频,开拍APP一键生成
人工智能·音视频
Mahir089 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
EasyDSS12 小时前
私有化视频会议平台/视频高清直播点播EasyDSS构建智慧校园音视频协作新生态
音视频
IT_陈寒13 小时前
Redis缓存击穿把我整不会了,原来还有这手操作
前端·人工智能·后端
txp玩Linux13 小时前
音频 AI 模型开源方案与音频 3A / ASR / TTS 全链路解析
人工智能·音视频
kyriewen14 小时前
面试官让我查各部门工资最高的员工,我用AI三秒写出窗口函数,他愣了
后端·mysql·面试