Анализ WAV файлов C (libsndfile, fftw3)

Я пытаюсь разработать простое приложение C, которое может давать значение от 0 до 100 в определенном диапазоне частот в заданную временную метку в WAV файле.

Пример: у меня есть частотный диапазон 44,1 кГц (обычный MP3 файл), и я хочу разбить этот диапазон на n количество диапазонов (начиная с 0). Затем мне нужно получить амплитуду каждого диапазона, от 0 до 100.

То, что мне удалось:

Использование libsndfile Теперь я могу прочитать данные WAV файла.

infile = sf_open(argv [1], SFM_READ, &sfinfo);

float samples[sfinfo.frames];

sf_read_float(infile, samples, 1);

Однако мое понимание БПФ довольно ограничено. Но я знаю, что это необходимо для того, чтобы получить амплитуды в диапазонах, которые мне нужны. Но как мне двигаться дальше? Я нашел библиотеку FFTW-3, которая, кажется, подходит для этой цели.

Я нашел некоторую помощь здесь: qaru.site/info/46893/...

и посмотрел учебник FFTW здесь: http://www.fftw.org/fftw2_doc/fftw_2.html

Но поскольку я не уверен в поведении FFTW, я не знаю, как продвигаться отсюда.

И еще один вопрос, предполагая, что вы используете libsndfile: если вы вынуждаете чтение быть одноканальным (со стереофайлом), а затем читаете образцы. Будете ли вы тогда читать только половину образцов всего файла? Поскольку половина из них относится к каналу 1 или автоматически отфильтровывает их?

Спасибо за вашу помощь.

EDIT: Мой код можно увидеть здесь:

double blackman_harris(int n, int N){
double a0, a1, a2, a3, seg1, seg2, seg3, w_n;
a0 = 0.35875;
a1 = 0.48829;
a2 = 0.14128;
a3 = 0.01168;

seg1 = a1 * (double) cos( ((double) 2 * (double) M_PI * (double) n) / ((double) N - (double) 1) );
seg2 = a2 * (double) cos( ((double) 4 * (double) M_PI * (double) n) / ((double) N - (double) 1) );
seg3 = a3 * (double) cos( ((double) 6 * (double) M_PI * (double) n) / ((double) N - (double) 1) );

w_n = a0 - seg1 + seg2 - seg3;
return w_n;
}

int main (int argc, char * argv [])
{   char        *infilename ;
SNDFILE     *infile = NULL ;
FILE        *outfile = NULL ;
SF_INFO     sfinfo ;


infile = sf_open(argv [1], SFM_READ, &sfinfo);

int N = pow(2, 10);

fftw_complex results[N/2 +1];
double samples[N];

sf_read_double(infile, samples, 1);


double normalizer;
int k;
for(k = 0; k < N;k++){
    if(k == 0){

        normalizer = blackman_harris(k, N);

    } else {
        normalizer = blackman_harris(k, N);
    }

}

normalizer = normalizer * (double) N/2;



fftw_plan p = fftw_plan_dft_r2c_1d(N, samples, results, FFTW_ESTIMATE);

fftw_execute(p);


int i;
for(i = 0; i < N/2 +1; i++){
    double value = ((double) sqrtf(creal(results[i])*creal(results[i])+cimag(results[i])*cimag(results[i]))/normalizer);
    printf("%f\n", value);

}



sf_close (infile) ;

return 0 ;
} /* main */

Ответ 1

Ну, все зависит от того диапазона частот, который вам нужен. БПФ работает, беря 2 ^ n выборок и предоставляя вам 2 ^ (n-1) реальные и мнимые числа. Я должен признать, что я довольно туманна в отношении того, что именно представляют эти ценности (у меня есть друг, который обещал пройти все это со мной вместо кредита, который я ему сделал, когда у него были финансовые проблемы;)), кроме угол вокруг круга. Эффективно они предоставляют вам arccos параметра угла для синуса и косинуса для каждого частотного бункера, из которого исходные 2 ^ n выборок могут быть полностью восстановлены.

В любом случае это имеет огромное преимущество в том, что вы можете рассчитать величину, взяв эвклидовое расстояние реальной и мнимой частей (sqrtf ((real * real) + (imag * imag))). Это дает вам ненормализованное значение расстояния. Это значение затем можно использовать для создания величины для каждой полосы частот.

Итак, давайте возьмем порядок 10 БПФ (2 ^ 10). Вы вводите 1024 образца. Вы получаете FFT эти образцы, и вы возвращаете 512 мнимых и реальных значений (конкретный порядок этих значений зависит от используемого вами алгоритма FFT). Таким образом, это означает, что для аудиофайла 44,1 кГц каждый бит представляет собой 44100/512 Гц или ~ 86 Гц для каждого бункера.

Одна вещь, которая должна выделяться из этого, заключается в том, что если вы используете больше образцов (из того, что называется временем или пространственным доменом при работе с многомерными сигналами, такими как изображения), вы получаете лучшее частотное представление (в том, что называется частотной областью), Однако вы жертвуете одним за другого. Это как раз то, что происходит, и вам придется жить с ним.

В основном вам нужно будет настроить частотные модули и временное/пространственное разрешение, чтобы получить требуемые данные.

Сначала немного номенклатуры. Образцы времени 1024, о которых я упоминал ранее, называют вашим окном. Обычно, когда вы выполняете такой процесс, вы захотите сдвинуть окно на какую-то сумму, чтобы получить следующие 1024 отсчета, которые вы хотите FFT. Очевидным было бы сделать выборки 0- > 1023, затем 1024- > 2047 и т.д. Это, к сожалению, не дает лучших результатов. В идеале вы хотите в какой-то степени перекрывать окна, чтобы с течением времени добиться более плавного изменения частоты. Чаще всего люди сдвигают окно на половину размера окна. т.е. ваше первое окно будет 0- > 1023 второго 512- > 1535 и т.д. и т.д.

Теперь это вызывает еще одну проблему. Хотя эта информация обеспечивает идеальную обратную коррекцию FFT-сигнала, она оставляет вам проблему, которая в какой-то степени течет в объемные бункеры. Чтобы решить эту проблему, некоторые математики (гораздо более умные, чем я) придумали концепцию функции окна. Функция окна обеспечивает гораздо лучшую частотную изоляцию в частотной области, но приводит к потере информации во временной области (т.е. Ее невозможно полностью перестроить сигнал после того, как вы использовали оконную функцию AFAIK).

Теперь существуют различные типы оконной функции, начиная от прямоугольного окна (фактически ничего не делая с сигналом) до различных функций, которые обеспечивают гораздо лучшую изоляцию по частоте (хотя некоторые могут также убивать окружающие частоты, которые могут вас заинтересовать!!). Существует, увы, ни один размер не подходит для всех, но я большой поклонник (для спектрограмм) функции окна черманмана-гарриса. Я думаю, что это дает лучшие результаты!

Однако, как я упоминал ранее, FFT предоставляет вам ненормализованный спектр. Чтобы нормализовать спектр (после вычисления евклидова расстояния), вам нужно разделить все значения на коэффициент нормировки (я подробнее рассмотрю здесь).

эта нормализация даст вам значение от 0 до 1. Таким образом, вы можете легко увеличить это значение на 100, чтобы получить шкалу от 0 до 100.

Это, однако, не там, где оно заканчивается. Спектр, который вы получаете от этого, довольно неудовлетворен. Это потому, что вы смотрите на величину с использованием линейной шкалы. К сожалению, человеческое ухо слышит, используя логарифмический масштаб. Это довольно часто вызывает проблемы с тем, как выглядит спектрограмма/спектр.

Чтобы обойти это, вам нужно преобразовать эти 0 в 1 значения (я назову его "x" ) в шкалу децибел. Стандартное преобразование 20.0f * log10f (x), Затем это даст вам значение, при котором 1 преобразуется в 0 и 0, преобразуется в -infinity. ваши величины теперь находятся в соответствующем логарифмическом масштабе. Однако это не всегда полезно.

На этом этапе вам нужно изучить исходную глубину бита образца. При 16-битной выборке вы получаете значение, которое находится между 32767 и -32768. Это означает, что динамический диапазон - это fabsf (20.0f * log10f (1.0f/65536.0f)) или ~ 96.33dB. Итак, теперь мы имеем это значение.

Возьмите значения, которые мы получили из приведенного выше дБ. Добавьте это значение -96.33 к нему. Очевидно, максимальная амплитуда (0) теперь равна 96,33. Теперь он действует по тому же значению, и теперь у вас есть значение от -infinity до 1.0f. Закрепите нижний конец на 0, и теперь у вас есть диапазон от 0 до 1 и умножьте его на 100, и у вас есть конечный диапазон от 0 до 100.

И это намного больше монстра, чем я изначально предполагал, но должен дать вам хорошее обоснование того, как создать хороший спектр/спектрограмму для входного сигнала.

и дышать

Дальнейшее чтение (для людей, отличных от оригинального плаката, который уже нашел его):

Преобразование БПФ в спектрограмму

Изменить. В стороне я обнаружил, что поцелуй FFT намного проще в использовании, мой код для выполнения прямого fft выглядит следующим образом:

CFFT::CFFT( unsigned int fftOrder ) :
    BaseFFT( fftOrder )
{
    mFFTSetupFwd    = kiss_fftr_alloc( 1 << fftOrder, 0, NULL, NULL );
}

bool CFFT::ForwardFFT( std::complex< float >* pOut, const float* pIn, unsigned int num )
{
    kiss_fftr( mFFTSetupFwd, pIn, (kiss_fft_cpx*)pOut );
    return true;
}