http://zhuyufufu.iteye.com/blog/2078404
对于没法用ffmpeg处理的rm等格式,使用了 mplayer的 mencoder来处理。
我的项目重点在视频分割。在此只写rmvb切分的代码。对于别的 ffmpeg无法处理的格式, mencoder应该都能处理。
下面贴出暂时使用的视频处理工具类,有获 取视频信息、截取视频、获取截图、加水印、视频转换等功能。
其原理都很简单: 使用java调用外部组件对视频进行处理。
后面要添加的功能: 解析组件输出流,生成视频处理进度及结果。
下面贴上代码,方便以后查阅
properties配置文件
#视频切割配置参数 ffmpeg_home=D:/ffmpeg/ffmpeg-20140611-git-b2fb65c-win64-static/ ffmpeg=D:/ffmpeg/ffmpeg-20140611-git-b2fb65c-win64-static/bin/ffmpeg.exe mplayer=D:/ffmpeg/MPlayer-generic-r37220+gd4be3a8/mplayer.exe mencoder=D:/ffmpeg/MPlayer-generic-r37220+gd4be3a8/mencoder.exe
package com.zas.util; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; /** * 视频操作类 * @author zas * */ public class VideoUtil { static Logger logger = Logger.getLogger(VideoUtil.class); final static String FFMPEG = PropertyToolkits.getProperty("ffmpeg"); final static String MENCODER = PropertyToolkits.getProperty("mencoder"); final static String MPLAYER = PropertyToolkits.getProperty("mplayer"); /** * 获取一个指定视频的基本信息 * @param inputVideoFile * @return 视频信息 */ public static String getVideoInfo(String inputVideoFile){ //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("文件有误"); } ListcommandList = new ArrayList (); commandList.add(FFMPEG); commandList.add("-i"); commandList.add(inputVideoFile); // mplayer -identify $inputFileName -nosound -vc dummy -vo null // List commandList = new ArrayList (); // commandList.add(MPLAYER); // commandList.add("-identify"); // commandList.add(inputVideoFile); // commandList.add("-nosound"); // commandList.add("-vc"); // commandList.add("dummy"); // commandList.add("-vo"); // commandList.add("null"); //获取视频信息 String videoInfo = process(commandList); return videoInfo; } /** * 获取视频截图 * @param inputVideoFile 要处理的视频 * @param imgPath 要截取的截图 * @param parameterMap 参数 * @return */ public static String getVideoSnapshots(String inputVideoFile, String imgPath, Map parameterMap){ //ffmpeg -i 1111.wma -y -ss 00:00:09 -t 00:00:10 -s 320*240 -f mjpeg -vframes 10 1111_1.jpg //获取图片的第一帧 ffmpeg commandLine: ffmpeg -y -i 1111.wma -vframes 1 -r 1 -ac 1 -ab 2 -s 320x240 -f image2 1111_1.jpg //把视频的前30帧转换成一个Animated Gif : //ffmpeg -i 1111.wma -vframes 30 -y -f gif 1111.gif //图片时间截取也很重要,很有可能是无效图片或者是黑屏 //建议 增加关键帧,通常第一帧为关键帧,可以使用:vframes:帧参数,舍弃微秒参数,只保留时间参数 //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("视频文件有误"); } /* -i filename 输入文件 -y 覆盖输出文件 -t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持 -ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持 */ List commandList = new ArrayList (); commandList.add(FFMPEG); commandList.add("-i"); commandList.add(inputVideoFile); commandList.add("-y"); //位置参数太靠后,会影响抓图效率 commandList.add("-ss"); commandList.add("00:00:09"); // commandList.add("-t"); // commandList.add("00:00:01"); commandList.add("-vframes"); commandList.add("1"); // commandList.add("-r"); // commandList.add("1"); // commandList.add("-ac"); // commandList.add("1"); // commandList.add("-ab"); // commandList.add("2"); // commandList.add("-s"); // commandList.add("320*240"); commandList.add("-f"); commandList.add("mjpeg"); commandList.add(imgPath); String processInfo = process(commandList); return processInfo; } /** * 使用ffmpeg 从指定时间开始截取特定时长视频 * @param fromTime 开始时间 00:5:28 * @param inputVideoFile 要截取的视频 * @param duration 要截取的视频时长 00:03:25 * @param outputFile 截取的视频的输出位置 * @param parameterMap 参数 * @return 处理信息 */ public static String cutting(String fromTime, String inputVideoFile, String duration, String outputFile, Map parameterMap){ //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("视频文件有误"); } File file = new File(outputFile); if(file.exists()){ System.out.println("outputFile exists !"); return "outputFile exists!"; } String filetype = inputVideoFile.substring(inputVideoFile.lastIndexOf(".") + 1); if("rm".equalsIgnoreCase(filetype) || "rmvb".equalsIgnoreCase(filetype)){ return cuttingRmvb(fromTime, inputVideoFile, duration, outputFile, parameterMap); } //ffmpeg -ss 00:5:28 -i "1111.wmv" -acodec copy -vcodec copy -t 00:03:25 output.wmv //这行命令解释为:从文件 1111.wmv 第 5:28 分秒开始,截取 03: 25 的时间,其中视频和音频解码不变,输出文件名为 output.wmv 。 List commandList = new ArrayList (); commandList.add(FFMPEG); commandList.add("-ss"); commandList.add(fromTime); commandList.add("-i"); commandList.add(inputVideoFile); commandList.add("-acodec"); commandList.add("copy"); commandList.add("-vcodec"); commandList.add("copy"); commandList.add("-t"); commandList.add(duration); commandList.add(outputFile); String processInfo = process(commandList); return processInfo; } /** * 使用ffmpeg 从指定时间开始截取特定时长视频 * @param fromTime 开始时间 00:5:28 * @param inputVideoFile 要截取的视频 * @param duration 要截取的视频时长 00:03:25 * @param outputFile 截取的视频的输出位置 * @param parameterMap 参数 * @return 处理信息 */ public static String cuttingRmvb(String fromTime, String inputVideoFile, String duration, String outputFile, Map parameterMap){ //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("视频文件有误"); } // mencoder basket.rm -ovc lavc -oac mp3lame -o basket.avi -ss 5:00 -endpos 8:00 List commandList = new ArrayList (); commandList.add(MENCODER); commandList.add(inputVideoFile); commandList.add("-ovc"); commandList.add("lavc"); commandList.add("-oac"); commandList.add("mp3lame"); commandList.add("-ss"); commandList.add(fromTime); commandList.add("-endpos"); commandList.add(duration); commandList.add("-o"); commandList.add(outputFile); String processInfo = process(commandList); return processInfo; } /** * 视频转换 * @param inputVideoFile 视频文件 * @param outputFile 转换后的视频文件 * @param parameterMap 其余参数 * @return */ public static String videoConverter(String inputVideoFile, String outputFile, Map parameterMap){ //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("视频文件有误"); } //视频的格式有很多,以mp4和flv为例子 // ffmpeg -i test.mp4 -ab 56 -ar 22050 -qmin 2 -qmax 16 -b 320k -r 15 -s 320x240 outputfile.flv //mp4 转 flv // ffmpeg -i outputfile.flv -copyts -strict -2 test.mp4 //flv 转 mp4 List commandList = new ArrayList (); commandList.add(FFMPEG); commandList.add("-y"); commandList.add("-i"); commandList.add(inputVideoFile); commandList.add("-ab"); commandList.add("5600000"); commandList.add("-ar"); commandList.add("22050"); commandList.add("-b"); commandList.add("500"); commandList.add("-s"); commandList.add("320*240"); commandList.add(outputFile); String processInfo = process(commandList); return processInfo; } /** * 视频加水印 * @param inputVideoFile 要处理的视频文件 * @param outputFile 输出的视频文件 * @param parameterMap 参数 * @return */ public static String addWatermark(String inputVideoFile, String outputFile, Map parameterMap){ //检查是否能够处理 if(!checkVideoFile(inputVideoFile)){ throw new RuntimeException("视频文件有误"); } //http://blog.51yip.com/linux/1584.html //#ffmpeg -y -i test.mp4 -acodec copy -vf "movie=logo.jpg [logo]; [in][logo] overlay=10:10:1 [out]" test2.mp4 //overlay=10:10:1,后三个数据表示是距离左边的距离,距离上边的距离,是否透明,1表示透明。上例我用的是jpg,当然不可能透明。 //# ffmpeg -y -i test.mp4 -acodec copy -vf "movie=uwsgi.jpg [logo]; [in][logo] overlay=enable='lte(t,1)' [out]" test2.mp4 //overlay=enable='lte(t,1)' ,这个参数表示,水印在前一秒显示。 //ffmpeg -y -i D:/ffmpeg/video/w.mkv -acodec copy -t 00:01:10 -vf "movie=logo.png [logo]; [in][logo] overlay=10:10:1 [out]" D:/ffmpeg/video/w_logo.mkv List commandList = new ArrayList (); commandList.add(FFMPEG); commandList.add("-i"); commandList.add(inputVideoFile); commandList.add("-y"); commandList.add("-acodec"); commandList.add("copy"); commandList.add("-vf"); commandList.add("\"movie=logo.png [logo]; [in][logo] overlay=10:10:1 [out]\""); commandList.add(outputFile); // // String command = FFMPEG + " -y -i \"D:/ffmpeg/video/a b/w.mkv\" -acodec copy -t 00:01:10 -vf \"movie=logo.png [logo]; [in][logo] overlay=10:10:1 [out]\" D:/ffmpeg/video/w_logo.mkv"; // String processInfo = exec(command); String processInfo = process(commandList); return processInfo; } /** * 命令执行 * @param command * @return */ public static String exec(String command) { long beginTime = System.nanoTime(); Runtime rt = Runtime.getRuntime(); try { Process process = rt.exec(command); /* StringTokenizer st = new StringTokenizer(command); String[] cmd = new String[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++){ cmd[i] = st.nextToken(); System.out.println(cmd[i]); } cmd[0] = new File(cmd[0]).getPath(); StringBuilder cmdbuf = new StringBuilder(80); for (int i = 0; i < cmd.length; i++) { if (i > 0) { cmdbuf.append(' '); } String s = cmd[i]; if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) { if (s.charAt(0) != '"') { cmdbuf.append('"'); cmdbuf.append(s); if (s.endsWith("\\")) { cmdbuf.append("\\"); } cmdbuf.append('"'); } else if (s.endsWith("\"")) { The argument has already been quoted. cmdbuf.append(s); } else { Unmatched quote for the argument. throw new IllegalArgumentException(); } } else { cmdbuf.append(s); } } String cmdstr = cmdbuf.toString(); System.out.println("cmdstr : " + cmdstr);*/ final InputStream isNormal = process.getInputStream(); new Thread(new Runnable() { public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(isNormal)); StringBuilder buf = new StringBuilder(); String line = null; try { while((line = br.readLine()) != null){ buf.append(line + "\n"); } } catch (IOException e) { e.printStackTrace(); } System.out.println("输出结果为:" + buf); } }).start(); // 启动单独的线程来清空process.getInputStream()的缓冲区 InputStream isError = process.getErrorStream(); BufferedReader br2 = new BufferedReader(new InputStreamReader(isError)); StringBuilder buf = new StringBuilder(); String line = null; while((line = br2.readLine()) != null){ buf.append(line + "\n"); } System.out.println("错误输出结果为:" + buf); try { process.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } long endTime = System.nanoTime(); System.out.println("视频处理耗时: " + (endTime - beginTime) / 1000000 + " 毫秒 "); return null; } /** * 根据命令处理视频 * @param commandList * @param processInfo * @return 处理信息 */ private static String process(List commandList) { StringBuffer processInfo = new StringBuffer(); ProcessBuilder builder = new ProcessBuilder(); builder.command(commandList); builder.redirectErrorStream(true); long beginTime = System.nanoTime(); try { Process p = builder.start(); //保存ffmpeg的输出结果流 BufferedReader buf = null; buf = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = null; while ((line = buf.readLine()) != null) { System.out.println(line); processInfo.append(line + "\n"); } p.waitFor();// 这里线程阻塞,将等待外部转换进程运行成功运行结束后,才往下执行 } catch (IOException e) { e.printStackTrace(); logger.error(e); throw new RuntimeException("视频处理出错"); } catch (InterruptedException e) { e.printStackTrace(); logger.error(e); throw new RuntimeException("视频处理出错"); } long endTime = System.nanoTime(); System.out.println("处理耗时: " + (endTime - beginTime) / 1000000 + " 毫秒。 "); System.out.println("视频处理结果信息: \n" + processInfo); return processInfo.toString(); } /** * 检测视频是否能够被处理 * @param videoPath * @return */ private static boolean checkVideoFile(String videoPath) { if(null == videoPath){ return false; } //根据后缀做类型检测 //检查是否文件以及文件是否存在 File videoFile = new File(videoPath); if(!videoFile.isFile() || !videoFile.exists()){ return false; } return true; } public static void main(String[] args) { // String inputVideoFile = "D:/ffmpeg/video/【天下足球网www.txzqw.com】下半场.rmvb"; String outputFile = "D:/ffmpeg/video/【天下足球网www.txzqw.com】下半场_1.rmvb"; String inputVideoFile = "D:/ffmpeg/video/a b/w.mkv"; // String outputFile = "D:/ffmpeg/video/w_1.mkv"; // String inputVideoFile = "D:/ffmpeg/video/Wildlife.wmv"; // String outputFile = "D:/ffmpeg/video/Wildlife.wmv.flv"; String fromTime = "00:00:00"; String duration = "00:3:28"; VideoUtil.getVideoInfo(inputVideoFile); // VideoUtil.cuttingRmvb(fromTime, inputVideoFile, duration, outputFile, null); // String imgPath = "D:/ffmpeg/video/1111.jpg"; // VideoUtil.getVideoSnapshots(inputVideoFile, imgPath, null); // VideoUtil.addWatermark(inputVideoFile, outputFile, null); // VideoUtil.videoConverter(inputVideoFile, outputFile, null); // VideoUtil.getVideoInfo(inputVideoFile); } }