1. 引入 ws.schild MAVEN 依赖:
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-win64</artifactId>
<version>3.5.0</version>
</dependency>
2. MyVideoUtils:
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.info.VideoSize;
import ws.schild.jave.progress.EncoderProgressListener;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.Set;
public class MyVideoUtils {
public static void main(String[] args) throws EncoderException {
// 压缩前文件路径
File source = new File("D:\\素材\\视频\\video_0001.mp4");
// 压缩后的文件路径
File target = new File("D:\\素材\\视频\\video_0001_output1.mp4");
compress( source,target,0.3 );
}
public static void compress(File sourceVideoFile, File targetVideoFile, Double scale) throws EncoderException {
try {
System.out.println("---------------开始压缩---------------");
long start = System.currentTimeMillis();
// 尺寸的比例
BigDecimal scale_size = BigDecimal.valueOf(scale);
// 码率相关的比率
BigDecimal scale_rate = BigDecimal.valueOf(scale)
.multiply(BigDecimal.valueOf(scale));
// 输出源视频的信息
MultimediaObject multimediaObject_source = new MultimediaObject(sourceVideoFile);
MultimediaInfo multimediaInfo_source = multimediaObject_source.getInfo();
AudioInfo audioInfo_source = multimediaInfo_source.getAudio();
VideoInfo videoInfo_source = multimediaInfo_source.getVideo();
// 时长
long seconds = multimediaInfo_source.getDuration() / 1000L;
// 每秒几帧
float frameRate = videoInfo_source.getFrameRate();
System.out.println( "seconds = " + seconds );
System.out.println( "frameRate = " + frameRate );
int totalFrame = BigDecimal.valueOf(seconds).multiply(BigDecimal.valueOf(frameRate)).intValue();
System.out.println( "totalFrame = " + totalFrame );
System.out.println( "原视频 bitRate = " + videoInfo_source.getBitRate() );
System.out.println( "原视频 frameRate = " + videoInfo_source.getFrameRate() );
System.out.println( "原视频 decoder = " + videoInfo_source.getDecoder() );
VideoSize videoSize_source = videoInfo_source.getSize();
System.out.println( "源视频宽x高:" + videoSize_source.getWidth() + "x" + videoSize_source.getHeight() );
Map<String, String> metadata = videoInfo_source.getMetadata();
Set<String> keys = metadata.keySet();
for( String key:keys ){
System.out.println( key + " = " + metadata.get( key ) );
}
System.out.println();
// 音频编码属性配置
AudioAttributes audioAttributes = new AudioAttributes();
audioAttributes.setCodec("libmp3lame");
int audioBitRate_new = BigDecimal.valueOf(audioInfo_source.getBitRate()).multiply(scale_rate).intValue();
System.out.println( "audioBitRate_new = " + audioBitRate_new );
audioAttributes.setBitRate( audioBitRate_new );
// 设置重新编码的音频流中使用的声道数(1 =单声道,2 = 双声道(立体声))
audioAttributes.setChannels(1);
// int audioSamplingRate_new = BigDecimal.valueOf(audioInfo_source.getSamplingRate()).multiply(scale_rate).intValue();
// System.out.println( "audioSamplingRate_new = " + audioSamplingRate_new );
// todo 设置此值报错 "ws.schild.jave.EncoderException: Exit code of ffmpeg encoding run is 1",暂不知道什么原因???
// audioAttributes.setSamplingRate( audioSamplingRate_new );
// 视频编码属性配置
VideoAttributes videoAttributes = new VideoAttributes();
// 设置编码
// videoAttributes.setCodec("mpeg4");
videoAttributes.setCodec( "h264" );
int videoBitRate_new = BigDecimal.valueOf(videoInfo_source.getBitRate()).multiply(scale_rate).intValue();
System.out.println( "videoBitRate_new = " + videoBitRate_new );
videoAttributes.setBitRate( videoBitRate_new );
int newHeight = BigDecimal.valueOf(videoInfo_source.getSize().getHeight()).multiply(scale_size).intValue();
int newWidth = BigDecimal.valueOf(videoInfo_source.getSize().getWidth()).multiply(scale_size).intValue();
// 新的宽高都必须是2的整数倍!!!
newHeight = MyMathUtils.getClosestNuumberThatCanBeDividedBy2( newHeight );
newWidth = MyMathUtils.getClosestNuumberThatCanBeDividedBy2( newWidth );
VideoSize videoSize_new = new VideoSize( newWidth,newHeight );
videoAttributes.setSize( videoSize_new );
// 编码设置
EncodingAttributes encodingAttributes = new EncodingAttributes();
encodingAttributes.setOutputFormat("mp4");
encodingAttributes.setAudioAttributes( audioAttributes );
encodingAttributes.setVideoAttributes( videoAttributes );
// 设置值编码
Encoder encoder = new Encoder();
// encoder.encode( multimediaObject_source, targetVideoFile, encodingAttributes );
// 压缩转换进度监听
EncoderProgressListener encoderProgressListener = new EncoderProgressListener() {
@Override
public void sourceInfo(MultimediaInfo info) {
}
@Override
public void progress(int permil) {
double processPercent = BigDecimal.valueOf(permil)
.divide(BigDecimal.valueOf(1000d), 4, RoundingMode.HALF_UP)
.doubleValue();
System.out.println( "压缩转换进度:" + MyMathUtils.double2PercentFormat( processPercent ) );
}
@Override
public void message(String message) {
System.out.println( "message ------------------------------> " + message );
}
};
encoder.encode( multimediaObject_source, targetVideoFile, encodingAttributes,encoderProgressListener );
System.out.println("---------------结束压缩---------------");
long end = System.currentTimeMillis();
System.out.println("压缩前大小:"+ MyMathUtils.byte2M( sourceVideoFile.length() ) + "M,压缩后大小:" + MyMathUtils.byte2M( targetVideoFile.length() ) + "M" );
System.out.println("压缩耗时:" + MyMathUtils.mill2Second( ( end - start ) ) + "秒" );
} catch (EncoderException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
3. MyMathUtils:
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
public class MyMathUtils {
public static Integer getScale(Double num) {
if( num == null || num == 0d ){
return 0;
}
double decimalPart = BigDecimal.valueOf(num).subtract(BigDecimal.valueOf(num.intValue())).doubleValue();
if( decimalPart == 0d ){
return 0;
}
String decimalPartStr = String.valueOf(decimalPart);
String decimalPartStr1 = decimalPartStr.substring( decimalPartStr.indexOf(".") + 1 );
return decimalPartStr1.length();
}
/**
* 计算 Double 集合中不为空并且大于0的元素的个数
* @param nums
* @return
*/
public static Integer calculateCountForNotNullAndBiggerThanZero(List<Double> nums) {
if( nums == null || nums.size()== 0 ){
return 0;
}
Integer count = 0;
for( Double num:nums ){
if( num != null && num > 0d ){
count++;
}
}
return count;
}
public static Double calculateSum(List<Double> nums) {
if( nums == null || nums.size() == 0 ){
return 0d;
}
BigDecimal sum = BigDecimal.ZERO;
for( Double num:nums ){
if( num == null || num == 0d ){
continue;
}
sum = sum.add( BigDecimal.valueOf( num ) );
}
return sum.doubleValue();
}
public static Double nullDouble2Zero(Double num) {
if( num == null ){
return 0d;
}
return num;
}
public static Integer nullInteger2Zero(Integer num) {
if( num == null ){
return 0;
}
return num;
}
public static Double float2Double(Float f) {
if( f == null ){
return null;
}
return f.doubleValue();
}
public static String double2PercentFormat(Double d) {
if( d == null ){
d = 0d;
}
double d_percent = BigDecimal.valueOf(d).multiply(BigDecimal.valueOf(100d)).setScale(4, RoundingMode.HALF_UP).doubleValue();
return d_percent + "%";
}
public static Double byte2M( Long byteLenth ){
return BigDecimal.valueOf( byteLenth )
.divide( BigDecimal.valueOf( 1024d ),4,RoundingMode.HALF_UP )
.divide( BigDecimal.valueOf( 1024d ),4,RoundingMode.HALF_UP )
.doubleValue();
}
public static Double mill2Second( Long mill ){
return BigDecimal.valueOf( mill )
.divide( BigDecimal.valueOf( 1000d ),4,RoundingMode.HALF_UP )
.doubleValue();
}
/**
* 获得与传入的数字 num 最接近的能被2整除的数字
* @param num
* @return
*/
public static int getClosestNuumberThatCanBeDividedBy2(int num) {
int result = BigDecimal.valueOf(BigDecimal.valueOf(num)
.divide(BigDecimal.valueOf(2d), 2, RoundingMode.HALF_UP)
.intValue())
.multiply(BigDecimal.valueOf(2d))
.intValue();
return result;
}
}
4. 将压缩后的文件上传至文件服务器( 例如 minio )获得在线 url,例如 :https://xxx.xxx.com/minio/xxx-bucket/20240705131522-124765sd65sad65sa6d7asd6sa7d56235e675sadasd.mp4,并使用 ht video 标签播放:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MP4 Video Player Demo</title>
</head>
<body>
<video width="320" height="240" controls>
<source src="https://xxx.xxx.com/minio/xxx-bucket/20240705131522-124765sd65sad65sa6d7asd6sa7d56235e675sadasd.mp4" type="video/mp4">
您的浏览器不支持Video标签。
</video>
</body>
</html>
播放效果如下:
只有声音没有画面,但是播放压缩之前的原始视频就ok,使用 vlc查看下视频编码信息:
发现编解码器不一样,于是尝试将编解码器换成H264试一下:
// videoAttributes.setCodec("mpeg4");
videoAttributes.setCodec( "h264" );
重新压缩后播放出画面了: