最近客户不知道从哪里拿来了很多MP4,上传到系统后无法在网页上直接用H5播放。
用格式工厂将编码改为H264也播放不了,无奈之下,查询了一下网上的资料,用ffmpeg自己写了上传文件的转码功能。
首先,去 https://ffmpeg.zeranoe.com/builds/ 下载最新的ffmpeg的static版,解压后找到bin下面的ffmpeg.exe,拷到你的项目下。
上传并调用ffmpeg:可直接调用uploadSingleVideoFil()方法
FileUploadUtil.java:
package org.sun.com;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.multipart.MultipartFile;
public class FileUploadUtil {
private static final Logger logger = LoggerFactory
.getLogger(FileUploadUtil.class);
public static final String ROOT_PATH = ContextLoader.getCurrentWebApplicationContext().getServletContext().getRealPath("").replace("\\",File.separator);
public static final String UPLOAD_ROOT_PATH = "upload";
public static final String UPLOAD_VIDEO_PATH="videos";
public static String getUploadPath(String path){
return ROOT_PATH+File.separator+UPLOAD_ROOT_PATH+path;
}
public static String getUploadRootPath(){
return getUploadPath("");
};
public static String getUploadIMGPath(){
return getUploadPath(File.separator+UPLOAD_IMG_PATH);
}
public static File mkDir(String path){
File dir = new File(path);
if(!dir.exists())
dir.mkdirs();
return dir;
}
//生成下载根目录
public static File mkUploadRootDir(){
return mkDir(getUploadRootPath());
}
/**
*
* @param originalFileName
* @return 返回原文件的后缀
*
*/
public static String getOriginalFileSuffix(String originalFileName){
int index=originalFileName.lastIndexOf(".");
if(index!=-1){
return originalFileName.substring(index);
}else
return originalFileName;
}
/**
*
* @param path 这个path 是upload的子目录路径
* @param orginalFileName
* @return
*/
public static File createServerFile(String path,String orginalFileName){
// Creating the directory to store file
File dir = mkDir(getUploadPath(path));
String orginalFileNameSuffix = getOriginalFileSuffix(orginalFileName);
// Create the file on server
File serverFile = new File(dir.getAbsolutePath()
+ File.separator +new Date().getTime()+orginalFileNameSuffix);
return serverFile;
}
/**
*
* @param file
* @return 返回从upload目录下面的相对路径 */
public static String getRelativePathFromUploadDir(File file){
if(null==file)
return "";
String absolutePath = file.getAbsolutePath();
if(absolutePath.indexOf(ROOT_PATH)!=-1 && ROOT_PATH.length()<absolutePath.length())
return absolutePath.substring(absolutePath.indexOf(ROOT_PATH)+ROOT_PATH.length());
else
return "";
}
public static String uploadSingleVideoFile(MultipartFile file) {
String path = File.separator+UPLOAD_VIDEO_PATH;
if (!file.isEmpty()) {
try {
// Create the file on server
File serverFile = createServerFile(path,file.getOriginalFilename());
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
int length=0;
byte[] buffer = new byte[1024];
InputStream inputStream = file.getInputStream();
while ((length = inputStream.read(buffer)) != -1) {
stream.write(buffer, 0, length);
}
stream.flush();
stream.close();
logger.info("Server File Location=" + serverFile.getAbsolutePath());
System.out.println("文件上传完成,并开始转换:" + serverFile.getAbsolutePath());
String ffmpegPath = ROOT_PATH + "\\scripts\\ffmpeg\\ffmpeg.exe";
int dot = serverFile.getAbsolutePath().lastIndexOf('.');
if ((dot >-1) && (dot < (serverFile.getAbsolutePath().length()))) {
String codcFilePath = serverFile.getAbsolutePath().substring(0, dot) + "_changed.mp4";
ExecuteCodecs executeCodecs = new ExecuteCodecs();
executeCodecs.exchangeToMp4(ffmpegPath, serverFile.getAbsolutePath(), codcFilePath);
File newFile = new File(codcFilePath);
return getRelativePathFromUploadDir(newFile).replaceAll("\\\\", "/");
}
else
return null;
} catch (IOException e) {
e.printStackTrace();
System.out.println(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}else{
System.out.println("文件内容为空");
}
return null;
}
}
ExecuteCodecs.java:
package org.sun.com;
import java.util.ArrayList;
import java.util.List;
public class ExecuteCodecs {
/**
* 视频转码 (PC端MP4)
* @param ffmpegPath 转码工具的存放路径
* @param upFilePath 用于指定要转换格式的文件,要截图的视频源文件
* @param codcFilePath 格式转换后的的文件保存路径
* @return
* @throws Exception
*/
public boolean exchangeToMp4(String ffmpegPath, String upFilePath, String codcFilePath) throws Exception {
// 创建List集合来保存转换视频文件为flv格式的命令
List<String> convert = new ArrayList<String>();
convert.add(ffmpegPath); // 添加转换工具路径
convert.add("-y"); // 该参数指定将覆盖已存在的文件
convert.add("-i");
convert.add(upFilePath);
convert.add("-c:v");
convert.add("libx264");
convert.add("-c:a");
convert.add("aac");
convert.add("-strict");
convert.add("-2");
convert.add("-pix_fmt");
convert.add("yuv420p");
convert.add("-movflags");
convert.add("faststart");
//convert.add("-vf"); // 添加水印
//convert.add("movie=watermark.gif[wm];[in][wm]overlay=20:20[out]");
convert.add(codcFilePath);
boolean mark = true;
try {
Process videoProcess = new ProcessBuilder(convert).redirectErrorStream(true).start();
new PrintStream(videoProcess.getInputStream()).start();
//videoProcess.waitFor(); // 加上这句,系统会等待转换完成。不加,就会在服务器后台自行转换。
} catch (Exception e) {
mark = false;
System.out.println(e);
e.printStackTrace();
}
return mark;
}
}
PrintStream.java:
package org.sun.com;
public class PrintStream extends Thread
{
java.io.InputStream __is = null;
public PrintStream(java.io.InputStream is)
{
__is = is;
}
public void run()
{
try
{
while(this != null)
{
int _ch = __is.read();
if(_ch != -1)
System.out.print((char)_ch);
else break;
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
其他:
在客户的服务器上部署的时候遇到这样一个问题。
我上传个500M的文件它可以正常转换;上传个1G的文件,ffmpeg转到1分钟左右就停住了。
直接在cmd中打ffmpeg命令也是同样。
可是同样的文件同样的命令,在我们开发的机器上就没问题,可以转换完成。
开发机的系统是win7,客户服务器是windows server 2008 R2,可能是由于系统原因导致ffmpeg内存溢出?
无奈之下下载了最新的ffmpeg-20170327-d65b595-win64-static试试,竟然ok了。
可能是我以前用的是老版本的ffmpeg,里面有一些问题,在新版本中已经被修正了吧。
接下来有空我要好好学学ffmpeg视频转码的原理了。