java實(shí)現(xiàn)監(jiān)控rtsp流轉(zhuǎn)flv方法實(shí)例(前端播放,前后端代碼都有)
一.服務(wù)代碼
目錄結(jié)構(gòu)
maven配置文件引入坐標(biāo):
<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>
服務(wù)器代碼
controller層:
import com.xr.web.rtspconverterflvspringbootstarter.service.IFLVService; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * FLV流轉(zhuǎn)換 * * @author gc.x */ @Api(tags = "flv") @RequestMapping("/flv") @RestController public class FLVController { @Autowired private IFLVService service; @GetMapping() public void open4(HttpServletResponse response, HttpServletRequest request) { String test = "rtsp://admin:sdxr@2022@192.168.0.205:554"; service.open(test, response, request); } }
config層:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * 使用多線(xiàn)程執(zhí)行定時(shí)任務(wù) * * @author gc.x * */ @Configuration @EnableScheduling public class SchedulerConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 線(xiàn)程池大小 scheduler.setPoolSize(3); // 線(xiàn)程名字前綴 scheduler.setThreadNamePrefix("task-thread-"); return scheduler; } }
factories層:
/** * 轉(zhuǎn)換器狀態(tài)(初始化、打開(kāi)、關(guān)閉、錯(cuò)誤、運(yùn)行) * * @author gc.x */ public enum ConverterState { INITIAL, OPEN, CLOSE, ERROR, RUN }
import javax.servlet.AsyncContext; import java.io.IOException; public interface Converter { /** * 獲取該轉(zhuǎn)換的key */ public String getKey(); /** * 獲取該轉(zhuǎn)換的url * * @return */ public String getUrl(); /** * 添加一個(gè)流輸出 * * @param entity */ public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException; /** * 退出轉(zhuǎn)換 */ public void exit(); /** * 啟動(dòng) */ public void start(); }
import com.alibaba.fastjson.util.IOUtils; import lombok.extern.slf4j.Slf4j; import org.bytedeco.ffmpeg.avcodec.AVPacket; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameRecorder; import javax.servlet.AsyncContext; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; /** * javacv轉(zhuǎn)包裝<br/> * 無(wú)須轉(zhuǎn)碼,更低的資源消耗,更低的延遲<br/> * 確保流來(lái)源視頻H264格式,音頻AAC格式 * * @author gc.x */ @Slf4j public class ConverterFactories extends Thread implements Converter { public volatile boolean runing = true; /** * 讀流器 */ private FFmpegFrameGrabber grabber; /** * 轉(zhuǎn)碼器 */ private FFmpegFrameRecorder recorder; /** * 轉(zhuǎn)FLV格式的頭信息<br/> * 如果有第二個(gè)客戶(hù)端播放首先要返回頭信息 */ private byte[] headers; /** * 保存轉(zhuǎn)換好的流 */ private ByteArrayOutputStream stream; /** * 流地址,h264,aac */ private String url; /** * 流輸出 */ private List<AsyncContext> outEntitys; /** * key用于表示這個(gè)轉(zhuǎn)換器 */ private String key; /** * 轉(zhuǎn)換隊(duì)列 */ private Map<String, Converter> factories; public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys) { this.url = url; this.key = key; this.factories = factories; this.outEntitys = outEntitys; } @Override public void run() { boolean isCloseGrabberAndResponse = true; try { grabber = new FFmpegFrameGrabber(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); // 來(lái)源視頻H264格式,音頻AAC格式 // 無(wú)須轉(zhuǎn)碼,更低的資源消耗,更低的延遲 stream = new ByteArrayOutputStream(); recorder = new FFmpegFrameRecorder(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("沒(méi)有輸出退出"); break; } } avcodec.av_packet_unref(k); } else { nullNumber++; if (nullNumber > 200) { break; } } Thread.sleep(5); } } else { isCloseGrabberAndResponse = false; // 需要轉(zhuǎn)碼為視頻H264格式,音頻AAC格式 ConverterTranFactories c = new ConverterTranFactories(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 */ public void writeResponse(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("移除一個(gè)輸出"); it.remove(); } } } /** * 退出轉(zhuǎn)換 */ public void closeConverter(boolean isCloseGrabberAndResponse) { if (isCloseGrabberAndResponse) { IOUtils.close(grabber); factories.remove(this.key); } IOUtils.close(recorder); IOUtils.close(stream); } /** * 關(guān)閉異步響應(yīng) * * @param isCloseGrabberAndResponse */ public void completeResponse(boolean isCloseGrabberAndResponse) { if (isCloseGrabberAndResponse) { Iterator<AsyncContext> it = outEntitys.iterator(); while (it.hasNext()) { AsyncContext o = it.next(); o.complete(); } } } @Override public String getKey() { return this.key; } @Override public String getUrl() { return this.url; } @Override public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException { if (headers == null) { outEntitys.add(entity); } else { entity.getResponse().getOutputStream().write(headers); entity.getResponse().getOutputStream().flush(); outEntitys.add(entity); } } @Override public void exit() { this.runing = false; try { this.join(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
import com.alibaba.fastjson.util.IOUtils; import lombok.extern.slf4j.Slf4j; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.Frame; import javax.servlet.AsyncContext; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; /** * javacv轉(zhuǎn)碼<br/> * 流來(lái)源不是視頻H264格式,音頻AAC格式 轉(zhuǎn)碼為視頻H264格式,音頻AAC格式 * * @author gc.x */ @Slf4j public class ConverterTranFactories extends Thread implements Converter { public volatile boolean runing = true; /** * 讀流器 */ private FFmpegFrameGrabber grabber; /** * 轉(zhuǎn)碼器 */ private FFmpegFrameRecorder recorder; /** * 轉(zhuǎn)FLV格式的頭信息<br/> * 如果有第二個(gè)客戶(hù)端播放首先要返回頭信息 */ private byte[] headers; /** * 保存轉(zhuǎn)換好的流 */ private ByteArrayOutputStream stream; /** * 流地址,h264,aac */ private String url; /** * 流輸出 */ private List<AsyncContext> outEntitys; /** * key用于表示這個(gè)轉(zhuǎn)換器 */ private String key; /** * 轉(zhuǎn)換隊(duì)列 */ private Map<String, Converter> factories; public ConverterTranFactories(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; } @Override public void run() { 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 = new ByteArrayOutputStream(); recorder = new FFmpegFrameRecorder(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 { // 轉(zhuǎn)碼 recorder.record(f); } catch (Exception e) { } if (stream.size() > 0) { byte[] b = stream.toByteArray(); stream.reset(); writeResponse(b); if (outEntitys.isEmpty()) { log.info("沒(méi)有輸出退出"); 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 */ public void writeResponse(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("移除一個(gè)輸出"); it.remove(); } } } /** * 退出轉(zhuǎn)換 */ public void closeConverter() { IOUtils.close(grabber); IOUtils.close(recorder); IOUtils.close(stream); } /** * 關(guān)閉異步響應(yīng) */ public void completeResponse() { Iterator<AsyncContext> it = outEntitys.iterator(); while (it.hasNext()) { AsyncContext o = it.next(); o.complete(); } } @Override public String getKey() { return this.key; } @Override public String getUrl() { return this.url; } @Override public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException { if (headers == null) { outEntitys.add(entity); } else { entity.getResponse().getOutputStream().write(headers); entity.getResponse().getOutputStream().flush(); outEntitys.add(entity); } } @Override public void exit() { this.runing = false; try { this.join(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
result層:
import com.alibaba.fastjson.JSONObject; import java.io.Serializable; import java.util.HashMap; /** * 封裝返回結(jié)果 * */ public class JsonResult extends HashMap<String, Object> implements Serializable { private static final long serialVersionUID = 1L; public static final int SUCCESS = 200; public JsonResult() { } /** * 返回成功 */ public static JsonResult ok() { return ok("操作成功"); } /** * 返回成功 */ public static JsonResult okFallBack() { return okFallBack("操作成功"); } /** * 返回成功 */ public JsonResult put(Object obj) { return this.put("data", obj); } /** * 返回成功 */ public static JsonResult ok(String message) { return result(200, message); } /** * 降級(jí)函數(shù) - 返回成功 */ public static JsonResult okFallBack(String message) { return result(205, message); } /** * 返回成功 */ public static JsonResult result(int code, String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", code); jsonResult.put("message", message); return jsonResult; } /** * 返回失敗 */ public static JsonResult error() { return error("操作失敗"); } /** * 返回失敗 */ public static JsonResult error(String message) { return error(500, message); } /** * 返回失敗 */ public static JsonResult error(int code, String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", code); jsonResult.put("message", message); return jsonResult; } /** * 設(shè)置code */ public JsonResult setCode(int code) { super.put("status", code); return this; } /** * 設(shè)置message */ public JsonResult setMessage(String message) { super.put("message", message); return this; } /** * 放入object */ @Override public JsonResult put(String key, Object object) { super.put(key, object); return this; } /** * 權(quán)限禁止 */ public static JsonResult forbidden(String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", 401); jsonResult.put("message", message); return jsonResult; } @Override public String toString() { return JSONObject.toJSONString(this); } public JSONObject toJSONObject() { return JSONObject.parseObject(toString()); } }
service層:
import com.alibaba.fastjson.JSONObject; import java.io.Serializable; import java.util.HashMap; /** * 封裝返回結(jié)果 * */ public class JsonResult extends HashMap<String, Object> implements Serializable { private static final long serialVersionUID = 1L; public static final int SUCCESS = 200; public JsonResult() { } /** * 返回成功 */ public static JsonResult ok() { return ok("操作成功"); } /** * 返回成功 */ public static JsonResult okFallBack() { return okFallBack("操作成功"); } /** * 返回成功 */ public JsonResult put(Object obj) { return this.put("data", obj); } /** * 返回成功 */ public static JsonResult ok(String message) { return result(200, message); } /** * 降級(jí)函數(shù) - 返回成功 */ public static JsonResult okFallBack(String message) { return result(205, message); } /** * 返回成功 */ public static JsonResult result(int code, String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", code); jsonResult.put("message", message); return jsonResult; } /** * 返回失敗 */ public static JsonResult error() { return error("操作失敗"); } /** * 返回失敗 */ public static JsonResult error(String message) { return error(500, message); } /** * 返回失敗 */ public static JsonResult error(int code, String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", code); jsonResult.put("message", message); return jsonResult; } /** * 設(shè)置code */ public JsonResult setCode(int code) { super.put("status", code); return this; } /** * 設(shè)置message */ public JsonResult setMessage(String message) { super.put("message", message); return this; } /** * 放入object */ @Override public JsonResult put(String key, Object object) { super.put(key, object); return this; } /** * 權(quán)限禁止 */ public static JsonResult forbidden(String message) { JsonResult jsonResult = new JsonResult(); jsonResult.put("timestamp", System.currentTimeMillis()); jsonResult.put("status", 401); jsonResult.put("message", message); return jsonResult; } @Override public String toString() { return JSONObject.toJSONString(this); } public JSONObject toJSONObject() { return JSONObject.parseObject(toString()); } }
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface IFLVService { /** * 打開(kāi)一個(gè)流地址 * * @param url * @param response */ public void open(String url, HttpServletResponse response, HttpServletRequest request); }
二.客戶(hù)端代碼基于flv.js進(jìn)行播放
<!-- @format --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script src="flv.min.js"></script> <video id="videoElement"></video> <button onclick="playflv()">點(diǎn)擊播放</button> <script> if (flvjs.isSupported()) { var videoElement = document.getElementById('videoElement'); var flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'http://127.0.0.1:10010/xr/flv', }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); } //自動(dòng)播放,瀏覽器不支持 function playflv() { flvPlayer.play(); } </script> </body> </html>
這里因?yàn)闉g覽器把自動(dòng)播放給禁止了,加了個(gè)按鈕點(diǎn)擊事件
https://www.bootcdn.cn/
引入的flv.js文件在如下網(wǎng)站下載即可:
總結(jié)
到此這篇關(guān)于java實(shí)現(xiàn)監(jiān)控rtsp流轉(zhuǎn)flv的文章就介紹到這了,更多相關(guān)java監(jiān)控rtsp流轉(zhuǎn)flv內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中整合MyBatis-Plus的方法示例
這篇文章主要介紹了SpringBoot中整合MyBatis-Plus的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09全面詳解java代碼重構(gòu)與設(shè)計(jì)模式
這篇文章主要為大家介紹了全面詳解java代碼重構(gòu)與設(shè)計(jì)模式的全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Java輕松入門(mén)冒泡?選擇?插入?希爾?歸并排序算法
這篇文章主要介紹了Java常用的排序算法及代碼實(shí)現(xiàn),在Java開(kāi)發(fā)中,對(duì)排序的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。那Java有哪些排序算法呢?本文小編就來(lái)詳細(xì)說(shuō)說(shuō)Java常見(jiàn)的排序算法,需要的朋友可以參考一下2022-02-02聊聊SpringBoot的@Scheduled的并發(fā)問(wèn)題
這篇文章主要介紹了聊聊SpringBoot的@Scheduled的并發(fā)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11java加密MD5實(shí)現(xiàn)及密碼驗(yàn)證代碼實(shí)例
這篇文章主要介紹了java加密MD5實(shí)現(xiàn)及密碼驗(yàn)證代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12通過(guò)Java?Reflection實(shí)現(xiàn)編譯時(shí)注解正確處理方法
Java注解是一種標(biāo)記在JDK5及以后的版本中引入,用于Java語(yǔ)言中向程序添加元數(shù)據(jù)的方法,這篇文章主要介紹了通過(guò)Java?Reflection實(shí)現(xiàn)編譯時(shí)注解處理方法,需要的朋友可以參考下2023-06-06