處理範圍與淡入淡出(固定在最前面)
- 原理
- 只處理你在步驟 2 選取的一段,起訖處各加一小段升餘弦淡入與淡出(預設各 10 毫秒),避免切口造成「啪」聲。試聽範圍(最長 30 秒)必須落在這段之內。
- 適合
- 只想處理重點片段;長錄音只處理需要的部分,可以節省時間與記憶體。
- 代價
- 淡入淡出會改變起訖處數毫秒的波形;選段以外的聲音不會出現在輸出中。
- 建議
- 保留預設值。這個步驟固定在所有步驟之前,不能移動或移除;這是和其他步驟不同的地方。
錄音增益降噪副本只在你的瀏覽器內處理聲音,不上傳、不使用麥克風、不使用瀏覽器儲存空間。本頁說明它做什麼、不做什麼、每個步驟的白話原理與參數建議、處理紀錄怎麼讀、如何自己動手驗證、實測結果與限制,以及最常被誤解的地方。
下面依工具內的選單順序排列。每個步驟都附上「代價」:不是只有好處。順序也會影響結果,常見的順序是:先去直流、濾波與陷波,再降噪,接著加強人聲與壓縮,最後調整音量。工具會在順序不合理時提醒(例如降噪放在壓縮之後),但不會阻止你。
預設只是起點,不是「最佳設定」。它們是依經驗與下方的合成測試調整的參數,沒有在大量真實錄音上驗證過。
| 預設 | 適合的情況 | 步驟與主要參數 |
|---|---|---|
| 人聲清晰 | 說話聲偏小、背景有持續的沙沙聲 | 削波偵測、去直流、高通 80 Hz 4 階、頻譜減法降噪 12 dB/靈敏度 1.5、語音頻段 +4 dB、壓縮(−30 dBFS、3:1)、RMS 正規化到 −20 dBFS(限幅補足) |
| 去除嗡嗡聲 | 低沉的電器嗡嗡聲 | 削波偵測、去直流、市電陷波(自動偵測 50/60 Hz,8 個諧波)、高通 40 Hz 2 階、峰值正規化到 −1 dBFS |
| 遠距離錄音 | 說話者離麥克風很遠、聲音小而悶 | 削波偵測、去直流、高通 100 Hz 4 階、頻譜減法降噪 14 dB/靈敏度 1.6、語音頻段 +5 dB、壓縮(−36 dBFS、3:1)、RMS 正規化到 −18 dBFS(限幅補足) |
| 快速聽取 | 很長的錄音想快點聽過一遍 | 削波偵測、去直流、靜音壓縮(門檻為噪音底加 10 dB,超過 1 秒的靜音保留 0.4 秒)、變速 1.5 倍(音高不變)、RMS 正規化到 −20 dBFS(限幅補足);時間軸會改變,紀錄附對照表 |
每次套用到整段,都會產生 JSON 與 TXT 兩種紀錄(內容相同,TXT 給人讀、JSON 給程式讀)。主要欄位如下:
| 欄位 | 內容 |
|---|---|
| schema、tool | 格式標記(gib-audio-enhance-log-1)、工具名稱、頁面、版本(V236)與核心版本 |
| createdAt、createdAtNote | 產生時間(來自這台裝置的時鐘,未經第三方驗證) |
| input | 原檔的檔名、大小、SHA-256、格式、讀取方式(內建解析器或瀏覽器解碼)、取樣率、聲道、位元深度、長度與位準統計 |
| selection | 處理範圍的起訖秒數、淡入淡出毫秒 |
| steps | 實際執行的步驟,依序列出 id、參數、說明、各步驟自己算出的值(derived,例如降噪使用的噪音片段與視窗大小、陷波採用的頻率、壓縮壓低最多的量、正規化的增益)與用時 |
| output | 副本的檔名、大小、SHA-256、取樣率、聲道、位元深度、長度、輸出前縮小的量(exportGainDb)與編碼時被夾住的取樣數 |
| timeline | 時間軸是否改變;改變時附輸出時間對應原檔時間的對照表與被縮短的區間 |
| processing、stats | 處理用時;處理前後的位準統計(峰值、平均、噪音底、人聲位準,皆為粗略值,不是訊噪比) |
| environment、notes | 瀏覽器識別字串;限制說明(這是工作副本、不能取代原檔、不是公證) |
紀錄是一般文字檔,可以被編輯;它的用途是讓別人能照著重做並比對,而不是替內容背書。工具也能「匯入先前的處理紀錄」還原步驟與參數,並核對目前載入的原檔雜湊是否相同。
import hashlib
import json
def sha256_of(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
ORIGINAL = "範例.wav" # 你保存的原檔
COPY = "範例_增強版.wav" # 下載的增強副本
LOG = "範例_增強版_處理紀錄.json" # 下載的處理紀錄(JSON)
log = json.load(open(LOG, encoding="utf-8"))
print("原檔與紀錄相符:", sha256_of(ORIGINAL) == log["input"]["sha256"])
print("副本與紀錄相符:", sha256_of(COPY) == log["output"]["sha256"])ffprobe -v error -show_streams -of json 範例_增強版.wav
import json
import numpy as np
from scipy import signal
from scipy.io import wavfile
ORIGINAL = "範例.wav" # 原檔:單聲道、16 位元 PCM
COPY = "範例_增強版.wav" # 下載的增強副本(流程只含一個高通或低通濾波步驟)
LOG = "範例_增強版_處理紀錄.json" # 下載的處理紀錄(JSON)
log = json.load(open(LOG, encoding="utf-8"))
sr, x = wavfile.read(ORIGINAL)
x = x.astype(np.float64) / 32768
sel = log["selection"]
x = x[round(sel["startSec"] * sr): round(sel["endSec"] * sr)]
f = [s for s in log["steps"] if s["id"] == "filter"][0]["derived"]["applied"][0] # 例:{"type": "highpass", "fc": 80, "order": 4}
kind = "highpass" if f["type"] == "highpass" else "lowpass"
sos = signal.butter(f["order"], f["fc"], kind, fs=sr, output="sos") # Butterworth,與工具使用相同的傳遞函數
y = signal.sosfilt(sos, x)
out = wavfile.read(COPY)[1].astype(np.float64)
mid = slice(int(0.2 * sr), -int(0.2 * sr)) # 避開兩端的淡入淡出
print("與副本逐點比較,差最多的一點相差(單位:16 位元量化步階):", np.max(np.abs(np.round(y * 32768) - out)[mid]))import json
import numpy as np
from scipy.io import wavfile
ORIGINAL = "範例.wav" # 原檔:單聲道、16 位元 PCM
COPY = "範例_增強版.wav" # 下載的增強副本(流程只含一個降噪步驟,方法為頻譜減法)
LOG = "範例_增強版_處理紀錄.json" # 下載的處理紀錄(JSON)
log = json.load(open(LOG, encoding="utf-8"))
sr, full = wavfile.read(ORIGINAL)
full = full.astype(np.float64) / 32768
sel = log["selection"]
x = full[round(sel["startSec"] * sr): round(sel["endSec"] * sr)].copy()
fi, fo = round(sel["fadeInMs"] * sr / 1000), round(sel["fadeOutMs"] * sr / 1000) # 選段的升餘弦淡入淡出
x[:fi] *= 0.5 - 0.5 * np.cos(np.pi * (np.arange(fi) + 0.5) / fi)
x[len(x) - fo:] *= (0.5 - 0.5 * np.cos(np.pi * (np.arange(fo) + 0.5) / fo))[::-1]
d = [s for s in log["steps"] if s["id"] == "denoise"][0]
p, v = d["params"], d["derived"]
N = v["frameSamples"]; H = N // 4; bins = N // 2 + 1
w = 0.5 - 0.5 * np.cos(2 * np.pi * np.arange(N) / N) # 週期式 Hann 視窗
noise = full[round(v["noiseStartSec"] * sr): round(v["noiseEndSec"] * sr)] # 噪音片段取自原檔,不含淡入淡出
prof = np.mean([np.abs(np.fft.rfft(noise[s:s + N] * w)) for s in range(0, len(noise) - N + 1, H)], axis=0)
floor = 10 ** (-p["reduction"] / 20)
a = 0.92 * p["smooth"] / 100 # 時間平滑:釋放係數;啟動係數為其 1/4
L = len(x); off = N - H; M = int(np.ceil((L + N - H) / H))
xp = np.concatenate([np.zeros(off), x, np.zeros(N + 4 * H)])
out = np.zeros(len(xp)); Gp = None
for m in range(M):
X = np.fft.rfft(xp[m * H: m * H + N] * w)
mg = np.abs(X)
m2 = mg - prof * p["sens"] # 頻譜減法:幅度減去 靈敏度×噪音輪廓
g = np.where(m2 > floor * mg, m2 / np.maximum(mg, 1e-20), floor) # 最多只壓到「降噪量」
fs = p["fsmooth"]
if fs > 0: # 頻率平滑:前後各 fs 格取平均
g = np.array([g[max(0, k - fs): min(bins - 1, k + fs) + 1].mean() for k in range(bins)])
Gp = g.copy() if Gp is None else g * (1 - np.where(g > Gp, 0.25 * a, a)) + Gp * np.where(g > Gp, 0.25 * a, a)
out[m * H: m * H + N] += np.fft.irfft(X * Gp, N) * w / 1.5 # 合成:再乘一次 Hann,疊加後除以 1.5
y = out[off: off + L]
res = wavfile.read(COPY)[1].astype(np.float64)
print("與副本逐點比較,差最多的一點相差(單位:16 位元量化步階):", np.max(np.abs(np.round(y * 32768) - res)))| 項目 | 對照對象 | 通過 | 實測最差誤差 | 說明 |
|---|---|---|---|---|
| FFT、實數 FFT 與反變換 | NumPy numpy.fft | 25 | 7.1e-14 | 浮點數誤差 |
| Butterworth 高通/低通(2 至 8 階) | SciPy signal.butter、signal.sosfilt | 40 | 頻率響應 5.1e-12;逐點 8.8e-08 | 工具以單精度浮點運算,所以逐點誤差約 1e-8 等級 |
| 市電陷波係數 | SciPy signal.iirnotch | 含於上列 | 0 | 係數逐項相同 |
| 噪音輪廓與降噪(頻譜減法、頻譜閘門) | 以 NumPy 獨立寫的參考實作 | 25 | 輪廓 3.3e-16;降噪 4.4e-08 | 4 組參數;降噪量 0 dB 時視窗重疊相加可完整重建 |
| 重取樣(Kaiser 窗 sinc) | SciPy signal.resample_poly(峰值 0.5 的帶內雜訊) | 16 | 5.5e-04 | 濾波器設計不同,屬於「解釋或近似」;48 kHz 轉 16 kHz 對 10 kHz 的抑制實測 88 dB |
| WAV 讀取(PCM 8/16/24/32、浮點 32/64、A-law、μ-law、EXTENSIBLE、3 聲道、截斷與異常檔頭)與寫出 | SciPy wavfile、ffmpeg、ffprobe | 61 | 0 | 取樣值逐點相同;16/24 位元寫出再讀回逐取樣相同 |
| SHA-256 | Python hashlib、NIST 範例 | 20 | 0 | 含空檔、分段餵入與大檔 |
| 變速不變調、靜音壓縮、動態處理、時間對照表、分析器、頻譜圖、流程與紀錄、限制與記憶體 | 性質檢查(長度、音高、不變量)與獨立計算 | 241 | — | 這類沒有現成的外部參考,改驗證性質(例如音高中位數、輸出長度、取消時能停下來) |
誤差越小越一致。這些是對合成訊號與測試檔的比對,不代表所有真實錄音都相同。
| 比對 | 實測差異 | 說明 |
|---|---|---|
| 高通 4 階,工具輸出(16 位元)對 SciPy butter+sosfilt | 1 | 單位:16 位元量化步階(LSB);差 1 是四捨五入的末位差 |
| 高通 2 階,工具輸出對 ffmpeg highpass 濾波器 | 最差 2,均方根 0.72 | 兩邊的濾波器實作不同(ffmpeg 的階數與運算精度另有取捨),屬於「解釋或近似」 |
| 頻譜減法降噪(12 dB),工具輸出對 NumPy 參考實作 | 1 | 用本指南下方的 Python 程式即可重現 |
| 頻譜減法降噪(20 dB、頻率平滑 0),工具輸出對 NumPy 參考實作 | 1 | |
| 輸出 WAV:ffmpeg 解碼對工具內部取樣值(16 位元、24 位元) | 0、0 | ffprobe 回報 pcm_s16le、pcm_s24le,取樣率與長度正確 |
| 輸出 WAV:Chromium decodeAudioData 對工具內部取樣值(24 位元) | 0.0e+00 | 以滿刻度 1.0 為準的取樣值差 |
| 同一組步驟:瀏覽器(Chromium)輸出對 Node.js 命令列輸出 | 0(雜湊相同) | 單位:16 位元量化步階;同一份程式碼在不同引擎的數學函式可能有末位差異 |
| 檔案 | 內建解析器對預期值 | decodeAudioData 對預期值 | 兩者互差 |
|---|---|---|---|
| pcm16(立體聲) | 0 | 3.1e-05 | 3.1e-05 |
| pcm16(單聲道) | 0 | 3.1e-05 | 3.1e-05 |
| pcm24(立體聲) | 0 | 0 | 0 |
| pcm32(立體聲) | 0 | 0 | 0 |
| f32(立體聲) | 0 | 0 | 0 |
| pcm8(立體聲) | 0 | 7.8e-03 | 7.8e-03 |
| f64(立體聲) | 0 | 無法解碼 | — |
| mulaw(單聲道) | 0 | 3.0e-05 | 3.0e-05 |
| alaw(單聲道) | 0 | 3.0e-05 | 3.0e-05 |
「兩者互差」是兩條讀取路徑對同一個檔案的差:工具先用內建解析器讀 WAV,瀏覽器解碼只在內建解析器無法處理時使用。這個 Chromium 無法解碼 64 位元浮點 WAV,內建解析器可以。
| 情境 | 未處理的 SI-SDR | 頻譜減法 12 dB/靈敏度 1.5(預設) | 頻譜減法 20 dB/2.5 | 閘門 20 dB/2.5 | 頻譜減法 35 dB/3.0(極端) | 只做 50/60 Hz 陷波 |
|---|---|---|---|---|---|---|
| 白噪音(訊噪比 10 dB) | 6.3 | +6.0 / −1.7 | +5.6 / −0.3 | +7.9 / −1.4 | +5.7 / +5.6 | 0.0 / 0.0 |
| 粉紅噪音(5 dB) | 1.4 | +3.4 / −0.9 | +4.5 / +0.4 | +3.5 / 0.0 | +4.9 / +6.9 | 0.0 / 0.0 |
| 60 Hz 嗡嗡聲加諧波(5 dB) | 1.4 | +4.9 / −0.5 | +3.3 / +1.0 | +6.5 / −0.1 | +2.7 / +4.0 | +9.0 / −1.0 |
| 50 Hz 嗡嗡聲加諧波(5 dB) | 1.3 | +5.3 / −0.5 | +3.9 / +1.2 | +7.0 / 0.0 | +3.5 / +4.3 | +9.7 / −0.8 |
| 遠距離(殘響、低通、衰減,白噪音 8 dB) | 4.5 | +6.1 / −1.9 | +5.8 / −0.4 | +8.0 / −1.6 | +5.7 / +6.6 | 0.0 / 0.0 |
| 硬削波的語音(白噪音 20 dB) | 16.6 | +2.1 / +1.1 | −0.3 / +3.8 | +3.2 / +1.0 | −0.3 / +6.0 | 0.0 / 0.0 |
| 情境 | 人聲清晰 | 遠距離錄音 | 去除嗡嗡聲 |
|---|---|---|---|
| 白噪音(訊噪比 10 dB) | −9.6 | +7.0 | −12.9 | +6.1 | −1.1 | 0.0 |
| 粉紅噪音(5 dB) | −4.4 | +5.8 | −7.5 | +4.9 | +3.3 | 0.0 |
| 60 Hz 嗡嗡聲加諧波(5 dB) | −3.7 | +6.0 | −5.8 | +5.1 | +7.0 | +8.7 |
| 50 Hz 嗡嗡聲加諧波(5 dB) | −3.4 | +6.4 | −5.6 | +5.7 | +6.8 | +9.0 |
| 遠距離(殘響、低通、衰減,白噪音 8 dB) | −9.0 | +6.1 | −13.1 | +7.2 | −1.0 | 0.0 |
| 硬削波的語音(白噪音 20 dB) | −21.4 | +4.4 | −24.8 | +4.8 | −7.2 | 0.0 |
處理全部在背景工作者內進行。下表的「長任務」是主執行緒上超過 50 毫秒的任務數(PerformanceObserver 量測):「整段處理期間」是處理進度條顯示的期間;「切換與繪圖瞬間」是載入完成、切換步驟、首次按播放與畫出試聽結果的瞬間。目標是兩者都為 0:整段處理期間在多數未降速的實測中為 0,但切換與繪圖的瞬間在沙箱的軟體繪圖環境下仍偶爾有 50 至 110 毫秒的任務,沒有完全達成。沙箱可能同時有其他程序爭用 CPU(只有 2 個核心),爭用時計時與長任務數都會變差;每次測試開始時的 1 分鐘平均負載都記在結果檔裡。
| 情境 | 載入 秒 | 試聽計算 秒 | 整段處理 秒 | 處理用時占錄音長度 | 整段處理期間的長任務 個 | 切換與繪圖瞬間的長任務 個 |
|---|---|---|---|---|---|---|
| 14 秒,16 kHz 單聲道 WAV(桌面 1366×800) | 0.15 | 0.36 | 0.30 | — | 0 | 2(最長 142 ms) |
| 30 分鐘,44.1 kHz 單聲道 WAV(桌面) | 4.4 | 1.2 | 48.4 | 2.7 % | 0 | 3(最長 202 ms) |
| 同上,主執行緒降速 4 倍(CDP) | 4.5 | 1.8 | 47.3 | 2.6 % | 0 | 7(最長 187 ms) |
| 同上,整個瀏覽器限制在單一核心的 25 % | 21.0 | 5.3 | 225.4 | 12.5 % | 不判定(見下) | 不判定(見下) |
| 25 分鐘,44.1 kHz 立體聲 MP3(瀏覽器解碼),處理其中 60 秒 | 7.0 | 1.6 | 3.2 | — | 0 | 1(最長 83 ms) |
瀏覽器所有行程(含瀏覽器本體、GPU 與頁面,合計)的常駐記憶體:頁面載入後閒置約 1201.5 MB;處理 14 秒的小檔後約 1374.7 MB;處理 30 分鐘單聲道 44.1 kHz WAV 的尖峰約 1992.7 MB;載入 25 分鐘立體聲 MP3 並處理其中 60 秒的尖峰約 2323.5 MB(粗略值,每次都包含瀏覽器本體,不是只算本工具)。程式的估計只算背景工作者內的工作陣列(30 分鐘單聲道約 757 MiB),不含下載用的資料塊、檔案讀取緩衝與瀏覽器本身,所以不等於行程總量;在記憶體小的裝置上仍可能失敗,失敗時會顯示說明而不是當機。
| 長度 | WAV 單聲道 | WAV 立體聲 | MP3 等壓縮格式 單聲道 | MP3 等壓縮格式 立體聲 |
|---|---|---|---|---|
| 10 分鐘 | 約 252 MiB | 約 404 MiB | 約 353 MiB | 約 606 MiB |
| 30 分鐘 | 約 757 MiB | 約 1211 MiB | 約 1060 MiB | 約 1817 MiB,超過保護上限 |
| 60 分鐘 | 約 1514 MiB | 約 2422 MiB,超過保護上限 | 約 2120 MiB,超過保護上限 | 約 3634 MiB,超過保護上限 |
保護上限:預設 1.5 GiB;瀏覽器回報裝置記憶體時,再取「裝置記憶體 × 0.25」;瀏覽器識別字串看起來是手機或平板時,再不超過 0.75 GiB;三者取最小的。壓縮格式(MP3 等)必須整段解碼成未壓縮資料才能處理,所以估計較大,60 分鐘的立體聲 MP3 在預設上限下會被拒絕並建議切段。WAV 只讀取你選取的範圍,記憶體較省。