windows的ffmpeg包下载地址:https://ffmpeg.zeranoe.com/builds/
开发环境使用的windows包,生产环境需要下载linux的包
包的结构是这样的:
进入bin目录
这个.exe文件就是我解析视频使用的工具
文件上传略,一起看一下后台使用ffmpeg的方法:
@ResponseBody @RequestMapping("/upload") public Object upload(@RequestParam("file") MultipartFile file)throws Exception{ File localFile = new File("本地的路径(不带文件名)","文件名(xxx.mp4)"); file.transferTo(localFile); ProcessVideo processVideo = new ProcessVideo("本地的路径(不带文件名)","视频分段后保存的路径","文件名(xxx.mp4)","这里是ffmpeg.exe的全路径"); processVideo.run(); return "ok"; }
ProcessVideo 是一个线程类,看代码可以知道,文件上传到本地,程序就结束了,解析视频的过程是一个单独的线程再跑。
解析的过程,日志打印是这样的,像心跳一样一行一行的跳:
解析之后的效果是这样的:
一个.m3u8格式的文件,多个.ts文件,m3u8文件中记录了.ts文件名列表,想一个节目表单,这样顺序播放
解析需要用到的三个工具类:
@Slf4j public class ProcessVideo extends Thread { private String source; private String target; private String fileName; private String ffmpeg; private VideoConvert videoConvert; public ProcessVideo(String source, String target, String fileName, String ffmpeg){ this.source = source; this.target = target; this.fileName = fileName; this.ffmpeg = ffmpeg; } @Override public void run() { VideoConvert convert = new VideoConvert(ffmpeg); String sourceFile = source + "/" +fileName; log.info("fileName============="+fileName); String nameStr = fileName; String[] split = nameStr.split("\\."); log.info("长度是=============="+split.length); log.info("资源路径================"+sourceFile); File file = new File(sourceFile); long length = file.length(); log.info("length==="+length); long l = length / 1024 / 1024; log.info("此文件的大小是===="+l+"M"); log.info("split======="+split); String name = split[0]; String videoList = target + "/" + name + ".m3u8"; log.info("目标路径================"+videoList); try { convert.start(sourceFile, videoList); }catch (Exception e){ e.printStackTrace(); log.error("",e); } videoConvert = convert; log.info("分割视频方法最后一行"); } }
public class VideoConvert { private String ffmpeg = "ffmpeg"; private List<String> cmdList; private static Process p; private String status; private String source; private String target; private VideoConvert() {} /** * 初始化:指定ffmpeg路径 * @param ffmpegPath */ public VideoConvert(String ffmpegPath){ this.ffmpeg = ffmpegPath; } /** * 开始转换 * @param source 目前只支持mp4格式,源视频路径 * @param target 输出路径,必须是m3u8格式。 * @throws InterruptedException * @throws FileNotFoundException */ public void start(String source, String target) throws InterruptedException, FileNotFoundException { this.source = source; this.target = target; // checkFfmpeg(); checkSource(); processM3U8(source, target); } private void checkSource() throws FileNotFoundException { File fm = new File(source); if (!fm.exists()) { throw new FileNotFoundException("source不存在:" + source); } } /** * 拼接ffmpeg命令:ffmpeg -i test.mp4 -c:v libx264 -hls_time 60 -hls_list_size 0 -c:a aac -strict -2 -f hls output.m3u8 * @param source * @return */ private boolean processM3U8(String source, String target) { File targetFile = new File(target); File parentDir = targetFile.getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } List<String> commend = new ArrayList<String>(); commend.add(ffmpeg); commend.add("-i"); commend.add(source); commend.add("-c:v"); commend.add("libx264"); commend.add("-hls_time"); commend.add("60"); 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(target); this.cmdList = commend; // 通过ProcessBuilder创建 // processBuilder(commend); // 通过runtime创建 runtimeBuilder(getCommand()); return true; } /** * 使用ProcessBuilder类调用cmd * @param command cmd命令组合,list集合,元素不能包含空格,否则可能会报错 * @return */ private static boolean processBuilder(List<String> command){ try { ProcessBuilder builder = new ProcessBuilder(command); builder.command(command); new Thread(){ @Override public void run(){ try { Process p = builder.start(); VideoConvert.p = p; } catch (IOException e) { e.printStackTrace(); } } }.start(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 检查ffmpeg是否有效 * @throws FileNotFoundException */ private void checkFfmpeg() throws FileNotFoundException { File fm = new File(ffmpeg); if (!fm.exists()) { throw new FileNotFoundException("ffmpeg 不存在:" + ffmpeg); } } /** * 获取状态 * @return */ public String getStatus(){ if (p == null) { setStatus(VideoStatus.NO_STARTED); } if (p.isAlive()) { setStatus(VideoStatus.PROCESSING); } else { setStatus(VideoStatus.COMPLETED); } return status; } /** * 使用runtime形式调用cmd * @param cmdStr */ private static void runtimeBuilder(String cmdStr){ try { Process process = Runtime.getRuntime().exec(cmdStr); VideoConvert.p = process; VideoThread myThread = new VideoThread(process); myThread.start(); } catch (IOException e) { e.printStackTrace(); } } private void setStatus(String status){ this.status = status; } /** * 获取执行的cmd命令 * @return */ public String getCommand(){ StringBuffer bf = new StringBuffer(); for (String str : cmdList) { bf.append(" ").append(str); } return bf.toString(); } public static class VideoStatus{ public static final String COMPLETED = "completed"; public static final String PROCESSING = "processing"; public static final String NO_STARTED = "no started"; } }
@Slf4j public class VideoThread extends Thread { private Process p; public VideoThread(Process p){ this.p = p; } @Override public void run(){ BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream())); String line = null; try { while ((line = err.readLine()) != null) { log.info(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { err.close(); try { p.waitFor(); p.destroy(); log.info("video convert completed..."); } catch (InterruptedException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } } }