--- title: 视频压缩 date: 2026-01-29 --- # 一、简介   视频码率(Bit Rate),指的是视频文件在单位时间内(通常是每秒)所包含的数据量。单位通常是 Mbps(兆比特每秒)或 Kbps(千比特每秒)。   码率越高:视频中包含的细节信息越多,画面越清晰、色彩越丰富、动态画面(如快速运动、爆炸等)越流畅,不容易出现马赛克或模糊。但代价是文件体积越大,传输所需的网络带宽也越高。码率越低:视频文件更小,传输更快,但在复杂画面中容易产生压缩瑕疵(如马赛克、模糊、色带等)。   如果不压缩,原始视频数据量极其庞大,一部2小时的未压缩1080p电影可能占用数TB的存储空间,普通硬盘无法承受,家庭宽带或移动网络无法实时传输如此巨大的数据量,视频播放会不断卡顿。   如果服务器的带宽为10M,适合 720p,勉强支持低码率 1080p。 # 二、引入依赖 ```xml ws.schild jave-all-deps 3.5.0 ``` # 三、工具类 ```java import lombok.extern.slf4j.Slf4j; import ws.schild.jave.Encoder; 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.VideoSize; import java.io.File; /** * 视频压缩工具类 * 提供将视频压缩到720p的静态方法 */ @Slf4j public class VideoCompressor { // 支持的视频格式 private static final String[] SUPPORTED_FORMATS = { ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv", ".webm", ".mpeg", ".mpg", ".3gp" }; // 压缩参数常量 private static final int TARGET_WIDTH = 1280; private static final int TARGET_HEIGHT = 720; private static final int VIDEO_BITRATE = 1500000; // 1.5 Mbps private static final int AUDIO_BITRATE = 128000; // 128 kbps private static final String VIDEO_CODEC = "libx264"; private static final String AUDIO_CODEC = "aac"; private static final String OUTPUT_FORMAT = "mp4"; private static final String OUTPUT_SUFFIX = "_720p.mp4"; private static final int MAX_BITRATE = 2500; // 2.5 Mbps private static final int MIN_BITRATE = 1000; // 1 Mbps /** * 压缩视频到720p,统一输出为MP4格式 * * @param inputFile 输入视频文件 * @return 压缩后的视频文件,如果不需要压缩则返回原文件 * @throws Exception 压缩过程中可能出现的异常 */ public static File compressTo720p(File inputFile) throws Exception { if (!isSupportedVideoFile(inputFile)) { log.info("文件格式不支持或文件不存在,直接返回源文件"); return inputFile; } // 判断是否需要压缩 if (!needCompression(inputFile)) { return inputFile; } File outputFile = createOutputFile(inputFile); Encoder encoder = new Encoder(); // 配置编码参数 EncodingAttributes encodingAttributes = createEncodingAttributes(); log.info("开始压缩视频: {} -> {}", inputFile.getName(), outputFile.getName()); encoder.encode(new MultimediaObject(inputFile), outputFile, encodingAttributes); log.info("视频压缩完成: {}", outputFile.getName()); return outputFile; } /** * 智能判断视频是否需要压缩 * 基于分辨率、码率、数据密度等多维度综合判断 */ public static boolean needCompression(File videoFile) throws Exception { MultimediaObject media = new MultimediaObject(videoFile); // 获取视频基本信息 double fileSizeMB = videoFile.length() / 1048576.0; // 转换为MB double duration = media.getInfo().getDuration() / 1000.0; int bitRateKbps = media.getInfo().getVideo().getBitRate() / 1000; // 转换为kbps // 获取视频分辨率 int width = media.getInfo().getVideo().getSize().getWidth(); int height = media.getInfo().getVideo().getSize().getHeight(); log.info("视频分析: 分辨率 {}x{}, 大小 {}MB, 时长 {}秒, 码率约 {}kbps", width, height, fileSizeMB, duration, bitRateKbps); // 判断逻辑:基于分辨率和码率的智能判断 boolean needsCompression = false; // 1. 如果分辨率已经<=720p,检查码率是否过高 if (height <= TARGET_HEIGHT && width <= TARGET_WIDTH) { if (bitRateKbps > MAX_BITRATE) { log.info("视频分辨率达标但码率({}kbps)过高,需要压缩", bitRateKbps); needsCompression = true; } else { log.info("视频分辨率已达720p或以下,且码率合理,无需压缩"); } } // 2. 如果分辨率>720p,但码率很低,说明已是优化过的视频,无需压缩 else if (height > TARGET_HEIGHT && bitRateKbps < MIN_BITRATE) { log.info("视频虽为{}p高分辨率,但码率({}kbps)已优化,无需压缩", height, bitRateKbps); } // 3. 高分辨率+高码率,需要压缩 else if (height > TARGET_HEIGHT && bitRateKbps >= MAX_BITRATE) { log.info("视频为{}p高分辨率且码率较高({}kbps),需要压缩至720p", height, bitRateKbps); needsCompression = true; } // 4. 其他情况(如异常数据)默认不压缩 else { log.info("视频参数异常,保守处理:不进行压缩"); } return needsCompression; } /** * 检查文件是否为支持的视频格式 */ private static boolean isSupportedVideoFile(File file) { if (file == null || !file.exists()) { return false; } String filename = file.getName().toLowerCase(); for (String format : SUPPORTED_FORMATS) { if (filename.endsWith(format)) { return true; } } return false; } /** * 创建输出文件对象 */ private static File createOutputFile(File inputFile) { String baseName = getFileBaseName(inputFile.getName()); String outputFilename = baseName + OUTPUT_SUFFIX; return new File(inputFile.getParent(), outputFilename); } /** * 获取文件名(不含扩展名) */ private static String getFileBaseName(String filename) { int dotIndex = filename.lastIndexOf('.'); return (dotIndex == -1) ? filename : filename.substring(0, dotIndex); } /** * 创建编码配置参数 */ private static EncodingAttributes createEncodingAttributes() { // 音频配置 AudioAttributes audio = new AudioAttributes(); audio.setCodec(AUDIO_CODEC); audio.setBitRate(AUDIO_BITRATE); // 视频配置 VideoAttributes video = new VideoAttributes(); video.setCodec(VIDEO_CODEC); video.setBitRate(VIDEO_BITRATE); video.setSize(new VideoSize(TARGET_WIDTH, TARGET_HEIGHT)); // 编码设置 EncodingAttributes attributes = new EncodingAttributes(); attributes.setOutputFormat(OUTPUT_FORMAT); attributes.setAudioAttributes(audio); attributes.setVideoAttributes(video); return attributes; } } ```   通过分辨率和码率的双重判断,确定是否需要压缩。 # 四、使用 ```java public void uploadFile(MultipartFile file, String filePath) { try { MultiValueMap body = new LinkedMultiValueMap<>(); File tempFile = convertMultipartFileToFile(file); log.info("临时文件地址:{}", tempFile.toPath()); log.info("开始压缩文件:{}", tempFile.getName()); File compressFile = VideoCompressor.compressTo720p(tempFile); log.info("完成压缩文件:{}", tempFile.getName()); log.info("压缩文件地址:{}", tempFile.toPath()); body.add("file", new FileSystemResource(compressFile)); aListClient.put() .uri("api/fs/form") .header(HttpHeaders.AUTHORIZATION, token) .header("File-Path", filePath) .contentType(MediaType.MULTIPART_FORM_DATA) .body(body) .retrieve() .body(Map.class); Files.deleteIfExists(compressFile.toPath()); Files.deleteIfExists(tempFile.toPath()); } catch (Exception e) { log.error("文件上传失败{}", e.getMessage()); throw new RuntimeException("文件上传失败", e); } } private File convertMultipartFileToFile(MultipartFile file) throws IOException { String suffix = (file.getOriginalFilename() != null && file.getOriginalFilename().contains(".")) ? file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")) : ".tmp"; Path tempFile = Files.createTempFile(System.currentTimeMillis() + "", suffix); file.transferTo(tempFile); return tempFile.toFile(); } ```