SpringBoot中获取wav音频文件的属性

news2025/7/23 5:59:43

前言

wav文件定义

WAV 文件是以 WAVE 格式保存的音频文件,这是一种用于存储波形数据的标准数字音频文件格式。WAV 文件可能包含具有不同采样率和比特率的音频记录,但通常以 44.1 kHz、16 位、立体声格式保存,这是用于 CD 音频的标准格式。

wav文件结构

以下内容来源于 WAVE PCM soundfile format。

创建的标准 WAVE 格式如下:

文件具体含义如下:

例如,以下是 WAVE 文件的开头 72 个字节,其中字节显示为十六进制数字:

52 49 46 46 46 24 08 00 00 57 41 56 45 66 66 6D 74 20 10 10 00 00 00 00 02 00 02 00 22 00 
22 56 00 00 88 58 58 01 0004 00 104 00 10 00 64 61 74 61 74 61 0008 08 00 00 00 00 00 00 00 00 00 00 00 00 
24 17 17 24 17 17 24 17 17 00 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d

MultipartFile与File

在 Java 中,File 类是 java.io 包中唯一代表磁盘文件本身的对象,也就是说,如果希望在程序中操作文件和目录,则都可以通过 File 类来完成。File 类定义了一些方法来操作文件,如新建、删除、重命名文件和目录等。

File 类不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。

File 类提供了如下三种形式构造方法。

  1. File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。
  2. File(String path, String name):path 是路径名,name 是文件名。
  3. File(File dir, String name):dir 是路径对象,name 是文件名。

File 类位于 java.io 包下,类定义如下所示:

package java.io;
public class File implements Serializable, Comparable<File>

MultipartFile 是 Spring 类型,代表 HTML 中 form data 方式上传的文件,包含二进制数据+文件名称。

MultipartFile 类位于 org.springframework.web.multipart,类定义如下所示:

public interface MultipartFile extends InputStreamSource

Spring MultipartFile转换为File

1、多部分文件#getBytes

MultipartFile multipartFile = new MockMultipartFile("sourceFile.tmp", "Hello World".getBytes());

File file = new File("src/main/resources/targetFile.tmp");

try (OutputStream os = new FileOutputStream(file)) {
    os.write(multipartFile.getBytes());
}

assertThat(FileUtils.readFileToString(new File("src/main/resources/targetFile.tmp"), "UTF-8"))
  .isEqualTo("Hello World");

getBytes()方法对于我们想要在写入磁盘之前对文件执行额外操作的情况很有用,比如计算文件哈希。

2、MultipartFile#getInputStream

MultipartFile multipartFile = new MockMultipartFile("sourceFile.tmp", "Hello World".getBytes());

InputStream initialStream = multipartFile.getInputStream();
byte[] buffer = new byte[initialStream.available()];
initialStream.read(buffer);

File targetFile = new File("src/main/resources/targetFile.tmp");

try (OutputStream outStream = new FileOutputStream(targetFile)) {
    outStream.write(buffer);
}

assertThat(FileUtils.readFileToString(new File("src/main/resources/targetFile.tmp"), "UTF-8"))
  .isEqualTo("Hello World");

这里我们使用 getInputStream()方法获取 InputStream,从 InputStream 读取字节并将它们存储在 byte[] 缓冲区中。然后我们创建一个 File 和 OutputStream 来写入缓冲区内容。

getInputStream()方法在我们需要将 InputStream包装在另一个 InputStream 中的情况下很有用,例如如果上传的文件被 gzip 压缩,则为 GZipInputStream 。

3、多部分文件#transferTo

MultipartFile multipartFile = new MockMultipartFile("sourceFile.tmp", "Hello World".getBytes());

File file = new File("src/main/resources/targetFile.tmp");

multipartFile.transferTo(file);

assertThat(FileUtils.readFileToString(new File("src/main/resources/targetFile.tmp"), "UTF-8"))
  .isEqualTo("Hello World");

使用 transferTo()方法,我们只需创建要写入字节的文件,然后将该文件传递给 transferTo ()方法。

当只需要将 MultipartFile 写入 File 时,transferTo()方法很有用。

RandomAccessFile

  • RandomAccessFile 用于在文件的任意位置读写数据,并且不会消耗太多的内存。
  • RandomAccessFile 虽然属于 java.io 下的类,但它不是 InputStream 或者 OutputStream 的子类;它也不同于 FileInputStream 和 FileOutputStream。 FileInputStream 只能对文件进行读操作,而 FileOutputStream 只能对文件进行写操作。
  • RandomAccessFile 与输入流和输出流不同之处就是 RandomAccessFile 可以访问文件的任意地方同时支持文件的读和写,并且它通过 seek 方法实现在文件的任意位置读写访问。
  • RandomAccessFile 包含 InputStream 的三个 read 方法,也包含 OutputStream 的三个 write 方法。同时 RandomAccessFile 还包含一系列的 readXxx 和 writeXxx 方法完成输入输出。

关键方法

1、创建对象

//只读
RandomAccessFile raf = new RandomAccessFile(文件,"r");
//读写
RandomAccessFile raf = new RandomAccessFile(文件,"rw");

2、通过 seek 方法设置开始随机读写文件的位置,以字节为单位。

try (RandomAccessFile rdf = new RandomAccessFile(file, "r")) {
  int pos = 22;
  int length = 2;
  rdf.seek(pos);
  result = new byte[length];
  for (int i = 0; i < length; i++) {
    result[i] = rdf.readByte();
  }
}

file.deleteOnExit();

关于 RandomAccessFile 的更多知识讲解,推荐阅读 详解 RandomAccessFile 的使用以及使用场景分析

Jaudiotagger

Jaudiotagger 是用于标记音频文件中数据的音频标记库。它目前完全支持 Mp3、Mp4(Mp4 音频、M4a 和 M4p 音频)Ogg Vorbis、Flac 和 Wma,对 Wav 和 Real 格式的支持有限。

当下我们可以利用 Jaudiotagger 来获取音频文件的基本信息。

1、首先引入依赖

<dependency>
  <groupId>org</groupId>
  <artifactId>jaudiotagger</artifactId>
  <version>2.0.3</version>
</dependency>

2、核心方法

public void transferAudio(File file) {
  AudioFile audioFile;
  audioFile = new WavFileReader().read(file);
  if (Objects.isNull(audioFile)) {
    return;
  }
  AudioHeader audioHeader = audioFile.getAudioHeader();

  System.out.println("audio format: " + audioHeader.getFormat()); // 音频格式,1-PCM

  System.out.println("num channels: " + audioHeader.getChannels()); // 1-单声道;2-双声道

  System.out.println("sample rate: " + audioHeader.getSampleRate()); // 采样率、音频采样级别

  System.out.println("byte rate: " + audioHeader.getBitRate()); // 每秒波形的数据量

  System.out.println("block align: "); // 采样帧的大小

  System.out.println("音频时长:" + audioHeader.getTrackLength()); //单位为s
}

执行结果如下:

audio format: WAV-RIFF 16 bits
num channels: 1
sample rate: 48000
byte rate: 768
block align: 
音频时长:3

除了解析 Wav 文件,还可以解析 flac、mp3 等文件,如下图所示,该工具包中有其他音频格式的封装处理类。

相比于使用 RandomAccessFile 方式更加友好,不过两者可获取的基本属性稍有差异。

实操

介绍了那么多,又是 WAVE 文件结构,又是三种文件类,接下来我们进入正题,将获取 WAVE 文件的 header 信息,包括声道数、文件大小、采样率等等。

想要读取 WAVE 文件的 header 信息,常用的 File 类是无法实现的,在得知 WAVE 文件结构的前提下,我们选用 RandomAccessFile 类按字节来读取数据。因为 SpringBoot 接收的文件类型为 MultipartFile,而创建 RandomAccessFile 对象需要 File 对象,所以又涉及到 MultipartFile 转换为 File 的操作。

关于 Jaudiotagger 的示例就不演示了,稍微修改一下就完事了。

示例

这里沿用之前文章中的项目,只需稍作修改。

1、controller 层,接收 WAVE 文件。

@PostMapping("/picture")
public void uploadPicture(@RequestPart(value = "multipartFile") MultipartFile multipartFile) {
  userService.uploadPicture(multipartFile);
}

2、FileUtil 工具类,主要处理 MultipartFile 到 File 的转换,以及文件的复制。

public static File multipartFileToFile(MultipartFile multiFile) throws IOException {
  String fileName = multiFile.getOriginalFilename();
  assert fileName != null;
  String[] fileNameArr = fileName.split("\\.");

  // 根据multiFile的文件名创建一个临时File文件,暂无数据
  File file = File.createTempFile(fileNameArr[0] + "_temp", fileNameArr[1]);
  copyFileUsingFileChannels(multiFile, file);
  return file;
}

// 使用FileChannel来将MultipartFile的数据复制到File文件中
public static void copyFileUsingFileChannels(MultipartFile source, File dest) throws IOException {
  FileInputStream inputStream = (FileInputStream) source.getInputStream();
  FileChannel inputChannel = inputStream.getChannel();
  FileChannel outputChannel;
  try (FileOutputStream outputStream = new FileOutputStream(dest)) {
    outputChannel = outputStream.getChannel();
    outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
    inputChannel.close();
    outputChannel.close();
  }
}

File 类的 createTempFile()方法会在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。临时文件能够使用默认路径,可以避免存在创建文件是因为路径错误导致创建文件失败的问题。 如果需求中需要创建一个临时文件,这个临时文件可能作为存储使用,但在程序运行结束后需要删除文件,可以使用 deleteOnExit()方法。注意,deleteOnExit 是程序退出虚拟机时才会删除。

可能你对于 FileUtil 中的 copyFileUsingFileChannels()方法有些疑惑,为什么这里要复制数据,以及为什么不直接使用 MultipartFile 的 transferTo()方法?

我们尝试直接使用 transferTo()方法,不再使用 copyFileUsingFileChannels()方法,看看执行结果如何?

  public static File multipartFileToFile(MultipartFile multipartFile) throws IOException {
    String fileName = multipartFile.getOriginalFilename();
    assert fileName != null;
    String[] fileNameArr = fileName.split("\\.");

    File file = File.createTempFile(fileNameArr[0] + "_temp", "." + fileNameArr[1]);
    multipartFile.transferTo(file);

//    copyFileUsingFileChannels(multiFile, file);
    return file;
  }

执行正常流程后,发现控制台报错了,错误信息如下:

java.io.FileNotFoundException: /private/var/folders/fw/yn74xzcx70n0vw9yzyw528540000gn/T/tomcat.3075802565760283748.8081/work/Tomcat/localhost/ROOT/upload_d35d47f6_843a_4e39_83a3_92d66506b025_00000004.tmp (No such file or directory)

根据错误可知,找不到 MultipartFile 文件才导致报错,这是为什么呢?我们只是换用了 transferTo()方法,接下来我们 debug 调试一下,看看 transferTo()到底做了什么。

深入代码调用后,发现问题出在了 DiskFileItem 文件中的 write()方法,该方法中会调用 File 类的 renameTo()方法。

public boolean renameTo(File dest) {
  SecurityManager security = System.getSecurityManager();
  if (security != null) {
    security.checkWrite(path);
    security.checkWrite(dest.path);
  }
  if (dest == null) {
    throw new NullPointerException();
  }
  if (this.isInvalid() || dest.isInvalid()) {
    return false;
  }
  return fs.rename(this, dest);
}

继续往下挖掘:

//UnixFileSystem的实现
public boolean rename(File f1, File f2) {
  //清除路径解析的缓存
  cache.clear();
  javaHomePrefixCache.clear();
  return rename0(f1, f2);
}
private native boolean rename0(File f1, File f2);

涉及到本地方法,就不继续往下看了,这块代码核心逻辑是:调用 rename0()方法,是将 f1 文件移动并重命名,即 f2,所以 f1 文件就不存在了。

我们还可以用一个简单的案例来测试一下:

public static void main(String[] args) throws IOException {
  String imgPath = "xxx";
  String newImgPath = "xxxx";

  File file = new File(imgPath);
  File file2 = new File(newImgPath);
  file.renameTo(file2);

  FileInputStream inputStream = new FileInputStream(file);
}

执行上述代码会输出如下内容:

Exception in thread "main" java.io.FileNotFoundException: src/main/resources/static/icon.png (No such file or directory)

综上所述,我们没有使用 transferTo()方法,而是使用 FileChannel 来将 MultipartFile 的数据复制到 File 文件中

关于上述报错,还有一种情况,即创建 File 对象时使用了相对路径

3、service 层,创建 RandomAccessFile 对象,按字节来读取 WAVE 文件 header 信息。

@SneakyThrows
public void uploadPicture(MultipartFile multipartFile) {
  File file = FileUtil.multipartFileToFile(multipartFile);
  RandomAccessFile rdf = new RandomAccessFile(file, "r");

  System.out.println("audio size: " + getAudioSize(rdf)); // 音频文件大小

  System.out.println("audio format: " + getAudioFormat(rdf)); // 音频格式,1-PCM

  System.out.println("num channels: " + getAudioChannelCount(rdf)); // 1-单声道;2-双声道

  System.out.println("sample rate: " + getAudioSampleRate(rdf)); // 采样率、音频采样级别

  System.out.println("byte rate: " + getByteRate(rdf)); // 每秒波形的数据量

  System.out.println("block align: " + getBlockAlign(rdf)); // 采样帧的大小

  System.out.println("bits per sample: " + getBitsPerSample(rdf)); // 采样位数

  rdf.close();
  file.deleteOnExit();

  // 假设上传的文件还有其他用处
}

private short getAudioChannelCount(RandomAccessFile rdf) throws IOException {
  return toShort(read(rdf, 22, 2));
}

private int getAudioSize(RandomAccessFile rdf) throws IOException {
  return toInt(read(rdf, 4, 4));
}

private int getAudioFormat(RandomAccessFile rdf) throws IOException {
  return toShort(read(rdf, 20, 2));
}

private int getAudioSampleRate(RandomAccessFile rdf) throws IOException {
  return toInt(read(rdf, 24, 4));
}

private int getByteRate(RandomAccessFile rdf) throws IOException {
  return toInt(read(rdf, 28, 4));
}

private int getBlockAlign(RandomAccessFile rdf) throws IOException {
  return toShort(read(rdf, 32, 2));
}

private int getBitsPerSample(RandomAccessFile rdf) throws IOException {
  return toShort(read(rdf, 34, 2));
}

private int toInt(byte[] b) {
  return (b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0];
}

private short toShort(byte[] b) {
  return (short) ((b[1] << 8) + b[0]);
}

private byte[] read(RandomAccessFile rdf, int pos, int length) throws IOException {
  rdf.seek(pos);
  byte result[] = new byte[length];
  for (int i = 0; i < length; i++) {
    result[i] = rdf.readByte();
  }
  return result;
}

根据代码可知,想要读取 WAVE 文件的某一属性,指定开始读取的偏移量,然后再划好长度,就可以获取对应的十六进制数,再转换为十进制即可。

执行结果如下:

audio size: 272150
audio format: 1
num channels: 1
sample rate: -17792
byte rate: 96000
block align: 2
bits per sample: 16

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在React项目中引入字体文件并使用

一、背景 设计稿里某些文字所用的字体&#xff0c;系统默认不支持。 比如设计需要的这个字体&#xff1a;EmerlandRegular&#xff0c;即使在css里将文字字体设置为他们&#xff0c;实际效果也显示不出来。 二、现象及原因 1、样式 2、期待效果 3、实际效果 实际上是因为这个…

java设计模式之装饰器设计模式

介绍 装饰器设计模式是一种结构型设计模式&#xff0c;它允许动态地将行为添加到对象中&#xff0c;而无需在对象的类中使用子类化。它允许您通过将对象封装在一个具有新行为的对象中来动态地修改对象的行为。 这种模式是基于组合的思想&#xff0c;而不是继承。 可动态地将责…

CFS三层内网渗透

目录 环境搭建 拿ubuntu主机 信息收集 thinkphp漏洞利用 上线msf 添加路由建立socks代理 bagecms漏洞利用 拿下centos主机 msf上线centos 添加路由&#xff0c;建立socks代理 拿下win7主机 环境搭建 设置三块虚拟网卡 开启虚拟机验证&#xff0c;确保所处网段正确&a…

展会邀约 | 昂视与您相约BTF第12届上海锂电展

BTF第12届上海国际新能源锂电展将于3月7日在上海新国际博览中心举办。此次展会以“锂想动力&#xff0c;共创未来”为主题&#xff0c;汇聚行业内一众翘楚企业与专业观众&#xff0c;为各位展商以及观众提供专业的锂电交流平台&#xff0c;了解与碰撞新产品、新技术与解决方案&…

APISIX网关系列之Dashboard配置路由(二)

APISIX网关系列之Dashboard配置路由(二) 1.概述 APISIX作为系列介绍&#xff0c;将它所有的功能按照职责划分输出到每篇文章中。 上篇文章作为系列的开篇文章对APISIX进行了分析和安装介绍&#xff0c;查看详情地址&#xff1a;https://blog.csdn.net/m0_38039437/article/de…

【经典数据结构OJ讲解】你知道如何用两个队列实现一个栈,如何用两个栈实现一个队列吗?

目录 0.前言 1.回顾什么是队列和栈 2.如何用两个队列实现一个栈 2.1思路讲解 2.2按照思路实现仿生栈的各接口 2.2.1栈的初始化 2.2.2栈的销毁 2.2.3栈的插入 2.2.4栈的删除 2.2.5 栈的栈顶数据 2.2.6 判断当前栈是否为空 3.如何用两个栈实现一个队列 3.1 思路分析…

梯度下降优化器:SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam -> AdamW

目录 1 前言 2 梯度概念 3 一般梯度下降法 4 BGD 5 SGD 6 MBGD 7 Momentum 8 SGDM&#xff08;SGD with momentum&#xff09; 9 NAG(Nesterov Accelerated Gradient) 10 AdaGrad 11 RMSProp 12 Adadelta 13 Adam 13 Nadam 14 AdamW 15 Lion&#xff08;EvoLve…

js 实现 Logo(图片)根据图片后面的图片颜色而变化成相反的颜色【解决logo固定后 会出现与不同板块的颜色相同导致于看不清logo的情况】

效果展示&#xff1a; <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <meta http-equiv"X-UA-Compatible" content"ieedge"><style type"text/css…

进程概念(二)

文章目录进程概念&#xff08;二&#xff09;1. 进程状态1.1 阻塞和挂起状态1.2 进程状态1.2.1 进程查看S状态R状态1.2.2 D状态1.2.3 T状态1.2.4 t状态1.2.5 Z状态(僵尸状态)1.3 孤儿进程2. 环境变量2.1 背景2.2 认识环境变量2.3 获取环境变量2.4 环境变量是什么2.5 认识命令行…

vue:pdf.js使用细节/隐藏按钮/设置、获取当前页码/记录阅读进度/切换语言(国际化)

需求描述 在网页中预览pdf时&#xff0c;希望实现3点需求&#xff1a;1、隐藏一些功能按钮&#xff08;比如下载&#xff09;&#xff1b;2、打开pdf时自动定位到最后浏览的页&#xff08;记录阅读进度&#xff09;&#xff1b;3、实现国际化&#xff08;在代码中更改pdf插件使…

Java面试题-Spring框架

Spring框架 1. BeanFactory和ApplicationContext有何区别 BeanFactory是Spring最底层的接口&#xff0c;是IoC的核心&#xff0c;定义IoC的基本功能。 ​ BeanFactory具有&#xff1a;延迟实例化的特性。在启动的时候&#xff0c;不会实例化Bean&#xff0c;只有有需要从容器…

ESMM的理解和高频面试问题

ESMM的理解首先&#xff0c;理解部分主要是ESMM要解决什么问题&#xff0c;以及解决方案。弱未度过原文的可以查阅原论文。论文地址&#xff1a;https://arxiv.org/pdf/1804.07931.pdf实现代码&#xff1a;https://github.com/PaddlePaddle/PaddleRec/tree/master/models/multi…

2023最新谷粒商城笔记之购物车篇(全文总共13万字,超详细)

购物车 环境搭建 创建购物车项目 第一步、创建gulimall-cart服务&#xff0c;并进行降版本处理 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.8.RELEASE<…

angular框架表格自定义导出,ui组件库为【devExpress by devExtreme】导出插件为exceljs、file-saver

前言 使用的ui组件库为devExtreme注意&#xff1a;如果你没有使用这个组件库&#xff0c;那后续的代码可能对你不适用&#xff01;&#xff01;&#xff01;&#xff0c;因为devExtreme和exceljs是结合着来的 其地址如下&#xff1a; devexpress https://js.devexpress.com/ …

一文速学-Pandas查询索引操作详解+实例代码展示

目录 前言 一、按列表索引查询 查询单值 1.at(单值查询-loc) 2.iat(单值查询-iloc) 3. loc(行/列名索引查询) 4. iloc(行/列索引查询) 二、按条件查询 单条件查询 多条件查询 嵌套筛选 总结 前言 关于一文速学Pandas系列已经将基础部分内容更完&#xff0c;基础部分的…

ASEMI高压MOS管ASE65R330参数,ASE65R330图片

编辑-Z ASEMI高压MOS管ASE65R330参数&#xff1a; 型号&#xff1a;ASE65R330 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;650V 栅源电压&#xff08;VGS&#xff09;&#xff1a;20V 漏极电流&#xff08;ID&#xff09;&#xff1a;12.5A 功耗&#xff08;P…

[数据结构]:03-栈(C语言实现)

目录 前言 已完成内容 单链表实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-StackCommon.cpp 04-StackFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码。使用C引用主要是为了简…

跨境卖家必看的沃尔玛Walmart商家入驻教程

沃尔玛Walmart作为作为全球连锁超市的鼻祖&#xff0c;有不可比拟的知名度。当沃尔玛从线下延伸到线上后&#xff0c;就成为一个自带IP与流量的线上平台&#xff0c;在全世界都拥有数量庞大的消费者群体。所以龙哥就结合自己注册Walmart的过程给大家详细讲解一下。 Walmart卖家…

365智能云打印怎么样?365小票无线订单打印机好用吗?

365智能云打印怎么样&#xff1f;365智能云打印是有赞官方首推的订单小票打印机&#xff0c;荣获2016年有赞最佳硬件服务商。可以实现远程云打印&#xff0c;无需连接电脑&#xff0c;只需通过GPRS流量或者WIFI即可连接&#xff0c;不受地理位置和距离限制。365小票无线订单打印…

1W+企业都在用的数字化管理秘籍,快收藏!

企业数字化&#xff0c;绕不开的话题。 随着国家相继出台各种举措助力中小企业数字化转型&#xff0c;积极推动产业数字化转型&#xff0c;培育数字经济新生态&#xff0c;企业想要谋生存&#xff0c;求发展&#xff0c;必然需要做好数字化转型和管理。 本篇文章想跟大家一起…