java使用ffmpeg转码并上传视频
1、ffmpeg的安装和环境配置(windows操作系统下)
下载地址https://ffmpeg.org/download.html,解压后,选择文件路径至bin文件下,例:C:\Users\xx\Downloads\ffmpeg-20171225-be2da4c-win64-static\bin,将此路径赋值,打开高级系统设置,打开系统环境变量,选择path,新增。
2、实现
2.1 视频转码
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ConvertVideoUtil {
private static final Log log = LogFactory.getLog(ConvertVideoUtil.class);
private static String inputPath = "";
// //转换视频的插件
private static String ffmpegPath = "C:\\Users\\XX\\Downloads\\ffmpeg-20171225-be2da4c-win64-static\\bin\\";
//水印logo path ,注意盘符后面的"\\\\"
private static String logoPath="C\\\\:XXXXXXXX/logo.png";
private static int checkContentType() {
String type = inputPath.substring(inputPath.lastIndexOf(".") + 1, inputPath.length())
.toLowerCase();
// ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
if (type.equals("avi")) {
return 0;
} else if (type.equals("mpg")) {
return 0;
} else if (type.equals("wmv")) {
return 0;
} else if (type.equals("3gp")) {
return 0;
} else if (type.equals("mov")) {
return 0;
} else if (type.equals("mp4")) {
return 0;
} else if (type.equals("asf")) {
return 0;
} else if (type.equals("asx")) {
return 0;
} else if (type.equals("flv")) {
return 0;
}
// 对ffmpeg没法解析的文件格式(wmv9,rm,rmvb等),
// 能够先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
else if (type.equals("wmv9")) {
return 1;
} else if (type.equals("rm")) {
return 1;
} else if (type.equals("rmvb")) {
return 1;
}
return 9;
}
private static boolean checkfile(String path) {
File file = new File(path);
if (!file.isFile()) {
return false;
}
return true;
}
public static String toMp4(String oldfilepath,String newfilepath) throws Exception {
if (!checkfile(oldfilepath)) {
System.out.println(oldfilepath + " is not file");
return "";
}
File convertFile = new File(newfilepath); //NOSONAR
List<String> commend = new ArrayList<>();
commend.add(ffmpegPath + "ffmpeg");
commend.add("-i");
commend.add(oldfilepath);
commend.add("-c:v");
commend.add("libx264");
commend.add("-mbd");
commend.add("0");
commend.add("-c:a");
commend.add("aac");
commend.add("-strict");
commend.add("-2");
commend.add("-pix_fmt");
commend.add("yuv420p");
commend.add("-movflags");
commend.add("faststart");
commend.add(convertFile.getPath());
Process process = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
process = builder.start();
byte[] b = new byte[1024];
int readbytes;
try (InputStream error = process.getErrorStream(); InputStream is = process.getInputStream()) {
while ((readbytes = error.read(b)) != -1) {
log.error("FFMPEG视频转换进程信息:" + new String(b, 0, readbytes));
}
while ((readbytes = is.read(b)) != -1) {
log.info("FFMPEG视频转换进程输出内容为:" + new String(b, 0, readbytes));
}
} catch (IOException e) {
log.error("读取FFMPEG转换信息异常", e);
}
log.info("视频格式转换为:" + convertFile.getPath());
HashMap<String, String> dto = new HashMap<String, String>();
dto.put("ffmpeg_path", ffmpegPath+"ffmpeg.exe");// 必填
dto.put("input_path", oldfilepath);// 必填
dto.put("video_converted_path", convertFile.getPath());// 必填
dto.put("logo", logoPath);// 可选(注意windows下面的logo地址前面要写4个反斜杠,如果用浏览器里面调用servlet并传参只用两个,如
// d:\\:/ffmpeg/input/logo.png)
String secondsString = new AddWatermarker().videoTransfer(dto);
System.out.println("转换共用:" + secondsString + "秒");
} catch (Exception e) {
log.error("视频转换 Exception:", e);
throw new Exception("视频转换失败");
} finally {
if (null != process) {
process.destroy();
}
}
return convertFile.getPath();
}
}
2.2 视频加图片水印
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* o 给视频加水印
*
* @author admin
*
*/
public class AddWatermarker {
public static String dealString(String str) {
Matcher m = java.util.regex.Pattern.compile("^frame=.*").matcher(str);
String msg = "";
while (m.find()) {
msg = m.group();
}
return msg;
}
/**
* 如果是数字就是成功的时间(秒数)
*
* @param str
* @return
*/
public static boolean isNumeric(String str) {
Pattern pattern = Pattern.compile("[0-9]*");
Matcher isNum = pattern.matcher(str);
if (!isNum.matches()) {
return false;
}
return true;
}
/**
* 如果返回不是null的值就是成功(值为转换用时单位:秒)
*
* @param instr
* @return
*/
public static String returnSecond(String instr) {
String returnValue = null;
if (null != instr) {
String[] a = instr.split("\\.");
String[] b = a[0].split(":");
int returnNumber = 0;
if (null != instr && b[0].length() != 0) {
returnNumber = Integer.valueOf(b[0]) * 60 * 60 + Integer.valueOf(b[1]) * 60 + Integer.valueOf(b[2]);
returnValue = String.valueOf(returnNumber);
} else {
returnValue = null;
}
}
return returnValue;
}
/**
* 获取视频格式(转码前的格式和转码后的格式都可以调用)
*
* @param outputPath
* @return
*/
public static String returnVideoFormat(String outputPath) {
return outputPath.substring(outputPath.lastIndexOf(".") + 1);
}
/**
* @ HashMap<String,String> dto 参数传递对象<br>
* dto中包含的参数<br>
* (必填)1.ffmpeg_path:ffmpeg执行文件地址,如 d:\\ffmpeg\\ffmpeg.exe
* Linux下直接调用ffmpeg命令(当然你事先已经有这个程序了)<br>
* (必填)2.input_path:原视频路径<br>
* (必填)3.video_converted_path:转换后视频输出路径<br>
* (可选)4.screen_size:视频尺寸 长度乘宽度 乘号用英文小写"x"如 512x480<br>
* (可选)5.logo:水印地址(其实在ffmpeg中有一个专门的watermark参数,logo跟它有何不同,我还没看,不过对我来说效果一样
* 貌似需要png图片才行)<br>
* (可选,如果填写必须有logo才行,默认为0)6.xaxis:水印logo的横坐标(只有logo参数为一个正确路径才行) 比如0<br>
* (可选,如果填写必须有logo才行,默认为0)6.yaxis:水印logo的纵坐标(只有logo参数为一个正确路径才行) 比如0<br>
* (可选)vb:视频比特率,传入一个数值,单位在程序里面拼接了k (可选)ab:音频比特率,传入一个数值,单位在程序里面拼接了k
*/
public String videoTransfer(HashMap<String, String> dto) {
// String ffmpeg_path,String input_path,String video_converted_path,String
// logo,String screen_size,String xaxis,String yaxis,String vb,String ab
List<String> cmd = new ArrayList<String>();
cmd.add(dto.get("ffmpeg_path"));
cmd.add("-y");
cmd.add("-i");
cmd.add(dto.get("input_path"));
if (null != dto.get("screen_size")) {
cmd.add("-s");
cmd.add(dto.get("screen_size"));
}
if (null != dto.get("logo")) {
String logo = dto.get("logo");
cmd.add("-vf");
String xaxis = dto.get("xaxis");
String yaxis = dto.get("yaxis");
xaxis = xaxis != null && !xaxis.equals("") ? xaxis : "0";
yaxis = yaxis != null && !yaxis.equals("") ? yaxis : "0";
// String logoString = "movie=" + logo + "[logo],[in][logo]overlay=x=" + xaxis + ":y=" + yaxis + "[out]"; //左上角
// String logoString = "movie=" + logo + "[logo],[in][logo]overlay=" +"10:main_h-overlay_h-10"; //左下角
String logoString = "movie=" + logo + "[logo],[in][logo]overlay=" +"main_w-overlay_w-20:main_h-overlay_h-50"; //右下角
cmd.add(logoString);
}
cmd.add("-strict");
cmd.add("-2");
if (null != dto.get("vb") && !dto.get("vb").equals("")) {
cmd.add("-vb");
cmd.add(dto.get("vb") + "k");
}
if (null != dto.get("ab") && !dto.get("ab").equals("")) {
cmd.add("-ab");
cmd.add(dto.get("ab") + "k");
}
cmd.add("-q:v");
cmd.add("4");
cmd.add(dto.get("video_converted_path"));
String converted_time = CmdExecuter.exec(cmd);
return returnSecond(converted_time);// 获取转换时间
}
}
2.3 系统执行
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
public class CmdExecuter {
public static String exec(List<String> cmd) {
String converted_time = null;
Process proc = null;
BufferedReader stdout = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(cmd);
builder.redirectErrorStream(true);
proc = builder.start();
stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
int lineNumber = 1;
List<String> returnStringList = new LinkedList<String>();
while ((line = stdout.readLine()) != null) {
// System.out.println("第" + lineNumber + "行:" + line);
lineNumber = lineNumber + 1;
returnStringList.add(AddWatermarker.dealString(line));
}
String info = "";
for (int i = returnStringList.size() - 1; i >= 0; i--) {
if (null != returnStringList.get(i) && returnStringList.get(i).startsWith("frame=")) {
info = returnStringList.get(i);
break;
}
}
if (null != info) {
converted_time = info.split("time=")[1].split("bitrate=")[0].trim();
}
} catch (IndexOutOfBoundsException ex) {
converted_time = null;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
proc.waitFor();
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return converted_time;
}
}
3、视频流播放
public void getVideo(HttpServletRequest request, HttpServletResponse response, String id) {
//视频资源存储信息
response.reset();
//获取从那个字节开始读取文件
String rangeString = request.getHeader("Range");
try {
//获取响应的输出流
OutputStream outputStream = response.getOutputStream();
String uploadPath = hospitalVideoService.getViewPath(id).getUploadPath();
String fileName = hospitalVideoService.getViewPath(id).getOriginalFileName();
System.out.println("viewPath = " + uploadPath);
String filePath = basicPath+uploadPath;
System.out.println("filePath = " + filePath);
File file = new File(filePath);
if(file.exists()){
RandomAccessFile targetFile = new RandomAccessFile(file, "r");
long fileLength = targetFile.length();
//播放
if(rangeString != null){
long range = Long.parseLong(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
//设置内容类型
response.setHeader("Content-Type", "video/mov");
//设置此次相应返回的数据长度
response.setHeader("Content-Length", String.valueOf(fileLength - range));
//设置此次相应返回的数据范围
response.setHeader("Content-Range", "bytes "+range+"-"+(fileLength-1)+"/"+fileLength);
//返回码需要为206,而不是200
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//设定文件读取开始位置(以字节为单位)
targetFile.seek(range);
}else {//下载
//设置响应头,把文件名字设置好
response.setHeader("Content-Disposition", "attachment; filename="+fileName );
//设置文件长度
response.setHeader("Content-Length", String.valueOf(fileLength));
//解决编码问题
response.setHeader("Content-Type","application/octet-stream");
}
byte[] cache = new byte[1024 * 300];
int flag;
while ((flag = targetFile.read(cache))!=-1){
outputStream.write(cache, 0, flag);
}
}else {
String message = "file:"+fileName+" not exists";
//解决编码问题
response.setHeader("Content-Type","application/json");
outputStream.write(message.getBytes(StandardCharsets.UTF_8));
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
4、计算文件大小
public String ReadVideoSize(File source) {
FileChannel fc = null;
String size = "";
try {
FileInputStream fis = new FileInputStream(source);
fc = fis.getChannel();
BigDecimal fileSize = new BigDecimal(fc.size());
size = fileSize.divide(new BigDecimal(1024 * 1024), 2, RoundingMode.HALF_UP) + "MB";
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fc) {
try {
fc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return size;
}
5.前端播放(VUE)
<video controls="controls" controls="controls">
<source src="http://ip:port/xxxxxxx" type="video/mp4" />
</video>