视频支持avi、mp4、flv等格式文件
fmpeg 的两种切片方式,一是直接切片成多个小ts文件,二是先转换成一个大ts文件,再切分成多个小ts文件; windows系统第一种方式快,linux系统第二种方式快;
(问题仍没找到原因)
maven依赖
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.1.1</version>
</dependency>
package com.meta.util;
import com.amazonaws.services.cloudsearchdomain.model.SearchResult;
import com.fajiajia.meta.common.constant.Constants;
import com.fajiajia.meta.exception.ServiceException;
import com.fajiajia.meta.model.base.FileInfo;
import com.fajiajia.meta.service.base.FileInfoService;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import java.util.concurrent.*;
/**
* @Description:(cmd方式调用ffmpeg使用)
*/
@Log4j2
@Component
public class FfmpegCmdUtil {
@Resource
FileInfoService fileInfoService;
@Resource
RedisTemplate redisTemplate;
//执行成功0,失败1
private static int CODE_SUCCESS = 0;
private static int CODE_FAIL = 1;
//将荣耀视频测试.mp4转换荣耀视频测试_转码.mov格式
// private static String cmd_mp4_2_mov = " -i E:\\IdeaProjects\\judicial-info-society-backend\\file\\2022\\05\\16\\fb3a5850-2132-40c7-aa41-e98c3f4f429d.mp4 -c copy E:\\IdeaProjects\\judicial-info-society-backend\\file\\2022\\05\\16\\999999999999999.mov ";
//private static String cmd_mp4_2_ts = " -y -i E:\\IdeaProjects\\judicial-info-society-backend\\file\\2022\\05\\16\\fb3a5850-2132-40c7-aa41-e98c3f4f429d.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb D:\\test-ffmpeg\\荣耀视频测试_ts.ts ";
private static String cmd_mp4_2_ts = " -i D:\\e2c127e6f200500a1161a9071c4fa795.mp4 -hls_time 30 -hls_list_size 0 -hls_segment_filename D:/file/lucene_%05d.ts D:/file/lucene.m3u8";
//将荣耀视频测试_转码.mov添加水印(2356899074@qq.com)荣耀视频测试_转码_水印.mov
private static String cmd_mov_water = " -i I:\\荣耀视频测试_转码.mov -vf \"drawtext=fontfile=Arial.ttf:text='2356899074@qq.com':y=h-line_h-20:x=(w-text_w)/2:fontsize=34:fontcolor=yellow:shadowy=2\" -b:v 3000k I:\\\\荣耀视频测试_转码_水印.mov ";
//多线程
private static int core = Runtime.getRuntime().availableProcessors();
private static ExecutorService pool = new ThreadPoolExecutor(core,//核心
core * 2,//最大
0L,//空闲立即退出
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024),//无边界阻塞队列
new ThreadPoolExecutor.AbortPolicy());
/**
* 第一步:mp4转mov 第二步:mov添加水印
*
* @throws
* @param: @param args
* @return: void
*/
public static void main(String[] args) {
String aa = "-y -i C:\\Users\\Fadada\\Videos\\e79c541f2a473bb1ff87d94c31499c44.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb D:\\test\\fb3a5850-2132-40c7-aa41-e98c3f4f429d12.ts";
String bb = "-i C:\\Users\\Fadada\\Videos\\e79c541f2a473bb1ff87d94c31499c44.mp4 -hls_time 60 -hls_list_size 0 -threads 16 -preset ultrafast -hls_segment_filename D:\\test\\fb3a5850-2132-40c7-aa41-e98c3f4f429d_%05d.ts D:\\test\\fb3a5850-2132-40c7-aa41-e98c3f4f429d.m3u8";
String cc = "-i D:\\test\\fb3a5850-2132-40c7-aa41-e98c3f4f429d12.ts -c copy -map 0 -f segment -segment_list D:\\bbb\\fb3a5850-2132-40c7-aa41-e98c3f4f429d2.m3u8 -segment_time 60 D:\\bbb\\fb3a5850-2132-40c7-aa41-e98c3f4f429d2.m3u8_%5d.ts";
cmdExecut(aa);
cmdExecut(cc);
//异步执行,获取执行结果code
// CompletableFuture<Integer> completableFutureTask = CompletableFuture.supplyAsync(() -> {
// return cmdExecut(cc);
// }, pool)
// .thenApplyAsync((Integer code) -> {
// if (CODE_SUCCESS != code) {
// return CODE_FAIL;
// }
// System.out.println("第一步:mp4转mov,成功!");
Integer codeTmp = cmdExecut(cmd_mov_water);
if (CODE_SUCCESS != codeTmp) {
return CODE_FAIL;
}
// return 0;
// }, pool);
//
// //获取执行结果
// //code=0表示正常
// try {
// System.out.println(String.format("获取最终执行结果:%s", completableFutureTask.get() == CODE_SUCCESS ? "成功!" : "失败!"));
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
}
public static void createM3U8(String sourceFileUrl, String slicePath, String key) {
//组装切片命令
File file = new File(slicePath);
if (!file.exists()) {
file.mkdirs();
}
//切片文件存放路径-ts
String sliceFileTsUrl = slicePath + key + "_%05d.ts";
//切片文件存放路径-m3u8
String sliceFileM3u8Url = slicePath + key + ".m3u8";
String command = " -i " + sourceFileUrl + " -hls_time 120 -hls_list_size 0 -threads 16 -preset ultrafast -hls_segment_filename " + sliceFileTsUrl + " " + sliceFileM3u8Url;
//异步执行,获取执行结果code
CompletableFuture<Integer> completableFutureTask = CompletableFuture.supplyAsync(() -> {
return cmdExecut(command);
}, pool)
.thenApplyAsync((Integer code) -> {
if (0 != code) {
return 1;
}
return 0;
}, pool);
//获取执行结果
//code=0表示正常
try {
log.info(String.format("获取最终执行结果:%s", completableFutureTask.get() == 0 ? "成功!" : "失败!"));
if (completableFutureTask.get() == 0) {
//切片成功
log.info("视频文件切片成功!文件uuid为:" + sliceFileM3u8Url);
} else {
log.info("视频文件切片失败!文件uuid为:" + sliceFileM3u8Url);
}
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("视频切片失败");
}
}
//
// public static void createM3U8(String sourceFileUrl, String slicePath, String key) {
// try {
// //组装切片命令
// File file = new File(slicePath);
// if (!file.exists()) {
// file.mkdirs();
// }
// // 大ts文件名
// String tsFile = slicePath+key+".ts";
//
// //切片文件存放路径-ts
// String sliceFileTsUrl = slicePath + key + "_%05d.ts";
// //切片文件存放路径-m3u8
// String sliceFileM3u8Url = slicePath + key + ".m3u8";
// String aa = "-y -i " + sourceFileUrl +" -vcodec copy -acodec copy -vbsf h264_mp4toannexb "+tsFile;
// String cc = "-i "+tsFile+" -c copy -map 0 -f segment -segment_list "+sliceFileM3u8Url+" -segment_time 120 "+sliceFileTsUrl;
//
// Integer aatr=cmdExecut(aa);
// if(aatr==0){
// Integer cctr=cmdExecut(cc);
// if(cctr!=0){
// throw new ServiceException("视频切片失败");
// }
// }else {
// throw new ServiceException("视频切片失败");
// }
//
String command = " -i " + sourceFileUrl + " -hls_time 60 -hls_list_size 0 -threads 16 -preset ultrafast -hls_segment_filename " + sliceFileTsUrl + " " + sliceFileM3u8Url;
// //异步执行,获取执行结果code
CompletableFuture<Integer> completableFutureTask = CompletableFuture.supplyAsync(() -> {
return cmdExecut(command);
}, pool)
.thenApplyAsync((Integer code) -> {
if (0 != code) {
return 1;
}
return 0;
}, pool);
//获取执行结果
//code=0表示正常
try {
log.info(String.format("获取最终执行结果:%s", completableFutureTask.get() == 0 ? "成功!" : "失败!"));
if (completableFutureTask.get() == 0) {
//切片成功
log.info("视频文件切片成功!文件uuid为:" + sliceFileM3u8Url);
} else {
log.info("视频文件切片失败!文件uuid为:" + sliceFileM3u8Url);
}
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("视频切片失败");
}
// } catch (Exception e) {
// e.printStackTrace();
// throw new ServiceException("视频切片失败");
// }
// }
/**
* @throws
* @Description: (执行ffmpeg自定义命令)
* @param: @param cmdStr
* @param: @return
* @return: Integer
*/
public static Integer cmdExecut(String cmdStr) {
//code=0表示正常
Integer code = null;
FfmpegCmd ffmpegCmd = new FfmpegCmd();
/**
* 错误流
*/
InputStream errorStream = null;
try {
//destroyOnRuntimeShutdown表示是否立即关闭Runtime
//如果ffmpeg命令需要长时间执行,destroyOnRuntimeShutdown = false
//openIOStreams表示是不是需要打开输入输出流:
// inputStream = processWrapper.getInputStream();
// outputStream = processWrapper.getOutputStream();
// errorStream = processWrapper.getErrorStream();
ffmpegCmd.execute(false, true, cmdStr);
errorStream = ffmpegCmd.getErrorStream();
//打印过程
int len = 0;
while ((len = errorStream.read()) != -1) {
System.out.print((char) len);
}
//code=0表示正常
code = ffmpegCmd.getProcessExitCode();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
ffmpegCmd.close();
}
//返回
return code;
}
}
package com.fajiajia.meta.util;
import lombok.extern.log4j.Log4j2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ws.schild.jave.process.ProcessKiller;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @Description:(cmd方式调用ffmpeg)
*/
@Log4j2
public class FfmpegCmd {
private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class);
/**
* The process representing the ffmpeg execution.
*/
private Process ffmpeg = null;
/**
* A process killer to kill the ffmpeg process with a shutdown hook, useful if the jvm execution
* is shutted down during an ongoing encoding process.
*/
private ProcessKiller ffmpegKiller = null;
/**
* A stream reading from the ffmpeg process standard output channel.
*/
private InputStream inputStream = null;
/**
* A stream writing in the ffmpeg process standard input channel.
*/
private OutputStream outputStream = null;
/**
* A stream reading from the ffmpeg process standard error channel.
*/
private InputStream errorStream = null;
/**
* Executes the ffmpeg process with the previous given arguments.
*
* @param destroyOnRuntimeShutdown destroy process if the runtime VM is shutdown
* @param openIOStreams Open IO streams for input/output and errorout, should be false when
* destroyOnRuntimeShutdown is false too
* @param ffmpegCmd windows such as (mp4 transform to mov):
* " -i C:\\Users\\hsj\\AppData\\Local\\Temp\\jave\\honer.mp4 -c copy C:\\Users\\hsj\\AppData\\Local\\Temp\\jave\\honer_test.mov "
* @throws IOException If the process call fails.
*/
public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams, String ffmpegCmd) throws IOException {
DefaultFFMPEGLocator defaultFFMPEGLocator = new DefaultFFMPEGLocator();
StringBuffer cmd = new StringBuffer(defaultFFMPEGLocator.getExecutablePath());
//insert blank for delimiter
cmd.append(" ");
cmd.append(ffmpegCmd);
String cmdStr = String.format("ffmpegCmd final is :%s", cmd.toString());
System.out.println(cmdStr);
LOG.info(cmdStr);
Runtime runtime = Runtime.getRuntime();
try {
ffmpeg = runtime.exec(cmd.toString());
if (destroyOnRuntimeShutdown) {
ffmpegKiller = new ProcessKiller(ffmpeg);
runtime.addShutdownHook(ffmpegKiller);
}
if (openIOStreams) {
inputStream = ffmpeg.getInputStream();
outputStream = ffmpeg.getOutputStream();
errorStream = ffmpeg.getErrorStream();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns a stream reading from the ffmpeg process standard output channel.
*
* @return A stream reading from the ffmpeg process standard output channel.
*/
public InputStream getInputStream() {
return inputStream;
}
/**
* Returns a stream writing in the ffmpeg process standard input channel.
*
* @return A stream writing in the ffmpeg process standard input channel.
*/
public OutputStream getOutputStream() {
return outputStream;
}
/**
* Returns a stream reading from the ffmpeg process standard error channel.
*
* @return A stream reading from the ffmpeg process standard error channel.
*/
public InputStream getErrorStream() {
return errorStream;
}
/**
* If there's a ffmpeg execution in progress, it kills it.
*/
public void destroy() {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable t) {
LOG.warn("Error closing input stream", t);
}
inputStream = null;
}
if (outputStream != null) {
try {
outputStream.close();
} catch (Throwable t) {
LOG.warn("Error closing output stream", t);
}
outputStream = null;
}
if (errorStream != null) {
try {
errorStream.close();
} catch (Throwable t) {
LOG.warn("Error closing error stream", t);
}
errorStream = null;
}
if (ffmpeg != null) {
ffmpeg.destroy();
ffmpeg = null;
}
if (ffmpegKiller != null) {
Runtime runtime = Runtime.getRuntime();
runtime.removeShutdownHook(ffmpegKiller);
ffmpegKiller = null;
}
}
/**
* Return the exit code of the ffmpeg process If the process is not yet terminated, it waits for
* the termination of the process
*
* @return process exit code
*/
public int getProcessExitCode() {
// Make sure it's terminated
try {
ffmpeg.waitFor();
} catch (InterruptedException ex) {
LOG.warn("Interrupted during waiting on process, forced shutdown?", ex);
}
return ffmpeg.exitValue();
}
/**
* close
**/
public void close() {
destroy();
}
}