Понижение частоты звука 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, он по-прежнему выводит файл 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 и других друзей~ Хотя я как-то сам это понял, они позволили мне лучше узнать исходные вещи~~~ : )


person tristan_jia    schedule 04.08.2015    source источник
comment
Когда вы говорите без звука, вы предполагаете, что знаете область кодирования и воспроизведения звука, которая определяет, какие характеристики делают аудиофайл бесшумным для ушей или динамиков. Вероятно, было бы лучше сосредоточиться на математических свойствах входных и выходных последовательностей (можете ли вы их описать?), чтобы убрать один слой абстракции.   -  person doldt    schedule 04.08.2015
comment
@doldt Привет! спасибо за ваш ответ, silnent означает, что значение каждого элемента в массиве равно 0, и я добавлю это в свое описание как можно скорее~   -  person tristan_jia    schedule 05.08.2015


Ответы (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, и вы не можете разбить его на меньшие члены.

Я бы посоветовал вам найти какой-нибудь код или библиотеку, чтобы сделать это за вас. Что вам нужно искать, так это многофазный фильтр или преобразователь частоты дискретизации.

person jaket    schedule 04.08.2015
comment
Спасибо за объяснение~ это очень помогает~~~O(∩_∩)O~ - person tristan_jia; 08.08.2015
comment
stackoverflow.com/questions/53810058/ Я пробовал, но не работает. Я также прикрепил файл - person Kramer; 17.12.2018

Проблема в том, что вы пытаетесь получить доступ к массиву, используя число с плавающей запятой. Когда вы обращаетесь к 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;
}
person Guffa    schedule 05.08.2015

то, что сказал @jacket, верно, вы не можете просто уменьшить частоту звука, просто уменьшив no. элементов в массиве, я могу думать об этом двумя способами:

  1. если вас не интересует wav, который является несжатым форматом и будет истощать вашу пропускную способность, вы можете попробовать эту небольшую утилиту Я написал для записи в виде мп3 файла, просто изменить строку в scripts/recorder.js

     config: {
        sampleRate: this.context.sampleRate
      }
    

    to

      config: {
        sampleRate: 16000 // or any other sampling rate
      }
    
  2. Другой вариант: если вы уже выполняете какую-то обработку звука и не возражаете против добавления ffmpeg в стек, вы можете либо отправить файл wav (несжатый формат), либо файл ogg (сжатый формат, code) на сервер, там вы можете изменить его на любой формат, который вы предпочитаете, с любой частотой дискретизации, которую вы хотите, используя ffmpeg, прежде чем делать остальная обработка.

person mido    schedule 05.08.2015