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 to FLV Converter -->
|
||||||
# 📺 Easy-FLV: Java 实现的 RTSP/RTMP 到 FLV 转换器
|
# 📺 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
|
||||||
Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流转换为 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
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>me.zhengjie</groupId>
|
<groupId>io.github.javpower</groupId>
|
||||||
<artifactId>eladmin-flv</artifactId>
|
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||||
<version>1.1</version>
|
<version>1.5.9.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -35,7 +45,7 @@ public class RtspDataService implements IOpenFLVService {
|
|||||||
@Override
|
@Override
|
||||||
public String getUrl(Integer channel) {
|
public String getUrl(Integer channel) {
|
||||||
// 根据 channel 获取 RTSP 视频流地址
|
// 根据 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、提交修复、添加新功能、改进文档等。
|
我们欢迎任何形式的贡献,包括但不限于报告 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"
|
<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">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>me.zhengjie</groupId>
|
<groupId>io.github.javpower</groupId>
|
||||||
<artifactId>eladmin-flv</artifactId>
|
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||||
<version>1.1</version>
|
<version>1.5.9.1</version>
|
||||||
<name>视频模块</name>
|
<name>rtsp-converter-flv-spring-boot-starter</name>
|
||||||
<description>视频流转换 rtsp/rtmp 转flv</description>
|
<description>a tool about easy rtsp-converter-flv</description>
|
||||||
|
<url>https://github.com/javpower/easy-flv</url>
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name> The Apache Software License, Version 2.0 </name>
|
<name> The Apache Software License, Version 2.0 </name>
|
||||||
@ -38,11 +38,6 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>me.zhengjie</groupId>
|
|
||||||
<artifactId>eladmin-logging</artifactId>
|
|
||||||
<version>1.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<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.IFLVService;
|
||||||
import com.gc.easy.flv.service.IOpenFLVService;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -20,28 +14,31 @@ import java.io.UnsupportedEncodingException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FLV流转换
|
* FLV流转换
|
||||||
*
|
*
|
||||||
* @author gc.x
|
* @author gc.x
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@RestController
|
||||||
@RestController("api/front/flv")
|
|
||||||
@Api(tags = "微信:拉流播放")
|
|
||||||
public class FLVController {
|
public class FLVController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IFLVService service;
|
private IFLVService service;
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IOpenFLVService openFLVService;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@GetMapping(value = "/get/flv/hls/stream_{channel}.flv")
|
||||||
private IOpenFLVService openFLVService;
|
public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response,
|
||||||
|
HttpServletRequest request) {
|
||||||
@Log("拉流播放")
|
String url = openFLVService.getUrl(channel);
|
||||||
@ApiOperation(value = "打开视频流")
|
if(!StringUtils.isEmpty(url)){
|
||||||
// @GetMapping(value = "/get/stream")
|
service.open(channel,url, response, request);
|
||||||
@AnonymousGetMapping(value = "/get/stream")
|
}
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
|
}
|
||||||
if (!StringUtils.isEmpty(url)) {
|
@GetMapping(value = "/get/flv/hls/stream")
|
||||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
public void open1(String url, HttpServletResponse response,
|
||||||
service.open(decodedUrl, response, request, openFLVService);
|
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;
|
package com.gc.easy.flv.controller;
|
||||||
//
|
|
||||||
//import com.gc.easy.flv.config.FlvConfig;
|
import com.gc.easy.flv.config.FlvConfig;
|
||||||
//import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
//import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Controller;
|
||||||
//import org.springframework.stereotype.Controller;
|
import org.springframework.ui.Model;
|
||||||
//import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
//
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
//import java.io.UnsupportedEncodingException;
|
|
||||||
//
|
import java.io.UnsupportedEncodingException;
|
||||||
///**
|
|
||||||
// * FLV流转换
|
/**
|
||||||
// *
|
* FLV流转换
|
||||||
// * @author gc.x
|
*
|
||||||
// */
|
* @author gc.x
|
||||||
//@Controller
|
*/
|
||||||
//public class FLVPlayController {
|
@Controller
|
||||||
// @Autowired
|
public class FLVPlayController {
|
||||||
// private FlvConfig flvConfig;
|
@Autowired
|
||||||
//
|
private FlvConfig flvConfig;
|
||||||
// @AnonymousGetMapping(value = "/flv/hls/stream")
|
|
||||||
// public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
@GetMapping(value = "/flv/hls/stream_{channel}.flv")
|
||||||
// String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) {
|
||||||
// String videoPath="/api/front/flv/get/stream?url="+decodedUrl;
|
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv";
|
||||||
// model.addAttribute("videoPath", videoPath);
|
model.addAttribute("videoPath", videoPath);
|
||||||
// model.addAttribute("wight", flvConfig.getWight());
|
model.addAttribute("wight", flvConfig.getWight());
|
||||||
// model.addAttribute("height", flvConfig.getHeight());
|
model.addAttribute("height", flvConfig.getHeight());
|
||||||
// return "video";
|
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;
|
package com.gc.easy.flv.factories;
|
||||||
|
|
||||||
import com.alibaba.fastjson.util.IOUtils;
|
import com.alibaba.fastjson.util.IOUtils;
|
||||||
import com.gc.easy.flv.service.IOpenFLVService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
||||||
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avcodec;
|
||||||
@ -24,209 +23,206 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ConverterFactories extends Thread implements Converter {
|
public class ConverterFactories extends Thread implements Converter {
|
||||||
public volatile boolean runing = true;
|
public volatile boolean runing = true;
|
||||||
/**
|
/**
|
||||||
* 读流器
|
* 读流器
|
||||||
*/
|
*/
|
||||||
private FFmpegFrameGrabber grabber;
|
private FFmpegFrameGrabber grabber;
|
||||||
/**
|
/**
|
||||||
* 转码器
|
* 转码器
|
||||||
*/
|
*/
|
||||||
private FFmpegFrameRecorder recorder;
|
private FFmpegFrameRecorder recorder;
|
||||||
/**
|
/**
|
||||||
* 转FLV格式的头信息<br/>
|
* 转FLV格式的头信息<br/>
|
||||||
* 如果有第二个客户端播放首先要返回头信息
|
* 如果有第二个客户端播放首先要返回头信息
|
||||||
*/
|
*/
|
||||||
private byte[] headers;
|
private byte[] headers;
|
||||||
/**
|
/**
|
||||||
* 保存转换好的流
|
* 保存转换好的流
|
||||||
*/
|
*/
|
||||||
private ByteArrayOutputStream stream;
|
private ByteArrayOutputStream stream;
|
||||||
/**
|
/**
|
||||||
* 流地址,h264,aac
|
* 流地址,h264,aac
|
||||||
*/
|
*/
|
||||||
private String url;
|
private String url;
|
||||||
/**
|
/**
|
||||||
* 流输出
|
* 流输出
|
||||||
*/
|
*/
|
||||||
private List<AsyncContext> outEntitys;
|
private List<AsyncContext> outEntitys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* key用于表示这个转换器
|
* key用于表示这个转换器
|
||||||
*/
|
*/
|
||||||
private String key;
|
private String key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换队列
|
* 转换队列
|
||||||
*/
|
*/
|
||||||
private Map<String, Converter> factories;
|
private Map<String, Converter> factories;
|
||||||
|
|
||||||
private IOpenFLVService openFLVService;
|
|
||||||
|
|
||||||
|
|
||||||
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys, IOpenFLVService openFLVService) {
|
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.factories = factories;
|
this.factories = factories;
|
||||||
this.outEntitys = outEntitys;
|
this.outEntitys = outEntitys;
|
||||||
this.openFLVService = openFLVService;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean isCloseGrabberAndResponse = true;
|
boolean isCloseGrabberAndResponse = true;
|
||||||
try {
|
try {
|
||||||
grabber = new FFmpegFrameGrabber(url);
|
grabber = new FFmpegFrameGrabber(url);
|
||||||
if ("rtsp".equals(url.substring(0, 4))) {
|
if ("rtsp".equals(url.substring(0, 4))) {
|
||||||
grabber.setOption("rtsp_transport", "tcp");
|
grabber.setOption("rtsp_transport", "tcp");
|
||||||
grabber.setOption("timeout", "5000000");
|
grabber.setOption("stimeout", "5000000");
|
||||||
}
|
}
|
||||||
grabber.start();
|
grabber.start();
|
||||||
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
|
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
|
||||||
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec()) && (openFLVService == null || !openFLVService.openPreview())) {
|
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())) {
|
||||||
log.info("this url:{} converterFactories start", url);
|
log.info("this url:{} converterFactories start", url);
|
||||||
// 来源视频H264格式,音频AAC格式
|
// 来源视频H264格式,音频AAC格式
|
||||||
// 无须转码,更低的资源消耗,更低的延迟
|
// 无须转码,更低的资源消耗,更低的延迟
|
||||||
stream = new ByteArrayOutputStream();
|
stream = new ByteArrayOutputStream();
|
||||||
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||||
grabber.getAudioChannels());
|
grabber.getAudioChannels());
|
||||||
recorder.setInterleaved(true);
|
recorder.setInterleaved(true);
|
||||||
recorder.setVideoOption("preset", "ultrafast");
|
recorder.setVideoOption("preset", "ultrafast");
|
||||||
recorder.setVideoOption("tune", "zerolatency");
|
recorder.setVideoOption("tune", "zerolatency");
|
||||||
recorder.setVideoOption("crf", "25");
|
recorder.setVideoOption("crf", "25");
|
||||||
recorder.setFrameRate(grabber.getFrameRate());
|
recorder.setFrameRate(grabber.getFrameRate());
|
||||||
recorder.setSampleRate(grabber.getSampleRate());
|
recorder.setSampleRate(grabber.getSampleRate());
|
||||||
if (grabber.getAudioChannels() > 0) {
|
if (grabber.getAudioChannels() > 0) {
|
||||||
recorder.setAudioChannels(grabber.getAudioChannels());
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
||||||
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
||||||
recorder.setAudioCodec(grabber.getAudioCodec());
|
recorder.setAudioCodec(grabber.getAudioCodec());
|
||||||
}
|
}
|
||||||
recorder.setFormat("flv");
|
recorder.setFormat("flv");
|
||||||
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
||||||
recorder.setVideoCodec(grabber.getVideoCodec());
|
recorder.setVideoCodec(grabber.getVideoCodec());
|
||||||
recorder.start(grabber.getFormatContext());
|
recorder.start(grabber.getFormatContext());
|
||||||
if (headers == null) {
|
if (headers == null) {
|
||||||
headers = stream.toByteArray();
|
headers = stream.toByteArray();
|
||||||
stream.reset();
|
stream.reset();
|
||||||
writeResponse(headers);
|
writeResponse(headers);
|
||||||
}
|
}
|
||||||
int nullNumber = 0;
|
int nullNumber = 0;
|
||||||
while (runing) {
|
while (runing) {
|
||||||
AVPacket k = grabber.grabPacket();
|
AVPacket k = grabber.grabPacket();
|
||||||
if (k != null) {
|
if (k != null) {
|
||||||
try {
|
try {
|
||||||
recorder.recordPacket(k);
|
recorder.recordPacket(k);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
if (stream.size() > 0) {
|
if (stream.size() > 0) {
|
||||||
byte[] b = stream.toByteArray();
|
byte[] b = stream.toByteArray();
|
||||||
stream.reset();
|
stream.reset();
|
||||||
writeResponse(b);
|
writeResponse(b);
|
||||||
if (outEntitys.isEmpty()) {
|
if (outEntitys.isEmpty()) {
|
||||||
log.info("没有输出退出");
|
log.info("没有输出退出");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
avcodec.av_packet_unref(k);
|
avcodec.av_packet_unref(k);
|
||||||
} else {
|
} else {
|
||||||
nullNumber++;
|
nullNumber++;
|
||||||
if (nullNumber > 200) {
|
if (nullNumber > 200) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Thread.sleep(5);
|
Thread.sleep(5);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isCloseGrabberAndResponse = false;
|
isCloseGrabberAndResponse = false;
|
||||||
// 需要转码为视频H264格式,音频AAC格式
|
// 需要转码为视频H264格式,音频AAC格式
|
||||||
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber, openFLVService);
|
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber);
|
||||||
factories.put(key, c);
|
factories.put(key, c);
|
||||||
c.start();
|
c.start();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
closeConverter(isCloseGrabberAndResponse);
|
closeConverter(isCloseGrabberAndResponse);
|
||||||
completeResponse(isCloseGrabberAndResponse);
|
completeResponse(isCloseGrabberAndResponse);
|
||||||
log.info("this url:{} converterFactories exit", url);
|
log.info("this url:{} converterFactories exit", url);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输出FLV视频流
|
* 输出FLV视频流
|
||||||
*
|
*
|
||||||
* @param b
|
* @param b
|
||||||
*/
|
*/
|
||||||
public void writeResponse(byte[] b) {
|
public void writeResponse(byte[] b) {
|
||||||
Iterator<AsyncContext> it = outEntitys.iterator();
|
Iterator<AsyncContext> it = outEntitys.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
AsyncContext o = it.next();
|
AsyncContext o = it.next();
|
||||||
try {
|
try {
|
||||||
o.getResponse().getOutputStream().write(b);
|
o.getResponse().getOutputStream().write(b);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("移除一个输出");
|
log.info("移除一个输出");
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出转换
|
* 退出转换
|
||||||
*/
|
*/
|
||||||
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
||||||
if (isCloseGrabberAndResponse) {
|
if (isCloseGrabberAndResponse) {
|
||||||
IOUtils.close(grabber);
|
IOUtils.close(grabber);
|
||||||
factories.remove(this.key);
|
factories.remove(this.key);
|
||||||
}
|
}
|
||||||
IOUtils.close(recorder);
|
IOUtils.close(recorder);
|
||||||
IOUtils.close(stream);
|
IOUtils.close(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭异步响应
|
* 关闭异步响应
|
||||||
*
|
*
|
||||||
* @param isCloseGrabberAndResponse
|
* @param isCloseGrabberAndResponse
|
||||||
*/
|
*/
|
||||||
public void completeResponse(boolean isCloseGrabberAndResponse) {
|
public void completeResponse(boolean isCloseGrabberAndResponse) {
|
||||||
if (isCloseGrabberAndResponse) {
|
if (isCloseGrabberAndResponse) {
|
||||||
Iterator<AsyncContext> it = outEntitys.iterator();
|
Iterator<AsyncContext> it = outEntitys.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
AsyncContext o = it.next();
|
AsyncContext o = it.next();
|
||||||
o.complete();
|
o.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return this.key;
|
return this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
|
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
|
||||||
if (headers == null) {
|
if (headers == null) {
|
||||||
outEntitys.add(entity);
|
outEntitys.add(entity);
|
||||||
} else {
|
} else {
|
||||||
entity.getResponse().getOutputStream().write(headers);
|
entity.getResponse().getOutputStream().write(headers);
|
||||||
entity.getResponse().getOutputStream().flush();
|
entity.getResponse().getOutputStream().flush();
|
||||||
outEntitys.add(entity);
|
outEntitys.add(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit() {
|
public void exit() {
|
||||||
this.runing = false;
|
this.runing = false;
|
||||||
try {
|
try {
|
||||||
this.join();
|
this.join();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package com.gc.easy.flv.factories;
|
package com.gc.easy.flv.factories;
|
||||||
|
|
||||||
import com.alibaba.fastjson.util.IOUtils;
|
import com.alibaba.fastjson.util.IOUtils;
|
||||||
import com.gc.easy.flv.service.IOpenFLVService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avcodec;
|
||||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||||
import org.bytedeco.javacv.Frame;
|
import org.bytedeco.javacv.Frame;
|
||||||
import org.bytedeco.javacv.OpenCVFrameConverter;
|
|
||||||
import org.bytedeco.opencv.opencv_core.IplImage;
|
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -62,18 +59,13 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
*/
|
*/
|
||||||
private Map<String, Converter> factories;
|
private Map<String, Converter> factories;
|
||||||
|
|
||||||
private OpenCVFrameConverter.ToIplImage converter;
|
|
||||||
private IOpenFLVService openFLVService;
|
|
||||||
|
|
||||||
|
|
||||||
public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
|
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.url = url;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.factories = factories;
|
this.factories = factories;
|
||||||
this.outEntitys = outEntitys;
|
this.outEntitys = outEntitys;
|
||||||
this.grabber = grabber;
|
this.grabber = grabber;
|
||||||
this.openFLVService=openFLVService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,8 +82,6 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
stream = new ByteArrayOutputStream();
|
stream = new ByteArrayOutputStream();
|
||||||
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||||
grabber.getAudioChannels());
|
grabber.getAudioChannels());
|
||||||
converter = new OpenCVFrameConverter.ToIplImage();
|
|
||||||
|
|
||||||
recorder.setInterleaved(true);
|
recorder.setInterleaved(true);
|
||||||
recorder.setVideoOption("preset", "ultrafast");
|
recorder.setVideoOption("preset", "ultrafast");
|
||||||
recorder.setVideoOption("tune", "zerolatency");
|
recorder.setVideoOption("tune", "zerolatency");
|
||||||
@ -117,14 +107,6 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
while (runing) {
|
while (runing) {
|
||||||
// 抓取一帧
|
// 抓取一帧
|
||||||
Frame f = grabber.grab();
|
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) {
|
if (f != null) {
|
||||||
try {
|
try {
|
||||||
// 转码
|
// 转码
|
||||||
|
@ -11,7 +11,7 @@ public interface IFLVService {
|
|||||||
* @param url
|
* @param url
|
||||||
* @param response
|
* @param response
|
||||||
*/
|
*/
|
||||||
public void open(Integer channel,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,IOpenFLVService openFLVService);
|
public void open(String url, HttpServletResponse response, HttpServletRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package com.gc.easy.flv.service;
|
package com.gc.easy.flv.service;
|
||||||
|
|
||||||
import org.bytedeco.opencv.opencv_core.IplImage;
|
|
||||||
|
|
||||||
public interface IOpenFLVService {
|
public interface IOpenFLVService {
|
||||||
|
|
||||||
|
|
||||||
@ -10,17 +8,5 @@ public interface IOpenFLVService {
|
|||||||
* @param channel
|
* @param channel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
String getUrl(Integer channel);
|
public String getUrl(Integer channel);
|
||||||
|
|
||||||
/**
|
|
||||||
* 抓取一帧视频并将其转换为图像预处理
|
|
||||||
* @param iplImage
|
|
||||||
*/
|
|
||||||
void preview(IplImage iplImage);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开启预处理
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
boolean openPreview();
|
|
||||||
}
|
}
|
@ -4,7 +4,6 @@ package com.gc.easy.flv.service.impl;
|
|||||||
import com.gc.easy.flv.factories.Converter;
|
import com.gc.easy.flv.factories.Converter;
|
||||||
import com.gc.easy.flv.factories.ConverterFactories;
|
import com.gc.easy.flv.factories.ConverterFactories;
|
||||||
import com.gc.easy.flv.service.IFLVService;
|
import com.gc.easy.flv.service.IFLVService;
|
||||||
import com.gc.easy.flv.service.IOpenFLVService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@ -20,70 +19,68 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FLV流转换
|
* FLV流转换
|
||||||
*
|
*
|
||||||
* @author gc.x
|
* @author gc.x
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class FLVService implements IFLVService {
|
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 String md5(String plainText) {
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
StringBuilder buf = null;
|
||||||
open(null, url, response, request, openFLVService);
|
try {
|
||||||
}
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(plainText.getBytes());
|
||||||
@Override
|
byte b[] = md.digest();
|
||||||
public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
int i;
|
||||||
String key = md5(url);
|
buf = new StringBuilder("");
|
||||||
AsyncContext async = request.startAsync();
|
for (int offset = 0; offset < b.length; offset++) {
|
||||||
async.setTimeout(0);
|
i = b[offset];
|
||||||
if (converters.containsKey(key)) {
|
if (i < 0)
|
||||||
Converter c = converters.get(key);
|
i += 256;
|
||||||
try {
|
if (i < 16)
|
||||||
c.addOutputStreamEntity(key, async);
|
buf.append("0");
|
||||||
} catch (IOException e) {
|
buf.append(Integer.toHexString(i));
|
||||||
log.error(e.getMessage(), e);
|
}
|
||||||
throw new IllegalArgumentException(e.getMessage());
|
} catch (NoSuchAlgorithmException e) {
|
||||||
}
|
log.error(e.getMessage(), e);
|
||||||
} else {
|
}
|
||||||
List<AsyncContext> outs = new ArrayList<>();
|
return buf.toString();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
<title>FLV Video Player</title>
|
||||||
|
|
||||||
<!-- 引入flv.js库 -->
|
<!-- 引入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>
|
</head>
|
||||||
<body>
|
<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">
|
<script th:inline="javascript">
|
||||||
(function () {
|
(function() {
|
||||||
var videoPath = /*[[${videoPath}]]*/ '';
|
var videoPath = /*[[${videoPath}]]*/ '';
|
||||||
|
|
||||||
if (flvjs.isSupported()) {
|
if (flvjs.isSupported()) {
|
||||||
var videoElement = document.getElementById('videoPlayer');
|
var videoElement = document.getElementById('videoPlayer');
|
||||||
var flvPlayer = flvjs.createPlayer({
|
var flvPlayer = flvjs.createPlayer({
|
||||||
type: 'flv',
|
type: 'flv',
|
||||||
url: 'http://localhost:8000' + videoPath
|
url: videoPath
|
||||||
});
|
});
|
||||||
|
|
||||||
flvPlayer.attachMediaElement(videoElement);
|
flvPlayer.attachMediaElement(videoElement);
|
||||||
flvPlayer.load();
|
flvPlayer.load();
|
||||||
|
|
||||||
// 播放视频
|
// 播放视频
|
||||||
videoElement.play();
|
videoElement.play();
|
||||||
} else {
|
} else {
|
||||||
console.error('FLV not supported');
|
console.error('FLV not supported');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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