转自:https://blog.csdn.net/cuishiying/article/details/78589347
前言:
之前已经对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执行过程
[java] view plain copy
- 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、持久化服务
增加任务信息
删除任务信息
删除全部任务信息
查询任务信息
查询全部任务信息
任务是否存在
[java] view plain copy
- 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命令
[java] view plain copy
- 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执行文件
[java] view plain copy
- 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温馨提示:本博客所有原创文章均采用知识共享署名-相同方式共享 3.0 中国大陆许可协议进行许可。如有转载请注明出处和作者名!
- 本文已收录于以下专栏:
- 实时流媒体技术