Java集成Ffmpeg实现视频转M3U8
Ffmpeg是什么
FFmpeg 强大专用于处理音视频的开源库,包含了先进的音视频编解码库,提供了录制、转换以及流传输音视频的完整跨平台解决方案。
既可以使用它的API对音视频进行处理,也可以使用它提供的工具,如 ffmpeg, ffplay, ffprobe,来编辑音视频文件。
——————引用知乎:1. FFmpeg基本常识及编码流程 - 知乎 (zhihu.com)
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
——————引用知乎:30分带你从认识FFmpeg到玩转FFmpeg - 知乎 (zhihu.com)
为什么要使用Ffmpeg
Ffmpeg是一个功能十分强大的视频编解码库(音视频编码解读:引用知乎:[音视频编解码基础 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/28058109#:~:text=音视频编解码基础 1 流媒体技术的基本过程 总体上说,视频从产生到传递到观看者之间的过程主要分为这么几个阶段:录制—编码—传输—解码—播放 录制:即视频的制作者利用各种摄像设备,将现实中的一些连续的场景片段记录下来。 编码:对录制好的视频进行格式化处理,以方便在网络上传输。 主要有对视频和音频分别进行压缩编码、将音视频进行打包封装两个步骤。 …,和视频一样,音频编码也是为了将音频原始数据转换为音频码流,以便在网络传输。 常见的有: … 6 流媒体协议 RTP RTCP )),我们以下只使用它的其中一个功能,就是将音视频以某种编码格式编码后再切割成M3U8格式。
因为传统视频都是Mp4、MKV、Avi等格式,视频不长时还好说;视频很长时,浏览器或App从网络上加载视频时,需要将一部完整的视频加载下来后,视频才能播放,这无疑是十分耗费网络资源,又浪费时间;为什么不能边看边加载呢,或者我需要看哪一段就加载哪一段,于是M3U8流媒体格式便出来了。
M3U8:其实是一系列视频文件的索引,通过该索引文件,查找需要播放的某一段视频。
HLS:是具体的视频格式,比如将一个十分钟的视频按每10秒分割一段,那么将产生60段长度很短的视频,这种视频的格式就是HLS格式,为什么不用Mp4呢,因为Mp4在两段视频加载间隔会发生画面撕裂,影响观感。而储存这些HLS视频的索引文件就叫M3U8文件。
Java使用Ffmpeg
因为Ffmpeg是用C++编写的一个命令行软件,在没有官方提供的JNI程序的情况下,Java是无法直接调用Ffmpeg的;需要使用Java操作命令行的类以操作命令行的方式操作Ffmpeg;又由于没有对应的jar包支持,需要将网络上下载对应操作系统的Ffmpeg实现,将其封装成对应的jar包。
调用方式
导入依赖
<dependency> <groupId>ws.schild</groupId> <artifactId>jave-core</artifactId> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>ws.schild</groupId> <artifactId>jave-nativebin-win64</artifactId> </dependency>
<dependency> <groupId>ws.schild</groupId> <artifactId>jave-nativebin-linux64</artifactId> </dependency>
|
执行相关代码
DefaultFFMPEGLocator defaultFFMPEGLocator = new DefaultFFMPEGLocator(); String ffmpegPath = defaultFFMPEGLocator.getExecutablePath();
private int execute(String inVideoPath, String outVideoPath) { log.info("ffmepg位置:{}", ffmpegPath); String cmd = new StringBuilder(ffmpegPath) .append(" -i ") .append(inVideoPath) .append(" -c:v libx264 -c:a aac -hls_time ") .append(20) .append(" -hls_list_size 0 -strict -2 -s 1920x1080 -f hls -threads ") .append(10) .append(" -preset ultrafast ") .append(outVideoPath) .toString();
Runtime runtime = Runtime.getRuntime(); Process ffmpeg = null; InputStream errorIs = null; try { ffmpeg = runtime.exec(cmd); errorIs = ffmpeg.getErrorStream(); OutputStream os = ffmpeg.getOutputStream(); os.write("y".getBytes("UTF-8")); os.close(); } catch (IOException e) { e.printStackTrace(); } int res = close(ffmpeg, errorIs, false);
return res; }
private int getVideoInfo(String inVideoPath, String outCoverPath) { String cmd = new StringBuilder(ffmpegPath) .append(" -i ") .append(inVideoPath) .append(" -ss 00:00:01 -frames:v 1 ") .append(outCoverPath) .toString();
Runtime runtime = Runtime.getRuntime(); Process ffmpeg = null; InputStream errorIs = null; try { ffmpeg = runtime.exec(cmd); errorIs = ffmpeg.getErrorStream(); OutputStream os = ffmpeg.getOutputStream(); os.write("y".getBytes("UTF-8")); os.close(); } catch (IOException e) { e.printStackTrace(); } int duration = close(ffmpeg, errorIs, true);
return duration; }
private int close(Process ffmpeg, InputStream errorIs, boolean printLog) { StringBuilder info = new StringBuilder(); try { int len = 0; while (true) { if (!((len = errorIs.read()) != -1)) break; if (printLog) { info.append((char) len); } } } catch (IOException e) { e.printStackTrace(); } int duration = 0; if (printLog) { Matcher matcher = Pattern.compile("(?i)duration:\\s*([0-9\\:\\.]+)") .matcher(info.toString()); while (matcher.find()) { String group = matcher.group(1); duration = getSeconds4Str(group); break; } log.info("时长(s):{}", duration); }
info = null; if (errorIs != null) { try { errorIs.close(); } catch (Throwable t) { log.warn("关闭输入流失败", t); } } try { ffmpeg.waitFor(); } catch (InterruptedException ex) { log.error("在等待过程中强制关闭:{}", ex); } int res = ffmpeg.exitValue(); if (res != 0) { duration = 0; }
if (ffmpeg != null) { ffmpeg.destroy(); ffmpeg = null; } return duration; }
private int getSeconds4Str(String durationStr) { int duration = 0; if (null != durationStr && durationStr.length() > 0) { String[] durationStrArr = durationStr.split("\\:"); String hour = durationStrArr[0]; String minute = durationStrArr[1]; String second = ""; String secondTmp = durationStrArr[2]; if (secondTmp.contains(".")) { String[] seconedTmpArr = secondTmp.split("\\."); second = seconedTmpArr[0]; } else { second = secondTmp; } try { duration = Integer.parseInt(hour) * 3600 + Integer.parseInt(minute) * 60 + Integer.parseInt(second); } catch (Exception e) { return 0; } } return duration; }
|
写在最后
- 以上只是表示Java集成Ffmpeg将视频转成m3u8
- 其实SpringBoot集成Ffmpeg也是跟以上差不多,只是将代码执行部分放入Spring容器,将部分配置写入配置文件
- 像视频转码切分这种十分消耗系统资源与时间的操作,将操作放入线程池中
- 以下简略说明下Web应用从上传视频到将剪切好的视频上传到OSS的具体过程
- 前端上传的视频放入后端的某个临时目录
- 上传完毕后调用ffmpeg视频切割方法将视频转成m3u8,转好后放入后端的另一个临时目录(该过程丢到线程池中执行)
- 删除存储原版视频的目录,将存储m3u8视频目录中的所有.m3u8和.hls文件上传到OSS(该过程也可以丢到线程池中执行)
- 在上传过程中提前构造好视频目录地址,存入数据库
- 上传完毕,提示视频转码完毕