10

WAVファイルの特定のタイムスタンプで特定の周波数範囲で0〜100の値を与えることができる単純なCアプリケーションを開発しようとしています。

例:44.1kHzの周波数範囲(通常の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);

ただし、FFTについての私の理解はかなり限られています。しかし、必要な範囲で振幅を取得するために必要なことはわかっています。しかし、どうすればここから先に進むことができますか?その目的に合っていると思われるライブラリFFTW-3を見つけました。

私はここでいくつかの助けを見つけました:https ://stackoverflow.com/a/4371627/1141483

ここでFFTWチュートリアルを見てください:http ://www.fftw.org/fftw2_doc/fftw_2.html

しかし、FFTWの振る舞いがよく​​わからないので、ここから先に進むかどうかはわかりません。

また、libsndfileを使用していると仮定すると、別の質問があります。読み取りを(ステレオファイルを使用して)シングルチャネルに強制してから、サンプルを読み取る場合。それでは、実際には、ファイル全体のサンプルの半分しか読み取っていませんか?それらの半分はチャネル1からのものですか、それとも自動的にそれらを除外しますか?

あなたの助けをたくさんありがとう。

編集:私のコードはここで見ることができます:

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 */
4

1 に答える 1

14

まあそれはすべてあなたが求めている周波数範囲に依存します。FFTは、2 ^ nのサンプルを取得し、2 ^(n-1)の実数と虚数を提供することで機能します。私はこれらの価値観が正確に何を表しているのかについてかなりぼんやりしていることを認めなければなりません(彼が財政問題を抱えていたときに私が彼にしたローンの代わりに私と一緒にそれをすべてやり遂げることを約束した友人がいます;))円の周りの角度。事実上、これらは、元の2^nサンプルを完全に再構築できる各周波数ビンの正弦および余弦の角度パラメーターのアークコサインを提供します。

とにかく、これには、実数部と虚数部のユークリッド距離(sqrtf((real * real)+(imag * imag)))を使用して大きさを計算できるという大きな利点があります。これにより、正規化されていない距離値が提供されます。次に、この値を使用して、各周波数帯域の振幅を作成できます。

それでは、10 FFT(2 ^ 10)の注文を取りましょう。1024サンプルを入力します。これらのサンプルをFFTすると、512の虚数と実数の値が返されます(これらの値の特定の順序は、使用するFFTアルゴリズムによって異なります)。つまり、44.1Khzのオーディオファイルの場合、各ビンは44100/512Hzまたはビンあたり約86Hzを表します。

これから際立っていることの1つは、より多くのサンプルを使用すると(画像などの多次元信号を処理するときに時間または空間ドメインと呼ばれるものから)、より良い周波数表現(周波数ドメインと呼ばれるもの)が得られることです。しかし、あなたは一方を他方のために犠牲にします。これは物事が進む方法であり、あなたはそれと一緒に暮らす必要があります。

基本的に、必要なデータを取得するには、周波数ビンと時間/空間分解能を調整する必要があります。

最初に少し命名法を説明します。前に参照した1024の時間領域サンプルはウィンドウと呼ばれます。一般に、この種のプロセスを実行するときは、ウィンドウをある程度スライドさせて、FFTする次の1024サンプルを取得する必要があります。明らかなことは、サンプル0-> 1023、次に1024->2047などを取得することです。残念ながら、これでは最良の結果が得られません。理想的には、時間の経過とともによりスムーズな周波数変化が得られるように、ウィンドウをある程度オーバーラップさせたいと考えています。最も一般的な人々は、ウィンドウをウィンドウサイズの半分だけスライドさせます。つまり、最初のウィンドウは0-> 1023、2番目のウィンドウは512->1535などになります。

次に、これによりもう1つの問題が発生します。この情報は完全な逆FFT信号の再構成を提供しますが、周波数がある程度サラウンドビンに漏れるという問題が残ります。この問題を解決するために、一部の数学者(私よりはるかに賢い)がウィンドウ関数の概念を思いついた。ウィンドウ関数は、周波数ドメインではるかに優れた周波数分離を提供しますが、時間ドメインでの情報の損失につながります(つまり、ウィンドウ関数AFAIKを使用した後、信号を完全に再構築することは不可能です)。

現在、長方形のウィンドウ(信号に対して効果的に何もしない)から、はるかに優れた周波数分離を提供するさまざまな関数まで、さまざまなタイプのウィンドウ関数があります(ただし、関心のある周囲の周波数を殺す場合もあります!!)。残念ながら、1つのサイズですべてに対応できるわけではありませんが、私は(スペクトログラムの)ブラックマンハリスウィンドウ関数の大ファンです。最高の結果が得られると思います!

ただし、前述したように、FFTは正規化されていないスペクトルを提供します。スペクトルを正規化するには(ユークリッド距離の計算後)、すべての値を正規化係数で除算する必要があります(ここで詳しく説明します)。

この正規化により、0から1までの値が提供されます。したがって、この値に100を掛けて、0から100のスケールを簡単に取得できます。

ただし、これで終わりではありません。これから得られるスペクトルは、かなり満足のいくものではありません。これは、線形スケールを使用してマグニチュードを見ているためです。残念ながら、人間の耳は対数目盛を使用して聞こえます。これは、スペクトログラム/スペクトルの外観に問題を引き起こします。

これを回避するには、これらの0から1の値(これを「x」と呼びます)をデシベルスケールに変換する必要があります。標準の変換は20.0f*log10f(x)です。これにより、1が0に変換され、0が-無限大に変換される値が提供されます。これで、マグニチュードは適切な対数スケールになります。ただし、必ずしも役立つとは限りません。

この時点で、元のサンプルビット深度を調べる必要があります。16ビットサンプリングでは、32767〜-32768の値が得られます。これは、ダイナミックレンジがfabsf(20.0f * log10f(1.0f / 65536.0f))または〜96.33dBであることを意味します。これで、この値が得られました。

上記のdB計算から得られた値を取得します。この-96.33の値を追加します。明らかに、最大振幅(0)は96.33になりました。これで同じ値でdidivdeになり、-infinityから1.0fの範囲の値になります。下端を0にクランプすると、0から1の範囲になり、これに100を掛けると、最終的な0から100の範囲になります。

そして、それは私が当初意図していたよりもはるかにモンスターの投稿ですが、入力信号の優れたスペクトル/スペクトログラムを生成する方法についての十分な基礎を提供するはずです。

呼吸する

さらに読む(すでにそれを見つけた元のポスター以外の人のために):

FFTをスペクトログラムに変換する

編集:余談ですが、kiss 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;
}
于 2012-05-17T22:00:13.847 に答える