java封裝FFmpeg命令,支援原生ffmpeg全部命令,實現FFmpeg多程序處理與多執行緒輸出控制(開啟、關閉、查詢),rtsp/rtmp推流、拉流
阿新 • • 發佈:2019-01-03
前言:
之前已經對FFmpeg命令進行了封裝http://blog.csdn.net/eguid_1/article/details/51787646,但是當時沒有考慮到擴充套件性,所以總體設計不是太好,需要改動的地方也比較多,也不支援原生ffmpeg命令,所以本次版本推翻了前面的版本重新設計介面和實現,全面支援各個流程注入自己的實現,並且在原有命令組裝基礎上增加一個介面用來支援全部原生FFmpeg命令。
概述:
提供一個管理器用於方便管理FFmpeg命令的執行、停止和執行資訊持久化。
可以方便的使用ffmpeg來進行推流,拉流,轉流等任務
實現的功能:
①開啟一個程序+一個輸出執行緒來執行原生ffmpeg命令②開啟一個程序+一個輸出執行緒來執行組裝命令③查詢執行任務資訊④查詢全部正在執行的任務⑤ 停止程序和輸出執行緒⑥停止全部正在執行的任務
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();
}
}