原始視頻通常需要經過編碼處理,生成m3u8和ts文件方可基於HLS協議播放視頻。通常用戶上傳原始視頻,系統 自動處理成標准格式,系統對用戶上傳的視頻自動編碼、轉換,最終生成m3u8文件和ts文件,
處理流程如下:
1、用戶上傳視頻成功
2、系統對上傳成功的視頻自動開始編碼處理
3、用戶查看視頻處理結果,沒有處理成功的視頻用戶可在管理界面再次觸發處理
4、視頻處理完成將視頻地址及處理結果保存到數據庫
VideoUtil
package com.xuecheng.framework.utils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 此文件作為視頻文件處理父類,提供:
* 1、查看視頻時長
* 2、校驗兩個視頻的時長是否相等
*
*/
public class VideoUtil {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
public VideoUtil(String ffmpeg_path){
this.ffmpeg_path = ffmpeg_path;
}
//檢查視頻時間是否一致
public Boolean check_video_time(String source,String target) {
String source_time = get_video_time(source);
//取出時分秒
source_time = source_time.substring(0,source_time.lastIndexOf("."));
String target_time = get_video_time(target);
//取出時分秒
target_time = target_time.substring(0,target_time.lastIndexOf("."));
if(source_time == null || target_time == null){
return false;
}
if(source_time.equals(target_time)){
return true;
}
return false;
}
//獲取視頻時間(時:分:秒:毫秒)
public String get_video_time(String video_path) {
/*
ffmpeg -i lucene.mp4
*/
List<String> commend = new ArrayList<String>();
commend.add(ffmpeg_path);
commend.add("-i");
commend.add(video_path);
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
//將標准輸入流和錯誤輸入流合並,通過標准輸入流程讀取信息
builder.redirectErrorStream(true);
Process p = builder.start();
String outstring = waitFor(p);
System.out.println(outstring);
int start = outstring.trim().indexOf("Duration: ");
if(start>=0){
int end = outstring.trim().indexOf(", start:");
if(end>=0){
String time = outstring.substring(start+10,end);
if(time!=null && !time.equals("")){
return time.trim();
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public String waitFor(Process p) {
InputStream in = null;
InputStream error = null;
String result = "error";
int exitValue = -1;
StringBuffer outputString = new StringBuffer();
try {
in = p.getInputStream();
error = p.getErrorStream();
boolean finished = false;
int maxRetry = 600;//每次休眠1秒,最長執行時間10分種
int retry = 0;
while (!finished) {
if (retry > maxRetry) {
return "error";
}
try {
while (in.available() > 0) {
Character c = new Character((char) in.read());
outputString.append(c);
System.out.print(c);
}
while (error.available() > 0) {
Character c = new Character((char) in.read());
outputString.append(c);
System.out.print(c);
}
//進程未結束時調用exitValue將拋出異常
exitValue = p.exitValue();
finished = true;
} catch (IllegalThreadStateException e) {
Thread.currentThread().sleep(1000);//休眠1秒
retry++;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
return outputString.toString();
}
public static void main(String[] args) throws IOException {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
VideoUtil videoUtil = new VideoUtil(ffmpeg_path);
String video_time = videoUtil.get_video_time("E:\\ffmpeg_test\\1.avi");
System.out.println(video_time);
}
}
Mp4VideoUtil
package com.xuecheng.framework.utils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by admin on 2018/3/6.
*/
public class Mp4VideoUtil extends VideoUtil {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
String video_path = "D:\\BaiduNetdiskDownload\\test1.avi";
String mp4_name = "test1.mp4";
String mp4folder_path = "D:/BaiduNetdiskDownload/Movies/test1/";
public Mp4VideoUtil(String ffmpeg_path, String video_path, String mp4_name, String mp4folder_path){
super(ffmpeg_path);
this.ffmpeg_path = ffmpeg_path;
this.video_path = video_path;
this.mp4_name = mp4_name;
this.mp4folder_path = mp4folder_path;
}
//清除已生成的mp4
private void clear_mp4(String mp4_path){
//刪除原來已經生成的m3u8及ts文件
File mp4File = new File(mp4_path);
if(mp4File.exists() && mp4File.isFile()){
mp4File.delete();
}
}
/**
* 視頻編碼,生成mp4文件
* @return 成功返回success,失敗返回控制台日志
*/
public String generateMp4(){
//清除已生成的mp4
clear_mp4(mp4folder_path+mp4_name);
/*
ffmpeg.exe -i lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4
*/
List<String> commend = new ArrayList<String>();
//commend.add("D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe");
commend.add(ffmpeg_path);
commend.add("-i");
// commend.add("D:\\BaiduNetdiskDownload\\test1.avi");
commend.add(video_path);
commend.add("-c:v");
commend.add("libx264");
commend.add("-y");//覆蓋輸出文件
commend.add("-s");
commend.add("1280x720");
commend.add("-pix_fmt");
commend.add("yuv420p");
commend.add("-b:a");
commend.add("63k");
commend.add("-b:v");
commend.add("753k");
commend.add("-r");
commend.add("18");
commend.add(mp4folder_path + mp4_name );
String outstring = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
//將標准輸入流和錯誤輸入流合並,通過標准輸入流程讀取信息
builder.redirectErrorStream(true);
Process p = builder.start();
outstring = waitFor(p);
} catch (Exception ex) {
ex.printStackTrace();
}
Boolean check_video_time = this.check_video_time(video_path, mp4folder_path + mp4_name);
if(!check_video_time){
return outstring;
}else{
return "success";
}
}
public static void main(String[] args) throws IOException {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
String video_path = "E:\\ffmpeg_test\\1.avi";
String mp4_name = "809694a6a974c35e3a36f36850837d7c.mp4";
String mp4_path = "F:/develop/upload/8/0/809694a6a974c35e3a36f36850837d7c/";
Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
String s = videoUtil.generateMp4();
System.out.println(s);
}
}
HlsVideoUtil
package com.xuecheng.framework.utils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 此文件用於視頻文件處理,步驟如下:
* 1、生成mp4
* 2、生成m3u8
*
*/
public class HlsVideoUtil extends VideoUtil {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
String video_path = "D:\\BaiduNetdiskDownload\\test1.avi";
String m3u8_name = "test1.m3u8";
String m3u8folder_path = "D:/BaiduNetdiskDownload/Movies/test1/";
public HlsVideoUtil(String ffmpeg_path, String video_path, String m3u8_name,String m3u8folder_path){
super(ffmpeg_path);
this.ffmpeg_path = ffmpeg_path;
this.video_path = video_path;
this.m3u8_name = m3u8_name;
this.m3u8folder_path = m3u8folder_path;
}
private void clear_m3u8(String m3u8_path){
//刪除原來已經生成的m3u8及ts文件
File m3u8dir = new File(m3u8_path);
if(!m3u8dir.exists()){
m3u8dir.mkdirs();
}
/* if(m3u8dir.exists()&&m3u8_path.indexOf("/hls/")>=0){//在hls目錄方可刪除,以免錯誤刪除
String[] children = m3u8dir.list();
//刪除目錄中的文件
for (int i = 0; i < children.length; i++) {
File file = new File(m3u8_path, children[i]);
file.delete();
}
}else{
m3u8dir.mkdirs();
}*/
}
/**
* 生成m3u8文件
* @return 成功則返回success,失敗返回控制台日志
*/
public String generateM3u8(){
//清理m3u8文件目錄
clear_m3u8(m3u8folder_path);
/*
ffmpeg -i lucene.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8
*/
// String m3u8_name = video_name.substring(0, video_name.lastIndexOf("."))+".m3u8";
List<String> commend = new ArrayList<String>();
commend.add(ffmpeg_path);
commend.add("-i");
commend.add(video_path);
commend.add("-hls_time");
commend.add("10");
commend.add("-hls_list_size");
commend.add("0");
commend.add("-hls_segment_filename");
// commend.add("D:/BaiduNetdiskDownload/Movies/test1/test1_%05d.ts");
commend.add(m3u8folder_path + m3u8_name.substring(0,m3u8_name.lastIndexOf(".")) + "_%05d.ts");
// commend.add("D:/BaiduNetdiskDownload/Movies/test1/test1.m3u8");
commend.add(m3u8folder_path + m3u8_name );
String outstring = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
//將標准輸入流和錯誤輸入流合並,通過標准輸入流程讀取信息
builder.redirectErrorStream(true);
Process p = builder.start();
outstring = waitFor(p);
} catch (Exception ex) {
ex.printStackTrace();
}
//通過查看視頻時長判斷是否成功
Boolean check_video_time = check_video_time(video_path, m3u8folder_path + m3u8_name);
if(!check_video_time){
return outstring;
}
//通過查看m3u8列表判斷是否成功
List<String> ts_list = get_ts_list();
if(ts_list == null){
return outstring;
}
return "success";
}
/**
* 檢查視頻處理是否完成
* @return ts列表
*/
public List<String> get_ts_list() {
// String m3u8_name = video_name.substring(0, video_name.lastIndexOf("."))+".m3u8";
List<String> fileList = new ArrayList<String>();
List<String> tsList = new ArrayList<String>();
String m3u8file_path =m3u8folder_path + m3u8_name;
BufferedReader br = null;
String str = null;
String bottomline = "";
try {
br = new BufferedReader(new FileReader(m3u8file_path));
while ((str = br.readLine()) != null) {
bottomline = str;
if(bottomline.endsWith(".ts")){
tsList.add(bottomline);
}
//System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (bottomline.contains("#EXT-X-ENDLIST")) {
// fileList.add(hls_relativepath+m3u8_name);
fileList.addAll(tsList);
return fileList;
}
return null;
}
public static void main(String[] args) throws IOException {
String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安裝位置
String video_path = "E:\\ffmpeg_test\\1.mp4";
String m3u8_name = "1.m3u8";
String m3u8_path = "E:\\ffmpeg_test\\1\\";
HlsVideoUtil videoUtil = new HlsVideoUtil(ffmpeg_path,video_path,m3u8_name,m3u8_path);
String s = videoUtil.generateM3u8();
System.out.println(s);
System.out.println(videoUtil.get_ts_list());
}
}
MediaProcessTask
package com.xuecheng.manage_media_processor.mq;
import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.MediaFileProcess_m3u8;
import com.xuecheng.framework.utils.HlsVideoUtil;
import com.xuecheng.framework.utils.Mp4VideoUtil;
import com.xuecheng.manage_media_processor.dao.MediaFileMapper;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* Created by Administrator on 2020/6/7 0007.
*/
@Component
public class MediaProcessTask {
@Value("${xc-service-manage-media.ffmpeg-path}")
String ffmpeg_path;
//上傳文件根目錄
@Value("${xc-service-manage-media.video-location}")
String serverPath;
@Autowired
private MediaFileMapper mediaFileMapper;
//接收視頻處理消息進行視頻處理
@RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
public void receiveMediaProcessTask(String msg ){
//1.解析消息內容,得到mediaId
Map map = JSON.parseObject(msg, Map.class);
String mediaId = (String) map.get("mediaId");
//2.拿到mediaId從數據庫中查詢文件信息
MediaFile mediaFile = mediaFileMapper.findByFileId(mediaId);
if(null == mediaFile){
return;
}
//文件類型
String fileType = mediaFile.getFileType();
if(!fileType.equals("avi")){
mediaFile.setProcessStatus("303004");//無需處理
mediaFileMapper.save(mediaFile);
return ;
}else{
//需要處理
mediaFile.setProcessStatus("303001");//處理中
mediaFileMapper.save(mediaFile);
}
//3.使用工具類將avi文件生成mp4
//String ffmpeg_path, String video_path, String mp4_name, String mp4folder_path
//要處理的視頻文件的路徑
String video_path = serverPath + mediaFile.getFilePath() + mediaFile.getFileName();
//生成的mp4的文件名稱
String mp4_name = mediaFile.getFileId() + ".mp4";
//生成的mp4所在的路徑
String mp4folder_path = serverPath + mediaFile.getFilePath();
Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4folder_path);
//進行處理
String result = mp4VideoUtil.generateMp4();
if(result == null || !result.equals("success")){
//處理失敗
mediaFile.setProcessStatus("303003");
//定義mediaFileProcess_m3u8
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
//記錄失敗原因
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
mediaFileMapper.save(mediaFile);
return ;
}
//4.將mp4生成m3u8和ts文件
//String ffmpeg_path, String video_path, String m3u8_name,String m3u8folder_path
//mp4視頻文件路徑
String mp4_video_path = serverPath + mediaFile.getFilePath() + mp4_name;
//m3u8_name文件名稱
String m3u8_name = mediaFile.getFileId() +".m3u8";
//m3u8文件所在目錄
String m3u8folder_path = serverPath + mediaFile.getFilePath() + "hls/";
HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path,mp4_video_path,m3u8_name,m3u8folder_path);
//生成m3u8和ts文件
String tsResult = hlsVideoUtil.generateM3u8();
if(tsResult == null || !tsResult.equals("success")){
//處理失敗
mediaFile.setProcessStatus("303003");
//定義mediaFileProcess_m3u8
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
//記錄失敗原因
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
mediaFileMapper.save(mediaFile);
return ;
}
//處理成功
//獲取ts文件列表
List<String> ts_list = hlsVideoUtil.get_ts_list();
mediaFile.setProcessStatus("303002");
//定義mediaFileProcess_m3u8
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setTslist(ts_list);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
//保存fileUrl(此url就是視頻播放的相對路徑)
String fileUrl =mediaFile.getFilePath() + "hls/"+m3u8_name;
mediaFile.setFileUrl(fileUrl);
mediaFileMapper.save(mediaFile);
}
}
