JAVA RTSP转HTTP

方式一(Java集成ffmpeg)


<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.7</version>
</dependency>

核心代码

import lombok.SneakyThrows;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;


/**
 * rtsp转rtmp(转封装方式).
 */
public class ConvertVideoPacket {
    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder record = null;
    int width = -1, height = -1;

    // 视频参数
    protected int audiocodecid;
    protected int codecid;
    protected double framerate;// 帧率
    protected int bitrate;// 比特率

    /**
     * 选择视频源.
     */
    public ConvertVideoPacket from(String src) throws Exception {
        // 采集/抓取器
        grabber = new FFmpegFrameGrabber(src);
        if (src.indexOf("rtsp") >= 0) {
            grabber.setOption("rtsp_transport", "tcp");
        }
        grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
        if (width < 0 || height < 0) {
            width = grabber.getImageWidth();
            height = grabber.getImageHeight();
        }
        // 视频参数
        audiocodecid = grabber.getAudioCodec();
        return this;
    }

    /**
     * 选择输出.
     */
    public ConvertVideoPacket to(String out) throws IOException {
        // 录制/推流器
        record = new FFmpegFrameRecorder(out, width, height);
        record.setGopSize(2);
        record.setFrameRate(framerate);
        record.setVideoBitrate(bitrate);
        AVFormatContext fc = null;
        if (out.indexOf("rtmp") >= 0 || out.indexOf("flv") > 0) {
            // 封装格式flv
            record.setFormat("flv");
            record.setAudioCodecName("aac");
            fc = grabber.getFormatContext();
        }
        record.start(fc);
        return this;
    }

    /**
     * 转封装.
     */
    public ConvertVideoPacket go() throws IOException {
        long err_index = 0;//采集或推流导致的错误次数test
        //连续五次没有采集到帧则认为视频采集结束,程序错误次数超过1次即中断程序
        for (int no_frame_index = 0; no_frame_index < 5 || err_index > 1; ) {
            AVPacket pkt = null;
            try {
                //没有解码的音视频帧
                pkt = grabber.grabPacket();
                if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
                    //空包记录次数跳过
                    no_frame_index++;
                    continue;
                }
                //不需要编码直接把音视频帧推出去
                err_index += (record.recordPacket(pkt) ? 0 : 1);//如果失败err_index自增1
                av_packet_unref(pkt);
            } catch (Exception e) {//推流失败
                err_index++;
            }
        }
        return this;
    }

    @SneakyThrows
    public static void main(String[] args) throws Exception, IOException {
        String rtsp = "rtsp://admin:Ctkj@123456+@222.88.170.174:55103/LiveMedia/ch1/Media1";
        //String output = "C:\\Users\\zz\\Desktop\\a\\m3u8one.m3u8";
        // push(rtsp, output);
        //运行,设置视频源和推流地址
        new ConvertVideoPacket()
                .from(rtsp)
                .to("rtmp://127.0.0.1:1935/live/test")
                .go();

}

    /**
     * 转m3u8
     * input rtsp的账号密码端口流字符串
     * output 要保存的文件目录生成m3u8文件 一直生成ts文件
     */
    @SneakyThrows
    public static void push(String input, String output) {
        FFmpegFrameGrabber grabber = null;// 采集器
        FFmpegFrameRecorder recorder = null;// 解码器
        int bitrate = 2500000;// 比特率
        double framerate;// 帧率
        int err_index = 0;// 推流过程中出现错误的次数
        int timebase;// 时钟基
        long dts = 0, pts = 0;// pkt的dts、pts时间戳
        try {
            // 开启ffmpeg日志级别;QUIET是屏蔽所有,可选INFO、DEBUG、ERROR等
            avutil.av_log_set_level(avutil.AV_LOG_INFO);
            FFmpegLogCallback.set();
            grabber = new FFmpegFrameGrabber(input);
            grabber.setOption("rtsp_transport", "tcp");
            grabber.start();
            // 异常的framerate,强制使用25帧
            if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
                framerate = grabber.getFrameRate();
            } else {
                framerate = 25.0;
            }
            bitrate = grabber.getVideoBitrate();// 获取到的比特率 0
            recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight(), 0);
            // 设置比特率
            recorder.setVideoBitrate(bitrate);
            // h264编/解码器
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            // 设置音频编码
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            // 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
            recorder.setFrameRate(framerate);
            // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
            recorder.setGopSize((int) framerate);
            // 解码器格式
            recorder.setFormat("hls");
            // 单个切片时长,单位是s,默认为2s
            recorder.setOption("hls_time", "5");
            // HLS播放的列表长度,0标识不做限制
            recorder.setOption("hls_list_size", "20");
            // 设置切片的ts文件序号起始值,默认从0开始,可以通过此项更改
            recorder.setOption("start_number", "1");
//       recorder.setOption("hls_segment_type","mpegts");
            // 自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保 留hls_list_size个数量的切片
            recorder.setOption("hls_flags", "delete_segments");
            // ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
            //recorder.setOption("hls_delete_threshold","1");
            /*
             * hls的切片类型: 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。 'fmp4':以Fragmented
             * MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。
             */
//       recorder.setOption("hls_segment_type","mpegts");

            AVFormatContext fc = null;
            fc = grabber.getFormatContext();
            recorder.start(fc);
            // 清空探测时留下的缓存
//       grabber.flush();

            AVPacket pkt = null;
            for (int no_pkt_index = 0; no_pkt_index < 5000000 && err_index < 5000000; ) {
                pkt = grabber.grabPacket();
                if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
                    Thread.sleep(1);
                    no_pkt_index++;
                    continue;
                }
                // 获取到的pkt的dts,pts异常,将此包丢弃掉。
                if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {
                    err_index++;

                    av_packet_unref(pkt);
                    continue;
                }
                // 矫正dts,pts
                pkt.pts(pts);
                pkt.dts(dts);
                err_index += (recorder.recordPacket(pkt) ? 0 : 1);
                // pts,dts累加
                timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();

                pts += (timebase / (int) framerate);
                dts += (timebase / (int) framerate);
            }
        } catch (Exception e) {
            grabber.stop();
            grabber.close();
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        } finally {
            grabber.stop();
            grabber.close();
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        }
    }

    /**
     * 创建BufferedImage对象
     */
    public static BufferedImage FrameToBufferedImage(Frame frame) {
        Java2DFrameConverter converter = new Java2DFrameConverter();
        BufferedImage bufferedImage = converter.getBufferedImage(frame);
        //旋转90度
        bufferedImage = rotateClockwise90(bufferedImage);

        return bufferedImage;
    }

    /**
     * 处理图片,将图片旋转90度
     */
    public static BufferedImage rotateClockwise90(BufferedImage bi) {
        int width = bi.getWidth();
        int height = bi.getHeight();
        BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                bufferedImage.setRGB(j, i, bi.getRGB(i, j));
        return bufferedImage;
    }

    /**
     * 摄像头截图
     */
    public static void screenshot(String rtspURL, String fileName) throws IOException {
        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtspURL);
        grabber.setOption("rtsp_transport", "tcp");
        grabber.setImageWidth(960);
        grabber.setImageHeight(540);
        grabber.start();
        File outPut = new File(fileName);
        while (true) {
            Frame frame = grabber.grabImage();
            if (frame != null) {
                ImageIO.write(FrameToBufferedImage(frame), "jpg", outPut);
                grabber.stop();
                grabber.release();
                break;
            }
        }
    }

}

接口

@Slf4j
@RestController
public class RtspToHttpController {

    @Autowired
    private StreamConfig streamConfig;

    public volatile ExecutorService executor;

    /**
     * 播放rtsp流
     * @param stream
     * @return
     */
    @PostMapping("/rtsp/play")
    public JSONObject rtspPlay(@RequestBody RtspStream stream) {
        log.info("进入播发视频监控流参数:{}", stream);
        //随机数
        String random = RandomUtil.randomString(Integer.parseInt(streamConfig.getRandom()));
        String rtmpStream = streamConfig.getRtmp() + random;
        try {
            executor = Executors.newSingleThreadExecutor();
            executor.execute(new packetRun(rtmpStream, stream.getRtspStream()));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        JSONObject json = new JSONObject();
        json.put("http", streamConfig.getHttp() + random);
        json.put("rtmp", rtmpStream);
        log.info("播发视频监控流返回参数:{}", json);
        return json;
    }

    /**
     * 关闭流
     * @return
     */
    @GetMapping("/rtsp/stop")
    public JSONObject rtspStop() {
        log.info("关闭监控视频流");
        JSONObject resJson = new JSONObject();
        resJson.put("code", HttpStatus.HTTP_OK);
        resJson.put("msg", "正常关闭");
        executor.shutdownNow();
        if (executor.isShutdown()) {
            resJson.put("msg", "未正常关闭");
        }
        return resJson;
    }

    class packetRun implements Runnable {

        private String rtmp;
        private String rtsp;

        public packetRun(String rtmp, String rtsp) {
            this.rtmp = rtmp;
            this.rtsp = rtsp;
        }

        @Override
        public void run() {
            try {
                new ConvertVideoPacket().from(rtsp)
                        .to(rtmp)
                        .go();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }
}

请求参数

@Data
public class RtspStream {

    private String rtspStream;

}

配置文件

stream:
  http: http://127.0.0.1:8001/live?port=1935&app=live&stream=
  rtmp: rtmp://127.0.0.1:1935/live/
  random: 3

实体类

@Data
@Component
public class StreamConfig {

    @Value("${stream.http}")
    private String http;

    @Value("${stream.rtmp}")
    private String rtmp;

    @Value("${stream.random}")
    private String random;

}

NGINX配置

\#user nobody;
worker_processes 1;

events {
  worker_connections 1024;
}

http {
  include    mime.types;
  default_type application/octet-stream;
  sendfile    on;
  keepalive_timeout 65;

  server {
    listen    8001;
    error_page  500 502 503 504 /50x.html;
    location = /50x.html {
      root  html;
    }
    location /live {
      flv_live on; #打开HTTP播放FLV直播流功能
      chunked_transfer_encoding on; #支持'Transfer-Encoding: chunked'方式回复

      add_header 'Access-Control-Allow-Origin' '*'; #添加额外的HTTP头
      add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的HTTP头
    }
     
    location /hls {
      types {
        application/vnd.apple.mpegurl m3u8;
        video/mp2t ts;
      }
     
      root temp;
      add_header 'Cache-Control' 'no-cache';
    }
     
    location /dash {
      root temp;
      add_header 'Cache-Control' 'no-cache';
    }
     
    location /stat {
     
      rtmp_stat all;
      rtmp_stat_stylesheet stat.xsl;
    }
     
    location /stat.xsl {
      root www/rtmp; #指定stat.xsl的位置
    }

 

    location /control {
      rtmp_control all; #rtmp控制模块的配置
        }
    }    

}

rtmp {  
  server {  
    listen 1935;  
    application live {
      live on;
       gop_cache on;
    }  
  }  
}

NGINX添加插件命令

./configure --add-module=./nginx-http-flv-module
make & make install

方式二(独立部署ffmpeg)

FFMPEG

下载ffmpeg插件, 下面有提供插件包,也可自行百度下载, 安装好后配置环境变量即可

执行命令

ffmpeg -re -rtsp_transport tcp -i “rtsp://admin:wzn123456@127.0.0.1/cam/realmonitor?channel=1&subtype=0” -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 “rtmp://127.0.0.1:1935/live/test”

HTTP访问格式

http://127.0.0.1:8001/live?port=1935&app=live&stream=test

相关插件

nginx-flv-file.zip :已经集成http-flv-module的nginx

ffmpeg: 流转换工具, 使用方式二必须安装

nginx-http-flv-module: rtsp转http的nginx插件

链接:https://pan.baidu.com/s/1LEcUC3N0a6rezTi8Pv2sNg
提取码:20hw```java
方式一(Java集成ffmpeg)

org.bytedeco javacv-platform 1.5.7 ```

核心代码

import lombok.SneakyThrows;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;


/**
 * rtsp转rtmp(转封装方式).
 */
public class ConvertVideoPacket {
    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder record = null;
    int width = -1, height = -1;

    // 视频参数
    protected int audiocodecid;
    protected int codecid;
    protected double framerate;// 帧率
    protected int bitrate;// 比特率

    /**
     * 选择视频源.
     */
    public ConvertVideoPacket from(String src) throws Exception {
        // 采集/抓取器
        grabber = new FFmpegFrameGrabber(src);
        if (src.indexOf("rtsp") >= 0) {
            grabber.setOption("rtsp_transport", "tcp");
        }
        grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
        if (width < 0 || height < 0) {
            width = grabber.getImageWidth();
            height = grabber.getImageHeight();
        }
        // 视频参数
        audiocodecid = grabber.getAudioCodec();
        return this;
    }

    /**
     * 选择输出.
     */
    public ConvertVideoPacket to(String out) throws IOException {
        // 录制/推流器
        record = new FFmpegFrameRecorder(out, width, height);
        record.setGopSize(2);
        record.setFrameRate(framerate);
        record.setVideoBitrate(bitrate);
        AVFormatContext fc = null;
        if (out.indexOf("rtmp") >= 0 || out.indexOf("flv") > 0) {
            // 封装格式flv
            record.setFormat("flv");
            record.setAudioCodecName("aac");
            fc = grabber.getFormatContext();
        }
        record.start(fc);
        return this;
    }

    /**
     * 转封装.
     */
    public ConvertVideoPacket go() throws IOException {
        long err_index = 0;//采集或推流导致的错误次数test
        //连续五次没有采集到帧则认为视频采集结束,程序错误次数超过1次即中断程序
        for (int no_frame_index = 0; no_frame_index < 5 || err_index > 1; ) {
            AVPacket pkt = null;
            try {
                //没有解码的音视频帧
                pkt = grabber.grabPacket();
                if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
                    //空包记录次数跳过
                    no_frame_index++;
                    continue;
                }
                //不需要编码直接把音视频帧推出去
                err_index += (record.recordPacket(pkt) ? 0 : 1);//如果失败err_index自增1
                av_packet_unref(pkt);
            } catch (Exception e) {//推流失败
                err_index++;
            }
        }
        return this;
    }

    @SneakyThrows
    public static void main(String[] args) throws Exception, IOException {
        String rtsp = "rtsp://admin:Ctkj@123456+@222.88.170.174:55103/LiveMedia/ch1/Media1";
        //String output = "C:\\Users\\zz\\Desktop\\a\\m3u8one.m3u8";
        // push(rtsp, output);
        //运行,设置视频源和推流地址
        new ConvertVideoPacket()
                .from(rtsp)
                .to("rtmp://127.0.0.1:1935/live/test")
                .go();

}

    /**
     * 转m3u8
     * input rtsp的账号密码端口流字符串
     * output 要保存的文件目录生成m3u8文件 一直生成ts文件
     */
    @SneakyThrows
    public static void push(String input, String output) {
        FFmpegFrameGrabber grabber = null;// 采集器
        FFmpegFrameRecorder recorder = null;// 解码器
        int bitrate = 2500000;// 比特率
        double framerate;// 帧率
        int err_index = 0;// 推流过程中出现错误的次数
        int timebase;// 时钟基
        long dts = 0, pts = 0;// pkt的dts、pts时间戳
        try {
            // 开启ffmpeg日志级别;QUIET是屏蔽所有,可选INFO、DEBUG、ERROR等
            avutil.av_log_set_level(avutil.AV_LOG_INFO);
            FFmpegLogCallback.set();
            grabber = new FFmpegFrameGrabber(input);
            grabber.setOption("rtsp_transport", "tcp");
            grabber.start();
            // 异常的framerate,强制使用25帧
            if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
                framerate = grabber.getFrameRate();
            } else {
                framerate = 25.0;
            }
            bitrate = grabber.getVideoBitrate();// 获取到的比特率 0
            recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight(), 0);
            // 设置比特率
            recorder.setVideoBitrate(bitrate);
            // h264编/解码器
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            // 设置音频编码
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            // 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
            recorder.setFrameRate(framerate);
            // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
            recorder.setGopSize((int) framerate);
            // 解码器格式
            recorder.setFormat("hls");
            // 单个切片时长,单位是s,默认为2s
            recorder.setOption("hls_time", "5");
            // HLS播放的列表长度,0标识不做限制
            recorder.setOption("hls_list_size", "20");
            // 设置切片的ts文件序号起始值,默认从0开始,可以通过此项更改
            recorder.setOption("start_number", "1");
//       recorder.setOption("hls_segment_type","mpegts");
            // 自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保 留hls_list_size个数量的切片
            recorder.setOption("hls_flags", "delete_segments");
            // ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
            //recorder.setOption("hls_delete_threshold","1");
            /*
             * hls的切片类型: 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。 'fmp4':以Fragmented
             * MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。
             */
//       recorder.setOption("hls_segment_type","mpegts");

            AVFormatContext fc = null;
            fc = grabber.getFormatContext();
            recorder.start(fc);
            // 清空探测时留下的缓存
//       grabber.flush();

            AVPacket pkt = null;
            for (int no_pkt_index = 0; no_pkt_index < 5000000 && err_index < 5000000; ) {
                pkt = grabber.grabPacket();
                if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
                    Thread.sleep(1);
                    no_pkt_index++;
                    continue;
                }
                // 获取到的pkt的dts,pts异常,将此包丢弃掉。
                if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {
                    err_index++;

                    av_packet_unref(pkt);
                    continue;
                }
                // 矫正dts,pts
                pkt.pts(pts);
                pkt.dts(dts);
                err_index += (recorder.recordPacket(pkt) ? 0 : 1);
                // pts,dts累加
                timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();

                pts += (timebase / (int) framerate);
                dts += (timebase / (int) framerate);
            }
        } catch (Exception e) {
            grabber.stop();
            grabber.close();
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        } finally {
            grabber.stop();
            grabber.close();
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        }
    }

    /**
     * 创建BufferedImage对象
     */
    public static BufferedImage FrameToBufferedImage(Frame frame) {
        Java2DFrameConverter converter = new Java2DFrameConverter();
        BufferedImage bufferedImage = converter.getBufferedImage(frame);
        //旋转90度
        bufferedImage = rotateClockwise90(bufferedImage);

        return bufferedImage;
    }

    /**
     * 处理图片,将图片旋转90度
     */
    public static BufferedImage rotateClockwise90(BufferedImage bi) {
        int width = bi.getWidth();
        int height = bi.getHeight();
        BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                bufferedImage.setRGB(j, i, bi.getRGB(i, j));
        return bufferedImage;
    }

    /**
     * 摄像头截图
     */
    public static void screenshot(String rtspURL, String fileName) throws IOException {
        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtspURL);
        grabber.setOption("rtsp_transport", "tcp");
        grabber.setImageWidth(960);
        grabber.setImageHeight(540);
        grabber.start();
        File outPut = new File(fileName);
        while (true) {
            Frame frame = grabber.grabImage();
            if (frame != null) {
                ImageIO.write(FrameToBufferedImage(frame), "jpg", outPut);
                grabber.stop();
                grabber.release();
                break;
            }
        }
    }

}

接口

@Slf4j
@RestController
public class RtspToHttpController {

    @Autowired
    private StreamConfig streamConfig;

    public volatile ExecutorService executor;

    /**
     * 播放rtsp流
     * @param stream
     * @return
     */
    @PostMapping("/rtsp/play")
    public JSONObject rtspPlay(@RequestBody RtspStream stream) {
        log.info("进入播发视频监控流参数:{}", stream);
        //随机数
        String random = RandomUtil.randomString(Integer.parseInt(streamConfig.getRandom()));
        String rtmpStream = streamConfig.getRtmp() + random;
        try {
            executor = Executors.newSingleThreadExecutor();
            executor.execute(new packetRun(rtmpStream, stream.getRtspStream()));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        JSONObject json = new JSONObject();
        json.put("http", streamConfig.getHttp() + random);
        json.put("rtmp", rtmpStream);
        log.info("播发视频监控流返回参数:{}", json);
        return json;
    }

    /**
     * 关闭流
     * @return
     */
    @GetMapping("/rtsp/stop")
    public JSONObject rtspStop() {
        log.info("关闭监控视频流");
        JSONObject resJson = new JSONObject();
        resJson.put("code", HttpStatus.HTTP_OK);
        resJson.put("msg", "正常关闭");
        executor.shutdownNow();
        if (executor.isShutdown()) {
            resJson.put("msg", "未正常关闭");
        }
        return resJson;
    }

    class packetRun implements Runnable {

        private String rtmp;
        private String rtsp;

        public packetRun(String rtmp, String rtsp) {
            this.rtmp = rtmp;
            this.rtsp = rtsp;
        }

        @Override
        public void run() {
            try {
                new ConvertVideoPacket().from(rtsp)
                        .to(rtmp)
                        .go();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }
}

请求参数

@Data
public class RtspStream {

    private String rtspStream;

}

配置文件

stream:
  http: http://127.0.0.1:8001/live?port=1935&app=live&stream=
  rtmp: rtmp://127.0.0.1:1935/live/
  random: 3

实体类

@Data
@Component
public class StreamConfig {

    @Value("${stream.http}")
    private String http;

    @Value("${stream.rtmp}")
    private String rtmp;

    @Value("${stream.random}")
    private String random;

}

NGINX配置

\#user nobody;
worker_processes 1;

events {
  worker_connections 1024;
}

http {
  include    mime.types;
  default_type application/octet-stream;
  sendfile    on;
  keepalive_timeout 65;

  server {
    listen    8001;
    error_page  500 502 503 504 /50x.html;
    location = /50x.html {
      root  html;
    }
    location /live {
      flv_live on; #打开HTTP播放FLV直播流功能
      chunked_transfer_encoding on; #支持'Transfer-Encoding: chunked'方式回复

      add_header 'Access-Control-Allow-Origin' '*'; #添加额外的HTTP头
      add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的HTTP头
    }
     
    location /hls {
      types {
        application/vnd.apple.mpegurl m3u8;
        video/mp2t ts;
      }
     
      root temp;
      add_header 'Cache-Control' 'no-cache';
    }
     
    location /dash {
      root temp;
      add_header 'Cache-Control' 'no-cache';
    }
     
    location /stat {
     
      rtmp_stat all;
      rtmp_stat_stylesheet stat.xsl;
    }
     
    location /stat.xsl {
      root www/rtmp; #指定stat.xsl的位置
    }

 

    location /control {
      rtmp_control all; #rtmp控制模块的配置
        }
    }    

}

rtmp {  
  server {  
    listen 1935;  
    application live {
      live on;
       gop_cache on;
    }  
  }  
}

NGINX添加插件命令

./configure --add-module=./nginx-http-flv-module
make & make install

方式二(独立部署ffmpeg)
代码Demo

 public static void main(String[] args) {
            try {
                String cmd = "ffmpeg -re -rtsp_transport tcp -i \"" + rtsp + "\" -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 \"" + rtmp + "\"";
                // 调用CMD命令
                ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd); // /c参数表示执行后关闭CMD窗口
                processBuilder.redirectErrorStream(true); // 将错误输出流与标准输出流合并
                Process process = processBuilder.start();

                // 获取命令输出结果
                InputStream inputStream = process.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "GBK")); // 设置编码为GBK
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
                // 等待命令执行完成
                process.waitFor();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }

FFMPEG

下载ffmpeg插件, 下面有提供插件包,也可自行百度下载, 安装好后配置环境变量即可

执行命令

ffmpeg -re -rtsp_transport tcp -i “rtsp://admin:wzn123456@127.0.0.1/cam/realmonitor?channel=1&subtype=0” -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 “rtmp://127.0.0.1:1935/live/test”

HTTP访问格式

http://127.0.0.1:8001/live?port=1935&app=live&stream=test

相关插件

nginx-flv-file.zip :已经集成http-flv-module的nginx

ffmpeg: 流转换工具, 使用方式二必须安装

nginx-http-flv-module: rtsp转http的nginx插件

链接:https://pan.baidu.com/s/1LEcUC3N0a6rezTi8Pv2sNg
提取码:20hw

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值