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;
/**
* 使用多線程執(zhí)行定時(shí)任務(wù)
*
* @author gc.x
*
*/
@Configuration
@EnableScheduling
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// 線程池大小
scheduler.setPoolSize(3);
// 線程名字前綴
scheduler.setThreadNamePrefix("task-thread-");
return scheduler;
}
}factories層:
/**
* 轉(zhuǎn)換器狀態(tà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();
/**
* 啟動
*/
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/>
* 無須轉(zhuǎn)碼,更低的資源消耗,更低的延遲<br/>
* 確保流來源視頻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è)客戶端播放首先要返回頭信息
*/
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);
// 來源視頻H264格式,音頻AAC格式
// 無須轉(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("沒有輸出退出");
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/>
* 流來源不是視頻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è)客戶端播放首先要返回頭信息
*/
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("沒有輸出退出");
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);
}
/**
* 降級函數(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);
}
/**
* 降級函數(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 {
/**
* 打開一個(gè)流地址
*
* @param url
* @param response
*/
public void open(String url, HttpServletResponse response, HttpServletRequest request);
}二.客戶端代碼基于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();
}
//自動播放,瀏覽器不支持
function playflv() {
flvPlayer.play();
}
</script>
</body>
</html>這里因?yàn)闉g覽器把自動播放給禁止了,加了個(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中整合MyBatis-Plus的方法示例
這篇文章主要介紹了SpringBoot中整合MyBatis-Plus的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
全面詳解java代碼重構(gòu)與設(shè)計(jì)模式
這篇文章主要為大家介紹了全面詳解java代碼重構(gòu)與設(shè)計(jì)模式的全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
聊聊SpringBoot的@Scheduled的并發(fā)問題
這篇文章主要介紹了聊聊SpringBoot的@Scheduled的并發(fā)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
java加密MD5實(shí)現(xiàn)及密碼驗(yàn)證代碼實(shí)例
這篇文章主要介紹了java加密MD5實(shí)現(xiàn)及密碼驗(yàn)證代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
通過Java?Reflection實(shí)現(xiàn)編譯時(shí)注解正確處理方法
Java注解是一種標(biāo)記在JDK5及以后的版本中引入,用于Java語言中向程序添加元數(shù)據(jù)的方法,這篇文章主要介紹了通過Java?Reflection實(shí)現(xiàn)編譯時(shí)注解處理方法,需要的朋友可以參考下2023-06-06

