JAVACV 读取摄像头流将rtsp转flv 通过http-flv和flv.js播放 无插件 纯代码

1、pom

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

 2、摄像头类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Camera  {

    /**
     * 摄像头
     */
    private String cameraName;
    /**
     *摄像头地址
     */
    private String cameraUrl;
    /**
     *观看人数
     */
    private Integer viewsNumber;
    /**
     *摄像头状态
     */
    private Integer monitorState;
    /**
     *闲置关闭时间
     */
    private Integer recoveryTime;
    /**
     *保存回放
     */
    private Boolean savePlayback;
    /**
     *分片大小
     */
    private Integer fragmentSize;

}

3、服务实现

@Service
public class CameraImpl extends ServiceImpl<CameraMapper, Camera> implements ICameraService {
   public static Map<Long,CameraHandle> cameraHandleMap = new HashMap<>();
    public static List<String> cameraKeyList = new ArrayList<>();
    /**
     * 获取是否存在推送流,并返回一个唯一key 用于获取flv流
     */
    @Override
    public String getCamera(Long id) {
        if (!cameraHandleMap.containsKey(id)){
            Camera camera = getById(id);
            camera.setMonitorState(1);
            CameraHandle cameraHandle = new CameraHandle();
            cameraHandle.setCamera(camera);
            cameraHandle.setCameraImpl(this);
            cameraHandle.start();
            cameraHandleMap.put(id,cameraHandle);
            updateById(camera);
        }
        String uuid = UUID.randomUUID().toString().replace("-","");
        cameraKeyList.add(uuid);
        return uuid;
    }
    /**
     * 开启录制
     */
    @Override
    public boolean openRecord(Long id) {
        Camera camera = getById(id);
        camera.setMonitorState(1);
        camera.setSavePlayback(true);
        if (!cameraHandleMap.containsKey(id)){
            CameraHandle cameraHandle = new CameraHandle();
            cameraHandle.setCamera(camera);
            cameraHandle.setCameraImpl(this);
            cameraHandle.start();
            cameraHandleMap.put(id,cameraHandle);
        }else{
            cameraHandleMap.get(id).setCamera(camera);
        }
        updateById(camera);
        return true;
    }
    /**
     * 关闭录制
     */
    @Override
    public boolean closeRecord(Long id) {
        if (cameraHandleMap.containsKey(id)){
            Camera camera = getById(id);
            camera.setSavePlayback(false);
            updateById(camera);
            cameraHandleMap.get(id).setCamera(camera);
        }
        return true;
    }
    /**
     * 客户端关闭推送flv流
     */
    @Override
    public boolean closeHttpFlv(Long id,String cameraKey) {
        if (cameraHandleMap.containsKey(id)){
            CameraHandle cameraHandle = cameraHandleMap.get(id);
            cameraHandle.streamMap.remove(cameraKey);
            Camera camera = new Camera();
            camera.setId(id);
            camera.setViewsNumber(camera.getViewsNumber()-1);
            updateById(camera);
        }
        return true;
    }
    /**
     * 推送flv流
     */
    @Override
    public void getHttpFlv(Long id,String cameraKey, HttpServletResponse response) {
        try{
            if (cameraHandleMap.containsKey(id)&&cameraKeyList.contains(cameraKey)) {
                cameraKeyList.remove(cameraKey);
                CameraHandle cameraHandle = cameraHandleMap.get(id);
                response.setStatus(HttpStatus.OK.value());
                response.addHeader("Content-Disposition", "attachment;filename=\"" + java.net.URLEncoder.encode(cameraHandle.fileName)+ "\"");
                response.setContentType("video/x-flv");
                response.setHeader("Connection", "keep-alive");
                response.setHeader("accept_ranges", "bytes");
                response.setHeader("pragma", "no-cache");
                response.setHeader("cache_control", "no-cache");
                response.setHeader("transfer_encoding", "CHUNKED");
                response.setHeader("SERVER", "hmsm");
                ServletOutputStream stream = response.getOutputStream();
                stream.write(cameraHandle.getFlvHeader());
                stream.flush();
                cameraHandle.streamMap.put(cameraKey,stream);
                cameraHandle.setMonitoring(true);
                try {
                    Camera camera = cameraHandle.getCamera();
                    camera.setViewsNumber(camera.getViewsNumber()+1);
                    updateById(camera);
                }catch (Exception e){
                    e.printStackTrace();
                }
                while (cameraHandle.streamMap.containsKey(cameraKey)){

                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 4、拉流转码

@Data
public class CameraHandle extends Thread {

    /**
     * 是否开始拉流解码
     */
    private boolean isStart = true;
    FFmpegFrameGrabber grabber;
    FFmpegFrameRecorder recorder;
    /**
     * 视频流
     */
    ByteArrayOutputStream pipedOutputStream;
    /**
     * 推流
     */
    Map<String,ServletOutputStream>  streamMap = new HashMap<>();
    /**
     * 是否实时推流到客户端
     */
    private boolean isMonitoring = false;
    /**
     * flv header 输出流的头部信息
     */
    private byte[] flvHeader = null;
    /**
     * 没有客户链接的开始时间
     */
    protected long frameTime = System.currentTimeMillis();

    CameraImpl cameraImpl;
    Camera camera;
    String fileName;

    public CameraHandle() {
        pipedOutputStream = new ByteArrayOutputStream();
    }

    /**
     * 关闭流
     */
    public void close() {
        this.isStart = false;
    }

    /**
     * 拉取摄像头信息
     */
    @Override
    public void run() {
        try {
            fileName = camera.getCameraName() + DateTime.now().toString("yyyy_MM_dd") + ".flv";
            //设置日志级别
            avutil.av_log_set_level(avutil.AV_LOG_ERROR);
            //FFmpegLogCallback.set();
            init();
            int countErr = 0;
            while (isStart) {
                //获取画面  使用grabImage实时预览会等待2-3分钟
                Frame frame = grabber.grabFrame();
                if (frame != null) {
                    recorder.record(frame);
                    //业务需求 这个判断可以不要 设置太大实时预览会导致卡顿
                    if (pipedOutputStream.size() > camera.getFragmentSize()) {
                        //实时预览
                        sendFlvFrameData(pipedOutputStream.toByteArray());
                        //存储本地 用于历史回放
                        if (camera.getSavePlayback()) {
                            frameTime = System.currentTimeMillis();
                            if (Files.exists(Paths.get(fileName))) {
                                Files.write(Paths.get(fileName), pipedOutputStream.toByteArray(), StandardOpenOption.APPEND);
                            } else {
                                Files.write(Paths.get(fileName), flvHeader);
                                Files.write(Paths.get(fileName), pipedOutputStream.toByteArray(), StandardOpenOption.APPEND);
                            }
                        }
                        pipedOutputStream.reset();
                    }
                } else {
                    countErr++;
                    if (countErr > 10) {
                        System.out.println("拉流错误重启");
                        grabber.restart();
                        grabber.flush();
                        countErr = 0;
                    }
                }
                long date = System.currentTimeMillis();
                //5分钟没录制或预览关闭
                if ((date - frameTime) > camera.getRecoveryTime()) {
                    close();
                    System.out.println("退出:"+camera.getCameraUrl());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                recorder.stop();
                recorder.release();
            } catch (FrameRecorder.Exception exception) {
                exception.printStackTrace();
            }
            try {
                grabber.stop();
            } catch (FrameGrabber.Exception exception) {
                exception.printStackTrace();
            }
            try {
                isMonitoring = false;
                camera.setMonitorState(0);
                camera.setSavePlayback(false);
                cameraImpl.updateById(camera);
                CameraImpl.cameraHandleMap.remove(camera.getId());
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
    }

    private void init() throws FFmpegFrameRecorder.Exception, FrameGrabber.Exception {
        grabber = new FFmpegFrameGrabber(camera.getCameraUrl());
        // 超时时间(5秒)
        grabber.setOption("stimoout", "5000000");
        grabber.setOption("threads", "1");
        grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        // 设置缓存大小,提高画质、减少卡顿花屏
        grabber.setOption("buffer_size", "1024000");
        //grabber.setOption("buffer_size", "100");
        // 如果为rtsp流,增加配置
        if ("rtsp".equals(camera.getCameraUrl().substring(0, 4))) {
            // 设置打开协议tcp / udp
            grabber.setOption("rtsp_transport", "tcp");
            //首选TCP进行RTP传输
            grabber.setOption("rtsp_flags", "prefer_tcp");
            //设置超时时间
            // -stimeout的单位是us 微秒(1秒=1*1000*1000微秒)。
            grabber.setOption("stimeout", "5*1000*1000");
        }
        grabber.start();
        recorder = new FFmpegFrameRecorder(pipedOutputStream, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
        // 设置比特率
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        recorder.setFormat("flv");
           /* recorder.setInterleaved(false);
            recorder.setVideoOption("tune", "zerolatency");
            recorder.setVideoOption("preset", "ultrafast");
            recorder.setVideoOption("crf", "26");
            recorder.setVideoOption("threads", "1");
            recorder.setVideoCodecName("libx264");*/
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setSampleRate(grabber.getSampleRate());
        recorder.setAudioChannels(grabber.getAudioChannels());
        recorder.setPixelFormat(recorder.getPixelFormat());
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        // 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
        recorder.setFrameRate(grabber.getFrameRate());
        // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
        recorder.setGopSize((int) grabber.getFrameRate() * 2);
        recorder.setVideoQuality(1.0);

        //获取头文件信息
        recorder.start();
        grabber.flush();
        flvHeader = pipedOutputStream.toByteArray();
        pipedOutputStream.reset();
    }

    private void sendFlvFrameData(byte[] bytes) {
        try {
            if (streamMap.size()>0&& isMonitoring) {
                frameTime = System.currentTimeMillis();
                Iterator<Map.Entry<String, ServletOutputStream>> iterator = streamMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, ServletOutputStream> next = iterator.next();
                    ServletOutputStream outputStream = null;
                    try {
                        outputStream = next.getValue();
                        if (bytes.length > 0) {
                            outputStream.write(bytes, 0, bytes.length);
                            outputStream.flush();
                        }
                    } catch (Exception e) {
                        try {
                            if (null != outputStream) {
                                outputStream.close();
                            }
                        } catch (IOException ioException) {
                            ioException.printStackTrace();
                        }
                        iterator.remove();
                        camera.setViewsNumber(camera.getViewsNumber()-1);
                        cameraImpl.updateById(camera);
                        System.out.println("响应输出流出现错误");
                        e.printStackTrace();
                    }
                }
            }
        }catch (Exception e) {

        }
    }

}

5、前端(vue)

安装flv.js   npm install --save flv.js

<template>
  <div class="live-preview" style="background: #f0f2f5; width: 100%">
    <video id="liveVideo" controls autoplay muted width="1024" height="576">
      Your browser is too old which doesn't support HTML5 video.
    </video>
  </div>
</template>
<script>
import flvjs from "flv.js";
export default {

  data() {
    return {};
  },

  mounted: function () {
    this.$nextTick(function () {
      this.createPlayer();
    });
  },
  methods: {
    createPlayer() {
      if (flvjs.isSupported()) {
        var videoEle = document.getElementById("liveVideo"); //!注意:如果播放直播流(监控视频)必须将参数isLive:true加上,不然会导致短时间内断流会造成视频页面卡顿
        //需先请求getCamera获取cameraKey
        let player = flvjs.createPlayer(
          {
            type: "flv",
            url: "http://localhost:8101/camera/v1/camera/getHttpFlv?id=1&cameraKey=36ae75e6fe8941d8b8b56f174f749c0e", 
            isLive: true, //直播流
          },
          {
            enableWorker: false, //不启用分离线程
            enableStashBuffer: false, //关闭IO隐藏缓冲区
            stashInitialSize: 128, // 减少首帧显示等待时长
            lazyLoad: false, //如果有足够的数据可播放,则中止http连接
          }
        );
        player.attachMediaElement(videoEle);
        player.load();
        player.play();
        videoEle.play();
        player.on(
          flvjs.Events.ERROR,
          (errorType, errorDetail, errorInfo) => {
            console.log("errorType:", errorType);
            console.log("errorDetail:", errorDetail);
            console.log("errorInfo:", errorInfo);
          }
        );
      } else {
        this.$message.error("不支持flv格式视频");
      }
    },
  },
};
</script>

  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
使用 JavaCV 实现 RTSP HTTP-FLV 需要用到 FFmpeg 和 Netty 的相关功能。以下是实现步骤: 1. 引入 JavaCVFFmpeg 和 Netty 相关依赖: ```xml <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.63.Final</version> </dependency> <dependency> <groupId>com.github.mhrimaz</groupId> <artifactId>netty-http-flv</artifactId> <version>1.0.1</version> </dependency> ``` 2. 创建 FFmpegFrameGrabber 和 FFmpegFrameRecorder,并设置相关参数: ```java FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("rtsp://example.com/stream"); grabber.setOption("rtsp_transport", "tcp"); // 使用 TCP 协议传输数据 grabber.start(); FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("http://localhost:8080/stream.flv", grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels()); recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 设置视频编码器为 H.264 recorder.setFormat("flv"); // 设置输出格式为 FLV recorder.start(); ``` 3. 创建 Netty 的 HTTP-FLV 服务器,并将 FFmpegFrameRecorder 中的数据发给客户端: ```java EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new HttpContentCompressor()); pipeline.addLast(new HttpFLVServerHandler(recorder)); } }); ChannelFuture future = bootstrap.bind(8080).sync(); ``` 4. 循环读取 RTSP 数据,将数据换为 FLV 格式并写入 FFmpegFrameRecorder 中: ```java Frame frame = null; while ((frame = grabber.grab()) != null) { recorder.record(frame); } ``` 5. 释放资源: ```java grabber.stop(); grabber.release(); recorder.stop(); recorder.release(); future.channel().closeFuture().sync(); workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); ``` 以上是使用 JavaCV 实现 RTSP HTTP-FLV 的基本步骤。需要注意的是,该方法需要使用 Netty 的 HTTP-FLV 服务器将数据发给客户端,因此需要对 HTTP-FLV 相关技术有一定的了解。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值