Java 利用ffmpeg工具实现视频MP4转m3u8(一)
- 前言
- (一)ffmpeg工具转码
- 1.如何安装ffmpeg工具
- 2.如何使用ffmpeg工具进行视频转码
- (二)播放m3u8文件
- 1.video.js
- 2.具体使用
- 1)静态数据
- 2)动态数据
- (三)Java程序上传本地视频地址并通过ffmpeg工具转成m3u8文件
- (四)上传m3u8文件至OSS需要注意的问题
前言
ffmpeg工具实现视频转码网上有很多教程,但大多不够具体。本博客综合了下网上教程,从ffmpeg工具转码,ffmpeg视频播放,java语言操控ffmpeg转码,转码后视频上传阿里云oss,四个方面完整记录下这个流程,内容是基于我项目中的需求而定,不能使用所有情况,仅供参考。
具体技术原理不做描述,如有兴趣可自行研究。
(一)ffmpeg工具转码
1.如何安装ffmpeg工具
官网下载地址: https://ffmpeg.zeranoe.com/builds/
我是用的ffmpeg是windows版本,linux自行研究
下载完成后解压压缩包,完成后bin目录下ffmpeg.exe文件是之后程序启动时需要使用的
配置环境变量,至此ffmpeg工具安装到此结束
2.如何使用ffmpeg工具进行视频转码
打开cmd黑窗口,输入以下指令
ffmpeg -i xxxxxxx.mp4 -c:v libx264 -hls_time 60 -hls_list_size 0 -c:a aac -strict -2 -f hls xxxxxxx.m3u8
地址既可以写相对地址 ,也可以写绝对地址,看你自己情况
这条指令参数具体含义自行百度,这里只介绍几个重要的
参数解析:
-re :该参数表示ffmpeg将会按照当前视频的播放速率进行转码,这样就不会说切片的速度和播放速度不一致。不加这个参数,切片速度会非常快,客户端还来不及播放,列表已经被更新了。
-hls_time n :设置每片的长度,默认值为2,单位为秒。
-hls_list_size n :设置m3u8文件播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5。一般用于直播流,点播文件可以设置成0,即全部保存。
-hls_wrap n :设置多少片之后开始覆盖,设置为0则不会覆盖,默认值为0。这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量。
以上参数可以自己尝试调整看看效果。这是成功执行命令后,ffmpeg执行过程,出现这个界面没报错,就恭喜你成功了,安静等待工具切片就行了工具执行完毕之后,输出路径文件夹中会多出一个m3u8文件和若干ts文件,至此第一部分圆满成功
(二)播放m3u8文件
1.video.js
Video.js 是一个通用的在网页上嵌入视频播放器的 JS 库,Video.js 自动检测浏览器对 HTML5 的支持情况,如果不支持 HTML5 则自动使用 Flash 播放器。(要支持ie低版本请下载5.4.3版 )
官网地址: https://videojs.com
API地址:https://docs.videojs.com/index.html
2.具体使用
1)静态数据
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-COMPATIBLE" CONTENT="IE=edge,chrome=1">
<link href="css/style.css" rel="stylesheet" />
<link href="js/video-js.css" rel="stylesheet">
<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
<script src="js/videojs-ie8.min.js"></script>
</head>
<body>
<video id="myVideo" class="video-js vjs-default-skin vjs-big-play-centered">
<source type="application/x-mpegURL"></source>
</video>
</body>
<script type="text/javascript" src="js/jquery-3.3.1.js"></script>
<script src='js/video.js'></script>
<script>
videojs(document.querySelector('.video-js'), {
controls: true,
autoplay: false,
preload: 'auto',
sources: [{
src: "video/20191010173819_cjOaOJ.mp4",
type: "application/x-mpegURL"
}]
});
</script>
</html>
2)动态数据
动态数据赋值有个src方法,但是我使用的时候容易报错,用的vue框架,且视频地址是oss地址。
就想了个折中的方法,每次url变化的时候销毁原始video对象,重新初始化。
最近还在研究,如果有好方法会再更新上来。如有解决方案也可以留言我。
videojs(document.querySelector('.video-js'),{}).ready(function(){
var myPlayer = this;
myPlayer.src("http://www.example.com/path/to/video.mp4");
myPlayer.play();
});
需要注意,如果是在Vue里使用,建议用ref获取元素,我这里标出初始化播放器方法代码
initPalyer(){
const videoDom = this.$refs.myVideo; //不能用document 获取节点
let myPlayer = videojs( videoDom, {
controls: true,
autoplay: false,
preload: 'auto',
sources: [{
src: vm.yfVideoDetail.videoUrl,
type: "application/x-mpegURL"
}]
});
}
Api上有着详细的使用方法的介绍,我这里因为只用到这些所以就只写了这部分代码,如果想进一步深入,可以自行研究。
更新播放方法:
const vue = new Vue({
el: ".body",
data: {
myPlayer: null
},
methods: {
initPalyer() {
const videoDom = this.$refs.myVideo; //不能用document 获取节点
if(vurl) {
vue.myPlayer = videojs(videoDom, {
controls: true,
autoplay: false,
preload: 'auto',
sources: [{
src: vurl, //视频url
type: "application/x-mpegURL"
}]
});
}
},
//切换视频
switchVideo(item) {
vue.myPlayer.reset(); //重置 video
vue.myPlayer.src([{
src: item.videoUrl //新视频url
}, ]);
vue.myPlayer.load(item.videoUrl);
vue.myPlayer.currentTime(0);
}
}
})
(三)Java程序上传本地视频地址并通过ffmpeg工具转成m3u8文件
实现前提:笔者用的是springboot框架,,前端上传视频用的是elementui里的上传组件.
以下代码是根据网上已有的源码结合自身需求做了改变,仅供参考,请勿直接copy,直接copy报错了不要怪我哦。需要根据自身需求进行改变。
后台实现上传代码:
public R saveVideo(@RequestParam MultipartFile file) {
// TODO Auto-generated method stub
String url=null;
try {
String f = file.getOriginalFilename(); //获取文件名
String suffix = StringUtils.substringAfter(f, "."); //获取文件后缀
String filename = FileUtils.getFileName(null, null); //我自己封装的方法,给文件重新起个名字,文件名不带后缀
/*********本地上传(Tomcat配置映射C:/upload/file)*********/
//先将文件本地上传后,调用ffmpeg切片转成m3u8,在将转换后文件上传到oss上去
/*
我的思路就是根据我重新起的名字,生成对应文件夹,将mp4视频和转换后的m3u8以及ts放在一起,然后遍历文件目录,将文件上传后,删除本地文件夹和文件
*/
String folderUrl = FileUtils.localPath+filename; //文件夹路径
String fileName = filename+"."+suffix; //文件名带后缀
String uploadPath= folderUrl + "/" + fileName; //上传后路径
File fileFolder = new File(folderUrl);
if (!fileFolder.exists()) {
fileFolder.mkdirs();
}
File newFile = new File(uploadPath);
file.transferTo(newFile);
//mp4转m3u8
boolean b = convertM3U8.convertOss(folderUrl + "/", fileName);
if (!b){
return R.error("上传失败!系统转码异常!");
}
//访问本地上传文件夹所有文件,依次上传至oss服务器
File[] files = fileFolder.listFiles();
if (null == files || files.length == 0) {
return null;
}
boolean flag = true;
for (int i = 0; i < files.length ; i++) {
if (!files[i].isDirectory()) {
//上传
String name = files[i].getName();
String suf = StringUtils.substringAfter(name, ".");
String pre = StringUtils.substringBefore(name, ".");
FileInputStream fis = new FileInputStream(files[i]);
if ("m3u8".equals(suf)){
if (flag && filename.equals(pre)){
//这是封装的上传阿里云oss的方法
url = OSSFactory.build().upload(fis, "video/" + filename + "/" + name);
flag = false;
}
} else if ("ts".equals(suf)){
OSSFactory.build().uploadPublic(fis, "video/" + filename + "/" + name);
}
fis.close();
FileUtils.deletefile(files[i]);
}
}
//删除文件夹
fileFolder.delete();
/*********本地上传(Tomcat配置映射C:/upload/file)*********/
} catch (Exception e) {
e.printStackTrace();
R.error("上传异常");
}
return R.ok().put("data",url);
}
ffmpeg视频转码工具类:
/**
* mp4转换m3u8工具类
*/
@Component
public class ConvertM3U8 {
@Value("${m3u8.ffmpegpath}")
private String ffmpegpath; // ffmpeg.exe的目录
public boolean convertOss(String folderUrl,String fileName){
if (!checkfile(folderUrl + fileName)){
System.out.println("文件不存在!");
return false;
}
//验证文件后缀
String suffix = StringUtils.substringAfter(fileName, ".");
String fileFullName = StringUtils.substringBefore(fileName, ".");
if (!validFileType(suffix)){
return false;
}
return processM3U8(folderUrl,fileName,fileFullName);
}
/**
* 验证上传文件后缀
* @param type
* @return
*/
private boolean validFileType ( String type ) {
if ("mp4".equals(type)){
return true;
}
return false;
}
/**
* 验证是否是文件格式
* @param path
* @return
*/
private boolean checkfile(String path) {
File file = new File(path);
if (!file.isFile()) {
return false;
} else {
return true;
}
}
// ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
/**
* ffmpeg程序转换m3u8
* @param folderUrl
* @param fileName
* @param fileFullName
* @return
*/
private boolean processM3U8(String folderUrl,String fileName, String fileFullName) {
//这里就写入执行语句就可以了
List commend = new java.util.ArrayList();
commend.add(ffmpegpath);
commend.add("-i");
commend.add(folderUrl+fileName);
commend.add("-c:v");
commend.add("libx264");
commend.add("-hls_time");
commend.add("20");
commend.add("-hls_list_size");
commend.add("0");
commend.add("-c:a");
commend.add("aac");
commend.add("-strict");
commend.add("-2");
commend.add("-f");
commend.add("hls");
commend.add(folderUrl+ fileFullName +".m3u8");
try {
ProcessBuilder builder = new ProcessBuilder();//java
builder.command(commend);
Process p = builder.start();
int i = doWaitFor(p);
System.out.println("------>"+i);
p.destroy();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 监听ffmpeg运行过程
* @param p
* @return
*/
public int doWaitFor(Process p) {
InputStream in = null;
InputStream err = null;
int exitValue = -1; // returned to caller when p is finished
try {
System.out.println("comeing");
in = p.getInputStream();
err = p.getErrorStream();
boolean finished = false; // Set to true when p is finished
while (!finished) {
try {
while (in.available() > 0) {
Character c = new Character((char) in.read());
System.out.print(c);
}
while (err.available() > 0) {
Character c = new Character((char) err.read());
System.out.print(c);
}
exitValue = p.exitValue();
finished = true;
} catch (IllegalThreadStateException e) {
Thread.currentThread().sleep(500);
}
}
} catch (Exception e) {
System.err.println("doWaitFor();: unexpected exception - "
+ e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
if (err != null) {
try {
err.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
return exitValue;
}
}
(四)上传m3u8文件至OSS需要注意的问题
1.同一个视频的m3u8格式文件以及ts文件需要放在同一级目录。最好是一个视频的相关文件放入以文件名命名的路径下以示区别
2.m3u8链接无法播放,video.js提示无法访问对应资源,需要在阿里云oss上设置跨域。bucket空间—>基础设置—>跨域设置
3.ts文件的读写权限需要公共读不然video.js访问不到的,然后为了防止视频被恶意下载可以设置防盗链
参考资料:
1.网站播放视频较慢,利用mp4转m3u8解决 2.m3u8结构以及hls协议简单解析 3.windows使用ffmpeg转m3u8使用参数详解