项目场景:
公司最近的设备视频查看一直频繁报问题,看不到监控视频,设备安卓端多次查看无果后决定改变视频录制的方式,变成由安卓端截屏处理成jpg图片后面再由云端合成视频;
云端图片合成视频
选择依赖
<!--opencv 处理图片-->
<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.3</version>
</dependency>
<!--解压tar.gz文件-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.20</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.8.1</version>
</dependency>
代码测试:
@Test //测试方法
void javacv(){
String pathSuffix="C:\\data\\spring_logs\\";
String pathSuffixTar="C:\\data\\spring_logs\\sdcard\\DCIM\\";
String fileName="131081f8-4390-897b758e7c6c-test";
String filePathPrefixMP4=".mp4";
String filePathPrefixZIP=".zip";
String filePathPrefixTarGZ=".tar.gz";
File fileMP4 = new File(pathSuffix + fileName + filePathPrefixMP4);
if (fileMP4.exists()){
System.out.println(fileMP4.getName()+" mp4文件已存在");
return;
}
//本来只要处理上传上来的zip文件就好了,但安卓端非要处理成tar.gz,难受我又搞不定解压的路径
//tar.gz解压后会根据你解压文件的打包路径重新解压;就是会多出一个或多个文件包裹你解压的文件,我只能把解压好的文件移出到传入的路径下,并且把解压目录给删除掉;
if (new File(pathSuffix + fileName + filePathPrefixZIP).exists()){
// && !new File(pathSuffix + fileName).exists()){
//解压文件zip
try {
zipUncompressed(pathSuffix + fileName + filePathPrefixZIP,pathSuffix);
} catch (Exception e) {
System.out.println(pathSuffix + fileName + " zip文件解压异常");
if (new File(pathSuffix+fileName).exists()){
System.out.println("删除解压的异常文件夹:"+pathSuffix+fileName);
deleteDir(pathSuffix+fileName);
}
System.out.println("再次尝试解压:");
try {
zipUncompressed(pathSuffix + fileName + filePathPrefixZIP,pathSuffix);
} catch (Exception exception) {
System.out.println(pathSuffix + fileName + " zip文件解压再次异常");
return;
}
}
} else if (new File(pathSuffix + fileName + filePathPrefixTarGZ).exists()){
// &&!new File(pathSuffix+fileName).exists()){
try {
//解压文件tar.gz
// unTarGz(pathSuffix+fileName+filePathPrefixTarGZ,pathSuffix);
deCompressGZipFile(pathSuffix+fileName+filePathPrefixTarGZ,pathSuffix);
//CompressUtil.decompressTarGz(new File(pathSuffix+fileName+filePathPrefixTarGZ),pathSuffix);
} catch (Exception e) {
System.out.println(pathSuffix + fileName + " tar文件解压异常");
if (new File(pathSuffix+fileName).exists()){
System.out.println("删除解压的异常文件夹:"+pathSuffix+fileName);
deleteDir(pathSuffix+fileName);
}
System.out.println("再次尝试解压:");
try {
// unTarGz(pathSuffix+fileName+filePathPrefixTarGZ,pathSuffix);
deCompressGZipFile(pathSuffix+fileName+filePathPrefixTarGZ,pathSuffix);
//CompressUtil.decompressTarGz(new File(pathSuffix+fileName+filePathPrefixTarGZ),pathSuffix);
} catch (Exception exception) {
System.out.println(pathSuffix + fileName + " tar文件解压再次异常");
return;
}
}
}
//合成视频
File fileJPG = new File(pathSuffixTar + fileName);
if (fileJPG.isDirectory()&&fileJPG.exists()){
System.out.println("开始合成视频");
File[] files = fileJPG.listFiles();
HashMap<Integer, File> imgMap = new HashMap<>();
int width = 1600;
int height = 900;
for (File imgFile : files) {
String substring = imgFile.getName().substring(0, imgFile.getName().lastIndexOf(".")).trim();
int i =(int)Long.valueOf(substring).intValue();
imgMap.put(i, imgFile);
}
Object[] objects = imgMap.keySet().toArray();
Arrays.sort(objects);
// HashMap<Integer, File> imgMapSort = new HashMap<>();
// for (Object object : objects) {
// imgMapSort.put((int)object,imgMap.get((int)object));
// }
// System.out.println("imgMapSort" + imgMapSort.toString());
try {
createMp4(pathSuffixTar + fileName + filePathPrefixMP4, imgMap,objects, width, height);
} catch (FrameRecorder.Exception e) {
System.out.println("合成失败!");
}
}
File file = new File(pathSuffixTar+fileName+filePathPrefixMP4);
if (file.isFile()){
File toFile=new File(pathSuffix+fileName+filePathPrefixMP4);
if (toFile.exists()){
System.out.println("文件已存在");
}
else{
file.renameTo(toFile);
System.out.println("移动文件成功");
}
}
System.out.println("删除文件");
deleteDir(pathSuffix+"sdcard");
}
/**
* zip文件解压
*
* @param inputFile 待解压文件夹/文件
* @param destDirPath 解压路径
*/
public static void zipUncompressed(String inputFile, String destDirPath) throws Exception {
File srcFile = new File(inputFile);//获取当前压缩文件
// 判断源文件是否存在
// if (!srcFile.exists()) {
// throw new Exception(srcFile.getPath() + "所指文件不存在");
// }
//开始解压
//构建解压输入流
ZipInputStream zIn = new ZipInputStream(new FileInputStream(srcFile));
ZipEntry entry = null;
File file = null;
while ((entry = zIn.getNextEntry()) != null) {
if (!entry.isDirectory()) {
file = new File(destDirPath, entry.getName());
if (!file.exists()) {
new File(file.getParent()).mkdirs();//创建此文件的上级目录
}
OutputStream out = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(out);
int len = -1;
byte[] buf = new byte[1024];
while ((len = zIn.read(buf)) != -1) {
bos.write(buf, 0, len);
}
// 关流顺序,先打开的后关闭
bos.close();
out.close();
}
}
}
/**
* Tar文件解压方法
*
* @param tarGzFile 要解压的压缩文件名称(绝对路径名称)
* @param destDir 解压后文件放置的路径名(绝对路径名称)当路径不存在,会自动创建
* @return 解压出的文件列表
*/
public static void deCompressGZipFile(String tarGzFile, String destDir) throws Exception {
File sourceFile = new File(tarGzFile);
// decompressing *.tar.gz files to tar
TarArchiveInputStream fin = new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(sourceFile)));
File extraceFolder = new File(destDir);
TarArchiveEntry entry;
// 将 tar 文件解压到 extractPath 目录下
while ((entry = fin.getNextTarEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
File curfile = new File(extraceFolder,entry.getName());
// System.out.println("path : "+entry.getFile().getName());
File parent = curfile.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
// 将文件写出到解压的目录
IOUtils.copy(fin, new FileOutputStream(curfile));
}
}
太长了,我就再起一个,这里主要是视频合成和文件删除
//文件删除
public static boolean deleteDir(String path){
File file = new File(path);
if(!file.exists()){//判断是否待删除目录是否存在
System.err.println("The dir are not exists!");
return false;
}
String[] content = file.list();//取得当前目录下所有文件和文件夹
for(String name : content){
File temp = new File(path, name);
if(temp.isDirectory()){//判断是否是目录
deleteDir(temp.getAbsolutePath());//递归调用,删除目录里的内容
temp.delete();//删除空目录
}else{
if(!temp.delete()){//直接删除文件
System.err.println("Failed to delete " + name);
}
}
}
file.delete();
return true;
}
//视频合成
private static void createMp4(String mp4SavePath, Map<Integer, File> imgMap, Object[] objects, int width, int height) throws FrameRecorder.Exception {
//视频宽高最好是按照常见的视频的宽高 16:9 或者 9:16
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(mp4SavePath, width, height);
//设置视频编码层模式
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
//设置视频为25帧每秒
recorder.setFrameRate(25);
//设置视频图像数据格式
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.setFormat("mp4");
// recorder.setAudioBitrate(1300);
try {
recorder.start();
Java2DFrameConverter converter = new Java2DFrameConverter();
//根据文件数量定义视频时长
for (Object object : objects) {
File file = imgMap.get(object);
BufferedImage read = ImageIO.read(file);
//6:控制的是快进的速率/倍数
for (int i = 0; i < 6; i++) {
recorder.record(converter.getFrame(read));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//最后一定要结束并释放资源
recorder.stop();
recorder.release();
}
}
虽然代码写的稀碎但可以正常跑,合成的图片不是太清晰,但能看;
后续更新
太坑了整了个这项目ja包由原先的40多M飙升到了800多M这哪是导依赖呀!这是注水呀!注的还不是一点半点,只能简化下导入的依赖了,简化后还又140M呐,先跑着吧!
<!-- 测试减缩jar window&&Linux-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.1.0-1.5.1</version>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.1.0-1.5.1</version>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.6-1.5.1</version>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.6-1.5.1</version>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.2.2-1.5.3</version>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.2.2-1.5.3</version>
<classifier>windows-x86_64</classifier>
</dependency>