Java利用ffmpeg實現(xiàn)視頻MP4轉(zhuǎn)m3u8
前言
ffmpeg工具實現(xiàn)視頻轉(zhuǎn)碼網(wǎng)上有很多教程,但大多不夠具體。本博客綜合了下網(wǎng)上教程,從ffmpeg工具轉(zhuǎn)碼,ffmpeg視頻播放,java語言操控ffmpeg轉(zhuǎn)碼,轉(zhuǎn)碼后視頻上傳阿里云oss,四個方面完整記錄下這個流程,內(nèi)容是基于我項目中的需求而定,不能使用所有情況,僅供參考。
具體技術(shù)原理不做描述,如有興趣可自行研究。
(一)ffmpeg工具轉(zhuǎn)碼
1.如何安裝ffmpeg工具
官網(wǎng)下載地址: https://ffmpeg.zeranoe.com/builds/
我是用的ffmpeg是windows版本,linux自行研究

下載完成后解壓壓縮包,完成后bin目錄下ffmpeg.exe文件是之后程序啟動時需要使用的

配置環(huán)境變量,至此ffmpeg工具安裝到此結(jié)束

2.如何使用ffmpeg工具進行視頻轉(zhuǎn)碼
打開cmd黑窗口,輸入以下指令
ffmpeg -i xxxxxxx.mp4 -c:v libx264 -hls_time 60 -hls_list_size 0 -c:a aac -strict -2 -f hls xxxxxxx.m3u8地址既可以寫相對地址 ,也可以寫絕對地址,看你自己情況
這條指令參數(shù)具體含義自行百度,這里只介紹幾個重要的
參數(shù)解析:
-re :該參數(shù)表示ffmpeg將會按照當前視頻的播放速率進行轉(zhuǎn)碼,這樣就不會說切片的速度和播放速度不一致。不加這個參數(shù),切片速度會非???,客戶端還來不及播放,列表已經(jīng)被更新了。
-hls_time n :設(shè)置每片的長度,默認值為2,單位為秒。
-hls_list_size n :設(shè)置m3u8文件播放列表保存的最多條目,設(shè)置為0會保存有所片信息,默認值為5。一般用于直播流,點播文件可以設(shè)置成0,即全部保存。
-hls_wrap n :設(shè)置多少片之后開始覆蓋,設(shè)置為0則不會覆蓋,默認值為0。這個選項能夠避免在磁盤上存儲過多的片,而且能夠限制寫入磁盤的最多的片的數(shù)量。
以上參數(shù)可以自己嘗試調(diào)整看看效果。
這是成功執(zhí)行命令后,ffmpeg執(zhí)行過程,出現(xiàn)這個界面沒報錯,就恭喜你成功了,安靜等待工具切片就行了

工具執(zhí)行完畢之后,輸出路徑文件夾中會多出一個m3u8文件和若干ts文件,至此第一部分圓滿成功

(二)播放m3u8文件
1.video.js
Video.js 是一個通用的在網(wǎng)頁上嵌入視頻播放器的 JS 庫,Video.js 自動檢測瀏覽器對 HTML5 的支持情況,如果不支持 HTML5 則自動使用 Flash 播放器。(要支持ie低版本請下載5.4.3版 )
官網(wǎng)地址: https://videojs.com
2.具體使用
1)靜態(tài)數(shù)據(jù)
<!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="external nofollow" rel="stylesheet" />
<link href="js/video-js.css" rel="external nofollow" 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)動態(tài)數(shù)據(jù)
動態(tài)數(shù)據(jù)賦值有個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 獲取節(jié)點
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 獲取節(jié)點
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工具轉(zhuǎn)成m3u8文件
實現(xiàn)前提:筆者用的是springboot框架,,前端上傳視頻用的是elementui里的上傳組件.
以下代碼是根據(jù)網(wǎng)上已有的源碼結(jié)合自身需求做了改變,僅供參考,請勿直接copy,直接copy報錯了不要怪我哦。需要根據(jù)自身需求進行改變。
后臺實現(xiàn)上傳代碼:
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)*********/
//先將文件本地上傳后,調(diào)用ffmpeg切片轉(zhuǎn)成m3u8,在將轉(zhuǎn)換后文件上傳到oss上去
/*
我的思路就是根據(jù)我重新起的名字,生成對應(yīng)文件夾,將mp4視頻和轉(zhuǎn)換后的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轉(zhuǎn)m3u8
boolean b = convertM3U8.convertOss(folderUrl + "/", fileName);
if (!b){
return R.error("上傳失敗!系統(tǒng)轉(zhuǎn)碼異常!");
}
//訪問本地上傳文件夾所有文件,依次上傳至oss服務(wù)器
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視頻轉(zhuǎn)碼工具類:
/**
* mp4轉(zhuǎn)換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程序轉(zhuǎn)換m3u8
* @param folderUrl
* @param fileName
* @param fileFullName
* @return
*/
private boolean processM3U8(String folderUrl,String fileName, String fileFullName) {
//這里就寫入執(zhí)行語句就可以了
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;
}
}
/**
* 監(jiān)聽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文件需要放在同一級目錄。最好是一個視頻的相關(guān)文件放入以文件名命名的路徑下以示區(qū)別

2.m3u8鏈接無法播放,video.js提示無法訪問對應(yīng)資源,需要在阿里云oss上設(shè)置跨域。bucket空間—>基礎(chǔ)設(shè)置—>跨域設(shè)置


3.ts文件的讀寫權(quán)限需要公共讀不然video.js訪問不到的,然后為了防止視頻被惡意下載可以設(shè)置防盜鏈
總結(jié)
以上就是Java利用ffmpeg實現(xiàn)視頻MP4轉(zhuǎn)m3u8的詳細內(nèi)容,更多關(guān)于Java ffmpeg實現(xiàn)MP4轉(zhuǎn)m3u8的資料請關(guān)注腳本之家其它相關(guān)文章!
- Javacv使用ffmpeg實現(xiàn)音視頻同步播放
- Java基于FFmpeg實現(xiàn)Mp4視頻轉(zhuǎn)GIF
- java使用ffmpeg實現(xiàn)上傳視頻的轉(zhuǎn)碼提取視頻的截圖等功能(代碼操作)
- Java使用ffmpeg和mencoder實現(xiàn)視頻轉(zhuǎn)碼
- java調(diào)用ffmpeg實現(xiàn)轉(zhuǎn)換視頻
- Java+Windows+ffmpeg實現(xiàn)視頻轉(zhuǎn)換功能
- 詳解java調(diào)用ffmpeg轉(zhuǎn)換視頻格式為flv
- java調(diào)用ffmpeg實現(xiàn)視頻轉(zhuǎn)換的方法
- Java工程使用ffmpeg進行音視頻格式轉(zhuǎn)換的實現(xiàn)
相關(guān)文章
SpringBoot中使用AOP切面編程實現(xiàn)登錄攔截功能
本文介紹了如何使用AOP切面編程實現(xiàn)Spring Boot中的登錄攔截,通過實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧2024-12-12
一步步教你JAVA如何優(yōu)化Elastic?Search
想要榨干Java操作Elasticsearch的所有性能潛力?本指南將一步步教你如何優(yōu)化Java與Elasticsearch的交互!從此,提升ES查詢速度、降低資源消耗不再是難題,趕快一起來探索Java?Elasticsearch優(yōu)化的秘訣吧!2024-01-01
Java異步線程中的CompletableFuture與@Async詳解
這篇文章主要介紹了Java異步線程中的CompletableFuture與@Async詳解,CompletableFuture是java中提供的一個異步執(zhí)行類,@Async是Spring提供的異步執(zhí)行方法,當調(diào)用方法單獨開啟一個線程進行調(diào)用,需要的朋友可以參考下2024-01-01
利用JAVA反射,讀取數(shù)據(jù)庫表名,自動生成對應(yīng)實體類的操作
這篇文章主要介紹了利用JAVA反射,讀取數(shù)據(jù)庫表名,自動生成對應(yīng)實體類的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Java OpenCV實現(xiàn)圖像鏡像翻轉(zhuǎn)效果
這篇文章主要為大家詳細介紹了Java OpenCV實現(xiàn)圖像鏡像翻轉(zhuǎn)效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07

