package com.qxueyou.scc.teach.live.utils; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.qxueyou.scc.base.util.UUIDUtils; import com.qxueyou.scc.teach.live.utils.FfmpegFileVO; import com.qxueyou.scc.teach.live.utils.FfmpegVideoInfo; /** * 视频转码类 * @author cyq * */ public class FfmpegMediaHelper { /** * 实例化log */ private static final Logger log = LogManager.getLogger(FfmpegMediaHelper.class); /** * 高清码率 */ private static int bitRateHd = 800 * 1024; /** * 标清码率 */ private static int bitRateSd = 400 * 1024; /** * 低清码率 */ private static int bitRateLd = 200 * 1024; /** * 转mp4的固定命令 */ private static List commandMp4 = new ArrayList(10); /** * 转m3u8的固定命令 */ private static String commandM3u8; /** * 是否为视频 */ private static Boolean videoFlag = false; /** * 转mp4 * @param fileVO * @param videoInfo * @return */ public static boolean convertMp4(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } try { System.out.println("FfmpegMediaHelper--转码mp4中..."); commandMp4.clear(); commandMp4.add("ffmpeg"); commandMp4.add("-i"); commandMp4.add(fileVO.getInputPath()); commandMp4.add("-ab"); // 设置音频码率 commandMp4.add("128*1024"); commandMp4.add("-r"); commandMp4.add("23"); if (videoInfo.getVideoHeight() >= 1000) { convertMp4Hd(fileVO, videoInfo); convertMp4Sd(fileVO, videoInfo); convertMp4Ld(fileVO, videoInfo); } else if (videoInfo.getVideoHeight() >= 700) { convertMp4Sd(fileVO, videoInfo); convertMp4Ld(fileVO, videoInfo); } else { convertMp4Ld(fileVO, videoInfo); } System.out.println("FfmpegMediaHelper--转码mp4完成..."); return true; } catch (Exception e) { System.out.println("FfmpegMediaHelper--转码mp4异常..."); log.error("转码mp4异常:" + e.getMessage()); throw e; } } /** * 转m3u8 * @param fileVO * @param videoInfo * @return */ public static boolean convertM3u8(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } try { System.out.println("FfmpegMediaHelper--转码m3h8中..."); commandM3u8 = "ffmpeg -i " + fileVO.getInputPath() + " -strict -2 -c:v libx264 -c:a aac -f hls -hls_time 10 -hls_list_size 0"; if (videoInfo.getVideoHeight() >= 1000) { convertM3u8Hd(fileVO, videoInfo); convertM3u8Sd(fileVO, videoInfo); convertM3u8Ld(fileVO, videoInfo); } else if (videoInfo.getVideoHeight() >= 700) { convertM3u8Sd(fileVO, videoInfo); convertM3u8Ld(fileVO, videoInfo); } else { convertM3u8Ld(fileVO, videoInfo); } System.out.println("FfmpegMediaHelper--转码m3u8完成..."); return true; } catch (Exception e) { System.out.println("FfmpegMediaHelper--转码m3u8异常..."); log.error("转码m3u8异常:" + e.getMessage()); throw e; } } /** * 转高清mp4 */ private static void convertMp4Hd(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (1080 * videoInfo.getAspectRatio() / 2) * 2 + "*1080"; // 宽高必须是偶数 List command = new ArrayList(commandMp4); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateHd) { command.add("-vb"); // 视频码率 command.add(String.valueOf(bitRateHd)); } command.add("-s"); // 尺寸(分辨率) command.add(resolution); command.add(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-hd.mp4"); convertMp4(command); videoInfo.setMp4HdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-hd.mp4"); // 转码成功,将路径设置到videoInfo中 } catch (Exception e) { throw e; } } /** * 转高清m3u8 */ private static void convertM3u8Hd(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (1080 * videoInfo.getAspectRatio() / 2) * 2 + "*1080"; // 宽高必须是偶数 StringBuffer commandBuffer = new StringBuffer(commandM3u8); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateHd) { commandBuffer.append(" -vb ").append(String.valueOf(bitRateHd)).append(' '); } commandBuffer.append(" -s ").append(resolution).append(' '); commandBuffer.append(fileVO.getOutputPath()); commandBuffer.append(videoInfo.getUuid()).append("m3u8-hd.m3u8"); convertM3u8(commandBuffer.toString()); videoInfo.setM3u8HdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "m3u8-hd.m3u8"); } catch (Exception e) { throw e; } } /** * 转标清mp4 */ private static void convertMp4Sd(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (720 * videoInfo.getAspectRatio() / 2) * 2 + "*720"; List command = new ArrayList(commandMp4); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateSd) { command.add("-vb"); command.add(String.valueOf(bitRateSd)); } command.add("-s"); command.add(resolution); command.add(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-sd.mp4"); convertMp4(command); videoInfo.setMp4SdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-sd.mp4"); } catch (Exception e) { throw e; } } /** * 转标清m3u8 */ private static void convertM3u8Sd(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (720 * videoInfo.getAspectRatio() / 2) * 2 + "*720"; StringBuffer commandBuffer = new StringBuffer(commandM3u8); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateSd) { commandBuffer.append(" -vb ").append(String.valueOf(bitRateSd)).append(' '); } commandBuffer.append(" -s ").append(resolution).append(' '); commandBuffer.append(fileVO.getOutputPath()).append(videoInfo.getUuid()).append("m3u8-sd.m3u8"); convertM3u8(commandBuffer.toString()); videoInfo.setM3u8SdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "m3u8-sd.m3u8"); } catch (Exception e) { throw e; } } /** * 转低清mp4 */ private static void convertMp4Ld(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (480 * videoInfo.getAspectRatio() / 2) * 2 + "*480"; List command = new ArrayList(commandMp4); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateLd) { command.add("-vb"); command.add(String.valueOf(bitRateLd)); } command.add("-s"); command.add(resolution); command.add(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-ld.mp4"); convertMp4(command); videoInfo.setMp4LdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "mp4-ld.mp4"); } catch (Exception e) { throw e; } } /** * 转低清m3u8 */ private static void convertM3u8Ld(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { try { String resolution = (int) (480 * videoInfo.getAspectRatio() / 2) * 2 + "*480"; StringBuffer commandBuffer = new StringBuffer(commandM3u8); if (videoInfo.getVideoBitRate() == null || videoInfo.getVideoBitRate() > bitRateLd) { commandBuffer.append(" -vb ").append(String.valueOf(bitRateSd)).append(' '); } commandBuffer.append(" -s ").append(resolution).append(' '); commandBuffer.append(fileVO.getOutputPath()).append(videoInfo.getUuid()).append("m3u8-ld.m3u8"); convertM3u8(commandBuffer.toString()); videoInfo.setM3u8LdUrl(fileVO.getOutputPath() + videoInfo.getUuid() + "m3u8-ld.m3u8"); } catch (Exception e) { throw e; } } /** * 获取媒体信息 * * @param fileVO * @param videoInfo * @return */ public static boolean mediaInfo(FfmpegFileVO fileVO, FfmpegVideoInfo videoInfo) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } BufferedReader buf = null; // 保存ffmpeg的输出结果流 String currLine = null; String line = null; try { log.debug("获取媒体信息..."); List command = new ArrayList(); // ffprobe 命令获取媒体信息 command.add("ffprobe"); command.add("-print_format"); // json格式输出 command.add("json"); command.add("-show_streams"); command.add(fileVO.getInputPath()); ProcessBuilder process = new ProcessBuilder(); process.command(command); process.redirectErrorStream(true); Process p = process.start(); buf = new BufferedReader(new InputStreamReader(p.getInputStream())); while ((currLine = buf.readLine()) != null) { // 处理多余字符 line = currLine.trim().replaceAll("\"", "").replaceAll(",", "").replace(" ", ""); handleLine(line, videoInfo); // 获取文件时长(flv需要这么取) if(currLine.trim().startsWith("Duration:") && currLine.contains(",") ){ String strFirst = currLine.trim().split(",")[0].substring(9).trim().substring(0,8); String[] arrTime = strFirst.split(":"); if(null != arrTime && arrTime.length == 3 ){ Integer iHour = Integer.parseInt(arrTime[0]); Integer iMinute = Integer.parseInt(arrTime[1]); Integer iSecond = Integer.parseInt(arrTime[2]); videoInfo.setPlayTime(iHour * 60 * 60 + iMinute * 60 + iSecond); log.debug("视频时长:" + videoInfo.getPlayTime()); } } } p.waitFor();// 这里线程阻塞,将等待外部转换进程运行成功运行结束后,才往下执行 log.debug("获取媒体信息完成"); return true; } catch (Exception e) { log.debug("获取媒体信息异常:" + e.getMessage()); throw e; } } private static void handleLine(String line, FfmpegVideoInfo videoInfo) { if (line.contains(":")) { String[] arr = line.split(":"); if (null != arr && arr.length == 2) { // 1. 如果是标志位 if ("codec_type".equals(arr[0].trim())) { // 视频 if ("video".equals(arr[1].trim())) { videoFlag = true; } // 音频 if ("audio".equals(arr[1].trim())) { videoFlag = false; } } // 视频宽度 if (videoFlag && "width".equals(arr[0])) { videoInfo.setVideoWidth(Integer.valueOf(arr[1])); if (videoInfo.getVideoHeight() != null) { videoInfo.setAspectRatio((float) (videoInfo.getVideoWidth().floatValue() / videoInfo.getVideoHeight().floatValue())); log.debug("计算宽高比:" + videoInfo.getVideoWidth() + "/" + videoInfo.getVideoHeight() + ":" + videoInfo.getAspectRatio()); } } // 视频高度 if (videoFlag && "height".equals(arr[0])) { videoInfo.setVideoHeight(Integer.valueOf(arr[1])); if (videoInfo.getVideoWidth() != null) { videoInfo.setAspectRatio((float) (videoInfo.getVideoWidth().floatValue() / videoInfo.getVideoHeight().floatValue())); log.debug("计算宽高比:" + videoInfo.getVideoWidth() + "/" + videoInfo.getVideoHeight() + ":" + videoInfo.getAspectRatio()); } } //获取视屏帧率表达式 if (videoFlag && "avg_frame_rate".equals(arr[0])) { log.info("开始计算帧率,"+arr[1]); if(StringUtils.isNotEmpty(arr[1])){ String [] strFrameArr = arr[1].split("/"); if(strFrameArr!=null && strFrameArr.length==2 && Double.valueOf(strFrameArr[1].trim())>0){ int frameRate = (int) Math.ceil((Double.valueOf(strFrameArr[0].trim())/Double.valueOf(strFrameArr[1].trim()))); videoInfo.setFrameRate(frameRate); videoInfo.setFrameRateExp(arr[1]); log.info("计算帧率:frameRate="+frameRate+" frameRateExp="+arr[1]); } } } // 视频码率 if (videoFlag && "bit_rate".equals(arr[0])) { videoInfo.setVideoBitRate(Integer.valueOf(arr[1])); log.debug("视频码率:" + videoInfo.getVideoBitRate()); } // 文件时长 if (videoFlag && "duration".equals(arr[0])) { if (arr[1].contains(".")) { //videoInfo.setPlayTime(Integer.valueOf(arr[1].indexOf('.'))); videoInfo.setPlayTime(Integer.parseInt(arr[1].substring(0, arr[1].indexOf('.')))); } else { videoInfo.setPlayTime(Integer.valueOf(arr[1])); } log.debug("视频时长:" + videoInfo.getPlayTime()); } } } } /** * 截图 * * @param fileVO * @param outputPath * @return */ public static boolean snapshot(FfmpegFileVO fileVO) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } try { log.debug("FfmpegMediaHelper--视频截屏..."); String comm = "ffmpeg -ss 00:00:01 -i " + fileVO.getInputPath() + " -t 0.1 -f mjpeg -y " + fileVO.getOutputPath() + "snapshot.jpg"; Process videoProcess = Runtime.getRuntime().exec(comm); new PrintStreamThread(videoProcess.getErrorStream()).start(); videoProcess.waitFor(); log.debug("FfmpegMediaHelper--视频截屏完成"); return true; } catch (Exception e) { log.error("视频截屏异常" + e.getMessage()); throw e; } } /** * 根据帧率截屏 * @param fileVO 待截图视频文件信息 * @param imgPrefixName 截取图片名称的前缀信息 * @param framePerSecond 每秒图片帧数 * @param width 截取图片宽度(ps:单位像素点) * @param height 截取图片高度(ps:单位像素点) * @return 是否截取成功 * @throws Exception */ /** * * @param fileVO 文件信息 * @param imgPrefixName 截取图片的前缀信息 * @param framePerSecond 每秒多少帧 * @return * @throws Exception */ public static boolean screenShot(FfmpegFileVO fileVO,String imgPrefixName,int framePerSecond,int width,int height) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } if(framePerSecond<=0 || width * height<=0){ return false; } Process process = null; try { // ffmpeg -ss 00:00:00.000 -i D:/usr/qxueyou/huifang/test.mp4 -f image2 -vf fps=fps=1/5 D:/usr/qxueyou/huifang/pic/new_%d.jpg String comm = "ffmpeg -ss 00:00:00.000 -i " + fileVO.getInputPath() + " -f image2 -s " + width + "*" + height + " -vf fps=fps=1/"+ framePerSecond +" "+ fileVO.getOutputPath()+"/" + imgPrefixName + "%d.jpg"; log.info("screenShot命令, "+ comm); process = Runtime.getRuntime().exec(comm); new PrintStreamThread(process.getErrorStream()).start(); process.waitFor(); return true; } catch (Exception e) { log.error("视频截屏异常" + e.getMessage()); throw e; }finally { closeProcess(process); } } public static boolean dividVideo(FfmpegFileVO fileVO,int statTime,int duration) throws Exception { if (!checkfile(fileVO.getInputPath())) { return false; } Process process = null; try { String comm = "ffmpeg -ss "+ statTime + " -t " + duration + " -i "+ fileVO.getInputPath() + " -y -vcodec copy -acodec copy " + fileVO.getOutputPath(); log.info("dividVideo命令, "+ comm); process = Runtime.getRuntime().exec(comm); new PrintStreamThread(process.getErrorStream()).start(); process.waitFor(); return true; } catch (Exception e) { log.error("切割视频失败" + e.getMessage()); throw e; }finally { closeProcess(process); } } /** * 合并视频 * * @param fileVO * @return * @throws Exception */ public static Process mergeVideo(FfmpegFileVO fileVO) throws Exception { if (!checkfile(fileVO.getInputPath())) { return null; } String comm = "ffmpeg -f concat -safe 0 -i " + fileVO.getInputPath() + " -c copy " + fileVO.getOutputPath(); log.info("mergeVideo命令, "+ comm); Process videoProcess = Runtime.getRuntime().exec(comm); new PrintStreamThread(videoProcess.getErrorStream()).start(); videoProcess.waitFor(); return videoProcess; } /** * 合并视频 * * @param fileVO * @return * @throws Exception */ public static boolean concatVideo(FfmpegFileVO fileVO) throws Exception { Process process = null; try { List command = new ArrayList(20); command.add("ffmpeg"); command.add("-f"); command.add("concat"); command.add("-safe"); command.add("0"); command.add("-i"); command.add( fileVO.getInputPath()); command.add( "-y"); command.add( "-c"); command.add( "copy"); command.add( "-bsf:a"); command.add( "aac_adtstoasc"); command.add( "-f"); command.add( "mp4"); command.add(fileVO.getOutputPath()); ProcessBuilder pBuilder = new ProcessBuilder(command); pBuilder.directory(new File(fileVO.getInputPath()).getParentFile()); log.info("ProcessBuilder directory:"+pBuilder.directory().getAbsolutePath()); process = pBuilder.start(); new PrintStreamThread(process.getErrorStream()).start(); process.waitFor(); return true; } catch (Exception e) { throw e; } finally { closeProcess(process); } } public static String converUploadVideoToLiveMp4(FfmpegFileVO fileVO,FfmpegVideoInfo videoInfo) throws Exception{ String outputPath = null; try { // log.info("FfmpegMediaHelper.TransferUploadVideoToWyMp4,直播上传..."); List command = new ArrayList(); String name = UUIDUtils.generateUUID().replace("-", ""); outputPath = fileVO.getOutputPath() +name + ".mp4"; command.add("ffmpeg"); command.add("-i"); command.add(fileVO.getInputPath()); command.add("-q:v"); command.add("6"); // command.add(" -r "); // command.add(videoInfo.getFrameRateExp()); command.add("-pix_fmt"); command.add("yuv420p"); command.add(outputPath); convertMp4(command); log.error(command.toString()); } catch (Exception e) { throw e; } return outputPath; } /** * mp4转ts文件 * ffmpeg -i 1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts out1.ts * * @param fileVO * @return * @throws Exception */ public static boolean mp4toannexb(FfmpegFileVO fileVO) throws Exception { Process process= null; try { String comm = "ffmpeg -i " + fileVO.getInputPath() + " -c copy -bsf:v h264_mp4toannexb -y -f mpegts " + fileVO.getOutputPath(); log.info("mp4toannexb命令, "+ comm); process = Runtime.getRuntime().exec(comm); new PrintStreamThread(process.getErrorStream()).start(); process.waitFor(); return true; } catch (Exception e) { throw e; }finally { closeProcess(process); } } private static void closeProcess(Process process) throws Exception { try { if (process != null) { if (process.getInputStream() != null) { process.getInputStream().close(); } if (process.getOutputStream() != null) { process.getOutputStream().close(); } if (process.getErrorStream() != null) { process.getErrorStream().close(); } process.destroy(); } } catch (IOException e) { log.error("关闭process失败。。", e); } } /** * 关闭进程 * * @param process * @return */ public static boolean stop(Process process) { if (process != null) { process.destroy(); return true; } return false; } /** * 解析成MP4格式 * * @param oldfilepath * @return */ private static boolean convertMp4(List command) throws Exception { Process process = null; try { process = new ProcessBuilder(command).redirectErrorStream(true).start(); new PrintStreamThread(process.getInputStream()).start(); process.waitFor(); return true; } catch (Exception e) { throw e; } finally { if (process != null) { try { process.getInputStream().close(); process.getOutputStream().close(); process.getErrorStream().close(); } catch (IOException e) { throw e; } } } } /** * 解析m3u8格式 * * @param oldfilepath * @return */ private static boolean convertM3u8(String command) throws Exception { Process process = null; try { process = Runtime.getRuntime().exec(command); new PrintStreamThread(process.getErrorStream()).start(); process.waitFor(); return true; } catch (Exception e) { throw e; } finally { if (process != null) { try { process.getInputStream().close(); process.getOutputStream().close(); process.getErrorStream().close(); } catch (IOException e) { throw e; } } } } /** * 检查转码输入文件是否存在 * @param path * @return */ private static boolean checkfile(String path) { File file = new File(path); return file.isFile(); } } /** * 打印线程 * @author cyq * */ class PrintStreamThread extends Thread { /** * 输入流 */ java.io.InputStream __is; /** * 构造方法 * @param is */ public PrintStreamThread(java.io.InputStream is) { __is = is; } /** * 线程执行方法 */ public void run() { try { while (this != null) { if (__is.read() == -1) { break; } } } catch (Exception e) { e.printStackTrace(); } } }