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();
},
});
}
});
});
});
};
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;
}
三种方法都试过,降采样后声音质量都没有问题。