Сокращение звука PCM с 44100 до 8000
Я некоторое время работал над демонстрацией распознавания звука, и для API требуется, чтобы я передал файл.wav с частотой дискретизации 8000 или 16000, поэтому я должен уменьшить его. Я попробовал 2 алгоритма следующим образом. Хотя ни один из них не решает проблему так, как мне хотелось бы, есть некоторые различия в результатах, и я надеюсь, что это прояснит ситуацию.
Это моя первая попытка, и она отлично работает, когда sampleRate% outputSampleRate = 0, однако, когда outputSampleRate = 8000 или 1600, конечный аудиофайл молчит (что означает, что значение каждого элемента выходного массива равно 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, он все равно выводит файл без вывода сообщений:
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 и других друзей ~ Хотя я кое-что понял, они позволили мне лучше узнать исходные вещи ~~~:)
3 ответа
Преобразование частоты дискретизации намного больше, чем просто выбрасывание выборок или их вставка.
Давайте возьмем простой случай понижающей дискретизации с коэффициентом 2. (например, 44100-> 22050). Наивным подходом было бы просто выбросить все остальные образцы. Но представьте на секунду, что в исходном файле с частотой 44,1 кГц присутствовала одиночная синусоида на частоте 20 кГц. Это хорошо в пределах Найквиста (fs / 2 = 22050) для этой частоты дискретизации. После того, как вы выбросите все остальные сэмплы, он все еще будет там на частоте 10 кГц, но теперь он будет выше nyquist (fs / 2 = 11025) и добавит псевдоним в ваш выходной сигнал. В результате вы получите большую жирную синусоидальную волну с частотой 8975 Гц!
Чтобы избежать этого псевдонима во время понижающей дискретизации, вы должны сначала спроектировать фильтр нижних частот с отсечкой, выбранной в соответствии с вашим коэффициентом прореживания. Для приведенного выше примера вы сначала обрежете все выше 11025, а затем уничтожите.
Обратная сторона медали называется повышающей дискретизацией и интерполяцией. Скажем, вы хотите увеличить частоту дискретизации в 2 раза. Сначала вы вставляете нули между каждой входной выборкой, а затем запускаете интерполяционный фильтр для вычисления значений, чтобы заменить нули, используя окружающие выборки.
Изменение скорости обычно включает в себя некоторую комбинацию прореживания и интерполяции - поскольку оба работают с целым числом выборок. Возьмите 48000-> 32000 в качестве примера. Соотношение выход / вход составляет 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
Это несжатый формат, который может уменьшить пропускную способность, вы можете попробовать эту небольшую утилиту, которую я написал для записи в формате mp3, просто измените строку вscripts/recorder.js
config: { sampleRate: this.context.sampleRate }
в
config: { sampleRate: 16000 // or any other sampling rate }
Другой вариант: если вы уже выполняете какую-то обработку звука и не возражаете добавить ffmpeg в стек, вы можете отправить файл wav (несжатый формат) / ogg (сжатый формат, код) на сервер, там вы можете изменить его на любой формат, который вы предпочитаете, с любой частотой дискретизации, которую вы хотите, используя ffmpeg, прежде чем делать остальную часть обработки.