主页 > 其他  > 

javacv将视频切分为m3u8视频并播放

javacv将视频切分为m3u8视频并播放
学习链接

ffmpeg-demo 当前对应的 gitee代码

Spring boot视频播放(解决MP4大文件无法播放),整合ffmpeg,用m3u8切片播放。

springboot 通过javaCV 实现mp4转m3u8 上传oss

ffmpeg视频转切片m3u8并加密&videojs播放&hls.js播放&dplayer播放(弹幕效果)

video标签学习 & xgplayer视频播放器分段播放mp4

SpringBoot&FFmpeg实现上传视频到本地,使用M3U8切片转码后,下方使用hls.js播放(支持mp4&avi),SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统

文章目录 学习链接简介效果图代码pom.xmlapplication.ymlWebConfigTestControllerFFmpegProcessorApp nginx配置player.html测试

简介

将上传的视频文件,使用javacv拆分成m3u8文件和ts文件,m3u8文件和ts文件通过nginx访问,而key文件则通过web服务来获取。使用dplayer播放视频。

也可以使用ffmpeg命令来做,可以参考上面链接。

效果图

m3u8文件和ts文件通过nginx访问,而key文件则通过web服务来获取

拿不到key文件是无法播放的

代码 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http:// .w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>ffmpeg-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <javacv.version>1.5.4</javacv.version> <ffmpeg.version>4.3.1-1.5.4</ffmpeg.version> </properties> <dependencies> <!--web 模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!--排除tomcat依赖 --> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!--undertow容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- javacv 和 ffmpeg的依赖包 --> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>${javacv.version}</version> <exclusions> <exclusion> <groupId>org.bytedeco</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg-platform</artifactId> <version>${ffmpeg.version}</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> application.yml server: port: 8080 spring: servlet: multipart: max-file-size: 500MB max-request-size: 500MB WebConfig @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/test/**").addResourceLocations("file:" + System.getProperty("user.dir") + "/test/"); registry.addResourceHandler("/tmp/**").addResourceLocations("file:" + System.getProperty("user.dir") + "/tmp/"); } @Override public void addCorsMappings(CorsRegistry registry) { registry .addMapping("/**") .maxAge(3600) .allowCredentials(true) .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("token","Authorization") ; } } TestController import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.file.FileReader; import cn.hutool.core.io.file.FileWriter; import com.zzhua.processor.FFmpegProcessor; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.IOException; @RestController public class TestController { /** * 目录路径,这个路径需要包含test.info文件,test.key文件和test.mp4文件 */ private static final String PATH = "D:\\Projects\\practice\\ffmpeg-demo\\test\\"; @RequestMapping("uploadToM3u8") public void uploadToM3u8() throws Exception { FileInputStream inputStream = new FileInputStream(PATH + "test.mp4"); /* 这里原来的逻辑是 1、m3u8Url是将生成的m3u8文件流写入的位置,可以填写接收该请求的接口路径 2、infoUrl是获取keyinfo文件的路径,可以是接口路径 3、上面2个都可以是本地路径*/ // String m3u8Url = "http://localhost:8080/upload/test.m3u8"; // String infoUrl = "http://localhost:8080/preview/test.keyinfo"; String m3u8Url = "D:\\Projects\\practice\\ffmpeg-demo\\test\\test.m3u8"; String infoUrl = "D:\\Projects\\practice\\ffmpeg-demo\\test\\test.keyinfo"; String segmentPattern = "http://localhost:8080/upload/test-%d.ts"; FFmpegProcessor.convertMediaToM3u8ByHttp(inputStream, m3u8Url, infoUrl, segmentPattern); } @RequestMapping("convertToM3u8") public void convertToM3u8(MultipartFile mfile) { FFmpegProcessor.convertMediaToM3u8(mfile); } @PostMapping("upload/{fileName}") public void upload(HttpServletRequest request, @PathVariable("fileName") String fileName) throws IOException { ServletInputStream inputStream = request.getInputStream(); FileWriter writer = new FileWriter(PATH + fileName); writer.writeFromStream(inputStream); IoUtil.close(inputStream); } /** * 预览加密文件 */ @PostMapping("preview/{fileName}") public void preview(@PathVariable("fileName") String fileName, HttpServletResponse response) throws IOException { FileReader fileReader = new FileReader(PATH + fileName); fileReader.writeToStream(response.getOutputStream()); } /** * 预览加密文件 */ @GetMapping("download/{fileName}") public void download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws IOException { FileReader fileReader = new FileReader(PATH + fileName); fileReader.writeToStream(response.getOutputStream()); } } FFmpegProcessor import org.bytedeco.ffmpeg.avcodec.AVPacket; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.ffmpeg.global.avutil; import org.bytedeco.javacv.*; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class FFmpegProcessor { /** * 这个方法的url地址都必须是一样的类型 同为post */ public static void convertMediaToM3u8ByHttp(InputStream inputStream, String m3u8Url, String infoUrl, String segmentPattern) throws IOException { avutil.av_log_set_level(avutil.AV_LOG_INFO); FFmpegLogCallback.set(); FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream); grabber.start(); FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(m3u8Url, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels()); recorder.setFormat("hls"); // 拆分时间片段长度 recorder.setOption("hls_time", "60"); recorder.setOption("hls_list_size", "0"); recorder.setOption("hls_flags", "delete_segments"); recorder.setOption("hls_delete_threshold", "1"); recorder.setOption("hls_segment_type", "mpegts"); /* 这里指定生成的ts文件保存位置,可以写接口路径, 该接口用于接收ts文件流*/ // recorder.setOption("hls_segment_filename", "http://localhost:8080/upload/test-%d.ts"); recorder.setOption("hls_segment_filename", segmentPattern); recorder.setOption("hls_key_info_file", infoUrl); // http属性 recorder.setOption("method", "POST"); recorder.setFrameRate(25); recorder.setGopSize(2 * 25); recorder.setVideoQuality(1.0); recorder.setVideoBitrate(10 * 1024); recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); /* // 只保存图像,而不保存声音 recorder.start(); Frame frame; while ((frame = grabber.grabImage()) != null) { try { recorder.record(frame); } catch (FrameRecorder.Exception e) { e.printStackTrace(); } } recorder.setTimestamp(grabber.getTimestamp()); recorder.close(); grabber.close();*/ /* 图像 + 声音 */ recorder.start(grabber.getFormatContext()); AVPacket packet; while ((packet = grabber.grabPacket()) != null) { try { recorder.recordPacket(packet); } catch (FrameRecorder.Exception e) { e.printStackTrace(); } } recorder.setTimestamp(grabber.getTimestamp()); recorder.stop(); recorder.release(); grabber.stop(); grabber.release(); } private static final String BASE_PATH = System.getProperty("user.dir") + "\\tmp\\"; public static void convertMediaToM3u8(MultipartFile mfile) { String origFileName = mfile.getOriginalFilename(); String fileName = origFileName.substring(0, origFileName.lastIndexOf(".")); String dirName = fileName; String fileDir = BASE_PATH + fileName; File dirFile = new File(fileDir); if (!dirFile.exists()) { dirFile.mkdirs(); } try { File rawFile = new File(dirFile, origFileName); // 保存文件 mfile.transferTo(rawFile); // 生成密钥文件 String commonFileName = fileDir + "\\" + fileName; generateKeyFile(commonFileName + ".key"); // 生成keyInfo文件 generateKeyInfoFile(dirName, commonFileName + ".key", commonFileName + ".keyinfo"); convertMediaToM3u8ByHttp(new FileInputStream(rawFile), commonFileName + ".m3u8", commonFileName + ".keyinfo", commonFileName + "-%d.ts"); } catch (Exception e) { e.printStackTrace(System.err); } } /** * 生成keyInfo文件 * * @param keyFilePath 密钥文件路径 * @param keyInfoFilePath keyInfo文件路径 */ private static void generateKeyInfoFile(String dirName, String keyFilePath, String keyInfoFilePath) { try { // 生成IV ProcessBuilder ivProcessBuilder = new ProcessBuilder("openssl", "rand", "-hex", "16"); Process ivProcess = ivProcessBuilder.start(); BufferedReader ivReader = new BufferedReader(new InputStreamReader(ivProcess.getInputStream())); String iv = ivReader.readLine(); // 写入keyInfo文件 String keyInfoContent = "http://127.0.0.1:8080/tmp/" + dirName + "/" + new File(keyFilePath).getName() + "\n" + keyFilePath + "\n" + iv; Files.write(Paths.get(keyInfoFilePath), keyInfoContent.getBytes()); System.out.println("keyInfo文件已生成: " + keyInfoFilePath); } catch (IOException e) { e.printStackTrace(); } } /** * 生成密钥文件 * * @param keyFilePath 密钥文件路径 */ private static void generateKeyFile(String keyFilePath) { try { ProcessBuilder processBuilder = new ProcessBuilder("openssl", "rand", "16"); processBuilder.redirectOutput(new File(keyFilePath)); Process process = processBuilder.start(); process.waitFor(); System.out.println("密钥文件已生成: " + keyFilePath); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } App @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } nginx配置 #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; add_header 'Access-Control-Allow-Origin' $http_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, token'; add_header 'Access-Control-Allow-Credentials' 'true'; location / { if ($request_method = 'OPTIONS') { return 204; } root D:/Projects/practice/ffmpeg-demo/tmp; } } } player.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { margin: 0; } .dplayer-container { width: 800px; height: 500px; display: flex; align-items: center; justify-content: center; } #dplayer { width: 100%; height: 100%; object-fit: cover; } </style> <script src=" cdn.jsdelivr.net/npm/hls.js@latest"></script> <script src=" cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js"></script> </head> <body> <div class="dplayer-container"> <div id="dplayer"></div> </div> <hr /> <script> // 另一种方式,使用 customType const dp = new DPlayer({ container: document.getElementById('dplayer'), autoplay: false, // 自动播放 video: { url: 'http://127.0.0.1/zzhua/zzhua.m3u8', type: 'customHls', customType: { customHls: function (video, player) { const hls = new Hls(); hls.loadSource(video.src); hls.attachMedia(video); }, }, }, }); Window.dp = dp; </script> </body> </html> 测试

上传1个301M的视频,耗时15s, 共188个文件,其中184个ts文件 播放效果

标签:

javacv将视频切分为m3u8视频并播放由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“javacv将视频切分为m3u8视频并播放