将音频缓冲区从44100重新采样到16000。

15

我有一个以data-uri格式存储的音频数据,现在我已将该数据转换为缓冲区,现在我需要将该缓冲区数据转换成新的采样率,目前音频数据采用44.1kHz,我需要16kHz的数据。如果我使用RecordRTC API录制音频,并且以低采样率录制音频,则会出现扭曲的音频声音,因此我不知道如何重新取样我的音频缓冲区。

如果您有任何关于此问题的想法,请帮助我解决。

提前致谢 :)

5个回答

18

你可以使用 OfflineAudioContext 进行重采样,但是需要先将 data-uri 转换为 ArrayBuffer。这个解决方案适用于浏览器,而不是服务器,因为在网络上传输低质量音频(较低的采样率)比发送大量数据并在服务器上进行重采样更好。

// `source` is an AudioBuffer instance of the source audio
// at the original sample rate.

var TARGET_SAMPLE_RATE = 16000;

var offlineCtx = new OfflineAudioContext(source.numberOfChannels,
                                         source.duration * TARGET_SAMPLE_RATE,
                                         TARGET_SAMPLE_RATE);

// Play it from the beginning.
var offlineSource = offlineCtx.createBufferSource();
offlineSource.buffer = source;
offlineSource.connect(offlineCtx.destination);
offlineSource.start();
offlineCtx.startRendering().then((resampled) => {
  // `resampled` contains an AudioBuffer resampled at 16000Hz.
  // use resampled.getChannelData(x) to get an Float32Array for channel x.
});

快速相关问题。FF 内部使用的重采样算法是什么? - notthetup
3
我们使用来自speex编解码器的重采样器:http://dxr.mozilla.org/mozilla-central/source/media/libspeex_resampler/src/resample.c#35,它经过优化以提高速度和良好的感知质量。 - padenot
一个小细节,但很重要:请注意,当floats[i] = 1时,floats[i] * Math.pow(2, 16) / 2会产生错误的值。建议使用:ints[i] = floats[i] < 0 ? floats[i] * 32768 : floats[i] * 32767;(当然,缓存floats[i]可能有助于性能,以避免双数组查找)。 - user1693593
2
这段代码对我来说不起作用 - 最终我得到了一个空的音频文件。 - skunkwerk
1
这段示例代码存在错误,这使得对音频API新手来说很难理解。"o.startRendering()" // o是什么?"offlineCtx.createBuffer(... buffer.length...)" // buffer是什么?这个变量并不存在。虽然看起来很明显,但我们有多个缓冲区只是略微不同的相同内容,因此正确获取变量可以帮助理解。 - Vectorjohn
显示剩余3条评论

5

没有一个答案是正确的。这里是完美的代码。

// `sourceAudioBuffer` is an AudioBuffer instance of the source audio
// at the original sample rate.
const DESIRED_SAMPLE_RATE = 16000;
const offlineCtx = new OfflineAudioContext(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.duration * DESIRED_SAMPLE_RATE, DESIRED_SAMPLE_RATE);
const cloneBuffer = offlineCtx.createBuffer(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.length, sourceAudioBuffer.sampleRate);
// Copy the source data into the offline AudioBuffer
for (let channel = 0; channel < sourceAudioBuffer.numberOfChannels; channel++) {
    cloneBuffer.copyToChannel(sourceAudioBuffer.getChannelData(channel), channel);
}
// Play it from the beginning.
const source = offlineCtx.createBufferSource();
source.buffer = cloneBuffer;
source.connect(offlineCtx.destination);
offlineCtx.oncomplete = function(e) {
  // `resampledAudioBuffer` contains an AudioBuffer resampled at 16000Hz.
  // use resampled.getChannelData(x) to get an Float32Array for channel x.
  const resampledAudioBuffer = e.renderedBuffer;
}
offlineCtx.startRendering();
source.start(0);

了解其他答案为何不正确以及你的答案为何完美将很有用。 - Mud

3
如果您正在使用Chrome浏览器,您可以在AudioContext中直接指定采样率。
1. 您可以通过麦克风直接录制声音。
var context = new AudioContext({
    sampleRate: 16000,
});

2. 如果您已经有一个文件或ArrayBuffer,则可以使用相同的音频上下文对其进行重新采样。

    const fileReader = new FileReader();
    fileReader.readAsArrayBuffer(target.files[0]);
    
    fileReader.onload =  (e) => {
        //e.target.result is an ArrayBuffer
        context.decodeAudioData(e.target.result, async function(buffer) {
        console.log(buffer)
    })
        
    

运行得非常好!你救了我的一天。我试过所有其他复杂的方法。 - sourabh gupta

1
这只是从padenot的答案复制过来的,我更新了一下,以免其他人在查看此帖子时遇到缺少变量定义或如何获取最终重采样float32array的问题而感到困惑。这对我在firefox quantum 64.0中有效。
  var sourceAudioBuffer = e.inputBuffer;  // directly received by the audioprocess event from the microphone in the browser

  var TARGET_SAMPLE_RATE = 8000;
  var offlineCtx = new OfflineAudioContext(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.duration * sourceAudioBuffer.numberOfChannels * TARGET_SAMPLE_RATE, TARGET_SAMPLE_RATE);
  var buffer = offlineCtx.createBuffer(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.length, sourceAudioBuffer.sampleRate);
  // Copy the source data into the offline AudioBuffer
  for (var channel = 0; channel < sourceAudioBuffer.numberOfChannels; channel++) {
      buffer.copyToChannel(sourceAudioBuffer.getChannelData(channel), channel);
  }
  // Play it from the beginning.
  var source = offlineCtx.createBufferSource();
  source.buffer = sourceAudioBuffer;
  source.connect(offlineCtx.destination);
  source.start(0);
  offlineCtx.oncomplete = function(e) {
    // `resampled` contains an AudioBuffer resampled at 16000Hz.
    // use resampled.getChannelData(x) to get an Float32Array for channel x.
    var resampled = e.renderedBuffer;
    var leftFloat32Array = resampled.getChannelData(0);
    // use this float32array to send the samples to the server or whatever
  }
  offlineCtx.startRendering();

在我的情况下,原始的重新采样后的8000PCM数据通过UDP广播管道传输到FFmpeg中,如下所示:

ffmpeg -fflags nobuffer -analyzeduration 1M -f f32le -ar 8000 -ac 1 -i udp://127.0.0.1:12000 -ar 44100 -ac 2 -f alsa hw:0

所以,WebSocket服务器只需接收Base64编码的PCM数据,解码Base64字符串并通过UDP广播。结果将由FFmpeg在扬声器上播放。

0
一种更简单的方法是使用一个独立的重采样调用,它只需要一个输入音频缓冲区、一个输入采样率、一个输出采样率,并返回输出缓冲区。我发现了这个链接: 音频重采样
这个方法效果相当不错(在音频频率范围内引入的噪声很少)。感谢作者。

1
该链接库使用线性插值,没有任何带宽限制,因此会引入混叠伪像,并且可能听起来相当糟糕。 - Dietrich Epp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接