16kHz 降采样
一、认识
16kHz
音频降采样: 将 audioContext.sampleRate
指定的采样率转换为 16000Hz
(16kHz
)。 并从基于 Linear Interpolation
(线性插值) 的 音频重采样(Resampling
) 算法优化为基于 Cubic Interpolation
(三次插值) 的音频重采样算法。
为什么要降采样到 16kHz
?: 人类语音的主要信息集中在 300Hz - 3400Hz
, 理论上 8kHz
采样率已足够, 16kHz
采样率可以捕捉到更多高频信息,使语音质量更自然、更清晰, 因此, 16kHz
足以保留语音信息,同时减少不必要的数据。另外, 音频文件的大小 = 采样率 × 量化位深 × 通道数 × 时长
, 例如,假设 16-bit PCM
单声道音频, 44.1kHz 采样率(CD 级别):每秒数据量 44,100 × 16 / 8 = 88.2 KB/s
, 而 16kHz 采样率(语音优化):每秒数据量 16,000 × 16 / 8 = 32 KB/s
, 直接减少 63.7%
的数据量,而语音质量损失很小, 因此, 16kHz
采样率可以大幅减少数据量,提高存储和传输效率。最后, 16kHz
是语音识别模型的标准输入格式,避免额外转换和计算开销, 也是现代语音通讯的行业标准。
二、基于线性插值的音频降采样
2.1 认识
基于 Linear Interpolation
(线性插值) 的 音频重采样(Resampling
) 算法: 是一种简单高效的重采样算法, 但是只考虑两个相邻点,可能会导致高频损失和失真。具体逻辑为: 1. 将原始采样的音频数据转换为 Float32Array
, data = new Float32Array(audioBuffer)
, 确保输入音频数据是 Float32Array
类型,方便处理。2. 计算目标采样点数, const fitCount = Math.round(data.length * (16000 / sampleRate));
计算新的采样点数量, 并转换为 Float32Array
。3. 计算插值系数(springFactor
) const springFactor = (data.length - 1) / (fitCount - 1);
, 用于计算原始数据和目标数据之间的比例关系(每个新采样点在原始数据中的映射位置)。4. 填充新数据(线性插值), 遍历 新数据的索引 i
,找到 它在原始数据中的映射位置 tmp
, before
是 向下取整 的索引,after
是 向上取整 的索引, atPoint
计算 新数据点在 before
和 after
之间的插值比例, 通过 线性插值公式: 计算新数据点的值。5. 首尾数据直接赋值, 让首尾数据点不变,避免误差累积。
2.2 实现
export function to16kHz(audioBuffer, sampleRate = 44100) {
const data = new Float32Array(audioBuffer);
const fitCount = Math.round(data.length * (16000 / sampleRate));
const newData = new Float32Array(fitCount);
const springFactor = (data.length - 1) / (fitCount - 1);
newData[0] = data[0];
for (let i = 1; i < fitCount - 1; i++) {
const tmp = i * springFactor;
const before = Math.floor(tmp).toFixed();
const after = Math.ceil(tmp).toFixed();
const atPoint = Number(tmp) - Number(before);
newData[i] = data[before] + (data[after] - data[before]) * atPoint;
}
newData[fitCount - 1] = data[data.length - 1];
return newData;
}
三、基于三次插值的音频降采样
3.1 认识
基于 Cubic Interpolation
(三次插值) 的 音频重采样 (Resampling
) 算法: 则考虑 前后四个点,能更好地平滑过渡,减少失真,提高音质。1. 将原始采样的音频数据转换为 Float32Array
, data = new Float32Array(audioBuffer)
, 确保输入音频数据是 Float32Array
类型,方便处理。2. 计算目标采样点数, const fitCount = Math.round(data.length * (16000 / sampleRate));
计算新的采样点数量, 并转换为 Float32Array
。3. 定义 Cubic Interpolation
计算方法: 使用三次插值公式计算新数据点的值, 公式为 。 4. 遍历新数组,进行插值计算: 计算当前点在原始数据中的 映射位置 tmp
, 找到最近的四个数据点(p0
, p1
, p2
, p3
), 使用 cubicInterpolate
计算新数据点值。5. 边界处理: p0
不能小于索引 0
,否则取 data[0]
, p3
不能超过 data.length - 1
,否则取 data[data.length - 1]
。
3.2 实现
export function to16kHz(audioBuffer, sampleRate = 44100) {
const data = new Float32Array(audioBuffer);
const fitCount = Math.round(data.length * (16000 / sampleRate));
const newData = new Float32Array(fitCount);
const springFactor = (data.length - 1) / (fitCount - 1);
function cubicInterpolate(p0, p1, p2, p3, x) {
return (
p1 +
0.5 *
x *
(p2 -
p0 +
x * (2 * p0 - 5 * p1 + 4 * p2 - p3 + x * (3 * (p1 - p2) + p3 - p0)))
);
}
for (let i = 0; i < fitCount; i++) {
const tmp = i * springFactor;
const index = Math.floor(tmp);
const x = tmp - index;
// 取出四个相邻的数据点,处理边界情况
const p0 = data[Math.max(0, index - 1)];
const p1 = data[index];
const p2 = data[Math.min(index + 1, data.length - 1)];
const p3 = data[Math.min(index + 2, data.length - 1)];
newData[i] = cubicInterpolate(p0, p1, p2, p3, x);
}
return newData;
}