Commit 047d69fc authored by yanzg's avatar yanzg

视频转换

parent 81c26d88
......@@ -173,6 +173,16 @@ public class StringHelper {
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 @@
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.4.3</version>
<version>1.5.3</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.bytedeco.javacpp-presets</groupId>-->
<!--<artifactId>ffmpeg-platform</artifactId>-->
<!--<version>4.0.2-1.4.3</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.0.2-1.4.3</version>
<version>4.2.2-1.5.3</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
......
package com.yanzuoguang.util;
import com.yanzuoguang.util.log.Log;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.IplImage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
......@@ -13,6 +14,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 视频帮助类
* <p>
......@@ -67,23 +69,12 @@ public class MediaHelper extends ImageHelper {
* @param parameter 转码后的参数,在转码完成后该参数值会被改变
*/
public static void convertVideo(String fromFile, String toFile, MediaParameter parameter) throws IOException {
initFile(fromFile, toFile);
// 视频压缩参数初始化
if (parameter == null) {
parameter = new MediaParameter();
}
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);
try {
......@@ -98,22 +89,32 @@ public class MediaHelper extends ImageHelper {
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
toFile,
parameter.getVideoWidthFinally(),
parameter.getVideoHeightFinally()
parameter.getVideoHeightFinally(),
grabber.getAudioChannels()
);
parameter.init(grabber, recorder);
try {
// 转码没有图像
if (Frame) {
// 开始转换
// recorder.start(grabber.getFormatContext());
recorder.start();
Frame frame;
while ((frame = grabber.grabFrame(true, true, true, false)) != null) {
while ((frame = grabber.grab()) != null) {
// 从视频帧中获取图片
if (frame.image != null) {
recorder.record(frame);
}
// 音频帧写入输出流
if (frame.samples != null) {
recorder.record(frame);
}
}
} else {
// 开始转换
recorder.start(grabber.getFormatContext());
avcodec.AVPacket packet;
AVPacket packet;
while ((packet = grabber.grabPacket()) != null) {
recorder.recordPacket(packet);
}
......@@ -121,9 +122,8 @@ public class MediaHelper extends ImageHelper {
} finally {
// 设置转码后的视频角度
recorder.setMetadata(MediaParameter.ROTATE, String.valueOf(parameter.getVideoRotateFinally()));
recorder.release();
recorder.stop();
recorder.release();
recorder.close();
}
......@@ -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 {
if (file2.exists()) {
throw new RuntimeException("视频文件" + filePath + "不存在");
}
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(filePath);
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePath);
MediaParameter parameter = new MediaParameter();
try {
// 打开视频
ff.start();
// 获取视频的角度
String rotate = ff.getVideoMetadata(MediaParameter.ROTATE);
grabber.start();
parameter.init(grabber);
double rotate = parameter.getVideoRotateFinally();
// 整个视频的长度
int ffLength = ff.getLengthInFrames();
int ffLength = grabber.getLengthInFrames();
// 取得能够取得的帧数
index = Math.min(ffLength, index);
for (int i = 0; i <= ffLength; i++) {
Frame f = ff.grabImage();
Frame f = grabber.grabImage();
if (i == index) {
// 判断是否需要旋转
if (null != rotate && rotate.length() > 1) {
if (rotate != 0) {
// 旋转角度,防止图像不正确
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
opencv_core.IplImage src = converter.convert(f);
f = converter.convert(rotate(src, Integer.valueOf(rotate)));
IplImage src = converter.convert(f);
f = converter.convert(rotate(src, rotate));
}
doExecuteFrame(f, toFile, type);
......@@ -193,8 +209,8 @@ public class MediaHelper extends ImageHelper {
}
}
} finally {
ff.close();
ff.stop();
grabber.close();
grabber.stop();
}
}
......@@ -205,13 +221,13 @@ public class MediaHelper extends ImageHelper {
* @param angle
* @return
*/
public static opencv_core.IplImage rotate(opencv_core.IplImage src, int angle) {
opencv_core.IplImage img = opencv_core.IplImage.create(
public static IplImage rotate(IplImage src, double angle) {
IplImage img = IplImage.create(
src.height(), src.width(),
src.depth(), src.nChannels()
);
opencv_core.cvTranspose(src, img);
opencv_core.cvFlip(img, img, angle);
opencv_core.cvFlip(img, img, (int) angle);
return img;
}
......
......@@ -4,14 +4,12 @@ import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.log.Log;
import com.yanzuoguang.util.vo.BaseVo;
import io.swagger.annotations.ApiModelProperty;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import static org.bytedeco.javacpp.avcodec.AV_CODEC_ID_NONE;
import static org.bytedeco.javacpp.avutil.AV_PIX_FMT_NONE;
import static org.bytedeco.javacpp.avutil.AV_SAMPLE_FMT_NONE;
import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avutil.*;
/**
* 压缩视频参数
......@@ -20,9 +18,10 @@ import static org.bytedeco.javacpp.avutil.AV_SAMPLE_FMT_NONE;
*/
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 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";
/**
......@@ -49,7 +48,7 @@ public class MediaParameter extends BaseVo {
* 音频比特率压缩比,算法为: 最终比特率= 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 {
*/
@ApiModelProperty(notes = "视频宽度高度压缩比,算法为: 最终宽度=Math.max(minVideoWidth,videoWidth * videoSizeZip)," +
"最终高度:=Math.max(minVideoHeight,videoHeight * videoSizeZip)")
private float videoSizeZip = 1;
private float videoSizeZip = 0;
/**
* 视频转码的比特率,为0时表示采取原来的值
*/
......@@ -122,7 +121,7 @@ public class MediaParameter extends BaseVo {
* 视频比特率压缩比,算法为: 最终比特率= 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 {
* @return
*/
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 {
* @return
*/
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));
}
}
/**
* 音频最终高度
......@@ -174,6 +181,9 @@ public class MediaParameter extends BaseVo {
* 检查参数
*/
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) {
throw new RuntimeException("audioBitRateZip请在0~1之间");
}
......@@ -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.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());
}
......@@ -218,28 +228,45 @@ public class MediaParameter extends BaseVo {
* @param 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.setAudioCodec(this.audioCodec);
/*
recorder.setAudioCodec(this.audioCodec);
// 设置声道
recorder.setAudioChannels(grabber.getAudioChannels());
// 设置声音质量
recorder.setAudioQuality(recorder.getAudioQuality());
// 设置音频属性
// recorder.setSampleFormat(grabber.getSampleFormat());
// recorder.setSampleRate(grabber.getSampleRate());
recorder.setSampleFormat(grabber.getSampleFormat());
recorder.setSampleRate(grabber.getSampleRate());
/* */
// 视频格式
recorder.setFormat(this.videoFormat);
// 设置转码视频格式
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.setFrameRate(grabber.getFrameRate());
recorder.setVideoQuality(this.videoFrameRate);
// 帧长度
recorder.setTimestamp(grabber.getTimestamp());
}
......@@ -247,14 +274,15 @@ public class MediaParameter extends BaseVo {
public void initZipMp4() {
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() {
this.audioCodec = SOUND_CODEC_MP3;
this.videoCodeC = VIDEO_CODEC_MP4;
this.videoFormat = VIDEO_FORMAT_MP4;
this.videoPixelFormat = VIDEO_FIXED_MP4;
......
......@@ -65,8 +65,7 @@ public class TestMediaHelper {
MediaParameter parameter = new MediaParameter();
parameter.setVideoSizeZip(size);
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