Compare commits
No commits in common. "0c6869f6b2a9bd76c0ac54068f75c1ccdccdfa9f" and "bb297dda318709a8a3a40589bde59761e3d96d96" have entirely different histories.
0c6869f6b2
...
bb297dda31
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
README.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
@ -1,6 +1,11 @@
|
||||
<!-- Easy-FLV: Java RTSP/RTMP to FLV Converter -->
|
||||
# 📺 Easy-FLV: Java 实现的 RTSP/RTMP 到 FLV 转换器
|
||||
|
||||
[](https://github.com/javpower/easy-flv)
|
||||
[](https://github.com/javpower/easy-flv/issues)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://adoptopenjdk.net/)
|
||||
[](https://spring.io/projects/spring-boot)
|
||||
|
||||
## 🌟 关于 Easy-FLV
|
||||
Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流转换为 FLV 格式,以便在浏览器中播放。它为实时视频监控、直播和视频流处理提供了一个高效、稳定且易于集成的解决方案。
|
||||
@ -11,6 +16,11 @@ Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流
|
||||
- **现代浏览器支持**:支持所有主流浏览器,无需额外插件。
|
||||
- **实时流处理**:适用于实时视频流的转换,如安防监控和直播。
|
||||
|
||||
## 📄 效果图
|
||||
以下是 Easy-FLV 在行动的效果图:
|
||||
|
||||

|
||||

|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
@ -19,9 +29,9 @@ Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>me.zhengjie</groupId>
|
||||
<artifactId>eladmin-flv</artifactId>
|
||||
<version>1.1</version>
|
||||
<groupId>io.github.javpower</groupId>
|
||||
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||
<version>1.5.9.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@ -35,7 +45,7 @@ public class RtspDataService implements IOpenFLVService {
|
||||
@Override
|
||||
public String getUrl(Integer channel) {
|
||||
// 根据 channel 获取 RTSP 视频流地址
|
||||
return "rtsp://xx.xx.xx.xx/openUrl/16HV8mA";
|
||||
return "rtsp://10.11.9.251:554/openUrl/16HV8mA";
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -73,4 +83,9 @@ public static void main(String[] args) throws UnsupportedEncodingException {
|
||||
我们欢迎任何形式的贡献,包括但不限于报告 bug、提交修复、添加新功能、改进文档等。
|
||||
|
||||
## 📄 许可证
|
||||
Easy-FLV 根据 [Apache License 2.0](LICENSE) 发布。
|
||||
Easy-FLV 根据 [Apache License 2.0](LICENSE) 发布。
|
||||
|
||||
## 📧 联系
|
||||
- Email: [javpower@163.com](mailto:your-email@example.com)
|
||||
- GitHub: [https://github.com/javpower/easy-flv](https://github.com/javpower/easy-flv)
|
||||
- Gitee: [https://gitee.com/giteeClass/easy-flv](https://gitee.com/giteeClass/easy-flv)
|
17
pom.xml
17
pom.xml
@ -2,12 +2,12 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>me.zhengjie</groupId>
|
||||
<artifactId>eladmin-flv</artifactId>
|
||||
<version>1.1</version>
|
||||
<name>视频模块</name>
|
||||
<description>视频流转换 rtsp/rtmp 转flv</description>
|
||||
|
||||
<groupId>io.github.javpower</groupId>
|
||||
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||
<version>1.5.9.1</version>
|
||||
<name>rtsp-converter-flv-spring-boot-starter</name>
|
||||
<description>a tool about easy rtsp-converter-flv</description>
|
||||
<url>https://github.com/javpower/easy-flv</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name> The Apache Software License, Version 2.0 </name>
|
||||
@ -38,11 +38,6 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>me.zhengjie</groupId>
|
||||
<artifactId>eladmin-logging</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
|
@ -2,16 +2,10 @@ package com.gc.easy.flv.controller;
|
||||
|
||||
import com.gc.easy.flv.service.IFLVService;
|
||||
import com.gc.easy.flv.service.IOpenFLVService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhengjie.annotation.Log;
|
||||
import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -20,28 +14,31 @@ import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* FLV流转换
|
||||
*
|
||||
*
|
||||
* @author gc.x
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("api/front/flv")
|
||||
@Api(tags = "微信:拉流播放")
|
||||
@RestController
|
||||
public class FLVController {
|
||||
|
||||
@Autowired
|
||||
private IFLVService service;
|
||||
@Autowired
|
||||
private IFLVService service;
|
||||
@Autowired(required = false)
|
||||
private IOpenFLVService openFLVService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private IOpenFLVService openFLVService;
|
||||
|
||||
@Log("拉流播放")
|
||||
@ApiOperation(value = "打开视频流")
|
||||
// @GetMapping(value = "/get/stream")
|
||||
@AnonymousGetMapping(value = "/get/stream")
|
||||
public void open(String url, HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
|
||||
if (!StringUtils.isEmpty(url)) {
|
||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||
service.open(decodedUrl, response, request, openFLVService);
|
||||
}
|
||||
}
|
||||
@GetMapping(value = "/get/flv/hls/stream_{channel}.flv")
|
||||
public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response,
|
||||
HttpServletRequest request) {
|
||||
String url = openFLVService.getUrl(channel);
|
||||
if(!StringUtils.isEmpty(url)){
|
||||
service.open(channel,url, response, request);
|
||||
}
|
||||
}
|
||||
@GetMapping(value = "/get/flv/hls/stream")
|
||||
public void open1(String url, HttpServletResponse response,
|
||||
HttpServletRequest request) throws UnsupportedEncodingException {
|
||||
if(!StringUtils.isEmpty(url)){
|
||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||
service.open(decodedUrl, response, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,39 @@
|
||||
//package com.gc.easy.flv.controller;
|
||||
//
|
||||
//import com.gc.easy.flv.config.FlvConfig;
|
||||
//import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.stereotype.Controller;
|
||||
//import org.springframework.ui.Model;
|
||||
//
|
||||
//import java.io.UnsupportedEncodingException;
|
||||
//
|
||||
///**
|
||||
// * FLV流转换
|
||||
// *
|
||||
// * @author gc.x
|
||||
// */
|
||||
//@Controller
|
||||
//public class FLVPlayController {
|
||||
// @Autowired
|
||||
// private FlvConfig flvConfig;
|
||||
//
|
||||
// @AnonymousGetMapping(value = "/flv/hls/stream")
|
||||
// public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
||||
// String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||
// String videoPath="/api/front/flv/get/stream?url="+decodedUrl;
|
||||
// model.addAttribute("videoPath", videoPath);
|
||||
// model.addAttribute("wight", flvConfig.getWight());
|
||||
// model.addAttribute("height", flvConfig.getHeight());
|
||||
// return "video";
|
||||
// }
|
||||
//}
|
||||
package com.gc.easy.flv.controller;
|
||||
|
||||
import com.gc.easy.flv.config.FlvConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* FLV流转换
|
||||
*
|
||||
* @author gc.x
|
||||
*/
|
||||
@Controller
|
||||
public class FLVPlayController {
|
||||
@Autowired
|
||||
private FlvConfig flvConfig;
|
||||
|
||||
@GetMapping(value = "/flv/hls/stream_{channel}.flv")
|
||||
public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) {
|
||||
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv";
|
||||
model.addAttribute("videoPath", videoPath);
|
||||
model.addAttribute("wight", flvConfig.getWight());
|
||||
model.addAttribute("height", flvConfig.getHeight());
|
||||
return "video";
|
||||
}
|
||||
@GetMapping(value = "/flv/hls/stream")
|
||||
public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream?url="+decodedUrl;
|
||||
model.addAttribute("videoPath", videoPath);
|
||||
model.addAttribute("wight", flvConfig.getWight());
|
||||
model.addAttribute("height", flvConfig.getHeight());
|
||||
return "video";
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.gc.easy.flv.factories;
|
||||
|
||||
import com.alibaba.fastjson.util.IOUtils;
|
||||
import com.gc.easy.flv.service.IOpenFLVService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
||||
import org.bytedeco.ffmpeg.global.avcodec;
|
||||
@ -24,209 +23,206 @@ import java.util.Map;
|
||||
*/
|
||||
@Slf4j
|
||||
public class ConverterFactories extends Thread implements Converter {
|
||||
public volatile boolean runing = true;
|
||||
/**
|
||||
* 读流器
|
||||
*/
|
||||
private FFmpegFrameGrabber grabber;
|
||||
/**
|
||||
* 转码器
|
||||
*/
|
||||
private FFmpegFrameRecorder recorder;
|
||||
/**
|
||||
* 转FLV格式的头信息<br/>
|
||||
* 如果有第二个客户端播放首先要返回头信息
|
||||
*/
|
||||
private byte[] headers;
|
||||
/**
|
||||
* 保存转换好的流
|
||||
*/
|
||||
private ByteArrayOutputStream stream;
|
||||
/**
|
||||
* 流地址,h264,aac
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 流输出
|
||||
*/
|
||||
private List<AsyncContext> outEntitys;
|
||||
public volatile boolean runing = true;
|
||||
/**
|
||||
* 读流器
|
||||
*/
|
||||
private FFmpegFrameGrabber grabber;
|
||||
/**
|
||||
* 转码器
|
||||
*/
|
||||
private FFmpegFrameRecorder recorder;
|
||||
/**
|
||||
* 转FLV格式的头信息<br/>
|
||||
* 如果有第二个客户端播放首先要返回头信息
|
||||
*/
|
||||
private byte[] headers;
|
||||
/**
|
||||
* 保存转换好的流
|
||||
*/
|
||||
private ByteArrayOutputStream stream;
|
||||
/**
|
||||
* 流地址,h264,aac
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 流输出
|
||||
*/
|
||||
private List<AsyncContext> outEntitys;
|
||||
|
||||
/**
|
||||
* key用于表示这个转换器
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* key用于表示这个转换器
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 转换队列
|
||||
*/
|
||||
private Map<String, Converter> factories;
|
||||
|
||||
private IOpenFLVService openFLVService;
|
||||
/**
|
||||
* 转换队列
|
||||
*/
|
||||
private Map<String, Converter> factories;
|
||||
|
||||
|
||||
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys, IOpenFLVService openFLVService) {
|
||||
this.url = url;
|
||||
this.key = key;
|
||||
this.factories = factories;
|
||||
this.outEntitys = outEntitys;
|
||||
this.openFLVService = openFLVService;
|
||||
}
|
||||
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("timeout", "5000000");
|
||||
}
|
||||
grabber.start();
|
||||
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
|
||||
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec()) && (openFLVService == null || !openFLVService.openPreview())) {
|
||||
log.info("this url:{} converterFactories start", url);
|
||||
// 来源视频H264格式,音频AAC格式
|
||||
// 无须转码,更低的资源消耗,更低的延迟
|
||||
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;
|
||||
// 需要转码为视频H264格式,音频AAC格式
|
||||
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber, openFLVService);
|
||||
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);
|
||||
@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格式
|
||||
// 无须转码,更低的资源消耗,更低的延迟
|
||||
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;
|
||||
// 需要转码为视频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("移除一个输出");
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 输出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("移除一个输出");
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出转换
|
||||
*/
|
||||
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
||||
if (isCloseGrabberAndResponse) {
|
||||
IOUtils.close(grabber);
|
||||
factories.remove(this.key);
|
||||
}
|
||||
IOUtils.close(recorder);
|
||||
IOUtils.close(stream);
|
||||
}
|
||||
/**
|
||||
* 退出转换
|
||||
*/
|
||||
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
||||
if (isCloseGrabberAndResponse) {
|
||||
IOUtils.close(grabber);
|
||||
factories.remove(this.key);
|
||||
}
|
||||
IOUtils.close(recorder);
|
||||
IOUtils.close(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭异步响应
|
||||
*
|
||||
* @param isCloseGrabberAndResponse
|
||||
*/
|
||||
public void completeResponse(boolean isCloseGrabberAndResponse) {
|
||||
if (isCloseGrabberAndResponse) {
|
||||
Iterator<AsyncContext> it = outEntitys.iterator();
|
||||
while (it.hasNext()) {
|
||||
AsyncContext o = it.next();
|
||||
o.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 关闭异步响应
|
||||
*
|
||||
* @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 getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
@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 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);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void exit() {
|
||||
this.runing = false;
|
||||
try {
|
||||
this.join();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
package com.gc.easy.flv.factories;
|
||||
|
||||
import com.alibaba.fastjson.util.IOUtils;
|
||||
import com.gc.easy.flv.service.IOpenFLVService;
|
||||
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 org.bytedeco.javacv.OpenCVFrameConverter;
|
||||
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -62,18 +59,13 @@ public class ConverterTranFactories extends Thread implements Converter {
|
||||
*/
|
||||
private Map<String, Converter> factories;
|
||||
|
||||
private OpenCVFrameConverter.ToIplImage converter;
|
||||
private IOpenFLVService openFLVService;
|
||||
|
||||
|
||||
public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
|
||||
List<AsyncContext> outEntitys, FFmpegFrameGrabber grabber, IOpenFLVService openFLVService) {
|
||||
List<AsyncContext> outEntitys, FFmpegFrameGrabber grabber) {
|
||||
this.url = url;
|
||||
this.key = key;
|
||||
this.factories = factories;
|
||||
this.outEntitys = outEntitys;
|
||||
this.grabber = grabber;
|
||||
this.openFLVService=openFLVService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,8 +82,6 @@ public class ConverterTranFactories extends Thread implements Converter {
|
||||
stream = new ByteArrayOutputStream();
|
||||
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||
grabber.getAudioChannels());
|
||||
converter = new OpenCVFrameConverter.ToIplImage();
|
||||
|
||||
recorder.setInterleaved(true);
|
||||
recorder.setVideoOption("preset", "ultrafast");
|
||||
recorder.setVideoOption("tune", "zerolatency");
|
||||
@ -117,14 +107,6 @@ public class ConverterTranFactories extends Thread implements Converter {
|
||||
while (runing) {
|
||||
// 抓取一帧
|
||||
Frame f = grabber.grab();
|
||||
if(openFLVService!=null&&openFLVService.openPreview()){
|
||||
//预处理
|
||||
IplImage iplImage = converter.convert(f);//抓取一帧视频并将其转换为图像,至于用这个图像用来做什么?加水印,人脸识别等等自行添加
|
||||
if (iplImage != null) {
|
||||
openFLVService.preview(iplImage);
|
||||
f = converter.convert(iplImage);
|
||||
}
|
||||
}
|
||||
if (f != null) {
|
||||
try {
|
||||
// 转码
|
||||
|
@ -11,7 +11,7 @@ public interface IFLVService {
|
||||
* @param url
|
||||
* @param response
|
||||
*/
|
||||
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
|
||||
public void open(String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
|
||||
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request);
|
||||
public void open(String url, HttpServletResponse response, HttpServletRequest request);
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.gc.easy.flv.service;
|
||||
|
||||
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||
|
||||
public interface IOpenFLVService {
|
||||
|
||||
|
||||
@ -10,17 +8,5 @@ public interface IOpenFLVService {
|
||||
* @param channel
|
||||
* @return
|
||||
*/
|
||||
String getUrl(Integer channel);
|
||||
|
||||
/**
|
||||
* 抓取一帧视频并将其转换为图像预处理
|
||||
* @param iplImage
|
||||
*/
|
||||
void preview(IplImage iplImage);
|
||||
|
||||
/**
|
||||
* 开启预处理
|
||||
* @return
|
||||
*/
|
||||
boolean openPreview();
|
||||
public String getUrl(Integer channel);
|
||||
}
|
@ -4,7 +4,6 @@ package com.gc.easy.flv.service.impl;
|
||||
import com.gc.easy.flv.factories.Converter;
|
||||
import com.gc.easy.flv.factories.ConverterFactories;
|
||||
import com.gc.easy.flv.service.IFLVService;
|
||||
import com.gc.easy.flv.service.IOpenFLVService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -20,70 +19,68 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* FLV流转换
|
||||
*
|
||||
*
|
||||
* @author gc.x
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FLVService implements IFLVService {
|
||||
|
||||
private ConcurrentHashMap<String, Converter> converters = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<String, Converter> converters = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public void open(String url, HttpServletResponse response, HttpServletRequest request) {
|
||||
open(null,url,response,request);
|
||||
}
|
||||
@Override
|
||||
public void open(Integer channel,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);
|
||||
throw new IllegalArgumentException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
List<AsyncContext> outs = new ArrayList<>();
|
||||
outs.add(async);
|
||||
ConverterFactories c = new ConverterFactories(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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
||||
open(null, url, response, request, openFLVService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
||||
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);
|
||||
throw new IllegalArgumentException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
List<AsyncContext> outs = new ArrayList<>();
|
||||
outs.add(async);
|
||||
ConverterFactories c = new ConverterFactories(url, key, converters, outs, openFLVService);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public String md5(String plainText) {
|
||||
StringBuilder buf = null;
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(plainText.getBytes());
|
||||
byte b[] = md.digest();
|
||||
int i;
|
||||
buf = new StringBuilder("");
|
||||
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();
|
||||
}
|
||||
public String md5(String plainText) {
|
||||
StringBuilder buf = null;
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(plainText.getBytes());
|
||||
byte b[] = md.digest();
|
||||
int i;
|
||||
buf = new StringBuilder("");
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
package com.gc.easy.flv.service.impl;
|
||||
|
||||
|
||||
import com.gc.easy.flv.service.IOpenFLVService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* FLV流转换
|
||||
*
|
||||
* @author gc.x
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpenFLVService implements IOpenFLVService {
|
||||
|
||||
|
||||
@Override
|
||||
public String getUrl(Integer channel) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preview(IplImage iplImage) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean openPreview() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -5,33 +5,33 @@
|
||||
<title>FLV Video Player</title>
|
||||
|
||||
<!-- 引入flv.js库 -->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flv.js@1.5.0/dist/flv.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<video id="videoPlayer" th:style="'width:500px; height:500px;'" controls></video>
|
||||
<video id="videoPlayer" th:style="'width:' + ${width} + 'px; height:' + ${height} + 'px;'" controls></video>
|
||||
|
||||
<script th:inline="javascript">
|
||||
(function () {
|
||||
var videoPath = /*[[${videoPath}]]*/ '';
|
||||
(function() {
|
||||
var videoPath = /*[[${videoPath}]]*/ '';
|
||||
|
||||
if (flvjs.isSupported()) {
|
||||
var videoElement = document.getElementById('videoPlayer');
|
||||
var flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
url: 'http://localhost:8000' + videoPath
|
||||
});
|
||||
if (flvjs.isSupported()) {
|
||||
var videoElement = document.getElementById('videoPlayer');
|
||||
var flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
url: videoPath
|
||||
});
|
||||
|
||||
flvPlayer.attachMediaElement(videoElement);
|
||||
flvPlayer.load();
|
||||
flvPlayer.attachMediaElement(videoElement);
|
||||
flvPlayer.load();
|
||||
|
||||
// 播放视频
|
||||
videoElement.play();
|
||||
} else {
|
||||
console.error('FLV not supported');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
// 播放视频
|
||||
videoElement.play();
|
||||
} else {
|
||||
console.error('FLV not supported');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,37 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>FLV Video Player</title>
|
||||
|
||||
<!-- 引入flv.js库 -->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<video id="videoPlayer" th:style="'width:500px; height:500px;'" controls></video>
|
||||
|
||||
<script th:inline="javascript">
|
||||
(function () {
|
||||
var videoPath = /*[[${videoPath}]]*/ '';
|
||||
|
||||
if (flvjs.isSupported()) {
|
||||
var videoElement = document.getElementById('videoPlayer');
|
||||
var flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
url: 'http://localhost:8000' + videoPath
|
||||
});
|
||||
|
||||
flvPlayer.attachMediaElement(videoElement);
|
||||
flvPlayer.load();
|
||||
|
||||
// 播放视频
|
||||
videoElement.play();
|
||||
} else {
|
||||
console.error('FLV not supported');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user