基于純Java實(shí)現(xiàn)WAV音頻切割的具體方案
摘要
在音頻處理領(lǐng)域,FFmpeg 一直是開(kāi)發(fā)者的首選工具,它功能強(qiáng)大,能處理幾乎所有格式的音視頻。但在某些應(yīng)用場(chǎng)景中,我們希望擺脫對(duì)外部依賴(lài)的束縛,尤其是在:
Java 原生項(xiàng)目中,不希望通過(guò)命令行調(diào)用外部程序;沙箱環(huán)境(如 Web 容器或受限服務(wù)器),無(wú)法執(zhí)行 FFmpeg;輕量級(jí)音頻工具開(kāi)發(fā)中,只需簡(jiǎn)單的分割功能,不想打包數(shù)十 MB 的二進(jìn)制文件。
本文將介紹一種基于 Java Sound API (javax.sound.sampled) 的方案,實(shí)現(xiàn)一個(gè)純 Java 的 WAV 音頻切割工具,無(wú)需依賴(lài)任何外部庫(kù)或命令行。

一、背景與限制
在開(kāi)始實(shí)現(xiàn)前,我們先了解一下Java Sound API 的局限性:
- 它僅支持 PCM 編碼的 WAV 文件,也就是“未壓縮”的音頻;
- 對(duì)于 MP3、AAC 等壓縮格式,需要額外的第三方庫(kù)(如
mp3spi、jlayer); - 所以本文僅聚焦于 WAV (PCM) 文件的分割。
但優(yōu)點(diǎn)也很明顯:
? 不需要 FFmpeg、SoX 等外部依賴(lài);
? 跨平臺(tái)純 Java 實(shí)現(xiàn);
? 操作精確到幀,切割后的文件可以立即播放。

二、切割思路解析
WAV 文件的音頻流由一系列幀(frame)組成,每一幀表示音頻信號(hào)在某一時(shí)刻的采樣結(jié)果。
切割的核心思想是:
根據(jù)起止時(shí)間計(jì)算出幀范圍 → 跳過(guò)前面幀 → 讀取目標(biāo)幀 → 寫(xiě)入新文件。
整個(gè)過(guò)程可以概括為以下步驟:
讀取源音頻文件
使用 AudioSystem.getAudioInputStream() 打開(kāi) WAV 文件。
計(jì)算幀位置
根據(jù)音頻采樣率(frameRate)和起止時(shí)間(秒),計(jì)算出對(duì)應(yīng)的幀區(qū)間。
跳過(guò)起始幀
通過(guò) AudioInputStream.skip() 精確跳過(guò)前面的音頻字節(jié)。
讀取并寫(xiě)入新的音頻段
創(chuàng)建一個(gè)新的 AudioInputStream,只包含目標(biāo)幀數(shù)量,然后寫(xiě)入到目標(biāo) WAV 文件。
三、完整實(shí)現(xiàn)代碼
以下是核心實(shí)現(xiàn)邏輯(已省略輔助類(lèi)的定義部分,如 AudioTime、AudioPairTime)。
/**
* 切割音頻
* 只支持 PCM WAV 文件
* @param originPath 原始地址
* @param pairTime 切割時(shí)間對(duì)
* @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("無(wú)法跳轉(zhuǎn)到指定開(kāi)始幀");
}
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);
}
}
四、核心要點(diǎn)解讀
1. 時(shí)間到幀的轉(zhuǎn)換
每秒鐘音頻包含 frameRate 幀,因此:
long startFrame = (long) (startTime.toSeconds() * frameRate);
舉例:如果采樣率為 44100Hz,想從第 10 秒切割,則應(yīng)從第 10 * 44100 = 441000 幀開(kāi)始。
2. 精確跳過(guò)字節(jié)
每一幀的大小由 frameSize 決定,單位是字節(jié)。
在 PCM 編碼下:
frameSize = 聲道數(shù) × 每個(gè)樣本的字節(jié)數(shù)
例如:16-bit 雙聲道音頻 → 2 * 2 = 4 字節(jié)/幀。
因此跳過(guò) N 幀應(yīng)跳過(guò):
skipBytes = N * bytesPerFrame;
3. AudioInputStream 的截取機(jī)制
Java 提供的 AudioInputStream 支持限定讀取幀數(shù)的構(gòu)造函數(shù):
new AudioInputStream(originalStream, format, framesToRead)
意味著即使源文件更長(zhǎng),輸出流也會(huì)在讀完指定幀數(shù)后自動(dòng)結(jié)束。
4. 寫(xiě)出 WAV 文件
輸出部分同樣使用標(biāo)準(zhǔn) API:
AudioSystem.write(shortenedStream, AudioFileFormat.Type.WAVE, targetFile);
無(wú)需手動(dòng)維護(hù)文件頭,Java 會(huì)自動(dòng)寫(xiě)入 WAV 頭部與數(shù)據(jù)塊。
五、輔助時(shí)間類(lèi)(可選設(shè)計(jì))
為了讓調(diào)用更直觀,我們可以設(shè)計(jì)如下輔助類(lèi):
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 ...
}
調(diào)用示例:
AudioPairTime segment = new AudioPairTime(
new AudioTime(0, 0, 10),
new AudioTime(0, 0, 20)
);
split("input.wav", segment, "output_cut.wav");
六、性能與可靠性
性能方面:
由于只進(jìn)行字節(jié)級(jí)拷貝,不做解碼/編碼,處理速度接近文件 IO 的理論極限。
一個(gè) 100MB 的 WAV 文件可在數(shù)百毫秒內(nèi)切割完成。
可靠性:
PCM WAV 是無(wú)壓縮格式,幀之間無(wú)依賴(lài)關(guān)系,因此切割不會(huì)破壞數(shù)據(jù)結(jié)構(gòu),切片可直接播放。
七、局限與擴(kuò)展方向
| 方面 | 當(dāng)前方案 | 可擴(kuò)展思路 |
|---|---|---|
| 格式支持 | 僅支持 PCM WAV | 可接入 JLayer(MP3)或 Tritonus SPI |
| 精度控制 | 基于幀精度(毫秒級(jí)) | 若需采樣點(diǎn)精度可使用 ByteBuffer 操作 |
| 批量處理 | 單文件 | 可批量循環(huán)分割多個(gè)片段 |
| 可視化 | 無(wú) | 可結(jié)合 JavaFX 或 Web 前端展示波形圖 |
八、總結(jié)
本文介紹了一個(gè)純 Java 實(shí)現(xiàn)的 WAV 音頻切割方案,不依賴(lài) FFmpeg 或任何第三方庫(kù)。
它充分利用了 Java 自帶的 AudioSystem 與 AudioInputStream,能夠在多平臺(tái)環(huán)境中輕量、穩(wěn)定地運(yùn)行。
適用于:
Java 桌面工具;嵌入式或服務(wù)端音頻預(yù)處理;自動(dòng)化音頻切片任務(wù)(如語(yǔ)音識(shí)別片段提?。?。
通過(guò)本文的實(shí)踐,我們證明了 Java 完全可以在不依賴(lài) FFmpeg 的情況下,實(shí)現(xiàn)對(duì) PCM WAV 音頻的高效切割。
核心思想在于利用 AudioInputStream 對(duì)幀級(jí)數(shù)據(jù)的精準(zhǔn)控制:根據(jù)時(shí)間計(jì)算幀范圍、跳過(guò)無(wú)關(guān)幀、讀取目標(biāo)段并重新寫(xiě)出文件。
這種方式不僅保持了 純 Java、跨平臺(tái)、無(wú)外部依賴(lài) 的特性,還能在毫秒級(jí)實(shí)現(xiàn)穩(wěn)定的音頻截取,適合用于語(yǔ)音數(shù)據(jù)預(yù)處理、音頻標(biāo)注、語(yǔ)音識(shí)別等任務(wù)。
雖然目前僅限于未壓縮的 WAV 文件,但該方案為進(jìn)一步擴(kuò)展(如 MP3 支持、批量切割、波形可視化)奠定了堅(jiān)實(shí)基礎(chǔ),是構(gòu)建輕量級(jí)音頻處理模塊的理想起點(diǎn)。
以上就是基于純Java實(shí)現(xiàn)WAV音頻切割的具體方案的詳細(xì)內(nèi)容,更多關(guān)于純Java WAV音頻切割的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot返回long,前端接收進(jìn)度丟失,@JsonSerialize不生效問(wèn)題
這篇文章主要介紹了SpringBoot返回long,前端接收進(jìn)度丟失,@JsonSerialize不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
SpringBoot 多環(huán)境配置和啟動(dòng)詳解
這篇文章主要為大家介紹了SpringBoot多環(huán)境配置和啟動(dòng)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
SpringBoot整合RabbitMQ實(shí)現(xiàn)延遲隊(duì)列和死信隊(duì)列
RabbitMQ的死信隊(duì)列用于接收其他隊(duì)列中的“死信”消息,所謂“死信”,是指滿(mǎn)足一定條件而無(wú)法被消費(fèi)者正確處理的消息,死信隊(duì)列通常與RabbitMQ的延遲隊(duì)列一起使用,本文給大家介紹了SpringBoot整合RabbitMQ實(shí)現(xiàn)延遲隊(duì)列和死信隊(duì)列,需要的朋友可以參考下2024-06-06
基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名
這篇文章主要介紹了基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
StringUtils工具包中字符串非空判斷isNotEmpty和isNotBlank的區(qū)別
今天小編就為大家分享一篇關(guān)于StringUtils工具包中字符串非空判斷isNotEmpty和isNotBlank的區(qū)別,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12

