springboot使用jcodec检测视频编码的方法
1.场景:
因为业务需求需要,需要在上传的接口检测视频编码格式 (业务不支持HEVC编码),如果不满足格式,那么需要给出提示,让用户去转换一下格式再上传。
2.使用依赖:
<!-- JCodec核心包(MP4解析核心) -->
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId>
<version>0.2.3</version>
</dependency>
<!-- JCodec JavaSE扩展(文件IO支持) -->
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec-javase</artifactId>
<version>0.2.3</version>
</dependency>
3.相关代码:
/**
* 检测当前视频的编码格式,如果不符合则抛出业务异常
*
* @param absolutePath 原文件的位置
* @return 返回原来的地址
*/
private String checkVideoCode(String absolutePath) {
try {
String codeC = Mp4CodecsUtil.detectVideoCodecId2(absolutePath);
log.info("检测到视频编码格式为:{}", codeC);
boolean supportedCodec = Mp4CodecsUtil.checkCodecSupport(codeC);
log.info("编码格式是否支持: {}", supportedCodec);
if (!supportedCodec) {
throw new BusinessException("上传视频文件不支持HEVC编码,推荐转为H.264编码的mp4文件进行上传");
} else {
log.info("视频编码格式已支持,无需转码");
return absolutePath;
}
} catch (Exception e) {
log.error("视频编码检测或转码过程中出现异常", e);
throw new BusinessException("视频处理失败: " + e.getMessage());
}
}
4.工具类:
/**
* @ClassName Mp4CodecsUtil
* @Description mp4编码格式检测工具类
* @Author xianhao
* @Date 2025/9/25 10:58
*/
@Slf4j
public class Mp4CodecsUtil {
// 1. 定义【支持的编码Fourcc列表】(直接用Jcodec拿到的编码标识,不再依赖FFmpeg)
private static final List<String> SUPPORTED_CODECS = Arrays.asList(
"avc1", "h264", // H.264
"h263", // H.263
"mjpeg", // MJPEG
"wmv2", // WMV2
"vp80", "vp8", // VP8
"vp90", "vp9", // VP9
"hap" // Hap
);
/**
* 核心方法:判断视频编码是否在支持列表中
* @param codecFourcc Jcodec拿到的编码Fourcc(如avc1、vp80、mp4v等)
* @return 是否支持 true支持 false不支持
*/
public static boolean checkCodecSupport(String codecFourcc) {
if (codecFourcc == null || codecFourcc.isEmpty()) {
return false;
}
// 统一转小写,兼容大小写问题
String codecKey = codecFourcc.toLowerCase();
// 获取对应的FFmpeg编码ID
String readableCodecName = getReadableCodecName(codecKey);
log.info("checkCodecSupport 视频编码信息 - codecFourcc:{}, codecKey:{}, readableCodecName:{}",
codecFourcc, codecKey, readableCodecName);
// 判断编码是否在支持列表中(忽略大小写)
return SUPPORTED_CODECS.stream()
.anyMatch(supported -> supported.equalsIgnoreCase(codecFourcc));
}
/**
* 辅助:Jcodec编码标识 → 易读名称(兼容Java 8)
*/
private static String getReadableCodecName(String codecKey) {
// 先判空,避免空指针
if (codecKey == null) {
return "未知编码";
}
// 传统switch-case(Java 8支持)
switch (codecKey) {
case "avc1":
case "h264":
return "H.264 (AVC)";
case "h263":
return "H.263";
case "mjpeg":
return "MJPEG";
case "wmv2":
return "WMV2";
case "vp80":
case "vp8":
return "VP8";
case "vp90":
case "vp9":
return "VP9";
case "hap":
return "Hap";
case "mp4v":
case "mpeg4":
return "MPEG-4 (ASP)";
case "hvc1":
case "hev1":
return "H.265 (HEVC)";
// 默认值:返回原编码标识
default:
return codecKey;
}
}
/**
* 检测视频文件的编码格式ID
* @param videoPath 视频文件路径
* @return 编码格式ID(如avcodec.AV_CODEC_ID_HEVC)
* @throws Exception 检测失败时抛出异常
*/
public static String detectVideoCodecId2(String videoPath) throws Exception {
File videoFile = new File(videoPath);
if (!videoFile.exists() || !videoFile.isFile()) {
throw new IllegalArgumentException("视频文件不存在或不是有效文件: " + videoPath);
}
SeekableByteChannel inputChannel = null;
MP4Demuxer demuxer = null;
try {
// 1. 创建 Jcodec 通道和解析复用器
inputChannel = NIOUtils.readableChannel(videoFile);
demuxer = MP4Demuxer.createMP4Demuxer(inputChannel);
// 2. 获取视频轨道
List<DemuxerTrack> videoTracks = demuxer.getVideoTracks();
if (videoTracks.isEmpty()) {
throw new RuntimeException("未检测到视频轨道");
}
AbstractMP4DemuxerTrack videoTrack = (AbstractMP4DemuxerTrack) videoTracks.get(0);
TrakBox videoTrakBox = videoTrack.getBox();
log.info("视频轨道信息 - mdia:{}, name:{}, fourcc:{}",
videoTrakBox.getMdia(), videoTrakBox.getName(), videoTrakBox.getFourcc());
// 验证轨道类型
String handlerType = videoTrakBox.getHandlerType();
if (!"vide".equals(handlerType)) {
throw new RuntimeException("视频轨道类型错误,请检查视频文件");
}
SampleDescriptionBox stsd = videoTrakBox.getStsd();
if (stsd == null) {
throw new RuntimeException("StsdBox为空,无编码信息");
}
String fourcc = stsd.getFourcc();
String path = stsd.getBoxes().get(0).getHeader().getFourcc();
// 打印相关日志
log.info("StsdBox的标识(仅容器类型):{}", fourcc); // 输出stsd
log.info("视频真实编码格式(Fourcc):{}", path); // 输出mp4v
log.info("StsdBox体大小:{}", stsd.getHeader().getBodySize());
log.info("StsdBox总大小:{}", stsd.getHeader().getSize());
return path;
} finally {
// 确保资源释放
if (demuxer != null) {
try {
demuxer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputChannel != null) {
try {
inputChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总结:该方法完美解决了视频编码格式检测的问题。
以下是豆包小姐姐总结的思路过程:
方法功能与核心思路总结
该方法基于 Jcodec 库 解析 MP4 视频文件的底层盒子(Box)结构,提取并返回视频的真实编码格式(Fourcc 标识),核心逻辑可分为「前置校验→解析核心→资源释放」三层,具体如下:
1. 前置校验:确保解析基础有效
- 入参校验:将视频路径转为 File 对象,检查文件是否存在且为有效文件,否则抛出
IllegalArgumentException; - 资源声明:提前声明
SeekableByteChannel(可读通道)和MP4Demuxer(MP4 解复用器),为后续解析做准备。
2. 核心解析流程:逐层解析 MP4 盒子结构(关键依赖 Jcodec 对 MP4 格式的封装)
MP4 文件以「盒子(Box)」为核心组织单元,方法通过逐层提取关键盒子,获取编码信息:
| 步骤 | 操作逻辑 | 校验/关键动作 |
|---|---|---|
| 初始化解析器 | 创建 Jcodec 可读通道 + MP4 解复用器 | 基于视频文件构建解析核心依赖 |
| 提取视频轨道 | 从解复用器获取视频轨道列表 videoTracks |
若轨道列表为空,抛出运行时异常(无视频轨道) |
| 校验轨道有效性 | 取第一个视频轨道转为 AbstractMP4DemuxerTrack,获取其 TrakBox(轨道盒子) |
1. 打印轨道关键信息(mdia/name/fourcc); 2. 校验轨道 handlerType 是否为 "vide"(确保是视频轨道),非则抛异常 |
| 提取编码信息 | 从 TrakBox 中获取 SampleDescriptionBox(stsd)(样本描述盒子,存储编码核心信息) |
1. 若 stsd 为空,抛异常(无编码信息);2. 区分两类 Fourcc: - stsd.getFourcc():stsd 盒子自身的 Fourcc(仅容器类型标识);- stsd.getBoxes().get(0).getHeader().getFourcc():stsd 下第一个子盒子的 Fourcc(视频真实编码格式);3. 打印 stsd 相关日志(标识、大小等),返回真实编码的 Fourcc |
3. 资源释放:确保 IO 资源不泄漏
- 通过
finally块兜底:无论解析过程是否抛出异常,均关闭MP4Demuxer和SeekableByteChannel; - 关闭资源时捕获
IOException并打印栈轨迹,避免关闭失败导致的异常扩散。
关键特点与注意点
- 格式依赖:仅适配 MP4 格式(基于
MP4Demuxer),核心是解析 MP4 的底层盒子结构(TrakBox/stsd 是关键); - 可靠性保障:多层校验(文件有效性→轨道存在性→轨道类型→stsd 非空),避免解析中途出错;
- 日志设计:输出轨道、stsd 关键信息(Fourcc/大小),便于调试定位问题;
- 资源安全:finally 块强制释放 IO 资源,符合 Java 资源管理最佳实践;
- 核心区分:明确区分「容器层 Fourcc」(stsd 自身)和「编码层 Fourcc」(stsd 子盒子),返回的是真实视频编码格式。
核心价值
通过解析 MP4 原生的盒子结构,绕过封装层直接获取视频编码的原始标识(Fourcc),相比上层封装的格式判断更精准,适用于视频格式校验、编码转换前置检测等场景。
本文来自博客园,作者:程序员鲜豪,转载请注明原文链接:https://www.cnblogs.com/hg-blogs/p/19329739

浙公网安备 33010602011771号