diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 21b477f..0000000 --- a/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -README.cn.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/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md deleted file mode 100644 index 8208e02..0000000 --- a/README.md +++ /dev/null @@ -1,91 +0,0 @@ - -# 📺 Easy-FLV: Java RTSP/RTMP to FLV Converter - -[![GitHub stars](https://img.shields.io/github/stars/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv) -[![GitHub issues](https://img.shields.io/github/issues/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv/issues) -[![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Java Version](https://img.shields.io/badge/java-1.8+-orange.svg)](https://adoptopenjdk.net/) -[![Spring Boot](https://img.shields.io/badge/spring--boot-2.7.+-blue.svg)](https://spring.io/projects/spring-boot) - -## 🌟 About Easy-FLV -Easy-FLV is a Java library that converts RTSP or RTMP video streams into FLV format for playback in web browsers. It provides an efficient, stable, and easily integrable solution for real-time video monitoring, live streaming, and video stream processing. - -### Why Choose Easy-FLV? -- **Efficient Conversion**: Quickly converts video streams to FLV format with no complex configuration required. -- **Easy Integration**: Used as a Spring Boot Starter, it can be easily integrated into any Java project. -- **Modern Browser Support**: Supports all major modern browsers without the need for additional plugins. -- **Real-time Stream Processing**: Suitable for the conversion of real-time video streams, such as security monitoring and live broadcasting. - -## 📄 Screenshots -Below are screenshots of Easy-FLV in action: - -![img_1.png](img_1.png) -![img.png](img.png) - -## 🚀 Quick Start - -### Add Maven Dependency -Include the following Maven dependency in your Spring Boot project: - -```xml - - io.github.javpower - rtsp-converter-flv-spring-boot-starter - 1.5.9.1 - -``` - -### Implement Interface -Create a service class that implements the `IOpenFLVService` interface to provide the stream address: - -```java -@Service -public class RtspDataService implements IOpenFLVService { - - @Override - public String getUrl(Integer channel) { - // Retrieve the RTSP stream address based on the channel - return "rtsp://10.11.9.251:554/openUrl/16HV8mA"; - } -} -``` - -### Configure YAML -Configure Easy-FLV in your `application.yml`: - -```yaml -easy: - flv: - host: http://localhost:8200 -``` - -### Use Interface -To get the converted stream address and play it in a browser: - -- Conversion URL: `GET http://ip:port/get/flv/hls/stream_{channel}.flv` -- Direct Browser Playback: `GET http://ip:port/flv/hls/stream_{channel}.flv` - -### Direct Usage -If you prefer not to implement an interface, you can directly encode the stream address and convert it: - -```java -public static void main(String[] args) throws UnsupportedEncodingException { - String url = "rtsp://XXXXXXXX"; - String encodedUrl = java.net.URLEncoder.encode(url, "UTF-8"); - System.out.println("Encoded Stream URL: " + encodedUrl); -} -``` - -- Conversion URL: `GET http://ip:port/get/flv/hls/stream?url=EncodedAddress` -- Direct Browser Playback: `GET http://ip:port/flv/hls/stream?url=EncodedAddress` - -## 🛠️ Contribution -Contributions of any kind are welcome, including but not limited to reporting bugs, submitting fixes, adding new features, and improving documentation. - -## 📄 License -Easy-FLV is released under the [Apache License 2.0](LICENSE). - -## 📧 Contact -- Email: [javpower@163.com](mailto:javpower@163.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) diff --git a/img.png b/img.png deleted file mode 100644 index e67c5c2..0000000 Binary files a/img.png and /dev/null differ diff --git a/img_1.png b/img_1.png deleted file mode 100644 index 1989444..0000000 Binary files a/img_1.png and /dev/null differ diff --git a/pom.xml b/pom.xml index a9b9a48..d6c6872 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,12 @@ 4.0.0 - io.github.javpower - rtsp-converter-flv-spring-boot-starter - 1.5.9.1 - rtsp-converter-flv-spring-boot-starter - a tool about easy rtsp-converter-flv - https://github.com/javpower/easy-flv + me.zhengjie + eladmin-flv + 1.1 + 视频模块 + 视频流转换 rtsp/rtmp 转flv + The Apache Software License, Version 2.0 @@ -38,6 +38,11 @@ + + me.zhengjie + eladmin-logging + 1.1 + org.springframework.boot spring-boot-starter diff --git a/src/main/java/com/gc/easy/flv/controller/FLVController.java b/src/main/java/com/gc/easy/flv/controller/FLVController.java index a89dad4..0b01b25 100644 --- a/src/main/java/com/gc/easy/flv/controller/FLVController.java +++ b/src/main/java/com/gc/easy/flv/controller/FLVController.java @@ -2,10 +2,16 @@ 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; @@ -14,31 +20,28 @@ import java.io.UnsupportedEncodingException; /** * FLV流转换 - * + * * @author gc.x */ -@RestController +@Slf4j +@RestController("api/front/flv") +@Api(tags = "微信:拉流播放") public class FLVController { - @Autowired - private IFLVService service; - @Autowired(required = false) - private IOpenFLVService openFLVService; + @Autowired + private IFLVService service; - @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,openFLVService); - } - } - @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,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); + } + } } diff --git a/src/main/java/com/gc/easy/flv/controller/FLVPlayController.java b/src/main/java/com/gc/easy/flv/controller/FLVPlayController.java index 6ebcd92..3bded18 100644 --- a/src/main/java/com/gc/easy/flv/controller/FLVPlayController.java +++ b/src/main/java/com/gc/easy/flv/controller/FLVPlayController.java @@ -1,39 +1,30 @@ -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"; - } -} +//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"; +// } +//} diff --git a/src/main/java/com/gc/easy/flv/factories/ConverterFactories.java b/src/main/java/com/gc/easy/flv/factories/ConverterFactories.java index 97ea29c..91081c4 100644 --- a/src/main/java/com/gc/easy/flv/factories/ConverterFactories.java +++ b/src/main/java/com/gc/easy/flv/factories/ConverterFactories.java @@ -24,209 +24,209 @@ import java.util.Map; */ @Slf4j public class ConverterFactories extends Thread implements Converter { - public volatile boolean runing = true; - /** - * 读流器 - */ - private FFmpegFrameGrabber grabber; - /** - * 转码器 - */ - private FFmpegFrameRecorder recorder; - /** - * 转FLV格式的头信息
- * 如果有第二个客户端播放首先要返回头信息 - */ - private byte[] headers; - /** - * 保存转换好的流 - */ - private ByteArrayOutputStream stream; - /** - * 流地址,h264,aac - */ - private String url; - /** - * 流输出 - */ - private List outEntitys; + public volatile boolean runing = true; + /** + * 读流器 + */ + private FFmpegFrameGrabber grabber; + /** + * 转码器 + */ + private FFmpegFrameRecorder recorder; + /** + * 转FLV格式的头信息
+ * 如果有第二个客户端播放首先要返回头信息 + */ + private byte[] headers; + /** + * 保存转换好的流 + */ + private ByteArrayOutputStream stream; + /** + * 流地址,h264,aac + */ + private String url; + /** + * 流输出 + */ + private List outEntitys; - /** - * key用于表示这个转换器 - */ - private String key; + /** + * key用于表示这个转换器 + */ + private String key; - /** - * 转换队列 - */ - private Map factories; + /** + * 转换队列 + */ + private Map factories; - private IOpenFLVService openFLVService; + private IOpenFLVService openFLVService; - public ConverterFactories(String url, String key, Map factories, List 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 factories, List outEntitys, IOpenFLVService openFLVService) { + this.url = url; + this.key = key; + this.factories = factories; + this.outEntitys = outEntitys; + this.openFLVService = openFLVService; + } - @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("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); - } - } + } + } - /** - * 输出FLV视频流 - * - * @param b - */ - public void writeResponse(byte[] b) { - Iterator 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 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 it = outEntitys.iterator(); - while (it.hasNext()) { - AsyncContext o = it.next(); - o.complete(); - } - } - } + /** + * 关闭异步响应 + * + * @param isCloseGrabberAndResponse + */ + public void completeResponse(boolean isCloseGrabberAndResponse) { + if (isCloseGrabberAndResponse) { + Iterator 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); + } + } } diff --git a/src/main/java/com/gc/easy/flv/service/IOpenFLVService.java b/src/main/java/com/gc/easy/flv/service/IOpenFLVService.java index 988c21a..3b53397 100644 --- a/src/main/java/com/gc/easy/flv/service/IOpenFLVService.java +++ b/src/main/java/com/gc/easy/flv/service/IOpenFLVService.java @@ -10,7 +10,7 @@ public interface IOpenFLVService { * @param channel * @return */ - String getUrl(Integer channel); + String getUrl(Integer channel); /** * 抓取一帧视频并将其转换为图像预处理 diff --git a/src/main/java/com/gc/easy/flv/service/impl/FLVService.java b/src/main/java/com/gc/easy/flv/service/impl/FLVService.java index 0a25e0f..3845f01 100644 --- a/src/main/java/com/gc/easy/flv/service/impl/FLVService.java +++ b/src/main/java/com/gc/easy/flv/service/impl/FLVService.java @@ -20,68 +20,70 @@ import java.util.concurrent.ConcurrentHashMap; /** * FLV流转换 - * + * * @author gc.x */ @Slf4j @Service public class FLVService implements IFLVService { - private ConcurrentHashMap converters = new ConcurrentHashMap<>(); - @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 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); - } - } + private ConcurrentHashMap converters = new ConcurrentHashMap<>(); - 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(); - } + @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 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(); + } } diff --git a/src/main/java/com/gc/easy/flv/service/impl/OpenFLVService.java b/src/main/java/com/gc/easy/flv/service/impl/OpenFLVService.java new file mode 100644 index 0000000..fdacfd0 --- /dev/null +++ b/src/main/java/com/gc/easy/flv/service/impl/OpenFLVService.java @@ -0,0 +1,33 @@ +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; + } +} diff --git a/src/main/resources/templates/video.html b/src/main/resources/templates/video.html index 89dd889..ab38be5 100644 --- a/src/main/resources/templates/video.html +++ b/src/main/resources/templates/video.html @@ -5,33 +5,33 @@ FLV Video Player - + - + + // 播放视频 + videoElement.play(); + } else { + console.error('FLV not supported'); + } + })(); +