JavaScript实现重采样

1. 参考Audio-Resampler

// 调用audioBufferToWav
import { audioBufferToWav } from "./audiobuffer-to-wav";

const getArrayBuffer = (url: string): Promise<ArrayBuffer> => {
  return new Promise(resolve => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      }
    };
    xhr.send();
  });
};

export const resampleAudioBuffer = (audioBuffer, targetSampleRate, oncomplete) => {
  const numCh_ = audioBuffer.numberOfChannels;
  const numFrames_ = (audioBuffer.length * targetSampleRate) / audioBuffer.sampleRate;

  let offlineContext_ = new OfflineAudioContext(numCh_, numFrames_, targetSampleRate);
  let bufferSource_ = offlineContext_.createBufferSource();
  bufferSource_.buffer = audioBuffer;

  offlineContext_.oncomplete = function(event) {
    const resampeledBuffer = event.renderedBuffer;
    if (typeof oncomplete === "function") {
      oncomplete({
        getAudioBuffer: function() {
          return resampeledBuffer;
        },
        getFileURL: function() {
          const wavBlob = new Blob([new DataView(audioBufferToWav(resampeledBuffer))], {
            type: "audio/wav",
          });
          return URL.createObjectURL(wavBlob);
        },
      });
    }
  };
  bufferSource_.connect(offlineContext_.destination);
  bufferSource_.start(0);
  offlineContext_.startRendering();
};

export const resampler = (src: string, targetSampleRate: number, oncomplete: Function) => {
  const audioContext = new AudioContext();
  getArrayBuffer(src).then(arrayBuffer => {
    audioContext.decodeAudioData(arrayBuffer, function(audioBeforeBuffer) {
      resampleAudioBuffer(audioBeforeBuffer, targetSampleRate, function(event) {
        if (typeof oncomplete === "function") {
          oncomplete({
            getAudioBuffer: function() {
              return event.getAudioBuffer();
            },
            getFileURL: function() {
              return event.getFileURL();
            },
          });
        }
      });
    });
  });
};

audioBufferToWav代码

resampler:输入音频url,输出可以选择:

  • getAudioBuffer:返回重采样器AudioBuffer;
  • getFileURL:返回从重新采样的音频创建的WAV文件的ObjectURL(不使用callback)

resampleAudioBuffer:输入audiobuffer,输出同上。

2. 降采样

后来研究别的方法时看到别人实现的。通过onaudioprocess获取audioBuffer,把Fload32Array放入.
录音代码:

let context = null,
    fragments = [],
    recorder  = null,
    audioInput = null,
    stream = null;

const DownSampleRate = 16000;

const onRecord = () => {
        context = new AudioContext();
        recorder = context.createScriptProcessor(4096, 1, 1);
        fragments = [];
        recorder.onaudioprocess = function (e) {
            const inputFrame = e.inputBuffer.getChannelData(0);//Float32Array
            const waveFrame = encode(inputFrame, context.sampleRate, DownSampleRate);//返回一个arraybuffer
            fragments.push(buffer);
        }

        navigator.mediaDevices.getUserMedia(mediaConstraints).then((stream) => {
            stream = stream;
            audioInput = context.createMediaStreamSource(stream);
        }).then(function () {
            audioInput.connect(recorder);
            recorder.connect(context.destination);
        })
}

const onStop = () => {
   recorder.disconnect();
   audioInput.disconnect();
   stream.getAudioTracks().forEach(function (track) {
       track.stop();
    });
    context = null,
    fragments = [],
    recorder  = null,
    audioInput = null,
    stream = null;
}

降采样函数:

export function floatTo16BitPCM(output: DataView, offset: number, input: Float32Array) {
    for (let i = 0; i < input.length; i++, offset += 2) {
        let s = Math.max(-1, Math.min(1, input[i]));
        output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
}

export const to16BitPCM = (samples: Float32Array): ArrayBuffer => {
    const bitDepth = 16;
    const bytesPerSample = bitDepth / 8;
    const offset = 0;
    let buffer = new ArrayBuffer(samples.length * bytesPerSample);
    let view = new DataView(buffer);
    floatTo16BitPCM(view, offset, samples);
    return buffer;
}

export const downSampleAudioFrame = (
    srcFrame: Float32Array,
    srcRate: number,
    dstRate: number): Float32Array => {

    if (dstRate === srcRate || dstRate > srcRate) {
        return srcFrame;
    }

    const ratio = srcRate / dstRate;
    const dstLength = Math.round(srcFrame.length / ratio);
    const dstFrame = new Float32Array(dstLength);
    let srcOffset = 0;
    let dstOffset = 0;
    while (dstOffset < dstLength) {
        const nextSrcOffset = Math.round((dstOffset + 1) * ratio);
        let accum = 0;
        let count = 0;
        while (srcOffset < nextSrcOffset && srcOffset < srcFrame.length) {
            accum += srcFrame[srcOffset++];
            count++;
        }
        dstFrame[dstOffset++] = accum / count;
    }

    return dstFrame;
}

export const encode = (actualAudioFrame: Float32Array, srcRate: number, dstRate: number): ArrayBuffer => {
    const audioFrame = downSampleAudioFrame(actualAudioFrame, srcRate, dstRate);

    if (!audioFrame) {
        return null;
    }

    const buffer = to16BitPCM(audioFrame);
    return buffer;
}

三种方法都试过,降采样后声音质量都没有问题。

posted @ 2020-08-19 21:19  Shaw_喆宇  阅读(865)  评论(0编辑  收藏  举报