基于纯Java实现WAV音频切割的具体方案

 更新时间:2025年11月04日 08:47:46   作者:一键难忘  
在音频处理领域,FFmpeg 一直是开发者的首选工具,它功能强大,能处理几乎所有格式的音视频,但在某些应用场景中,我们希望摆脱对外部依赖的束缚,本文将介绍一种基于Java Sound API (javax.sound.sampled)的方案,实现一个纯Java的WAV音频切割工具,需要的朋友可以参考下

摘要

在音频处理领域,FFmpeg 一直是开发者的首选工具,它功能强大,能处理几乎所有格式的音视频。但在某些应用场景中,我们希望摆脱对外部依赖的束缚,尤其是在:

Java 原生项目中,不希望通过命令行调用外部程序;沙箱环境(如 Web 容器或受限服务器),无法执行 FFmpeg;轻量级音频工具开发中,只需简单的分割功能,不想打包数十 MB 的二进制文件。

本文将介绍一种基于 Java Sound API (javax.sound.sampled) 的方案,实现一个纯 Java 的 WAV 音频切割工具,无需依赖任何外部库或命令行。

在这里插入图片描述

一、背景与限制

在开始实现前,我们先了解一下Java Sound API 的局限性

  • 仅支持 PCM 编码的 WAV 文件,也就是“未压缩”的音频;
  • 对于 MP3、AAC 等压缩格式,需要额外的第三方库(如 mp3spijlayer);
  • 所以本文仅聚焦于 WAV (PCM) 文件的分割。

但优点也很明显:

✅ 不需要 FFmpeg、SoX 等外部依赖;
✅ 跨平台纯 Java 实现;
✅ 操作精确到帧,切割后的文件可以立即播放。

在这里插入图片描述

二、切割思路解析

WAV 文件的音频流由一系列帧(frame)组成,每一帧表示音频信号在某一时刻的采样结果。
切割的核心思想是:

根据起止时间计算出帧范围 → 跳过前面帧 → 读取目标帧 → 写入新文件。

整个过程可以概括为以下步骤:

读取源音频文件
使用 AudioSystem.getAudioInputStream() 打开 WAV 文件。

计算帧位置
根据音频采样率(frameRate)和起止时间(秒),计算出对应的帧区间。

跳过起始帧
通过 AudioInputStream.skip() 精确跳过前面的音频字节。

读取并写入新的音频段
创建一个新的 AudioInputStream,只包含目标帧数量,然后写入到目标 WAV 文件。

三、完整实现代码

以下是核心实现逻辑(已省略辅助类的定义部分,如 AudioTimeAudioPairTime)。

    /**
     * 切割音频
     * 只支持 PCM WAV 文件
     * @param originPath 原始地址
     * @param pairTime   切割时间对
     * @param targetPath 切割后的音频存在地址
     */
    public static void split(String originPath, AudioPairTime pairTime, String targetPath) throws IOException {

        AudioTime startTime = pairTime.getStartTime();
        AudioTime endTime = pairTime.getEndTime();

        File sourceFile = new File(originPath);
        try (AudioInputStream originalStream = AudioSystem.getAudioInputStream(sourceFile)) {

            AudioFormat format = originalStream.getFormat();
            int bytesPerFrame = format.getFrameSize();
            float frameRate = format.getFrameRate();

            long startFrame = (long) (startTime.toSeconds() * frameRate);
            long endFrame = (long) (endTime.toSeconds() * frameRate);
            long framesToRead = endFrame - startFrame;

            long skippedBytes = originalStream.skip(startFrame * bytesPerFrame);
            if (skippedBytes != startFrame * bytesPerFrame) {
                throw new IOException("无法跳转到指定开始帧");
            }

            try (AudioInputStream shortenedStream = new AudioInputStream(originalStream, format, framesToRead)) {
                File targetFile = new File(targetPath);
                AudioSystem.write(shortenedStream, AudioFileFormat.Type.WAVE, targetFile);
            }
        } catch (javax.sound.sampled.UnsupportedAudioFileException e) {
            throw new IOException("只支持 PCM WAV 文件", e);
        }
    }



四、核心要点解读

1. 时间到帧的转换

每秒钟音频包含 frameRate 帧,因此:

long startFrame = (long) (startTime.toSeconds() * frameRate);

举例:如果采样率为 44100Hz,想从第 10 秒切割,则应从第 10 * 44100 = 441000 帧开始。

2. 精确跳过字节

每一帧的大小由 frameSize 决定,单位是字节。
在 PCM 编码下:

frameSize = 声道数 × 每个样本的字节数

例如:16-bit 双声道音频 → 2 * 2 = 4 字节/帧

因此跳过 N 帧应跳过:

skipBytes = N * bytesPerFrame;

3. AudioInputStream 的截取机制

Java 提供的 AudioInputStream 支持限定读取帧数的构造函数:

new AudioInputStream(originalStream, format, framesToRead)

意味着即使源文件更长,输出流也会在读完指定帧数后自动结束。

4. 写出 WAV 文件

输出部分同样使用标准 API:

AudioSystem.write(shortenedStream, AudioFileFormat.Type.WAVE, targetFile);

无需手动维护文件头,Java 会自动写入 WAV 头部与数据块。

五、辅助时间类(可选设计)

为了让调用更直观,我们可以设计如下辅助类:

public class AudioTime {
    private int hour;
    private int minute;
    private int second;

    public double toSeconds() {
        return hour * 3600 + minute * 60 + second;
    }
}

public class AudioPairTime {
    private AudioTime startTime;
    private AudioTime endTime;
    // getter / setter ...
}

调用示例:

AudioPairTime segment = new AudioPairTime(
    new AudioTime(0, 0, 10),
    new AudioTime(0, 0, 20)
);
split("input.wav", segment, "output_cut.wav");

六、性能与可靠性

性能方面:
由于只进行字节级拷贝,不做解码/编码,处理速度接近文件 IO 的理论极限。
一个 100MB 的 WAV 文件可在数百毫秒内切割完成。

可靠性:
PCM WAV 是无压缩格式,帧之间无依赖关系,因此切割不会破坏数据结构,切片可直接播放。

七、局限与扩展方向

方面当前方案可扩展思路
格式支持仅支持 PCM WAV可接入 JLayer(MP3)或 Tritonus SPI
精度控制基于帧精度(毫秒级)若需采样点精度可使用 ByteBuffer 操作
批量处理单文件可批量循环分割多个片段
可视化可结合 JavaFX 或 Web 前端展示波形图

八、总结

本文介绍了一个纯 Java 实现的 WAV 音频切割方案,不依赖 FFmpeg 或任何第三方库。
它充分利用了 Java 自带的 AudioSystemAudioInputStream,能够在多平台环境中轻量、稳定地运行。

适用于:

Java 桌面工具;嵌入式或服务端音频预处理;自动化音频切片任务(如语音识别片段提取)。

通过本文的实践,我们证明了 Java 完全可以在不依赖 FFmpeg 的情况下,实现对 PCM WAV 音频的高效切割。
核心思想在于利用 AudioInputStream 对帧级数据的精准控制:根据时间计算帧范围、跳过无关帧、读取目标段并重新写出文件。

这种方式不仅保持了 纯 Java、跨平台、无外部依赖 的特性,还能在毫秒级实现稳定的音频截取,适合用于语音数据预处理、音频标注、语音识别等任务。

虽然目前仅限于未压缩的 WAV 文件,但该方案为进一步扩展(如 MP3 支持、批量切割、波形可视化)奠定了坚实基础,是构建轻量级音频处理模块的理想起点。

以上就是基于纯Java实现WAV音频切割的具体方案的详细内容,更多关于纯Java WAV音频切割的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot返回long,前端接收进度丢失,@JsonSerialize不生效问题

    SpringBoot返回long,前端接收进度丢失,@JsonSerialize不生效问题

    这篇文章主要介绍了SpringBoot返回long,前端接收进度丢失,@JsonSerialize不生效问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • springboot整合JPA过程解析

    springboot整合JPA过程解析

    这篇文章主要介绍了springboot整合JPA过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java不可变类机制浅析

    Java不可变类机制浅析

    所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。接下来通过本文给大家介绍Java不可变类机制,需要的朋友参考下
    2017-02-02
  • SpringBoot 多环境配置和启动详解

    SpringBoot 多环境配置和启动详解

    这篇文章主要为大家介绍了SpringBoot多环境配置和启动详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • SpringBoot整合RabbitMQ实现延迟队列和死信队列

    SpringBoot整合RabbitMQ实现延迟队列和死信队列

    RabbitMQ的死信队列用于接收其他队列中的“死信”消息,所谓“死信”,是指满足一定条件而无法被消费者正确处理的消息,死信队列通常与RabbitMQ的延迟队列一起使用,本文给大家介绍了SpringBoot整合RabbitMQ实现延迟队列和死信队列,需要的朋友可以参考下
    2024-06-06
  • Activiti常用类简介

    Activiti常用类简介

    这篇文章主要介绍了Activiti常用类,需要的朋友可以参考下
    2014-08-08
  • Java正则环视和反向引用功能与用法详解

    Java正则环视和反向引用功能与用法详解

    这篇文章主要介绍了Java正则环视和反向引用功能与用法,结合实例形式较为详细的分析了java正则环视与反向引用的相关概念与使用方法,需要的朋友可以参考下
    2018-01-01
  • 基于Java实现获取本地IP地址和主机名

    基于Java实现获取本地IP地址和主机名

    这篇文章主要介绍了基于Java实现获取本地IP地址和主机名,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Java怎么重命名 Amazon S3 中的文件和文件夹

    Java怎么重命名 Amazon S3 中的文件和文件夹

    在本文中,我们探讨了使用适用于 Java 的 AWS 开发工具包重命名 S3 存储桶中的文件和文件夹的方法,我们探索了两种不同的情况,它们使用相同的概念来重命名对象,用新名称复制它们并删除原始名称
    2023-10-10
  • StringUtils工具包中字符串非空判断isNotEmpty和isNotBlank的区别

    StringUtils工具包中字符串非空判断isNotEmpty和isNotBlank的区别

    今天小编就为大家分享一篇关于StringUtils工具包中字符串非空判断isNotEmpty和isNotBlank的区别,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12

最新评论