|
|
|
@ -0,0 +1,182 @@
|
|
|
|
|
package com.example.jpg2mp4;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
|
|
import cn.hutool.core.io.FileUtil;
|
|
|
|
|
import cn.hutool.core.io.resource.ResourceUtil;
|
|
|
|
|
import org.bytedeco.ffmpeg.global.avcodec;
|
|
|
|
|
import org.bytedeco.ffmpeg.global.avutil;
|
|
|
|
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
|
|
|
|
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
|
|
|
|
import org.bytedeco.javacv.Frame;
|
|
|
|
|
import org.bytedeco.javacv.FrameGrabber;
|
|
|
|
|
import org.bytedeco.javacv.FrameRecorder;
|
|
|
|
|
import org.bytedeco.javacv.Java2DFrameConverter;
|
|
|
|
|
|
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
public class Image2Mp4 {
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
|
|
String absolutePath = FileUtil.getAbsolutePath("");
|
|
|
|
|
|
|
|
|
|
// 图片集合目录
|
|
|
|
|
String imgs = FileUtil.getAbsolutePath("data\\album\\");
|
|
|
|
|
String auds = FileUtil.getAbsolutePath("data\\music\\");
|
|
|
|
|
// 图片集合目录
|
|
|
|
|
String target = FileUtil.getAbsolutePath("data\\target\\");
|
|
|
|
|
//合成的MP4
|
|
|
|
|
String mp4 = DateUtil.date().toString("yyyyMMddHHmmss");
|
|
|
|
|
String tempMp4 = target + mp4 + "temp.mp4";
|
|
|
|
|
String outMp4 = target + mp4 + ".mp4";
|
|
|
|
|
// 视频宽度
|
|
|
|
|
int width = 1920;
|
|
|
|
|
// 视频高度
|
|
|
|
|
int height = 1080;
|
|
|
|
|
createTempMp4(imgs, tempMp4, width, height);
|
|
|
|
|
if (FileUtil.loopFiles(auds).size() > 0) {
|
|
|
|
|
mergeMp4(tempMp4, auds, outMp4);
|
|
|
|
|
} else {
|
|
|
|
|
FileUtil.move(new File(tempMp4), new File(mp4), true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建临时文件
|
|
|
|
|
*
|
|
|
|
|
* @param tempMp4
|
|
|
|
|
* @param imgs
|
|
|
|
|
* @param width
|
|
|
|
|
* @param height
|
|
|
|
|
* @throws FrameRecorder.Exception
|
|
|
|
|
*/
|
|
|
|
|
private static void createTempMp4(String imgs, String tempMp4, int width, int height) throws FrameRecorder.Exception {
|
|
|
|
|
//视频宽高最好是按照常见的视频的宽高 16:9 或者 9:16
|
|
|
|
|
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempMp4, width, height);
|
|
|
|
|
//设置视频编码层模式
|
|
|
|
|
recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
|
|
|
|
|
// 设置视频图像数据格式 yuv420p 先默认吧,这个应该属于设置视频的处理模式吧
|
|
|
|
|
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
|
|
|
|
|
recorder.setFormat("mp4");
|
|
|
|
|
//视频帧,一般不能小于24,否则有卡顿
|
|
|
|
|
int frameRate = 25;
|
|
|
|
|
recorder.setFrameRate(frameRate);
|
|
|
|
|
//这一步没有感觉到有多少影响,暂时不管
|
|
|
|
|
recorder.setGopSize(50);
|
|
|
|
|
//8000kb/s 这个说明视频每秒大小,值越大图片转过来的压缩率就越小质量就会越高
|
|
|
|
|
recorder.setVideoBitrate(8000000);
|
|
|
|
|
// 不可变(固定)音频比特率
|
|
|
|
|
recorder.setAudioOption("crf", "0");
|
|
|
|
|
// 最高质量
|
|
|
|
|
recorder.setAudioQuality(0);
|
|
|
|
|
// 音频比特率
|
|
|
|
|
recorder.setAudioBitrate(192000);
|
|
|
|
|
// 音频采样率11025Hz、22050Hz、24000Hz、44100Hz、48000Hz
|
|
|
|
|
recorder.setSampleRate(44100);
|
|
|
|
|
// 双通道(立体声)
|
|
|
|
|
recorder.setAudioChannels(2);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
recorder.start();
|
|
|
|
|
Java2DFrameConverter converter = new Java2DFrameConverter();
|
|
|
|
|
|
|
|
|
|
List<File> files = FileUtil.loopFiles(imgs);
|
|
|
|
|
for (int i = 0; i < files.size(); i++) {
|
|
|
|
|
BufferedImage read = ImageIO.read(files.get(i));
|
|
|
|
|
//一秒是25帧 所以要记录25次
|
|
|
|
|
for (int j = 0; j < frameRate; j++) {
|
|
|
|
|
System.out.println("正在写入,第" + (i + 1) + "张,第" + (j + 1) + "帧");
|
|
|
|
|
//编码格式 ##后面参数用于解决生成视频颜色不对问题
|
|
|
|
|
recorder.record(converter.getFrame(read));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
} finally {
|
|
|
|
|
//最后一定要结束并释放资源
|
|
|
|
|
recorder.stop();
|
|
|
|
|
recorder.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 合并视频和音频
|
|
|
|
|
*
|
|
|
|
|
* @param videoPath
|
|
|
|
|
* @param audioPath
|
|
|
|
|
* @param outPut
|
|
|
|
|
* @return
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
public static boolean mergeMp4(String videoPath, String audioPath, String outPut) throws Exception {
|
|
|
|
|
boolean isCreated = true;
|
|
|
|
|
File file = new File(videoPath);
|
|
|
|
|
if (!file.exists()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
FrameRecorder recorder = null;
|
|
|
|
|
FrameGrabber grabber1 = null;
|
|
|
|
|
FrameGrabber grabber2 = null;
|
|
|
|
|
try {
|
|
|
|
|
//抓取视频帧
|
|
|
|
|
grabber1 = new FFmpegFrameGrabber(videoPath);
|
|
|
|
|
//抓取音频帧
|
|
|
|
|
grabber2 = new FFmpegFrameGrabber(FileUtil.loopFiles(audioPath).get(0));
|
|
|
|
|
grabber1.start();
|
|
|
|
|
grabber2.start();
|
|
|
|
|
//创建录制
|
|
|
|
|
recorder = new FFmpegFrameRecorder(outPut, grabber1.getImageWidth(), grabber1.getImageHeight(), grabber2.getAudioChannels());
|
|
|
|
|
recorder.setFormat("mp4");
|
|
|
|
|
recorder.setFrameRate(grabber1.getFrameRate());
|
|
|
|
|
recorder.setSampleRate(grabber2.getSampleRate());
|
|
|
|
|
// 视频质量,0表示无损
|
|
|
|
|
recorder.setVideoQuality(0);
|
|
|
|
|
recorder.start();
|
|
|
|
|
Frame frame1;
|
|
|
|
|
//录入视频
|
|
|
|
|
while ((frame1 = grabber1.grabFrame()) != null) {
|
|
|
|
|
recorder.record(frame1);
|
|
|
|
|
}
|
|
|
|
|
//录入音频
|
|
|
|
|
audioEntry(grabber1, grabber2, recorder);
|
|
|
|
|
grabber1.stop();
|
|
|
|
|
grabber2.stop();
|
|
|
|
|
recorder.stop();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
} finally {
|
|
|
|
|
try {
|
|
|
|
|
if (recorder != null) {
|
|
|
|
|
recorder.release();
|
|
|
|
|
}
|
|
|
|
|
if (grabber1 != null) {
|
|
|
|
|
grabber1.release();
|
|
|
|
|
}
|
|
|
|
|
if (grabber2 != null) {
|
|
|
|
|
grabber2.release();
|
|
|
|
|
}
|
|
|
|
|
} catch (FrameRecorder.Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return isCreated;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void audioEntry(FrameGrabber grabber1, FrameGrabber grabber2, FrameRecorder recorder) throws Exception {
|
|
|
|
|
Frame frame = null;
|
|
|
|
|
long grabber1Timestamp = grabber1.getTimestamp();
|
|
|
|
|
while ((frame = grabber2.grabFrame()) != null) {
|
|
|
|
|
//如果视频时长小于音频时长 则截断音频帧
|
|
|
|
|
if (grabber1Timestamp <= grabber2.getTimestamp()) break;
|
|
|
|
|
recorder.record(frame);
|
|
|
|
|
}
|
|
|
|
|
long differ = grabber1Timestamp - grabber2.getTimestamp();
|
|
|
|
|
//如果视频时长大于音频时长 则循环录入
|
|
|
|
|
if (differ > 0) {
|
|
|
|
|
grabber1.setTimestamp(differ);
|
|
|
|
|
audioEntry(grabber1, grabber2, recorder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|