Commit 047d69fc authored by yanzg's avatar yanzg

视频转换

parent 81c26d88
...@@ -173,6 +173,16 @@ public class StringHelper { ...@@ -173,6 +173,16 @@ public class StringHelper {
return getFirstRun(0.0, 0.0, froms); return getFirstRun(0.0, 0.0, froms);
} }
/**
* 传入很多整形,获取第一个非0的值,至少需要两个参数
*
* @param froms 参数列表
* @return 第一个非0值
*/
public static Float getFirst(Float... froms) {
return getFirstRun(0.0f, 0.0f, froms);
}
/** /**
* 获取第一个非空值 * 获取第一个非空值
* *
......
...@@ -21,15 +21,22 @@ ...@@ -21,15 +21,22 @@
<dependency> <dependency>
<groupId>org.bytedeco</groupId> <groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId> <artifactId>javacv</artifactId>
<version>1.4.3</version> <version>1.5.3</version>
</dependency> </dependency>
<!--<dependency>-->
<!--<groupId>org.bytedeco.javacpp-presets</groupId>-->
<!--<artifactId>ffmpeg-platform</artifactId>-->
<!--<version>4.0.2-1.4.3</version>-->
<!--</dependency>-->
<dependency> <dependency>
<groupId>org.bytedeco.javacpp-presets</groupId> <groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId> <artifactId>ffmpeg-platform</artifactId>
<version>4.0.2-1.4.3</version> <version>4.2.2-1.5.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.coobird</groupId> <groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId> <artifactId>thumbnailator</artifactId>
......
package com.yanzuoguang.util; package com.yanzuoguang.util;
import com.yanzuoguang.util.log.Log; import com.yanzuoguang.util.log.Log;
import org.bytedeco.javacpp.avcodec; import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.*; import org.bytedeco.javacv.*;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.IplImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
...@@ -13,6 +14,7 @@ import java.util.ArrayList; ...@@ -13,6 +14,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* 视频帮助类 * 视频帮助类
* <p> * <p>
...@@ -67,23 +69,12 @@ public class MediaHelper extends ImageHelper { ...@@ -67,23 +69,12 @@ public class MediaHelper extends ImageHelper {
* @param parameter 转码后的参数,在转码完成后该参数值会被改变 * @param parameter 转码后的参数,在转码完成后该参数值会被改变
*/ */
public static void convertVideo(String fromFile, String toFile, MediaParameter parameter) throws IOException { public static void convertVideo(String fromFile, String toFile, MediaParameter parameter) throws IOException {
initFile(fromFile, toFile);
// 视频压缩参数初始化 // 视频压缩参数初始化
if (parameter == null) { if (parameter == null) {
parameter = new MediaParameter(); parameter = new MediaParameter();
} }
parameter.check(); parameter.check();
// 源文件判断
File source = new File(fromFile);
if (!source.exists()) {
throw new RuntimeException("视频转码压缩时不存在");
}
File target = new File(toFile);
if (!target.getParentFile().exists()) {
target.getParentFile().mkdirs();
}
if (target.exists()) {
target.delete();
}
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(fromFile); FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(fromFile);
try { try {
...@@ -98,22 +89,32 @@ public class MediaHelper extends ImageHelper { ...@@ -98,22 +89,32 @@ public class MediaHelper extends ImageHelper {
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder( FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
toFile, toFile,
parameter.getVideoWidthFinally(), parameter.getVideoWidthFinally(),
parameter.getVideoHeightFinally() parameter.getVideoHeightFinally(),
grabber.getAudioChannels()
); );
parameter.init(grabber, recorder); parameter.init(grabber, recorder);
try { try {
// 转码没有图像 // 转码没有图像
if (Frame) { if (Frame) {
// 开始转换 // 开始转换
// recorder.start(grabber.getFormatContext());
recorder.start(); recorder.start();
Frame frame; Frame frame;
while ((frame = grabber.grabFrame(true, true, true, false)) != null) {
while ((frame = grabber.grab()) != null) {
// 从视频帧中获取图片
if (frame.image != null) {
recorder.record(frame); recorder.record(frame);
} }
// 音频帧写入输出流
if (frame.samples != null) {
recorder.record(frame);
}
}
} else { } else {
// 开始转换 // 开始转换
recorder.start(grabber.getFormatContext()); recorder.start(grabber.getFormatContext());
avcodec.AVPacket packet; AVPacket packet;
while ((packet = grabber.grabPacket()) != null) { while ((packet = grabber.grabPacket()) != null) {
recorder.recordPacket(packet); recorder.recordPacket(packet);
} }
...@@ -121,9 +122,8 @@ public class MediaHelper extends ImageHelper { ...@@ -121,9 +122,8 @@ public class MediaHelper extends ImageHelper {
} finally { } finally {
// 设置转码后的视频角度 // 设置转码后的视频角度
recorder.setMetadata(MediaParameter.ROTATE, String.valueOf(parameter.getVideoRotateFinally()));
recorder.release();
recorder.stop(); recorder.stop();
recorder.release();
recorder.close(); recorder.close();
} }
...@@ -136,6 +136,21 @@ public class MediaHelper extends ImageHelper { ...@@ -136,6 +136,21 @@ public class MediaHelper extends ImageHelper {
} }
private static void initFile(String fromFile, String toFile) {
// 源文件判断
File source = new File(fromFile);
if (!source.exists()) {
throw new RuntimeException("视频转码压缩时不存在");
}
File target = new File(toFile);
if (!target.getParentFile().exists()) {
target.getParentFile().mkdirs();
}
if (target.exists()) {
target.delete();
}
}
/** /**
* 从视频文件中获取第一张图片 * 从视频文件中获取第一张图片
* *
...@@ -166,26 +181,27 @@ public class MediaHelper extends ImageHelper { ...@@ -166,26 +181,27 @@ public class MediaHelper extends ImageHelper {
if (file2.exists()) { if (file2.exists()) {
throw new RuntimeException("视频文件" + filePath + "不存在"); throw new RuntimeException("视频文件" + filePath + "不存在");
} }
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(filePath); FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePath);
MediaParameter parameter = new MediaParameter();
try { try {
// 打开视频 // 打开视频
ff.start(); grabber.start();
// 获取视频的角度 parameter.init(grabber);
String rotate = ff.getVideoMetadata(MediaParameter.ROTATE); double rotate = parameter.getVideoRotateFinally();
// 整个视频的长度 // 整个视频的长度
int ffLength = ff.getLengthInFrames(); int ffLength = grabber.getLengthInFrames();
// 取得能够取得的帧数 // 取得能够取得的帧数
index = Math.min(ffLength, index); index = Math.min(ffLength, index);
for (int i = 0; i <= ffLength; i++) { for (int i = 0; i <= ffLength; i++) {
Frame f = ff.grabImage(); Frame f = grabber.grabImage();
if (i == index) { if (i == index) {
// 判断是否需要旋转 // 判断是否需要旋转
if (null != rotate && rotate.length() > 1) { if (rotate != 0) {
// 旋转角度,防止图像不正确 // 旋转角度,防止图像不正确
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage(); OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
opencv_core.IplImage src = converter.convert(f); IplImage src = converter.convert(f);
f = converter.convert(rotate(src, Integer.valueOf(rotate))); f = converter.convert(rotate(src, rotate));
} }
doExecuteFrame(f, toFile, type); doExecuteFrame(f, toFile, type);
...@@ -193,8 +209,8 @@ public class MediaHelper extends ImageHelper { ...@@ -193,8 +209,8 @@ public class MediaHelper extends ImageHelper {
} }
} }
} finally { } finally {
ff.close(); grabber.close();
ff.stop(); grabber.stop();
} }
} }
...@@ -205,13 +221,13 @@ public class MediaHelper extends ImageHelper { ...@@ -205,13 +221,13 @@ public class MediaHelper extends ImageHelper {
* @param angle * @param angle
* @return * @return
*/ */
public static opencv_core.IplImage rotate(opencv_core.IplImage src, int angle) { public static IplImage rotate(IplImage src, double angle) {
opencv_core.IplImage img = opencv_core.IplImage.create( IplImage img = IplImage.create(
src.height(), src.width(), src.height(), src.width(),
src.depth(), src.nChannels() src.depth(), src.nChannels()
); );
opencv_core.cvTranspose(src, img); opencv_core.cvTranspose(src, img);
opencv_core.cvFlip(img, img, angle); opencv_core.cvFlip(img, img, (int) angle);
return img; return img;
} }
......
...@@ -4,14 +4,12 @@ import com.yanzuoguang.util.helper.StringHelper; ...@@ -4,14 +4,12 @@ import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.log.Log; import com.yanzuoguang.util.log.Log;
import com.yanzuoguang.util.vo.BaseVo; import com.yanzuoguang.util.vo.BaseVo;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.FFmpegFrameRecorder;
import static org.bytedeco.javacpp.avcodec.AV_CODEC_ID_NONE; import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.javacpp.avutil.AV_PIX_FMT_NONE; import static org.bytedeco.ffmpeg.global.avutil.*;
import static org.bytedeco.javacpp.avutil.AV_SAMPLE_FMT_NONE;
/** /**
* 压缩视频参数 * 压缩视频参数
...@@ -20,9 +18,10 @@ import static org.bytedeco.javacpp.avutil.AV_SAMPLE_FMT_NONE; ...@@ -20,9 +18,10 @@ import static org.bytedeco.javacpp.avutil.AV_SAMPLE_FMT_NONE;
*/ */
public class MediaParameter extends BaseVo { public class MediaParameter extends BaseVo {
public static final int VIDEO_CODEC_MP4 = avcodec.AV_CODEC_ID_H264; public static final int VIDEO_CODEC_MP4 = AV_CODEC_ID_H264;
public static final String VIDEO_FORMAT_MP4 = "mp4"; public static final String VIDEO_FORMAT_MP4 = "mp4";
public static final int VIDEO_FIXED_MP4 = avutil.AV_PIX_FMT_YUV420P; public static final int VIDEO_FIXED_MP4 = AV_PIX_FMT_YUV420P;
public static final int SOUND_CODEC_MP3 = AV_CODEC_ID_VORBIS;
public static final String ROTATE = "rotate"; public static final String ROTATE = "rotate";
/** /**
...@@ -49,7 +48,7 @@ public class MediaParameter extends BaseVo { ...@@ -49,7 +48,7 @@ public class MediaParameter extends BaseVo {
* 音频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip) * 音频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)
*/ */
@ApiModelProperty(notes = "音频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)") @ApiModelProperty(notes = "音频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)")
private float audioBitRateZip = 1; private float audioBitRateZip = 0;
/** /**
* 视频转码后的格式 * 视频转码后的格式
*/ */
...@@ -107,7 +106,7 @@ public class MediaParameter extends BaseVo { ...@@ -107,7 +106,7 @@ public class MediaParameter extends BaseVo {
*/ */
@ApiModelProperty(notes = "视频宽度高度压缩比,算法为: 最终宽度=Math.max(minVideoWidth,videoWidth * videoSizeZip)," + @ApiModelProperty(notes = "视频宽度高度压缩比,算法为: 最终宽度=Math.max(minVideoWidth,videoWidth * videoSizeZip)," +
"最终高度:=Math.max(minVideoHeight,videoHeight * videoSizeZip)") "最终高度:=Math.max(minVideoHeight,videoHeight * videoSizeZip)")
private float videoSizeZip = 1; private float videoSizeZip = 0;
/** /**
* 视频转码的比特率,为0时表示采取原来的值 * 视频转码的比特率,为0时表示采取原来的值
*/ */
...@@ -122,7 +121,7 @@ public class MediaParameter extends BaseVo { ...@@ -122,7 +121,7 @@ public class MediaParameter extends BaseVo {
* 视频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip) * 视频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)
*/ */
@ApiModelProperty(notes = "视频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)") @ApiModelProperty(notes = "视频比特率压缩比,算法为: 最终比特率= Math.max(minAudioBitRate,audioBitRate * audioBitRateZip)")
private float videoBitRateZip = 1; private float videoBitRateZip = 0;
/** /**
...@@ -149,7 +148,7 @@ public class MediaParameter extends BaseVo { ...@@ -149,7 +148,7 @@ public class MediaParameter extends BaseVo {
* @return * @return
*/ */
public int getVideoWidthFinally() { public int getVideoWidthFinally() {
return Math.max(this.minVideoWidth, (int) (this.videoWidth * this.videoSizeZip)); return getWidthHeightFinally(true);
} }
/** /**
...@@ -158,8 +157,16 @@ public class MediaParameter extends BaseVo { ...@@ -158,8 +157,16 @@ public class MediaParameter extends BaseVo {
* @return * @return
*/ */
public int getVideoHeightFinally() { public int getVideoHeightFinally() {
return getWidthHeightFinally(false);
}
private int getWidthHeightFinally(boolean width) {
if (width) {
return Math.max(this.minVideoWidth, (int) (this.videoWidth * this.videoSizeZip));
} else {
return Math.max(this.minVideoHeight, (int) (this.videoHeight * this.videoSizeZip)); return Math.max(this.minVideoHeight, (int) (this.videoHeight * this.videoSizeZip));
} }
}
/** /**
* 音频最终高度 * 音频最终高度
...@@ -174,6 +181,9 @@ public class MediaParameter extends BaseVo { ...@@ -174,6 +181,9 @@ public class MediaParameter extends BaseVo {
* 检查参数 * 检查参数
*/ */
public void check() { public void check() {
this.audioBitRateZip = StringHelper.getFirst(this.audioBitRateZip, 1f);
this.videoSizeZip = StringHelper.getFirst(this.videoSizeZip, 1f);
this.videoBitRateZip = StringHelper.getFirst(this.videoBitRateZip, 1f);
if (audioBitRateZip > 1) { if (audioBitRateZip > 1) {
throw new RuntimeException("audioBitRateZip请在0~1之间"); throw new RuntimeException("audioBitRateZip请在0~1之间");
} }
...@@ -208,7 +218,7 @@ public class MediaParameter extends BaseVo { ...@@ -208,7 +218,7 @@ public class MediaParameter extends BaseVo {
this.videoPixelFormat = StringHelper.getFirstRun(AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, this.videoPixelFormat, grabber.getPixelFormat()); this.videoPixelFormat = StringHelper.getFirstRun(AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, this.videoPixelFormat, grabber.getPixelFormat());
// 读取视频属性 // 读取视频属性
this.videoRotate = StringHelper.toDouble(grabber.getVideoMetadata(ROTATE)); this.videoRotate = StringHelper.toDouble(grabber.getVideoMetadata(ROTATE));
this.videoFrameRate = StringHelper.getFirst(this.videoFrameRate, grabber.getFrameRate()); this.videoFrameRate = StringHelper.getFirst(this.videoFrameRate, grabber.getVideoFrameRate());
this.videoBitRate = StringHelper.getFirst(this.videoBitRate, grabber.getVideoBitrate()); this.videoBitRate = StringHelper.getFirst(this.videoBitRate, grabber.getVideoBitrate());
} }
...@@ -218,28 +228,45 @@ public class MediaParameter extends BaseVo { ...@@ -218,28 +228,45 @@ public class MediaParameter extends BaseVo {
* @param recorder 需要转码的对象 * @param recorder 需要转码的对象
*/ */
public void init(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder) { public void init(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder) {
recorder.setTimestamp(grabber.getTimestamp());
recorder.setFormat(grabber.getFormat());
recorder.setOptions(grabber.getOptions());
recorder.setMetadata(grabber.getMetadata());
recorder.setAudioMetadata(grabber.getAudioMetadata());
recorder.setVideoMetadata(grabber.getVideoMetadata());
recorder.setAspectRatio(recorder.getAspectRatio());
recorder.setAudioOptions(recorder.getAudioOptions());
recorder.setVideoOptions(grabber.getVideoOptions());
// 设置音频比特率 // 设置音频比特率
recorder.setAudioBitrate(this.getAudioBitRateFinally()); recorder.setAudioBitrate(this.getAudioBitRateFinally());
// recorder.setAudioCodec(this.audioCodec); /*
recorder.setAudioCodec(this.audioCodec);
// 设置声道 // 设置声道
recorder.setAudioChannels(grabber.getAudioChannels()); recorder.setAudioChannels(grabber.getAudioChannels());
// 设置声音质量
recorder.setAudioQuality(recorder.getAudioQuality());
// 设置音频属性 // 设置音频属性
// recorder.setSampleFormat(grabber.getSampleFormat()); recorder.setSampleFormat(grabber.getSampleFormat());
// recorder.setSampleRate(grabber.getSampleRate()); recorder.setSampleRate(grabber.getSampleRate());
/* */
// 视频格式 // 视频格式
recorder.setFormat(this.videoFormat); recorder.setFormat(this.videoFormat);
// 设置转码视频格式 // 设置转码视频格式
recorder.setVideoCodec(this.videoCodeC); recorder.setVideoCodec(this.videoCodeC);
// 设置像素格式 // 设置像素格式
// recorder.setPixelFormat(this.videoPixelFormat); recorder.setPixelFormat(this.videoPixelFormat);
// 设置转码后的视频角度 // 设置转码后的视频角度
recorder.setMetadata(ROTATE, String.valueOf(this.getVideoRotateFinally())); recorder.setVideoMetadata(ROTATE, String.valueOf(this.getVideoRotateFinally()));
// 设置视频比特率 // 设置视频比特率
recorder.setVideoBitrate(StringHelper.getFirst(this.getVideoBitRateFinally(), this.getAudioBitRateFinally())); recorder.setVideoBitrate(StringHelper.getFirst(this.getVideoBitRateFinally(), this.getAudioBitRateFinally()));
// 设置帧速率 // 设置帧速率
recorder.setFrameRate(grabber.getFrameRate()); recorder.setVideoQuality(this.videoFrameRate);
// 帧长度 // 帧长度
recorder.setTimestamp(grabber.getTimestamp()); recorder.setTimestamp(grabber.getTimestamp());
} }
...@@ -247,14 +274,15 @@ public class MediaParameter extends BaseVo { ...@@ -247,14 +274,15 @@ public class MediaParameter extends BaseVo {
public void initZipMp4() { public void initZipMp4() {
this.initMp4(); this.initMp4();
// 设置压缩帧 // 设置压缩帧
this.videoFrameRate = 15; this.videoFrameRate = StringHelper.getFirst(this.videoFrameRate, 15d);
// 设置压缩大小 // 设置压缩大小
this.videoSizeZip = 0.5f; this.videoSizeZip = StringHelper.getFirst(this.videoSizeZip, 0.25f);
// 设置视频压缩率 // 设置视频压缩率
this.videoBitRateZip = 0.5f; this.videoBitRateZip = StringHelper.getFirst(this.videoBitRateZip, 0.25f);
} }
public void initMp4() { public void initMp4() {
this.audioCodec = SOUND_CODEC_MP3;
this.videoCodeC = VIDEO_CODEC_MP4; this.videoCodeC = VIDEO_CODEC_MP4;
this.videoFormat = VIDEO_FORMAT_MP4; this.videoFormat = VIDEO_FORMAT_MP4;
this.videoPixelFormat = VIDEO_FIXED_MP4; this.videoPixelFormat = VIDEO_FIXED_MP4;
......
...@@ -65,8 +65,7 @@ public class TestMediaHelper { ...@@ -65,8 +65,7 @@ public class TestMediaHelper {
MediaParameter parameter = new MediaParameter(); MediaParameter parameter = new MediaParameter();
parameter.setVideoSizeZip(size); parameter.setVideoSizeZip(size);
parameter.setVideoBitRateZip(quote); parameter.setVideoBitRateZip(quote);
// parameter.setVideoFrameRate(15);
MediaHelper.convertVideo(file, toFile, parameter); MediaHelper.convertVideoMp4(file, toFile, parameter);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment