Java—视频处理
原始视频通常需要经过编码处理,生成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); } }