将 PCM 音频从 44100 下采样到 8000
Downsample PCM audio from 44100 to 8000
我从事音频识别演示已有一段时间了,api 需要我传递采样率为 8000 的 .wav 文件或 16000,所以我必须对其进行下采样。我尝试了以下两种算法。尽管其中 none 解决了我所希望的问题,但结果存在一些差异,我希望这能让它更清楚。
这是我的第一次尝试,当 sampleRate % outputSampleRate = 0 时它工作正常,但是当 outputSampleRate = 8000 或 1600 时,结果音频文件是 silent(这意味着输出数组的每个元素的值都是 0):
function interleave(inputL){
var compression = sampleRate / outputSampleRate;
var length = inputL.length / compression;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length){
result[index++] = inputL[inputIndex];
inputIndex += compression;
}
return result;
}
这是我的第二次尝试,来自一家大公司,但也没有用。更重要的是,当我设置 sampleRate % outputSampleRate = 0 它仍然输出一个 silent 文件:
function interleave(e){
var t = e.length;
var n = new Float32Array(t),
r = 0,
i;
for (i = 0; i < e.length; i++){
n[r] = e[i];
r += e[i].length;
}
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (i = 0; i < u; i++) {
a[i] = n[Math.floor(s)];
s += o;
}
return a
}
如果我的设置有误,这里是 encodeWAV 函数:
function encodeWAV(samples){
var sampleBits = 16;
var dataLength = samples.length*(sampleBits/8);
var buffer = new ArrayBuffer(44 + dataLength);
var view = new DataView(buffer);
var offset = 0;
/* RIFF identifier */
writeString(view, offset, 'RIFF'); offset += 4;
/* file length */
view.setUint32(offset, 32 + dataLength, true); offset += 4;
/* RIFF type */
writeString(view, offset, 'WAVE'); offset += 4;
/* format chunk identifier */
writeString(view, offset, 'fmt '); offset += 4;
/* format chunk length */
view.setUint32(offset, 16, true); offset += 4;
/* sample format (raw) */
view.setUint16(offset, 1, true); offset += 2;
/* channel count */
view.setUint16(offset, outputChannels, true); offset += 2;
/* sample rate */
view.setUint32(offset, outputSampleRate, true); offset += 4;
/* byte rate (sample rate * block align) */
view.setUint32(offset, outputSampleRate*outputChannels*(sampleBits/8), true); offset += 4;
/* block align (channel count * bytes per sample) */
view.setUint16(offset, outputChannels*(sampleBits/8), true); offset += 2;
/* bits per sample */
view.setUint16(offset, sampleBits, true); offset += 2;
/* data chunk identifier */
writeString(view, offset, 'data'); offset += 4;
/* data chunk length */
view.setUint32(offset, dataLength, true); offset += 4;
floatTo16BitPCM(view, offset, samples);
return view;
}
这让我困惑了很长时间,请让我知道我错过了什么...
----------------------------解决后---------------- ------------------
我很高兴 运行 现在好了,这是函数 interleave():
的正确版本
function interleave(e){
var t = e.length;
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (i = 0; i < u; i++) {
a[i] = e[Math.floor(s)];
s += o;
}
return a;
}
所以你可以看到是我传给它的变量类型不对~
再次感谢亲爱的@jaket 和其他朋友~虽然我自己弄明白了,但他们让我更好地了解了原来的东西~~~ :)
采样率转换不仅仅是简单地丢弃或插入样本。
让我们以 2 倍的降采样为例(例如 44100->22050)。一种天真的方法是丢弃所有其他样本。但是想象一下,在原始的 44.1kHz 文件中有一个 20khz 的正弦波。对于该采样率,它完全在奈奎斯特 (fs/2=22050) 范围内。在你扔掉所有其他样本后,它仍然会以 10kHz 的频率出现,但现在它会高于奈奎斯特 (fs/2=11025),并且它会混叠到你的输出信号中。最终结果是您将拥有一个位于 8975 Hz 的大正弦波!
为了在下采样期间避免这种混叠,您需要首先设计一个低通滤波器,其截止频率根据您的抽取率选择。对于上面的示例,您将首先切断 11025 以上的所有内容,然后再进行抽取。
硬币的反面称为上采样和插值。假设您想将采样率提高 2 倍。首先,您在每个输入样本之间插入零,然后 运行 插值滤波器计算值以使用周围样本替换零。
速率改变通常涉及抽取和插值的某种组合——因为两者都通过整数样本工作。以48000->32000为例。 output/input 比率为 32000/48000 或 2/3。所以你将 48000 上采样 2 得到 96000,然后将它下采样 3 到 32000。另一件事是你可以将这些过程链接在一起。所以如果你想从 48000->16000 上升 3,下降 2,下降 2。另外,44100 特别困难。例如,要从 48000->44100 移动,您需要上升 147,下降 160,并且不能将其分解为更小的项。
我建议您找一些代码或库来为您完成这项工作。您需要寻找的是多相滤波器或采样率转换器。
问题是您正在尝试使用浮点数访问数组。当您访问 inputL[5.5125]
时,它与 input['5.5125']
相同,即您将尝试从数组对象中读取名为 5.5125
的 属性,而不是数组数据中的项目。
将数字四舍五入以获得最接近的整数索引:
function interleave(inputL){
var compression = sampleRate / outputSampleRate;
var length = inputL.length / compression;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length){
result[index++] = inputL[Math.round(inputIndex)];
inputIndex += compression;
}
return result;
}
@jacket 说的是真的,你不能仅仅通过减少 no 来降低音频采样。数组中的项目,我能想到的两种方法是:
如果你不介意wav
是未压缩的格式并且会耗尽你的带宽,你可以试试这个small utility我写的用于录制为mp3文件,只需修改scripts/recorder.js
中的行
config: {
sampleRate: this.context.sampleRate
}
到
config: {
sampleRate: 16000 // or any other sampling rate
}
另一种选择是,如果您已经在做某种音频处理后端,并且不介意将 ffmpeg 添加到堆栈中,您可以发送 wav 文件(未压缩格式)/ ogg 文件(压缩格式,code)到服务器,在那里你可以在进行其余处理之前使用 ffmpeg 将其更改为你喜欢的任何格式和你想要的任何采样率。
我从事音频识别演示已有一段时间了,api 需要我传递采样率为 8000 的 .wav 文件或 16000,所以我必须对其进行下采样。我尝试了以下两种算法。尽管其中 none 解决了我所希望的问题,但结果存在一些差异,我希望这能让它更清楚。
这是我的第一次尝试,当 sampleRate % outputSampleRate = 0 时它工作正常,但是当 outputSampleRate = 8000 或 1600 时,结果音频文件是 silent(这意味着输出数组的每个元素的值都是 0):
function interleave(inputL){
var compression = sampleRate / outputSampleRate;
var length = inputL.length / compression;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length){
result[index++] = inputL[inputIndex];
inputIndex += compression;
}
return result;
}
这是我的第二次尝试,来自一家大公司,但也没有用。更重要的是,当我设置 sampleRate % outputSampleRate = 0 它仍然输出一个 silent 文件:
function interleave(e){
var t = e.length;
var n = new Float32Array(t),
r = 0,
i;
for (i = 0; i < e.length; i++){
n[r] = e[i];
r += e[i].length;
}
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (i = 0; i < u; i++) {
a[i] = n[Math.floor(s)];
s += o;
}
return a
}
如果我的设置有误,这里是 encodeWAV 函数:
function encodeWAV(samples){
var sampleBits = 16;
var dataLength = samples.length*(sampleBits/8);
var buffer = new ArrayBuffer(44 + dataLength);
var view = new DataView(buffer);
var offset = 0;
/* RIFF identifier */
writeString(view, offset, 'RIFF'); offset += 4;
/* file length */
view.setUint32(offset, 32 + dataLength, true); offset += 4;
/* RIFF type */
writeString(view, offset, 'WAVE'); offset += 4;
/* format chunk identifier */
writeString(view, offset, 'fmt '); offset += 4;
/* format chunk length */
view.setUint32(offset, 16, true); offset += 4;
/* sample format (raw) */
view.setUint16(offset, 1, true); offset += 2;
/* channel count */
view.setUint16(offset, outputChannels, true); offset += 2;
/* sample rate */
view.setUint32(offset, outputSampleRate, true); offset += 4;
/* byte rate (sample rate * block align) */
view.setUint32(offset, outputSampleRate*outputChannels*(sampleBits/8), true); offset += 4;
/* block align (channel count * bytes per sample) */
view.setUint16(offset, outputChannels*(sampleBits/8), true); offset += 2;
/* bits per sample */
view.setUint16(offset, sampleBits, true); offset += 2;
/* data chunk identifier */
writeString(view, offset, 'data'); offset += 4;
/* data chunk length */
view.setUint32(offset, dataLength, true); offset += 4;
floatTo16BitPCM(view, offset, samples);
return view;
}
这让我困惑了很长时间,请让我知道我错过了什么...
----------------------------解决后---------------- ------------------
我很高兴 运行 现在好了,这是函数 interleave():
的正确版本 function interleave(e){
var t = e.length;
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (i = 0; i < u; i++) {
a[i] = e[Math.floor(s)];
s += o;
}
return a;
}
所以你可以看到是我传给它的变量类型不对~ 再次感谢亲爱的@jaket 和其他朋友~虽然我自己弄明白了,但他们让我更好地了解了原来的东西~~~ :)
采样率转换不仅仅是简单地丢弃或插入样本。
让我们以 2 倍的降采样为例(例如 44100->22050)。一种天真的方法是丢弃所有其他样本。但是想象一下,在原始的 44.1kHz 文件中有一个 20khz 的正弦波。对于该采样率,它完全在奈奎斯特 (fs/2=22050) 范围内。在你扔掉所有其他样本后,它仍然会以 10kHz 的频率出现,但现在它会高于奈奎斯特 (fs/2=11025),并且它会混叠到你的输出信号中。最终结果是您将拥有一个位于 8975 Hz 的大正弦波!
为了在下采样期间避免这种混叠,您需要首先设计一个低通滤波器,其截止频率根据您的抽取率选择。对于上面的示例,您将首先切断 11025 以上的所有内容,然后再进行抽取。
硬币的反面称为上采样和插值。假设您想将采样率提高 2 倍。首先,您在每个输入样本之间插入零,然后 运行 插值滤波器计算值以使用周围样本替换零。
速率改变通常涉及抽取和插值的某种组合——因为两者都通过整数样本工作。以48000->32000为例。 output/input 比率为 32000/48000 或 2/3。所以你将 48000 上采样 2 得到 96000,然后将它下采样 3 到 32000。另一件事是你可以将这些过程链接在一起。所以如果你想从 48000->16000 上升 3,下降 2,下降 2。另外,44100 特别困难。例如,要从 48000->44100 移动,您需要上升 147,下降 160,并且不能将其分解为更小的项。
我建议您找一些代码或库来为您完成这项工作。您需要寻找的是多相滤波器或采样率转换器。
问题是您正在尝试使用浮点数访问数组。当您访问 inputL[5.5125]
时,它与 input['5.5125']
相同,即您将尝试从数组对象中读取名为 5.5125
的 属性,而不是数组数据中的项目。
将数字四舍五入以获得最接近的整数索引:
function interleave(inputL){
var compression = sampleRate / outputSampleRate;
var length = inputL.length / compression;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length){
result[index++] = inputL[Math.round(inputIndex)];
inputIndex += compression;
}
return result;
}
@jacket 说的是真的,你不能仅仅通过减少 no 来降低音频采样。数组中的项目,我能想到的两种方法是:
如果你不介意
中的行wav
是未压缩的格式并且会耗尽你的带宽,你可以试试这个small utility我写的用于录制为mp3文件,只需修改scripts/recorder.js
config: { sampleRate: this.context.sampleRate }
到
config: { sampleRate: 16000 // or any other sampling rate }
另一种选择是,如果您已经在做某种音频处理后端,并且不介意将 ffmpeg 添加到堆栈中,您可以发送 wav 文件(未压缩格式)/ ogg 文件(压缩格式,code)到服务器,在那里你可以在进行其余处理之前使用 ffmpeg 将其更改为你喜欢的任何格式和你想要的任何采样率。