Java利用ffmpeg實(shí)現(xiàn)視頻MP4轉(zhuǎn)m3u8
前言
ffmpeg工具實(shí)現(xiàn)視頻轉(zhuǎn)碼網(wǎng)上有很多教程,但大多不夠具體。本博客綜合了下網(wǎng)上教程,從ffmpeg工具轉(zhuǎn)碼,ffmpeg視頻播放,java語(yǔ)言操控ffmpeg轉(zhuǎn)碼,轉(zhuǎn)碼后視頻上傳阿里云oss,四個(gè)方面完整記錄下這個(gè)流程,內(nèi)容是基于我項(xiàng)目中的需求而定,不能使用所有情況,僅供參考。
具體技術(shù)原理不做描述,如有興趣可自行研究。
(一)ffmpeg工具轉(zhuǎn)碼
1.如何安裝ffmpeg工具
官網(wǎng)下載地址: https://ffmpeg.zeranoe.com/builds/
我是用的ffmpeg是windows版本,linux自行研究
下載完成后解壓壓縮包,完成后bin目錄下ffmpeg.exe文件是之后程序啟動(dòng)時(shí)需要使用的
配置環(huán)境變量,至此ffmpeg工具安裝到此結(jié)束
2.如何使用ffmpeg工具進(jìn)行視頻轉(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地址既可以寫相對(duì)地址 ,也可以寫絕對(duì)地址,看你自己情況
這條指令參數(shù)具體含義自行百度,這里只介紹幾個(gè)重要的
參數(shù)解析:
-re :該參數(shù)表示ffmpeg將會(huì)按照當(dāng)前視頻的播放速率進(jìn)行轉(zhuǎn)碼,這樣就不會(huì)說(shuō)切片的速度和播放速度不一致。不加這個(gè)參數(shù),切片速度會(huì)非???,客戶端還來(lái)不及播放,列表已經(jīng)被更新了。
-hls_time n :設(shè)置每片的長(zhǎng)度,默認(rèn)值為2,單位為秒。
-hls_list_size n :設(shè)置m3u8文件播放列表保存的最多條目,設(shè)置為0會(huì)保存有所片信息,默認(rèn)值為5。一般用于直播流,點(diǎn)播文件可以設(shè)置成0,即全部保存。
-hls_wrap n :設(shè)置多少片之后開始覆蓋,設(shè)置為0則不會(huì)覆蓋,默認(rèn)值為0。這個(gè)選項(xiàng)能夠避免在磁盤上存儲(chǔ)過(guò)多的片,而且能夠限制寫入磁盤的最多的片的數(shù)量。
以上參數(shù)可以自己嘗試調(diào)整看看效果。
這是成功執(zhí)行命令后,ffmpeg執(zhí)行過(guò)程,出現(xiàn)這個(gè)界面沒(méi)報(bào)錯(cuò),就恭喜你成功了,安靜等待工具切片就行了
工具執(zhí)行完畢之后,輸出路徑文件夾中會(huì)多出一個(gè)m3u8文件和若干ts文件,至此第一部分圓滿成功
(二)播放m3u8文件
1.video.js
Video.js 是一個(gè)通用的在網(wǎng)頁(yè)上嵌入視頻播放器的 JS 庫(kù),Video.js 自動(dòng)檢測(cè)瀏覽器對(duì) HTML5 的支持情況,如果不支持 HTML5 則自動(dòng)使用 Flash 播放器。(要支持ie低版本請(qǐng)下載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)動(dòng)態(tài)數(shù)據(jù)
動(dòng)態(tài)數(shù)據(jù)賦值有個(gè)src方法,但是我使用的時(shí)候容易報(bào)錯(cuò),用的vue框架,且視頻地址是oss地址。
就想了個(gè)折中的方法,每次url變化的時(shí)候銷毀原始video對(duì)象,重新初始化。
最近還在研究,如果有好方法會(huì)再更新上來(lái)。如有解決方案也可以留言我。
videojs(document.querySelector('.video-js'),{}).ready(function(){ var myPlayer = this; myPlayer.src("http://www.example.com/path/to/video.mp4"); myPlayer.play(); });
需要注意,如果是在Vue里使用,建議用ref獲取元素,我這里標(biāo)出初始化播放器方法代碼
initPalyer(){ const videoDom = this.$refs.myVideo; //不能用document 獲取節(jié)點(diǎn) let myPlayer = videojs( videoDom, { controls: true, autoplay: false, preload: 'auto', sources: [{ src: vm.yfVideoDetail.videoUrl, type: "application/x-mpegURL" }] }); }
Api上有著詳細(xì)的使用方法的介紹,我這里因?yàn)橹挥玫竭@些所以就只寫了這部分代碼,如果想進(jìn)一步深入,可以自行研究。
更新播放方法:
const vue = new Vue({ el: ".body", data: { myPlayer: null }, methods: { initPalyer() { const videoDom = this.$refs.myVideo; //不能用document 獲取節(jié)點(diǎn) 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程序上傳本地視頻地址并通過(guò)ffmpeg工具轉(zhuǎn)成m3u8文件
實(shí)現(xiàn)前提:筆者用的是springboot框架,,前端上傳視頻用的是elementui里的上傳組件.
以下代碼是根據(jù)網(wǎng)上已有的源碼結(jié)合自身需求做了改變,僅供參考,請(qǐng)勿直接copy,直接copy報(bào)錯(cuò)了不要怪我哦。需要根據(jù)自身需求進(jìn)行改變。
后臺(tái)實(shí)現(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); //我自己封裝的方法,給文件重新起個(gè)名字,文件名不帶后綴 /*********本地上傳(Tomcat配置映射C:/upload/file)*********/ //先將文件本地上傳后,調(diào)用ffmpeg切片轉(zhuǎn)成m3u8,在將轉(zhuǎn)換后文件上傳到oss上去 /* 我的思路就是根據(jù)我重新起的名字,生成對(duì)應(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)碼異常!"); } //訪問(wè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; } //驗(yàn)證文件后綴 String suffix = StringUtils.substringAfter(fileName, "."); String fileFullName = StringUtils.substringBefore(fileName, "."); if (!validFileType(suffix)){ return false; } return processM3U8(folderUrl,fileName,fileFullName); } /** * 驗(yàn)證上傳文件后綴 * @param type * @return */ private boolean validFileType ( String type ) { if ("mp4".equals(type)){ return true; } return false; } /** * 驗(yàn)證是否是文件格式 * @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í)行語(yǔ)句就可以了 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)聽(tīng)ffmpeg運(yùn)行過(guò)程 * @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需要注意的問(wèn)題
1.同一個(gè)視頻的m3u8格式文件以及ts文件需要放在同一級(jí)目錄。最好是一個(gè)視頻的相關(guān)文件放入以文件名命名的路徑下以示區(qū)別
2.m3u8鏈接無(wú)法播放,video.js提示無(wú)法訪問(wèn)對(duì)應(yīng)資源,需要在阿里云oss上設(shè)置跨域。bucket空間—>基礎(chǔ)設(shè)置—>跨域設(shè)置
3.ts文件的讀寫權(quán)限需要公共讀不然video.js訪問(wèn)不到的,然后為了防止視頻被惡意下載可以設(shè)置防盜鏈
總結(jié)
以上就是Java利用ffmpeg實(shí)現(xiàn)視頻MP4轉(zhuǎn)m3u8的詳細(xì)內(nèi)容,更多關(guān)于Java ffmpeg實(shí)現(xiàn)MP4轉(zhuǎn)m3u8的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Javacv使用ffmpeg實(shí)現(xiàn)音視頻同步播放
- Java基于FFmpeg實(shí)現(xiàn)Mp4視頻轉(zhuǎn)GIF
- java使用ffmpeg實(shí)現(xiàn)上傳視頻的轉(zhuǎn)碼提取視頻的截圖等功能(代碼操作)
- Java使用ffmpeg和mencoder實(shí)現(xiàn)視頻轉(zhuǎn)碼
- java調(diào)用ffmpeg實(shí)現(xiàn)轉(zhuǎn)換視頻
- Java+Windows+ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換功能
- 詳解java調(diào)用ffmpeg轉(zhuǎn)換視頻格式為flv
- java調(diào)用ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換的方法
- Java工程使用ffmpeg進(jìn)行音視頻格式轉(zhuǎn)換的實(shí)現(xiàn)
相關(guān)文章
SpringBoot中使用AOP切面編程實(shí)現(xiàn)登錄攔截功能
本文介紹了如何使用AOP切面編程實(shí)現(xiàn)Spring Boot中的登錄攔截,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12一步步教你JAVA如何優(yōu)化Elastic?Search
想要榨干Java操作Elasticsearch的所有性能潛力?本指南將一步步教你如何優(yōu)化Java與Elasticsearch的交互!從此,提升ES查詢速度、降低資源消耗不再是難題,趕快一起來(lái)探索Java?Elasticsearch優(yōu)化的秘訣吧!2024-01-01Java異步線程中的CompletableFuture與@Async詳解
這篇文章主要介紹了Java異步線程中的CompletableFuture與@Async詳解,CompletableFuture是java中提供的一個(gè)異步執(zhí)行類,@Async是Spring提供的異步執(zhí)行方法,當(dāng)調(diào)用方法單獨(dú)開啟一個(gè)線程進(jìn)行調(diào)用,需要的朋友可以參考下2024-01-01利用JAVA反射,讀取數(shù)據(jù)庫(kù)表名,自動(dòng)生成對(duì)應(yīng)實(shí)體類的操作
這篇文章主要介紹了利用JAVA反射,讀取數(shù)據(jù)庫(kù)表名,自動(dòng)生成對(duì)應(yīng)實(shí)體類的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Java OpenCV實(shí)現(xiàn)圖像鏡像翻轉(zhuǎn)效果
這篇文章主要為大家詳細(xì)介紹了Java OpenCV實(shí)現(xiàn)圖像鏡像翻轉(zhuǎn)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07