基于純Java實(shí)現(xiàn)WAV音頻切割的具體方案
摘要
在音頻處理領(lǐng)域,FFmpeg 一直是開(kāi)發(fā)者的首選工具,它功能強(qiáng)大,能處理幾乎所有格式的音視頻。但在某些應(yīng)用場(chǎng)景中,我們希望擺脫對(duì)外部依賴的束縛,尤其是在:
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ú)需依賴任何外部庫(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 等外部依賴;
? 跨平臺(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)邏輯(已省略輔助類的定義部分,如 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í)間類(可選設(shè)計(jì))
為了讓調(diào)用更直觀,我們可以設(shè)計(jì)如下輔助類:
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ú)依賴關(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 音頻切割方案,不依賴 FFmpeg 或任何第三方庫(kù)。
它充分利用了 Java 自帶的 AudioSystem 與 AudioInputStream,能夠在多平臺(tái)環(huán)境中輕量、穩(wěn)定地運(yùn)行。
適用于:
Java 桌面工具;嵌入式或服務(wù)端音頻預(yù)處理;自動(dòng)化音頻切片任務(wù)(如語(yǔ)音識(shí)別片段提?。?/p>
通過(guò)本文的實(shí)踐,我們證明了 Java 完全可以在不依賴 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ú)外部依賴 的特性,還能在毫秒級(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)文章
java圖片色階調(diào)整和亮度調(diào)整代碼示例
這篇文章主要介紹了java圖片色階調(diào)整和亮度調(diào)整代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
Spring整合Kaptcha谷歌驗(yàn)證碼工具的開(kāi)發(fā)步驟
這篇文章主要介紹了Spring整合Kaptcha谷歌驗(yàn)證碼工具的開(kāi)發(fā)步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
java利用DFA算法實(shí)現(xiàn)敏感詞過(guò)濾功能
在最近的開(kāi)發(fā)中遇到了敏感詞過(guò)濾,便去網(wǎng)上查閱了很多敏感詞過(guò)濾的資料,在這里也和大家分享一下自己的理解。下面這篇文章主要給大家介紹了關(guān)于java利用DFA算法實(shí)現(xiàn)敏感詞過(guò)濾功能的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-06-06
使用Java實(shí)現(xiàn)文件流轉(zhuǎn)base64
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)文件流轉(zhuǎn)base64效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
Java中通過(guò)ZipOutputStream類如何將多個(gè)文件打成zip
ZipOutputStream?是Java中用于創(chuàng)建ZIP文件的類,它是?java.util.zip?包中的一部分,通過(guò)使用?ZipOutputStream?,可以將多個(gè)文件壓縮到一個(gè)ZIP文件中,這篇文章主要介紹了Java中(ZipOutputStream)如何將多個(gè)文件打成zip,需要的朋友可以參考下2023-09-09
聊聊@RequestMapping和@GetMapping @PostMapping的區(qū)別
這篇文章主要介紹了@RequestMapping和@GetMapping及@PostMapping的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
java虛擬機(jī)深入學(xué)習(xí)之內(nèi)存管理機(jī)制
java虛擬機(jī)在程序運(yùn)行時(shí)將內(nèi)存劃分為多個(gè)區(qū)域,每個(gè)區(qū)域作用,生命周期各不相同,下面這篇文章主要給大家介紹了關(guān)于java虛擬機(jī)深入學(xué)習(xí)之內(nèi)存管理機(jī)制的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-11-11
java后端操作樹(shù)結(jié)構(gòu)的案例代碼
這篇文章主要介紹了java后端操作樹(shù)結(jié)構(gòu),樹(shù)結(jié)構(gòu)的三種組裝方式(遞歸.雙層for循環(huán),map),通過(guò)實(shí)例代碼介紹了使用遞歸查詢某個(gè)節(jié)點(diǎn)所在的樹(shù)結(jié)構(gòu),需要的朋友可以參考下2023-10-10
基于Java實(shí)現(xiàn)中文分詞系統(tǒng)的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)易的中文分詞系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-07-07

