SpringBoot 集成 FFmpeg 解析音视频

文章目录

    • 1 摘要
    • 2 核心 Maven 依赖
    • 3 核心代码
      • 3.1 FFmpeg 解析音视频工具类
      • 3.2 音视频文件信息参数
      • 3.3 音视频文件上传Controller
      • 3.4 application 配置文件
    • 4 测试数据
      • 4.1 视频文件解析
      • 4.2 音频文件解析
    • 5 注意事项
      • 5.1 文件必须在本地
    • 6 推荐参考文档
    • 7 Github 源码

1 摘要

FFmpeg 是最常用的跨平台的音频、视频处理软件,但是其通过命令行的方式进行操作对于普通用户而言上手难度大,同时 FFmpeg 只是函数库,对于不同的编程语言,需要自行适配API接口,以便于操作文件。本文介绍SpringBoot 集成 FFmpeg 实现对音视频文件的解析。

FFmpeg 官网: https://ffmpeg.org

FFmpeg Java 平台常用适配仓库:

JavaCV : https://github.com/bytedeco/javacv

JavaCV 是一个集成第三方函数库的平台,包括 OpenCV、FFmpeg 等知名函数库,提供统一的 API 操作,应用广泛。在不考虑应用程序体积大小的情况下推荐使用 JavaCV 作为集成方案。

JAVE2: https://github.com/a-schild/jave2

JAVE2 是将 FFmpeg 进行封装,并提供 Java API 以供用户操作音视频文件的依赖库。用户可以根据软件运行的操作系统来自由选择所需平台的依赖。

本文是基于 JAVE2 依赖来实现解析音视频文件的功能。

2 核心 Maven 依赖

demo-ffmpeg-media/pom.xml
        <!-- ffmpeg 音视频处理 -->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>${schild-ffmpeg.version}</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-win64</artifactId>
            <version>${schild-ffmpeg.version}</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-linux64</artifactId>
            <version>${schild-ffmpeg.version}</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-osx64</artifactId>
            <version>${schild-ffmpeg.version}</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-osxm1</artifactId>
            <version>${schild-ffmpeg.version}</version>
        </dependency>

其中 schild-ffmpeg 版本为:

<schild-ffmpeg.version>3.5.0</schild-ffmpeg.version>

这里分别引入了 Windows、Linux、macOS 系统的依赖,可根据软件运行环境进行删减。

3 核心代码

3.1 FFmpeg 解析音视频工具类

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/common/util/FFmpegMediaUtil.java
package com.ljq.demo.springboot.ffmpeg.common.util;

import com.ljq.demo.springboot.ffmpeg.model.response.AudioInfoResponse;
import com.ljq.demo.springboot.ffmpeg.model.response.VideoInfoResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;

import java.io.File;
import java.nio.file.Files;
import java.util.Objects;

/**
 * @Description: FFmpeg 音视频工具类
 * @Author: junqiang.lu
 * @Date: 2024/5/10
 */
@Slf4j
public class FFmpegMediaUtil {


    /**
     * 获取视频信息
     *
     * @param videoPath 视频路径
     * @return 视频信息
     */
    public static VideoInfoResponse getVideoInfo(String videoPath) {
        VideoInfoResponse response = null;
        try {
            // 解析文件
            File videoFile = new File(videoPath);
            MultimediaObject multimediaObject = new MultimediaObject(videoFile);
            MultimediaInfo multimediaInfo = multimediaObject.getInfo();
            VideoInfo videoInfo = multimediaInfo.getVideo();
            // 判断是否为视频
            if (Objects.isNull(videoInfo) || videoInfo.getBitRate() < 0) {
                return null;
            }
            response = new VideoInfoResponse();
            response.setFormat(multimediaInfo.getFormat())
                    .setDuration(multimediaInfo.getDuration() / 1000)
                    .setSize(videoFile.length())
                    .setMd5(DigestUtils.md5DigestAsHex(Files.readAllBytes(videoFile.toPath())));
            response.setBitRate(videoInfo.getBitRate())
                    .setFrameRate(videoInfo.getFrameRate())
                    .setWidth(videoInfo.getSize().getWidth())
                    .setHeight(videoInfo.getSize().getHeight());
            return response;
        } catch (Exception e) {
            log.warn("Error processing video file", e);
        }
        return response;
    }

    /**
     * 获取音频信息
     *
     * @param audioPath 音频路径
     * @return 音频信息
     */
    public static AudioInfoResponse getAudioInfo(String audioPath) {
        AudioInfoResponse response = null;
        try {
            // 解析文件
            File videoFile = new File(audioPath);
            MultimediaObject multimediaObject = new MultimediaObject(videoFile);
            MultimediaInfo multimediaInfo = multimediaObject.getInfo();
            AudioInfo audioInfo = multimediaInfo.getAudio();
            // 判断是否为音频
            if (Objects.isNull(audioInfo) || Objects.nonNull(multimediaInfo.getVideo())) {
                return null;
            }
            response = new AudioInfoResponse();
            response.setFormat(multimediaInfo.getFormat())
                    .setDuration(multimediaInfo.getDuration() / 1000)
                    .setSize(videoFile.length())
                    .setMd5(DigestUtils.md5DigestAsHex(Files.readAllBytes(videoFile.toPath())));
            response.setSamplingRate(audioInfo.getSamplingRate())
                    .setBitRate(audioInfo.getBitRate())
                    .setChannels(audioInfo.getChannels())
                    .setBitDepth(audioInfo.getBitDepth());
            return response;
        } catch (Exception e) {
            log.warn("Error processing audio file", e);
        }
        return response;
    }


}

3.2 音视频文件信息参数

音视频文件公共信息参数

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/MediaInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @Description: 多媒体信息
 * @Author: junqiang.lu
 * @Date: 2024/5/10
 */
@Data
@Accessors(chain = true)
public class MediaInfoResponse implements Serializable {

    private static final long serialVersionUID = -326368230008457941L;

    /**
     * 文件格式
     */
    private String format;

    /**
     * 时长,单位:秒
     */
    private Long duration;

    /**
     * 文件大小,单位:字节数
     */
    private Long size;

    /**
     * 文件md5值
     */
    private String md5;

}

视频文件参数信息对象

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/VideoInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * @Description: 视频文件信息返回对象
 * @Author: junqiang.lu
 * @Date: 2024/5/10
 */
@Data
@Accessors(chain = true)
@ToString(callSuper = true)
public class VideoInfoResponse extends MediaInfoResponse {

    private static final long serialVersionUID = -9016123624628502571L;

    /**
     * 比特率,单位: bps
     */
    private Integer bitRate;

    /**
     * 帧率,单位: FPS
     */
    private Float frameRate;

    /**
     * 宽度,单位: px
     */
    private Integer width;

    /**
     * 高度,单位: px
     */
    private Integer height;

}

音频文件参数信息对象

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/AudioInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * @Description: 音频文件信息返回对象
 * @Author: junqiang.lu
 * @Date: 2024/5/10
 */
@Data
@Accessors(chain = true)
@ToString(callSuper = true)
public class AudioInfoResponse extends MediaInfoResponse {

    private static final long serialVersionUID = 3573655613715240188L;


    /**
     * 采样率
     */
    private Integer samplingRate;

    /**
     * 音频通道数量,1-单声道,2-立体声
     */
    private Integer channels;

    /**
     * 比特率,单位: bps
     */
    private Integer bitRate;

    /**
     * 位深度
     */
    private String bitDepth;

}

3.3 音视频文件上传Controller

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/controller/FFmpegMediaController.java
package com.ljq.demo.springboot.ffmpeg.controller;

import com.ljq.demo.springboot.ffmpeg.common.config.UploadConfig;
import com.ljq.demo.springboot.ffmpeg.common.util.FFmpegMediaUtil;
import com.ljq.demo.springboot.ffmpeg.model.response.AudioInfoResponse;
import com.ljq.demo.springboot.ffmpeg.model.response.VideoInfoResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;

/**
 * @Description: FFmpeg 媒体文件处理控制层
 * @Author: junqiang.lu
 * @Date: 2024/5/10
 */
@Slf4j
@RestController
@RequestMapping(value = "/api/ffmpeg/media")
public class FFmpegMediaController {

    @Resource
    private UploadConfig uploadConfig;


    /**
     * 视频上传
     *
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/upload/video", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<VideoInfoResponse> uploadVideo(MultipartFile file) throws IOException {
        // 文件上传保存
        String videoFilePath = uploadConfig.getUploadPath() + File.separator + file.getOriginalFilename();
        log.info("videoFilePath: {}", videoFilePath);
        File videoFile = new File(videoFilePath);
        if (!videoFile.getParentFile().exists()) {
            videoFile.getParentFile().mkdirs();
        }
        file.transferTo(videoFile);
        // 获取视频信息
        VideoInfoResponse videoInfoResponse = FFmpegMediaUtil.getVideoInfo(videoFilePath);
        return ResponseEntity.ok(videoInfoResponse);
    }

    /**
     * 音频上传
     *
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/upload/audio", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<AudioInfoResponse> uploadAudio(MultipartFile file) throws IOException {
        // 文件上传保存
        String audioFilePath = uploadConfig.getUploadPath() + File.separator + file.getOriginalFilename();
        log.info("audioFilePath: {}", audioFilePath);
        File audioFile = new File(audioFilePath);
        if (!audioFile.getParentFile().exists()) {
            audioFile.getParentFile().mkdirs();
        }
        file.transferTo(audioFile);
        // 获取音频信息
        AudioInfoResponse audioInfoResponse = FFmpegMediaUtil.getAudioInfo(audioFilePath);
        return ResponseEntity.ok(audioInfoResponse);
    }

}

3.4 application 配置文件

# config
server:
  port: 9250


# spring
spring:
  application:
    name: demo-ffmpeg-media
  servlet:
    multipart:
      max-file-size: 1000MB
      max-request-size: 1000MB

# uploadConfig
upload:
  path: D:\\upload  # linux/macOS 路径 /opt/upload

4 测试数据

示例文件下载: https://zh.getsamplefiles.com

4.1 视频文件解析

测试文件:

https://zh.getsamplefiles.com/download/mp4/sample-4.mp4

测试结果:

{
    "format": "mov",
    "duration": 30,
    "size": 7588608,
    "md5": "d7b5155ee54ee9c7dcd8cbb5395823dc",
    "bitRate": 2015000,
    "frameRate": 25.0,
    "width": 1280,
    "height": 720
}

4.2 音频文件解析

测试文件:

https://zh.getsamplefiles.com/download/mp3/sample-5.mp3

测试结果:

{
    "format": "mp3",
    "duration": 45,
    "size": 1830660,
    "md5": "843e2916b1c552fb5e8ee3d83faddb8c",
    "samplingRate": 44100,
    "channels": 2,
    "bitRate": 320000,
    "bitDepth": "fltp"
}

5 注意事项

5.1 文件必须在本地

在实际项目中,一般会将文件保存到专门的文件服务器,但是 FFmpeg 解析文件必须是本地文件,因此在上传至文件服务器之前需要将文件在本地服务器做中转,解析完毕后再上传,然后删除本地文件。

6 推荐参考文档

SpringBoot集成ffmpeg实现视频转码播放

Convert video to Another Format in Spring Boot(Java-based apps)

springboot如何获取视频文件的视频时间长度

java 视频识别 java 视频转码

JAVE2 官方 Github

JAVE2 官方文档 Getting informations about a multimedia file

javacv-ffmpeg(八)视频文件信息获取

7 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo/tree/master/demo-ffmpeg-media

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/613348.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

element-plus表单上传,唯一替换文件校验,封装方法统一管理

<el-formref"ruleFormRef":model"ruleForm":rules"rules"label-width"110px" ><el-form-item label"语言成绩材料" prop"languageList"><div class"dis-flex w100"><el-uploadref…

Elastic Stack--04-1--Kibana查数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Kibana查数1.查询所有记录2.匹配id字段matchterm 3.bool[复合查询]4.业务查询 Kibana查数 在ElasticSearch中支持两种检索方式 通过使用REST request URL 发送检索…

计算机网络实验1:交换机基本配置管理

实验目的和要求 安装Packer Tracer&#xff0c;了解Packer Tracer的基本操作掌握交换机基本命令集实验项目内容 认识Packet Tracer软件 交换机的基本配置与管理 交换机的端口配置与管理 交换机的端口聚合配置 交换机划分Vlan配置 实验环境 硬件&#xff1a;PC机&#x…

UML之用例图

1.用例图 用例图指参与者&#xff0c;用例&#xff0c;边界以及它们之间的关系构成的用于描述系统功能的视图。说明是谁要使用系统&#xff0c;以及可以使用该系统可以做些什么。展示了一个外部用户能够观察到的系统功能模型图 2.用例图的元素 &#xff08;1&#xff09;参与…

视频拼接融合产品的产品与架构设计(二)

视频拼接融合产品的产品与架构设计一 以上是第一期&#xff0c;以前思考的时候还是比较着急&#xff0c;现在思考的更多了&#xff0c;现实世界的拼接更加需要我们沉下心来做&#xff0c;尤其是对于更多画面&#xff0c;画面更加清晰怎么做 本篇章不在于其他功能&#xff0c;在…

【记录】Python3| 将 PDF 转换成 HTML/XML(✅⭐PyMuPDF+tqdm)

本文将会被汇总至 【记录】Python3&#xff5c;2024年 PDF 转 XML 或 HTML 的第三方库的使用方式、测评过程以及对比结果&#xff08;汇总&#xff09;&#xff0c;更多其他工具请访问该文章查看。 文章目录 PyMuPDF 使用体验与评估1 安装指南2 测试代码3 测试结果3.1 转 HTML …

谷歌地图商家采集在外贸客户开发中的作用和意义

谷歌地图商家采集在外贸客户开发中扮演着至关重要的角色&#xff0c;其主要作用和意义体现在以下几个方面&#xff1a; 精准定位目标市场&#xff1a;通过谷歌地图&#xff0c;外贸人员可以根据特定的行业关键词&#xff08;如“fabric stores”&#xff09;搜索目标国家或地区…

《十日终焉》中的定律整理-向虫队学习(举例+持续更新)

1、二八定律 二八定律&#xff0c;又称帕累托法则&#xff0c;也叫巴莱多定律。 是19世纪末20世纪初意大利经济学家巴莱多发明的。其中指出&#xff0c;约仅有20%的因素影响80%的结果。也就是说&#xff1a;所有变因中&#xff0c;最重要的仅有20%&#xff0c;虽然剩余的80%占…

基于Laravel 10 + Vue(scui) + MySQL的快速开发的后台管理系统

​ 系统介绍 ​基于Laravel 10 Vue(scui) MySQL的快速开发的后台管理系统 版权申明 禁止将本产品用于含诈骗、赌博、色情、木马、病毒等违法违规业务使用。 代码仓库 gitee地址&#xff1a; 基础版本 内置模块 用户管理&#xff1a;用于维护管理系统的用户&#xff0c…

格雷希尔GripSeal:E10系列低压信号电测试连接器,应用于新能源汽车的DCR测试和EOL测试

新能源车的电驱动、电池包等都有一些信号接口&#xff0c;从几针到几十针不等&#xff0c;而且每种接口都有独特的电性能要求&#xff0c;这些接口在电池包进DCR测试或是EOL测试时&#xff0c;为了满足这些信号接口的需求&#xff0c;我们设计了E10系列信号针快速接头&#xff…

【吃透Java手写】4-Tomcat-简易版

【吃透Java手写】Tomcat-简易版-源码解析 1 准备工作1.1 引入依赖1.2 创建一个Tomcat的启动类 2 线程池技术回顾2.1 线程池的使用流程2.2 线程池的参数2.2.1 任务队列&#xff08;workQueue&#xff09;2.2.2 线程工厂&#xff08;threadFactory&#xff09;2.2.3 拒绝策略&…

JAVA使用Apache POI动态导出Word文档

文章目录 一、文章背景二、实现步骤2.1 需要的依赖2.2 创建模板2.3 书写java类2.3.1 模板目录2.3.2 Controller类2.3.2 工具类 2.4 测试2.4.1 浏览器请求接口2.4.2 下载word 三、注意事项四、其他导出word实现方式 一、文章背景 基于Freemarker模版动态生成并导出word文档存在弊…

vue----- watch监听$attrs 的注意事项

目录 前言 原因分析 解决方案 总结 前言 在 Vue 开发过程中&#xff0c;如遇到祖先组件需要传值到孙子组件时&#xff0c;需要在儿子组件接收 props &#xff0c;然后再传递给孙子组件&#xff0c;通过使用 v-bind"$attrs" 则会带来极大的便利&#xff0c;但同时…

酷开科技丨母亲节,别让有爱瞬间轻易溜走

在母亲节这个充满温情的节日里&#xff0c;酷开科技以“健健康康才能长长久久”为主题&#xff0c;推出了一系列关怀活动&#xff0c;旨在通过科技的力量&#xff0c;提升母亲们的身体素质和生活质量&#xff0c;同时也为儿女们提供了表达孝心和关怀的机会。 酷开系统特别上线…

【Vue】Vue的核心

目录 计算属性-computed插值语法实现methods实现计算属性实现使用使用总结&#xff1a; 监视属性-watch监视的两种写法&#xff1a;深度监视备注&#xff1a; computed和watch之间的区别 绑定样式class样式绑定字符串写法数组写法对象写法 style样式绑定对象式1对象式2数组式 条…

5/11后面部分:+顺序排序+元素交换+计算每门课程的各种成绩+存放规律的数据 注意:一味的复制肯定要出问题,第2个的最后一部分有修改,注意观察

目录 第一个已经输出过一次&#xff1a; 第二个: 编程实现&#xff1a;程序功能是用起泡法对数组中n个元素按从大到小的顺序进行排序。 ​编辑的确出现了一些问题哦&#xff1a; ​编辑目前是可以运行&#xff0c;但AI不给我们通过&#xff1a; 最后还是我的代码获胜&#x…

网络安全专业岗位详解+自学学习路线图

很多网安专业同学一到毕业就开始迷茫&#xff0c;不知道自己能去做哪些行业&#xff1f;其实网络安全岗位还是蛮多的&#xff0c;下面我会介绍一些网络安全岗位&#xff0c;大家可以根据自身能力与喜好决定放哪个方向发展。 渗透测试/Web安全工程师 主要是模拟黑客攻击&#…

vue3 antd-vue 超简单方式实现a-table跨页勾选

一、效果如下&#xff1a; 第一页勾选了2&#xff0c; 3&#xff0c; 4 翻到第三页勾选24&#xff0c; 25 回显&#xff0c;如比返回第一页的时候触发分页改变&#xff0c; 在映射中的第一页的数据给到a-table绑定的state.selectedRowKeys即可&#xff0c;如下方法 二、勾选思路…

初识多线程

1. 前置知识——进程 在学习多线程前需要了解操作系统中的基本知识&#xff0c;这里简单回顾下。 1.1 进程控制块 一个进程对应着一个进程控制块PCB&#xff0c;PCB是一个用于管理和维护进程信息的数据结构&#xff0c;这个数据结构中大致包含下面内容&#xff08;并不完整&…

头歌实践教学平台:CG1-v1.0-点和直线的绘制

第1关&#xff1a;OpenGL点的绘制 一. 任务描述 根据下面要求&#xff0c;在右侧修改代码&#xff0c;绘制出预期输出的图片。平台会对你编写的代码进行测试。 1.本关任务 熟悉编程环境&#xff1b; 了解光栅图形显示器的特点&#xff1b; 了解计算机绘图的特点&#xff1b…