發布日期: 2026年6月30日
難度: 中高級
所需時間: 約 30-40 分鐘
前言
將 M4A、FLAC、WAV 或 OGG 音訊檔案轉換為常見的 MP3 格式,是日常工作中十分高頻的需求。網路上雖有許多免費的轉檔網站,但大部分都強迫使用者將音訊檔案「上傳至雲端伺服器」進行轉換。這不僅耗費上傳流量與等待時間,更存在個人隱私音檔外洩的極大安全隱憂。
這篇文章將帶大家使用 HTML5 原生的 Web Audio API,配合輕量開源 MP3 編碼庫 lamejs,實作一個完全在本機瀏覽器內運算、秒速解碼、具備雙播放器預覽且注重安全私密的「線上音訊格式轉換工具」!
前端音訊解碼與編碼架構
在純前端環境中處理音訊轉換,一般分為兩個核心步驟:
1. 解碼 (Decoding): 將各種封裝格式(如 M4A、MP3、FLAC 等)轉為未壓縮的原始 PCM (Pulse Code Modulation) 浮點數音軌數據。我們直接使用瀏覽器原生的 AudioContext.decodeAudioData() 即可高速完成,不需外掛任何解碼庫。 2. 編碼 (Encoding): 將解碼獲得的 PCM 數據重新封裝編碼為目標格式。
- WAV 格式:結構簡單,我們可直接用原生 JavaScript 寫入標準 RIFF 檔頭與 16-bit 採樣點數據,實現「無損 WAV 匯出」。
- MP3 格式:結構複雜,我們引入輕量級 MP3 編碼庫
lamejs,並設計「分片(Chunked)非同步編碼」以避免瀏覽器介面卡死。
第一步:規劃 HTML5 + CSS 介面
我們規劃了兩欄式的高質感毛玻璃(Glassmorphism)介面:
- 左側設定面板:包含目標輸出格式(MP3/WAV)、位元率(Bitrate)、採樣率與聲道設定。
- 右側工作區:包含拖曳上傳區、檔案詳細資訊卡、原始音訊播放器、即時編碼進度條、以及轉換後的音訊播放器與下載按鈕。
<div class="app-container">
<!-- 左側面板:輸出參數調整 -->
<div class="card controls-panel">
<h2><i class="fa-solid fa-sliders"></i> 轉換項目設定</h2>
<!-- 格式、位元率、採樣率下拉選單 -->
</div>
<!-- 右側面板:上傳與播放器 -->
<div class="card converter-workspace" id="drop-container">
<!-- 拖曳上傳 -->
<!-- 原檔播放器 -->
<audio id="audio-original" controls></audio>
<!-- 即時進度條 -->
<!-- 轉換成功播放器與下載按鈕 -->
</div>
</div>
第二步:使用 Web Audio API 解碼音軌
當使用者選擇檔案後,我們使用 FileReader 將檔案讀取為 ArrayBuffer,並交由 AudioContext 進行本地端音軌解碼:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const decodedBuffer = await audioCtx.decodeAudioData(arrayBuffer);
// 解碼後可取得:
// decodedBuffer.sampleRate (採樣率)
// decodedBuffer.numberOfChannels (聲道數)
// decodedBuffer.duration (時長)
// decodedBuffer.getChannelData(0) (左聲道原始 PCM 浮點數數據)
這段代碼執行在瀏覽器內部,不論上傳 50MB 還是 100MB 的 FLAC,解碼過程均只需數秒。
第三步:輕量無損 WAV 編碼器實作
WAV 檔案是 raw PCM 數據加上 44 位元組的 RIFF 檔頭。我們可以使用 ArrayBuffer 和 DataView 自己動手組裝:
function encodeWav(audioBuffer) {
const numOfChan = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate;
const result = (numOfChan === 2) ?
interleave(audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)) :
audioBuffer.getChannelData(0);
const buffer = new ArrayBuffer(44 + result.length * 2);
const view = new DataView(buffer);
writeString(view, 0, 'RIFF'); // RIFF 識別碼
view.setUint32(4, 36 + result.length * 2, true); // 檔案長度
writeString(view, 8, 'WAVE'); // WAVE 格式
writeString(view, 12, 'fmt '); // fmt 子區塊
view.setUint32(16, 16, true); // 子區塊大小
view.setUint16(20, 1, true); // PCM 格式 = 1
view.setUint16(22, numOfChan, true); // 聲道數
view.setUint32(24, sampleRate, true); // 採樣率
view.setUint32(28, sampleRate * numOfChan * 2, true); // 位元傳輸率
view.setUint16(32, numOfChan * 2, true); // 區塊對齊
view.setUint16(34, 16, true); // 採樣位數 = 16-bit
writeString(view, 36, 'data'); // data 子區塊識別碼
view.setUint32(40, result.length * 2, true); // 數據大小
// 將 Float32 轉為 16-bit 帶符號整數 PCM 寫入 DataView
floatTo16BitPCMIntoView(view, 44, result);
return new Blob([view], { type: 'audio/wav' });
}
這個無損 WAV 編碼器完全不需要引入任何外部 JS 庫,轉檔只需不到 0.1 秒!
第四步:非同步分片 MP3 編碼實作 (避免 UI 卡死)
編碼 MP3 需透過 CDN 載入 lamejs。因為音訊解碼後的 Float32Array 長度動輒數百萬,如果一次性同步編碼,瀏覽器會直接凍結並跳出「網頁無響應」的提示。
為了解決這個嚴重的效能痛點,我們採用 setTimeout 分片非同步遞迴 技術,將資料切為小區塊編碼,讓瀏覽器能在每輪編碼空隙刷新進度條 UI:
function encodeMp3(audioBuffer, bitrate) {
const channels = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate;
// 1. 將 Float32 轉換為 Lamejs 所需的 Int16 PCM 數據
const left = floatTo16BitPCM(audioBuffer.getChannelData(0));
const right = channels === 2 ? floatTo16BitPCM(audioBuffer.getChannelData(1)) : null;
const mp3encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitrate);
const mp3Data = [];
const sampleLength = left.length;
const chunkSize = 11520 * 4; // 每次處理的小數據塊大小
let offset = 0;
function encodeChunk() {
if (offset < sampleLength) {
const leftChunk = left.subarray(offset, offset + chunkSize);
const rightChunk = right ? right.subarray(offset, offset + chunkSize) : null;
let mp3buf;
if (channels === 2) {
mp3buf = mp3encoder.encodeBuffer(leftChunk, rightChunk);
} else {
mp3buf = mp3encoder.encodeBuffer(leftChunk);
}
if (mp3buf.length > 0) {
mp3Data.push(new Int8Array(mp3buf));
}
offset += chunkSize;
// 2. 即時更新進度條百分比
const progress = Math.min(100, Math.floor((offset / sampleLength) * 50) + 50);
updateProgress(progress, `正在編碼 MP3: ${progress}%`);
// 3. 使用 setTimeout 釋放線程控制權,讓瀏覽器有機會渲染 UI
setTimeout(encodeChunk, 0);
} else {
// 編碼結束,輸出最終 MP3 檔案
const endBuf = mp3encoder.flush();
if (endBuf.length > 0) {
mp3Data.push(new Int8Array(endBuf));
}
const mp3Blob = new Blob(mp3Data, { type: 'audio/mp3' });
handleConversionSuccess(mp3Blob, 'mp3');
}
}
encodeChunk();
}
這個分片機制可以保證即使在錄製 10 分鐘或更長的超長音訊時,使用者介面依然流暢無比。
結語
藉由 Web Audio API 的原生解碼功能以及 Lamejs 編碼器,我們只花了不到 300 行代碼就實現了一個高品質的線上音訊轉檔工具。
- 🔒 極致安全:完全在本地端運行,不耗費上傳流量,也絕不洩漏隱私音檔。
- ⚡ 體驗卓越:提供分片非同步編碼以避免卡頓,並配備「原檔與新檔」雙向播放器供聆聽對比。
*歡迎前往我們的轉檔工具頁面測試使用:(https://stillbuilding.life/audio-convert/)*



