0


java 实现监控rtsp流转flv,实现前端播放

目录

注意

先查看直接的视频流能不能播放,视频编解码必须是H264

video mediaplay官网 即(VLC)

下载、安装完VLC后,打开VLC 点击媒体 -> 打开网络串流,粘贴地址播放,不能播放可能地址有问题
在这里插入图片描述
查看编解码格式,右击视频选择工具->编解码器信息,格式为H264
在这里插入图片描述

后端代码

复制可直接使用

jar包

<dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version></dependency>

controller层

@RestController@RequestMapping("/flv")publicclassJyDeviceControllerextendsBaseController{@AutowiredprivateIFLVService service;@RequestMapping()publicvoidopen4(HttpServletResponse response,HttpServletRequest request){String rtsp ="rtsp://xxxxxxxxxx(自己的rtsp地址)"; 
        service.open(rtsp, response, request);}}

config层

importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.TaskScheduler;importorg.springframework.scheduling.annotation.EnableScheduling;importorg.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;/**
 * 使用多线程执行定时任务
 * 
 * @author gc.x
 *
 */@Configuration@EnableSchedulingpublicclassSchedulerConfig{@BeanpublicTaskSchedulertaskScheduler(){ThreadPoolTaskScheduler scheduler =newThreadPoolTaskScheduler();// 线程池大小
        scheduler.setPoolSize(3);// 线程名字前缀
        scheduler.setThreadNamePrefix("task-thread-");return scheduler;}}

factories层

factories结构
在这里插入图片描述

/**
 * 转换器状态(初始化、打开、关闭、错误、运行) 
 */publicenumConverterState{INITIAL,OPEN,CLOSE,ERROR,RUN}
importjavax.servlet.AsyncContext;importjava.io.IOException;/**
 * @Description Converter
 * @Author admin
 * @Date 2024/6/18 10:22
 */publicinterfaceConverter{/**
     * 获取该转换的key
     */publicStringgetKey();/**
     * 获取该转换的url
     *
     * @return
     */publicStringgetUrl();/**
     * 添加一个流输出
     *
     * @param entity
     */publicvoidaddOutputStreamEntity(String key,AsyncContext entity)throwsIOException;/**
     * 退出转换
     */publicvoidexit();/**
     * 启动
     */publicvoidstart();}
importcom.alibaba.fastjson2.util.IOUtils;importlombok.extern.slf4j.Slf4j;importorg.bytedeco.ffmpeg.avcodec.AVPacket;importorg.bytedeco.ffmpeg.global.avcodec;importorg.bytedeco.javacv.FFmpegFrameGrabber;importorg.bytedeco.javacv.FFmpegFrameRecorder;importjavax.servlet.AsyncContext;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.util.Iterator;importjava.util.List;importjava.util.Map;/**
 * javacv转包装<br/>
 * 无须转码,更低的资源消耗,更低的延迟<br/>
 * 确保流来源视频H264格式,音频AAC格式
 *
 * @author gc.x
 */@Slf4jpublicclassConverterFactoriesextendsThreadimplementsConverter{publicvolatileboolean runing =true;/**
     * 读流器
     */privateFFmpegFrameGrabber grabber;/**
     * 转码器
     */privateFFmpegFrameRecorder recorder;/**
     * 转FLV格式的头信息<br/>
     * 如果有第二个客户端播放首先要返回头信息
     */privatebyte[] headers;/**
     * 保存转换好的流
     */privateByteArrayOutputStream stream;/**
     * 流地址,h264,aac
     */privateString url;/**
     * 流输出
     */privateList<AsyncContext> outEntitys;/**
     * key用于表示这个转换器
     */privateString key;/**
     * 转换队列
     */privateMap<String,Converter> factories;publicConverterFactories(String url,String key,Map<String,Converter> factories,List<AsyncContext> outEntitys){this.url = url;this.key = key;this.factories = factories;this.outEntitys = outEntitys;}@Overridepublicvoidrun(){boolean isCloseGrabberAndResponse =true;try{
            grabber =newFFmpegFrameGrabber(url);if("rtsp".equals(url.substring(0,4))){
                grabber.setOption("rtsp_transport","tcp");
                grabber.setOption("stimeout","5000000");}
            grabber.start();if(avcodec.AV_CODEC_ID_H264== grabber.getVideoCodec()&&(grabber.getAudioChannels()==0|| avcodec.AV_CODEC_ID_AAC== grabber.getAudioCodec())){
                log.info("this url:{} converterFactories start", url);// 来源视频H264格式,音频AAC格式// 无须转码,更低的资源消耗,更低的延迟
                stream =newByteArrayOutputStream();
                recorder =newFFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
                        grabber.getAudioChannels());
                recorder.setInterleaved(true);
                recorder.setVideoOption("preset","ultrafast");
                recorder.setVideoOption("tune","zerolatency");
                recorder.setVideoOption("crf","25");
                recorder.setFrameRate(grabber.getFrameRate());
                recorder.setSampleRate(grabber.getSampleRate());if(grabber.getAudioChannels()>0){
                    recorder.setAudioChannels(grabber.getAudioChannels());//                    recorder.setAudioBitrate(grabber.getAudioBitrate());//转流后没有音频,不知道原因,注释掉就可以了
                    recorder.setAudioCodec(grabber.getAudioCodec());}
                recorder.setFormat("flv");
                recorder.setVideoBitrate(grabber.getVideoBitrate());
                recorder.setVideoCodec(grabber.getVideoCodec());
                recorder.start(grabber.getFormatContext());if(headers ==null){
                    headers = stream.toByteArray();
                    stream.reset();writeResponse(headers);}int nullNumber =0;while(runing){AVPacket k = grabber.grabPacket();if(k !=null){try{
                            recorder.recordPacket(k);}catch(Exception e){}if(stream.size()>0){byte[] b = stream.toByteArray();
                            stream.reset();writeResponse(b);if(outEntitys.isEmpty()){
                                log.info("没有输出退出");break;}}
                        avcodec.av_packet_unref(k);}else{
                        nullNumber++;if(nullNumber >200){break;}}Thread.sleep(5);}}else{
                isCloseGrabberAndResponse =false;// 需要转码为视频H264格式,音频AAC格式ConverterTranFactories c =newConverterTranFactories(url, key, factories, outEntitys, grabber);
                factories.put(key, c);
                c.start();}}catch(Exception e){
            log.error(e.getMessage(), e);}finally{closeConverter(isCloseGrabberAndResponse);completeResponse(isCloseGrabberAndResponse);
            log.info("this url:{} converterFactories exit", url);}}/**
     * 输出FLV视频流
     *
     * @param b
     */publicvoidwriteResponse(byte[] b){Iterator<AsyncContext> it = outEntitys.iterator();while(it.hasNext()){AsyncContext o = it.next();try{
                o.getResponse().getOutputStream().write(b);}catch(Exception e){
                log.info("移除一个输出");
                it.remove();}}}/**
     * 退出转换
     */publicvoidcloseConverter(boolean isCloseGrabberAndResponse){if(isCloseGrabberAndResponse){IOUtils.close(grabber);
            factories.remove(this.key);}IOUtils.close(recorder);IOUtils.close(stream);}/**
     * 关闭异步响应
     *
     * @param isCloseGrabberAndResponse
     */publicvoidcompleteResponse(boolean isCloseGrabberAndResponse){if(isCloseGrabberAndResponse){Iterator<AsyncContext> it = outEntitys.iterator();while(it.hasNext()){AsyncContext o = it.next();
                o.complete();}}}@OverridepublicStringgetKey(){returnthis.key;}@OverridepublicStringgetUrl(){returnthis.url;}@OverridepublicvoidaddOutputStreamEntity(String key,AsyncContext entity)throwsIOException{if(headers ==null){
            outEntitys.add(entity);}else{
            entity.getResponse().getOutputStream().write(headers);
            entity.getResponse().getOutputStream().flush();
            outEntitys.add(entity);}}@Overridepublicvoidexit(){this.runing =false;try{this.join();}catch(Exception e){
            log.error(e.getMessage(), e);}}}
importcom.alibaba.fastjson2.util.IOUtils;importlombok.extern.slf4j.Slf4j;importorg.bytedeco.ffmpeg.global.avcodec;importorg.bytedeco.javacv.FFmpegFrameGrabber;importorg.bytedeco.javacv.FFmpegFrameRecorder;importorg.bytedeco.javacv.Frame;importjavax.servlet.AsyncContext;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.util.Iterator;importjava.util.List;importjava.util.Map;/**
 * javacv转码<br/>
 * 流来源不是视频H264格式,音频AAC格式 转码为视频H264格式,音频AAC格式
 *
 * @author gc.x
 */@Slf4jpublicclassConverterTranFactoriesextendsThreadimplementsConverter{publicvolatileboolean runing =true;/**
     * 读流器
     */privateFFmpegFrameGrabber grabber;/**
     * 转码器
     */privateFFmpegFrameRecorder recorder;/**
     * 转FLV格式的头信息<br/>
     * 如果有第二个客户端播放首先要返回头信息
     */privatebyte[] headers;/**
     * 保存转换好的流
     */privateByteArrayOutputStream stream;/**
     * 流地址,h264,aac
     */privateString url;/**
     * 流输出
     */privateList<AsyncContext> outEntitys;/**
     * key用于表示这个转换器
     */privateString key;/**
     * 转换队列
     */privateMap<String,Converter> factories;publicConverterTranFactories(String url,String key,Map<String,Converter> factories,List<AsyncContext> outEntitys,FFmpegFrameGrabber grabber){this.url = url;this.key = key;this.factories = factories;this.outEntitys = outEntitys;this.grabber = grabber;}@Overridepublicvoidrun(){try{
            log.info("this url:{} converterTranFactories start", url);
            grabber.setFrameRate(25);if(grabber.getImageWidth()>1920){
                grabber.setImageWidth(1920);}if(grabber.getImageHeight()>1080){
                grabber.setImageHeight(1080);}
            stream =newByteArrayOutputStream();
            recorder =newFFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
                    grabber.getAudioChannels());
            recorder.setInterleaved(true);
            recorder.setVideoOption("preset","ultrafast");
            recorder.setVideoOption("tune","zerolatency");
            recorder.setVideoOption("crf","25");
            recorder.setGopSize(50);
            recorder.setFrameRate(25);
            recorder.setSampleRate(grabber.getSampleRate());if(grabber.getAudioChannels()>0){
                recorder.setAudioChannels(grabber.getAudioChannels());//                recorder.setAudioBitrate(grabber.getAudioBitrate());
                recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);}
            recorder.setFormat("flv");
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.start();if(headers ==null){
                headers = stream.toByteArray();
                stream.reset();writeResponse(headers);}int nullNumber =0;while(runing){// 抓取一帧Frame f = grabber.grab();if(f !=null){try{// 转码
                        recorder.record(f);}catch(Exception e){}if(stream.size()>0){byte[] b = stream.toByteArray();
                        stream.reset();writeResponse(b);if(outEntitys.isEmpty()){
                            log.info("没有输出退出");break;}}}else{
                    nullNumber++;if(nullNumber >200){break;}}Thread.sleep(5);}}catch(Exception e){
            log.error(e.getMessage(), e);}finally{closeConverter();completeResponse();
            log.info("this url:{} converterTranFactories exit", url);
            factories.remove(this.key);}}/**
     * 输出FLV视频流
     *
     * @param b
     */publicvoidwriteResponse(byte[] b){Iterator<AsyncContext> it = outEntitys.iterator();while(it.hasNext()){AsyncContext o = it.next();try{
                o.getResponse().getOutputStream().write(b);}catch(Exception e){
                log.info("移除一个输出");
                it.remove();}}}/**
     * 退出转换
     */publicvoidcloseConverter(){IOUtils.close(grabber);IOUtils.close(recorder);IOUtils.close(stream);}/**
     * 关闭异步响应
     */publicvoidcompleteResponse(){Iterator<AsyncContext> it = outEntitys.iterator();while(it.hasNext()){AsyncContext o = it.next();
            o.complete();}}@OverridepublicStringgetKey(){returnthis.key;}@OverridepublicStringgetUrl(){returnthis.url;}@OverridepublicvoidaddOutputStreamEntity(String key,AsyncContext entity)throwsIOException{if(headers ==null){
            outEntitys.add(entity);}else{
            entity.getResponse().getOutputStream().write(headers);
            entity.getResponse().getOutputStream().flush();
            outEntitys.add(entity);}}@Overridepublicvoidexit(){this.runing =false;try{this.join();}catch(Exception e){
            log.error(e.getMessage(), e);}}}

service层

importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/**
 * @Description IFLVService
 * @Author admin
 * @Date 2024/6/18 10:24
 */publicinterfaceIFLVService{/**
     * 打开一个流地址
     *
     * @param url
     * @param response
     */publicvoidopen(String url,HttpServletResponse response,HttpServletRequest request);}
importcom.emergency.device.jy.factories.Converter;importcom.emergency.device.jy.factories.ConverterFactories;importcom.emergency.device.jy.service.IFLVService;importorg.apache.commons.compress.utils.Lists;importorg.springframework.stereotype.Service;importjavax.servlet.AsyncContext;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;importjava.util.List;importjava.util.concurrent.ConcurrentHashMap;importstaticcom.emergency.framework.datasource.DynamicDataSourceContextHolder.log;/**
 * @Description FLVService
 * @Author admin
 * @Date 2024/6/18 10:34
 */@ServicepublicclassFLVServiceimplementsIFLVService{privateConcurrentHashMap<String,Converter> converters =newConcurrentHashMap<>();/**
     * 打开一个流地址
     *
     * @param url
     * @param response
     * @param request
     */@Overridepublicvoidopen(String url,HttpServletResponse response,HttpServletRequest request){String key =md5(url);AsyncContext async = request.startAsync();
        async.setTimeout(0);if(converters.containsKey(key)){Converter c = converters.get(key);try{
                c.addOutputStreamEntity(key, async);}catch(IOException e){
                log.error(e.getMessage(), e);thrownewIllegalArgumentException(e.getMessage());}}else{List<AsyncContext> outs =Lists.newArrayList();
            outs.add(async);ConverterFactories c =newConverterFactories(url, key, converters, outs);
            c.start();
            converters.put(key, c);}
        response.setContentType("video/x-flv");
        response.setHeader("Connection","keep-alive");
        response.setStatus(HttpServletResponse.SC_OK);try{
            response.flushBuffer();}catch(IOException e){
            log.error(e.getMessage(), e);}}publicStringmd5(String plainText){StringBuilder buf =null;try{MessageDigest md =MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());byte b[]= md.digest();int i;
            buf =newStringBuilder("");for(int offset =0; offset < b.length; offset++){
                i = b[offset];if(i <0)
                    i +=256;if(i <16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));}}catch(NoSuchAlgorithmException e){
            log.error(e.getMessage(), e);}return buf.toString();}}

前端代码

<template><div>// 浏览器不支持自动播放,需要在video标签中设置,controls是否显示播放器按钮<video preload="auto" muted autoplay></video></div></template><script>data(){return{flvPlayer:null,};},mounted(){this.$nextTick(()=>{this.playflv()})},methods:{//使用flv.js实现播放flv格式流,获取video节点playflv(){if(flvjs.isSupported()){var videoElement = document.getElementById('videoElement');var flvPlayer = flvjs.createPlayer({type:'flv',url:'http://127.0.0.1:19677/flv',//后端controller层的接口地址isLive:true,//数据源是否为直播流hasAudio:true,//数据源是否包含有音频hasVideo:true,//数据源是否包含有视频enableStashBuffer:false//是否启用缓存区},{enableWorker:false,//不启用分离线程enableStashBuffer:false,//关闭IO隐藏缓冲区autoCleanupSourceBuffer:true//自动清除缓存});this.flvPlayer = flvPlayer
                flvPlayer.attachMediaElement(videoElement);
                flvPlayer.load();
                flvPlayer.play();}// 报错重连this.flvPlayer.on(flvjs.Events.ERROR,(err, errdet)=>{// 参数 err 是一级异常,errdet 是二级异常if(err == flvjs.ErrorTypes.MEDIA_ERROR){
                      console.log('媒体错误')if(errdet == flvjs.ErrorDetails.MEDIA_FORMAT_UNSUPPORTED){
                            console.log('媒体格式不支持')}}if(err == flvjs.ErrorTypes.NETWORK_ERROR){
                    console.log('网络错误')if(errdet == flvjs.ErrorDetails.NETWORK_STATUS_CODE_INVALID){
                           console.log('http状态码异常')}}if(err == flvjs.ErrorTypes.OTHER_ERROR){
                  console.log('其他异常:', errdet)}if(this.flvPlayer){this.destoryVideo()this.playflv()}})},//关闭视频流destoryVideo(){if(this.flvPlayer){this.flvPlayer.pause();// 暂停播放数据流this.flvPlayer.unload();// 取消数据流加载this.flvPlayer.detachMediaElement();// 将播放实例从节点中取出this.flvPlayer.destroy();// 销毁播放实例this.flvPlayer =null;}},},beforeDestroy(){this.destoryVideo();},</script>

参考地址
https://developer.aliyun.com/article/867004#slide-4
gitee地址
https://gitee.com/giteeClass/rtsp-converter-flv-spring-boot-starter

标签: java

本文转载自: https://blog.csdn.net/weixin_51698741/article/details/139803258
版权归原作者 偏执怪人。 所有, 如有侵权,请联系我们删除。

“java 实现监控rtsp流转flv,实现前端播放”的评论:

还没有评论