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 块兜底:无论解析过程是否抛出异常,均关闭 MP4DemuxerSeekableByteChannel
  • 关闭资源时捕获 IOException 并打印栈轨迹,避免关闭失败导致的异常扩散。

关键特点与注意点

  1. 格式依赖:仅适配 MP4 格式(基于 MP4Demuxer),核心是解析 MP4 的底层盒子结构(TrakBox/stsd 是关键);
  2. 可靠性保障:多层校验(文件有效性→轨道存在性→轨道类型→stsd 非空),避免解析中途出错;
  3. 日志设计:输出轨道、stsd 关键信息(Fourcc/大小),便于调试定位问题;
  4. 资源安全:finally 块强制释放 IO 资源,符合 Java 资源管理最佳实践;
  5. 核心区分:明确区分「容器层 Fourcc」(stsd 自身)和「编码层 Fourcc」(stsd 子盒子),返回的是真实视频编码格式。

核心价值

通过解析 MP4 原生的盒子结构,绕过封装层直接获取视频编码的原始标识(Fourcc),相比上层封装的格式判断更精准,适用于视频格式校验、编码转换前置检测等场景。

posted @ 2025-12-10 10:02  程序员鲜豪  阅读(0)  评论(0)    收藏  举报