ffmpeg - 视频裁剪
最近遇到一个项目,需要实现一个视频在多个屏幕上播放,windows可以用硬件方案实现,在安卓系统上目测好像没有现成的硬件方案,只能硬着头皮用软件实现。
开始了解需求时还不知道要怎么做,由于之前用过ffmpeg做过视频截图,觉得很强大,就想到了能不能用ffmpeg分割视频,和其他人员商量了下觉得可行,接下来几天就是找资料,了解ffmpeg。
最后不负所望,做出来个demo,虽然还很多细节要考虑,但是前期工作已经完成了很大的一步,特在此记录下,也让有需要的参考下,让大佬提提建议(优化裁剪视频的时间)。
参考资料:
http://ffmpeg.org/ffmpeg.html#Video-and-Audio-file-format-conversion
https://www.it610.com/article/1280132859394277376.htm
ffmpeg linux环境安装教程:
- 安装:yasm
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
./configure --prefix=/usr/local/yasm
make
make install - 安装:x264
http://download.videolan.org/x264/snapshots/x264-snapshot-20191216-2245.tar.bz2
./configure --prefix=/usr/local/x264 --enable-shared --enable-static --enable-yasm
make
make install - 安装ffmpeg
下载:
sudo git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
sudo ./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-yasm --enable-libx264 --enable-gpl --enable-pthreads --extra-cflags=-I/usr/local/x264/include --extra-ldflags=-L/usr/local/x264/lib
make
make install - 执行ffmpeg命令
如遇问题:ffmpeg: error while loading shared libraries: libavdevice.so.58: cannot open shared object file: No such file or directory
问题解决:
sudo vi /etc/ld.so.conf
在文件中添加路径:
/usr/local/ffmpeg/lib
#usr/local/ffmpeg 目录是ffmpeg的安装目录,根据个人不同安装目录修改。
更新环境变量:
sudo ldconfig
视频裁剪Java代码:
/**
* 视频裁剪 一张一张裁剪
* @param url 文件路径
* @param dirPath 存放裁剪后的文件夹地址
* @param destRow 裁剪的行数
* @param destColumn 裁剪的列数
* @return
*/
public static List<String> cut2(String url, String dirPath, int destRow, int destColumn) {
log.info("视频裁剪入口2: url:{}, path:{}, destRow:{}, destColumn", url, dirPath, destRow, destColumn);
List<String> resultList = new ArrayList<>();
String suffix = url.substring(url.lastIndexOf("."));
List<CommandDTO> commandDTOS = generateCoordinateAndFileName(suffix, destRow, destColumn);
for (CommandDTO commandDTO : commandDTOS) {
String destFileName = dirPath + "/" + commandDTO.getFileName();
List<String> commands = new ArrayList<>();
commands.add("ffmpeg");
commands.add("-i");
commands.add(url);
commands.add("-vf");
commands.add("crop=" + commandDTO.getCommand());
commands.add("-vcodec");
commands.add("libx264"); // 视频编码 需要安装x264编码库
commands.add(destFileName);
if(runCommand(commands) == 0) {
resultList.add(destFileName);
}
}
return resultList;
}
/**
* 视频裁剪 一次性裁剪完成
* @param url 文件路径
* @param dirPath 存放裁剪后的文件夹地址
* @param destRow 裁剪的行数
* @param destColumn 裁剪的列数
* @return
*/
public static List<String> cut3(String url, String dirPath, int destRow, int destColumn) {
log.info("视频裁剪入口3: url:{}, path:{}, destRow:{}, destColumn:{}", url, dirPath, destRow, destColumn);
List<String> resultList = new ArrayList<>();
String suffix = url.substring(url.lastIndexOf("."));
List<CommandDTO> commandDTOS = generateCoordinateAndFileName(suffix, destRow, destColumn);
List<String> commands = new ArrayList<>();
commands.add("ffmpeg");
commands.add("-i");
commands.add(url);
List<String> regions = new ArrayList<>();
List<String> fileMaps = new ArrayList<>();
for (CommandDTO commandDTO : commandDTOS) {
String destFileName = dirPath + "/" + commandDTO.getFileName();
regions.add("-filter_complex");
regions.add("[0:v]crop=" + commandDTO.getCommand() + "[" + commandDTO.getCoordinate() + "]");
fileMaps.add("-map");
fileMaps.add("[" + commandDTO.getCoordinate() + "]");
fileMaps.add("-map");
fileMaps.add("0:a");
fileMaps.add(destFileName);
resultList.add(destFileName);
}
commands.addAll(regions);
commands.addAll(fileMaps);
log.info("即将执行的命令 command:{}", Arrays.toString(commands.toArray()));
if(runCommand(commands) == 0) {
return resultList;
}
return new ArrayList<>();
}
private static int runCommand(List<String> commands) {
log.info("执行命令 command:{}", Arrays.toString(commands.toArray()));
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
try {
Process process = builder.start();
int i = process.waitFor();
log.info("i:{}", i);
if (i != 0) {
log.error("命令执行错误:{}", process.getErrorStream());
} else {
log.info("命令执行成功");
}
return i;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}
/**
* 生成执行命令坐标以及对应文件名
* @param suffix
* @param destRow
* @param destColumn
* @return
*/
private static List<CommandDTO> generateCoordinateAndFileName(String suffix, int destRow, int destColumn) {
List<CommandDTO> commandDTOS = new ArrayList<>();
// 时间字符串,作为文件名使用,可自定义,最好不重复
String timeStr = FileUtil.generateTimeStr();
// 裁剪的宽高
String averageSize = "iw/" + destColumn + ":ih/" + destRow + ":";
// 裁剪的坐标组合
List<String> coordinates = buildCoordinates(destRow, destColumn);
for (String c : coordinates) {
String[] split = c.split(":");
String x = split[0].substring(split[0].lastIndexOf("*") + 1);
String y = split[1].substring(split[1].lastIndexOf("*") + 1);
// 裁剪的起始坐标
String area = x + "-" + y;
// 裁剪后的文件名
String fileName = timeStr + "_" + area + suffix;
CommandDTO commandDTO = new CommandDTO(averageSize + c, area, fileName);
commandDTOS.add(commandDTO);
}
return commandDTOS;
}
/**
* 坐标点计算
* @param row 行数
* @param column 列数
* @return
*/
private static List<String> buildCoordinates(int row, int column) {
if (row <= 0 || column <= 0) {
throw new RuntimeException("裁剪的行,列必须都大于0");
}
List<String> commands = new ArrayList<>();
for (int i = 0; i < column; i++) {
for (int k = 0; k < row; k++) {
String rowColumnCommand = "iw/" + column + "*" + i + ":ih/" + row + "*" + k;
commands.add(rowColumnCommand);
}
}
return commands;
}
第一次研究,有问题的可以一起探讨,欢迎各位留言,提意见,觉得有用的点个赞啥的…