java封装FFmpeg命令,支持原生ffmpeg全部命令,实现FFmpeg多进程处理与多线程输出控制(开启、关闭、查询),rtsp/rtmp推流、拉流
前言:
之前已经对FFmpeg命令进行了封装http://blog.csdn.net/eguid_1/article/details/51787646,但是当时没有考虑到扩展性,所以总体设计不是太好,需要改动的地方也比较多,也不支持原生ffmpeg命令,所以本次版本推翻了前面的版本重新设计接口和实现,全面支持各个流程注入自己的实现,并且在原有命令组装基础上增加一个接口用来支持全部原生FFmpeg命令。
概述:
提供一个管理器用于方便管理FFmpeg命令的执行、停止和执行信息持久化。
可以方便的使用ffmpeg来进行推流,拉流,转流等任务
实现的功能:
①开启一个进程+一个输出线程来执行原生ffmpeg命令②开启一个进程+一个输出线程来执行组装命令③查询执行任务信息④查询全部正在执行的任务⑤停止进程和输出线程⑥停止全部正在执行的任务
源码包下载:http://download.csdn.net/detail/eguid_1/9668143
github项目地址:https://github.com/eguid/FFmpegCommandHandler4java
1、接口设计
1.1、发布任务接口
通过原生ffmpeg命令发布处理任务
通过map组装成ffmpeg命令来处理任务
1.2、终止任务接口
结束任务
结束全部任务
1.3、任务查询接口
查询
查询全部
1.4、接口实现注入
执行处理器注入
命令组装器注入
持久化实现注入
2、内部实现
2.1、任务处理器
开启一个进程用于执行ffmpeg命令
开启一个子线程用于输出ffmpeg执行过程
停止进程
停止输出线程(需要在进程关闭前停止输出线程)
按照正确顺序停止进程和线程2.2、输出线程处理器
用于输出ffmpeg执行过程
package cc.eguid.FFmpegCommandManager.service; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import cc.eguid.FFmpegCommandManager.entity.TaskEntity; /** * 任务处理实现 * @author eguid * @since jdk1.7 * @version 2016年10月29日 */ public class TaskHandlerImpl implements TaskHandler { private Runtime runtime = null; @Override public TaskEntity process(String id, String command) { Process process = null; OutHandler outHandler = null; TaskEntity tasker = null; try { if (runtime == null) { runtime = Runtime.getRuntime(); } process = runtime.exec(command);// 执行本地命令获取任务主进程 outHandler = new OutHandler(process.getErrorStream(), id); outHandler.start(); tasker = new TaskEntity(id, process, outHandler); } catch (IOException e) { stop(outHandler); stop(process); // 出现异常说明开启失败,返回null return null; } return tasker; } @Override public boolean stop(Process process) { if (process != null && process.isAlive()) { process.destroy(); return true; } return false; } @Override public boolean stop(Thread outHandler) { if (outHandler != null && outHandler.isAlive()) { outHandler.stop(); outHandler.destroy(); return true; } return false; } @Override public boolean stop(Process process, Thread thread) { boolean ret; ret=stop(thread); ret=stop(process); return ret; } } /** * * @author eguid * */ class OutHandler extends Thread { /** * 控制状态 */ private volatile boolean desstatus = true; /** * 读取输出流 */ private BufferedReader br = null; /** * 输出类型 */ private String type = null; public OutHandler(InputStream is, String type) { br = new BufferedReader(new InputStreamReader(is)); this.type = type; } /** * 重写线程销毁方法,安全的关闭线程 */ @Override public void destroy() { setDesStatus(false); } public void setDesStatus(boolean desStatus) { this.desstatus = desStatus; } /** * 执行输出线程 */ @Override public void run() { String msg = null; int index = 0; int errorIndex = 0; int status = 10; try { System.out.println(type + "开始推流!"); while (desstatus && (msg = br.readLine()) != null) { if (msg.indexOf("[rtsp") != -1) { System.out.println("接收" + status + "个数据包" + msg); System.out.println(type + ",网络异常丢包,丢包次数:" + errorIndex++ + ",消息体:" + msg); status = 10; index = 0; } if (index % status == 0) { System.out.println("接收" + status + "个数据包" + msg); status *= 2; } index++; } } catch (IOException e) { System.out.println("发生内部异常错误,自动关闭[" + this.getId() + "]线程"); destroy(); } finally { if (this.isAlive()) { destroy(); } } } }
2.3、持久化服务
增加任务信息
删除任务信息
删除全部任务信息
查询任务信息
查询全部任务信息
任务是否存在
package cc.eguid.FFmpegCommandManager.dao; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import cc.eguid.FFmpegCommandManager.entity.TaskEntity; /** * 任务信息持久层实现 * * @author eguid * @since jdk1.7 * @version 2016年10月29日 */ public class TaskDaoImpl implements TaskDao { // 存放任务信息 private ConcurrentMap<String, TaskEntity> map = null; public TaskDaoImpl(int size) { map = new ConcurrentHashMap<>(size); } @Override public TaskEntity get(String id) { return map.get(id); } @Override public Collection<TaskEntity> getAll() { return map.values(); } @Override public int add(TaskEntity taskEntity) { String id = taskEntity.getId(); if (id != null && !map.containsKey(id)) { if (map.put(taskEntity.getId(), taskEntity) != null) { return 1; } } return 0; } @Override public int remove(String id) { if(map.remove(id) != null){ return 1; }; return 0; } @Override public int removeAll() { int size = map.size(); try { map.clear(); } catch (Exception e) { return 0; } return size; } @Override public boolean isHave(String id) { return map.containsKey(id); } }
2.3命令组装器
用于将参数组装成对应的ffmpeg命令
package cc.eguid.FFmpegCommandManager.util; import java.util.Map; public class CommandAssemblyUtil { /** * * @param map * -要组装的map * @param id * -返回参数:id * @param id * -返回参数:组装好的命令 * @return */ public static String assembly(Map<String, String> paramMap) { try { if (paramMap.containsKey("ffmpegPath")) { String ffmpegPath = (String) paramMap.get("ffmpegPath"); // -i:输入流地址或者文件绝对地址 StringBuilder comm = new StringBuilder(ffmpegPath + " -i "); // 是否有必输项:输入地址,输出地址,应用名,twoPart:0-推一个元码流;1-推一个自定义推流;2-推两个流(一个是自定义,一个是元码) if (paramMap.containsKey("input") && paramMap.containsKey("output") && paramMap.containsKey("appName") && paramMap.containsKey("twoPart")) { String input = (String) paramMap.get("input"); String output = (String) paramMap.get("output"); String appName = (String) paramMap.get("appName"); String twoPart = (String) paramMap.get("twoPart"); String codec = (String) paramMap.get("codec"); // 默认h264解码 codec = (codec == null ? "h264" : (String) paramMap.get("codec")); // 输入地址 comm.append(input); // 当twoPart为0时,只推一个元码流 if ("0".equals(twoPart)) { comm.append(" -vcodec " + codec + " -f flv -an " + output + appName); } else { // -f :转换格式,默认flv if (paramMap.containsKey("fmt")) { String fmt = (String) paramMap.get("fmt"); comm.append(" -f " + fmt); } // -r :帧率,默认25;-g :帧间隔 if (paramMap.containsKey("fps")) { String fps = (String) paramMap.get("fps"); comm.append(" -r " + fps); comm.append(" -g " + fps); } // -s 分辨率 默认是原分辨率 if (paramMap.containsKey("rs")) { String rs = (String) paramMap.get("rs"); comm.append(" -s " + rs); } // 输出地址+发布的应用名 comm.append(" -an " + output + appName); // 当twoPart为2时推两个流,一个自定义流,一个元码流 if ("2".equals(twoPart)) { // 一个视频源,可以有多个输出,第二个输出为拷贝源视频输出,不改变视频的各项参数并且命名为应用名+HD comm.append(" -vcodec copy -f flv -an ").append(output + appName + "HD"); } } return comm.toString(); } } } catch (Exception e) { return null; } return null; } }
2.4、配置文件读取器
读取配置文件中的ffmpeg路径配置
读取默认位置的ffmpeg执行文件
package cc.eguid.FFmpegCommandManager.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; /** * 读取配置文件并加载FFmpeg * * @author eguid * @since jdk1.7 * @version 2016年10月29日 */ public class LoadConfig { private volatile static boolean isHave = false; private volatile static String ffmpegPath = null; public LoadConfig() { super(); if (readConfig()) { System.out.println("读取FFmpeg执行路径成功!"); isHave = true; } else if (initConfInfo()) { // 配置文件读取失败,自动从项目路径加载ffmpeg isHave = true; } else { isHave = false; } } protected boolean readConfig() { String path = null; File confFile = new File(getClass().getResource("/").getPath() + "loadFFmpeg.properties"); System.out.println("读取FFMPEG配置文件:" + confFile.getPath()); if (confFile != null && confFile.exists() && confFile.isFile() && confFile.canRead()) { Properties prop = new Properties(); try { prop.load(new FileInputStream(confFile)); path = prop.getProperty("path"); if (path != null) { System.out.println("读取配置文件中的ffmpeg路径:" + path); ffmpegPath = path; return true; } } catch (IOException e) { System.err.println("读取配置文件失败!"); return false; } } System.err.println("读取配置文件失败!"); return false; } /** * 从配置文件中初始化参数 */ protected boolean initConfInfo() { String path = getClass().getResource("../").getPath() + "ffmpeg/ffmpeg.exe"; System.out.print("预加载默认项目路径FFMPEG配置:" + path); File ffmpeg = new File(path); ffmpegPath = ffmpeg.getPath(); if (isHave = ffmpeg.isFile()) { return true; } System.out.println("加载ffmpeg失败!"); return false; } /** * 判断ffmpeg环境配置 * * @return true:配置成功;false:配置失败 */ public boolean isHave() { return isHave; } /** * 获取ffmpeg环境调用地址 添加方法功能描述 * * @return */ public String getPath() { return ffmpegPath; } public static void main(String[] args) { LoadConfig conf = new LoadConfig(); } }
本文来自博客园,作者:eguid,没有作者允许禁止转载,取得作者同意后转载需注明作者名和原文链接:https://www.cnblogs.com/eguid/p/6821580.html