1. 程式人生 > >java封裝FFmpeg命令,支援原生ffmpeg全部命令,實現FFmpeg多程序處理與多執行緒輸出控制(開啟、關閉、查詢),rtsp/rtmp推流、拉流

java封裝FFmpeg命令,支援原生ffmpeg全部命令,實現FFmpeg多程序處理與多執行緒輸出控制(開啟、關閉、查詢),rtsp/rtmp推流、拉流

前言:

之前已經對FFmpeg命令進行了封裝http://blog.csdn.net/eguid_1/article/details/51787646,但是當時沒有考慮到擴充套件性,所以總體設計不是太好,需要改動的地方也比較多,也不支援原生ffmpeg命令,所以本次版本推翻了前面的版本重新設計介面和實現,全面支援各個流程注入自己的實現,並且在原有命令組裝基礎上增加一個介面用來支援全部原生FFmpeg命令。

概述:

提供一個管理器用於方便管理FFmpeg命令的執行、停止和執行資訊持久化。

可以方便的使用ffmpeg來進行推流,拉流,轉流等任務

實現的功能:

①開啟一個程序+一個輸出執行緒來執行原生ffmpeg命令②開啟一個程序+一個輸出執行緒來執行組裝命令③查詢執行任務資訊④查詢全部正在執行的任務

停止程序和輸出執行緒停止全部正在執行的任務

本專案已在github上進行維護,如需檢視或下載原始碼請至github進行檢視:

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();
	}
}