5

ここで何 度も説明したように、Apple の Accelerate フレームワークを使用して iPhone に FFT ピッチ検出を実装しています。

私は位相オフセット、ビン周波数を理解しており、FFT 技術 (単純なピッチ検出、自己相関、ケプストラムなど) を使用してピッチを検出するいくつかのオープンソース チューナーを調査しました。これが私の問題です:

FFT の結果は、ビンが 1 ~ 2 ヘルツしか離れていない場合でも、一貫して 5 ~ 10 Hz (+/-) ずれています。私はさまざまなアルゴリズムを試しましたが、高解像度でサンプリングされた単純な FFT でさえ、一見間違った場所でマグニチュード スパイクを示しています。これは一貫したオフセットではありません。高すぎるものもあれば、低すぎるものもあります。

たとえば、440Hz のトーンは 445.2 Hz として認識されます。214Hzとして220Hz; 874Hzとして880Hz; 1174Hzをトーンジェネレーターで1183Hzに。ほぼ同じアルゴリズムを使用する Mac 用の同様のオープンソース チューナーは、問題なくピッチを完全に検出します。(これらの違いは、デバイス上とシミュレータ上では異なりますが、オフのままです。)

実際のトーンと検出された振幅スパイクの間にいくつかのビンがあることが多いため、問題はビンの解像度ではないと思います。入力が間違ったピッチを聞いているかのようです。

以下にコードを貼り付けました。一般的な流れは単純です。

ステップを FFT バッファにプッシュします -> Hann Window -> FFT -> Phase/Magnitude -> 最大ピッチが間違っています。

enum {
    kOversample = 4,
    kSamples = MAX_FRAME_LENGTH,
    kSamples2 = kSamples / 2,
    kRange = kSamples * 5 / 16,
    kStep = kSamples / kOversample
};



const int PENDING_LEN = kSamples * 5;
static float pendingAudio[PENDING_LEN * sizeof(float)];
static int pendingAudioLength = 0;

- (void)processBuffer {
    static float window[kSamples];
    static float phase[kRange];
    static float lastPhase[kRange];
    static float phaseDeltas[kRange];
    static float frequencies[kRange];
    static float slidingFFTBuffer[kSamples];
    static float buffer[kSamples];

    static BOOL initialized = NO;
    if (!initialized) {
        memset(lastPhase, 0, kRange * sizeof(float));

        vDSP_hann_window(window, kSamples, 0);
        initialized = YES;
    }

    BOOL canProcessNewStep = YES;
    while (canProcessNewStep) {        

        @synchronized (self) {
            if (pendingAudioLength < kStep) {
                break; // not enough data
            }            
            // Rotate one step's worth of pendingAudio onto the end of slidingFFTBuffer
            memmove(slidingFFTBuffer, slidingFFTBuffer + kStep, (kSamples - kStep) * sizeof(float));
            memmove(slidingFFTBuffer + (kSamples - kStep), pendingAudio, kStep * sizeof(float));
            memmove(pendingAudio, pendingAudio + kStep, (PENDING_LEN - kStep) * sizeof(float));
            pendingAudioLength -= kStep;   
            canProcessNewStep = (pendingAudioLength >= kStep);
        }

        // Hann Windowing
        vDSP_vmul(slidingFFTBuffer, 1, window, 1, buffer, 1, kSamples);      
        vDSP_ctoz((COMPLEX *)buffer, 2, &splitComplex, 1, kSamples2);        

        // Carry out a Forward FFT transform.
        vDSP_fft_zrip(fftSetup, &splitComplex, 1, log2f(kSamples), FFT_FORWARD);        

        // magnitude to decibels
        static float magnitudes[kRange];        
        vDSP_zvmags(&splitComplex, 1, magnitudes, 1, kRange);        
        float zero = 1.0;
        vDSP_vdbcon(magnitudes, 1, &zero, magnitudes, 1, kRange, 0); // to decibels

        // phase
        vDSP_zvphas(&splitComplex, 1, phase, 1, kRange); // compute magnitude and phase        
        vDSP_vsub(lastPhase, 1, phase, 1, phaseDeltas, 1, kRange); // compute phase difference
        memcpy(lastPhase, phase, kRange * sizeof(float)); // save old phase

        double freqPerBin = sampleRate / (double)kSamples;
        double phaseStep = 2.0 * M_PI * (float)kStep / (float)kSamples;

        // process phase difference ( via https://stackoverflow.com/questions/4633203 )
        for (int k = 1; k < kRange; k++) {
            double delta = phaseDeltas[k];
            delta -= k * phaseStep;  // subtract expected phase difference
            delta = remainder(delta, 2.0 * M_PI);  // map delta phase into +/- M_PI interval
            delta /= phaseStep;  // calculate diff from bin center frequency
            frequencies[k] = (k + delta) * freqPerBin;  // calculate the true frequency
        }               

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        MCTunerData *tunerData = [[[MCTunerData alloc] initWithSize:MAX_FRAME_LENGTH] autorelease];        

        double maxMag = -INFINITY;
        float maxFreq = 0;
        for (int i=0; i < kRange; i++) {
            [tunerData addFrequency:frequencies[i] withMagnitude:magnitudes[i]];
            if (magnitudes[i] > maxMag) {
                maxFreq = frequencies[i];
                maxMag = magnitudes[i];
            }
        }

        NSLog(@"Max Frequency: %.1f", maxFreq);

        [tunerData calculate];

        // Update the UI with our newly acquired frequency value.
        [self.delegate frequencyChangedWithValue:[tunerData mainFrequency] data:tunerData];

        [pool drain];
    }

}

OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, 
                       const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, 
                       AudioBufferList *ioData)
{
    MCTuner* tuner = (MCTuner *)inRefCon;    

    OSStatus err = AudioUnitRender(tuner->audioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, tuner->bufferList);
    if (err < 0) {
        return err;
    }

    // convert SInt16 to float because iOS doesn't support recording floats directly
    SInt16 *inputInts = (SInt16 *)tuner->bufferList->mBuffers[0].mData;

    @synchronized (tuner) {
        if (pendingAudioLength + inNumberFrames < PENDING_LEN) {

            // Append the audio that just came in into the pending audio buffer, converting to float
            // because iOS doesn't support recording floats directly
            for(int i = 0; i < inNumberFrames; i++) {
                pendingAudio[pendingAudioLength + i] = (inputInts[i] + 0.5) / 32767.5;
            }
            pendingAudioLength += inNumberFrames;
        } else {
            // the buffer got too far behind. Don't give any more audio data.
            NSLog(@"Dropping frames...");
        }
        if (pendingAudioLength >= kStep) {
            [tuner performSelectorOnMainThread:@selector(processBuffer) withObject:nil waitUntilDone:NO];
        }
    }

    return noErr;
}
4

4 に答える 4

3

私はあなたのコードを詳細に調べていませんが、これは私にすぐに飛びつきました:

vDSP_zvmags(&splitComplex, 1, magnitudes, 1, kRange);

実際から複雑なfftの結果は、やや奇妙なレイアウトでパックされていることを覚えておくことが重要です。j番目のフーリエ係数の実数部と虚数部がR(j)とI(j)で表される場合、オブジェクトのrealimagコンポーネントにsplitComplexは次の内容が含まれます。

.real = {  R(0) , R(1), R(2), ... , R(n/2 - 1) } 
.imag = { R(n/2), I(1), I(2), ... , I(n/2 - 1) }

したがって、マグニチュードの計算は少し奇妙なことをしています。マグニチュードベクトルの最初のエントリはsqrt(R(0)^2 + R(n/2)^2)、です。ここで、は|R(0)|。すべての定数を注意深く調べたわけではありませんが、これがナイキストバンド(R(n/2))などを失うというオフバイワンエラーにつながる可能性があります。この種のオフバイワンエラーは、周波数帯域が実際よりもわずかに広いまたは狭いと見なされる可能性があり、その結果、範囲全体で小さなピッチスケールアップまたはスケールダウンが発生します。あなたが見ているもの。

于 2011-03-31T17:51:45.077 に答える
1

私のアルゴリズムでは実際には何もなかったと確信しています。むしろ、私の Apple の AUGraph の使い方に何か問題があったのだ。グラフを設定せずに単純なAudio Unitを使用するためにそれを取り除いたところ、適切にピッチを認識させることができました.

于 2011-04-06T04:10:02.593 に答える
1

FFT はメスではなく、チェーンソーです。一般に、FFT コーディングの現実を確認するには、(1) Parseval の定理 (時間領域での振幅の平均二乗は、スペクトルの合計に等しい丸めの範囲内である必要があります) を使用してテストし、(2) 逆 FFT を使用してそれを聞くだけです。申し訳ありませんが、fft の絶対精度を期待しすぎているようです。あなたは単にそれを手に入れるつもりはありません。ただし、コードでチェックする小さな項目のチェックリストがあります。ほとんどのアルゴリズムは DC とナイキストを移動してメモリ割り当てを均等にしますが、ナイキスト項を手動で移動し、さまざまなものを真ゼロにする必要があります。

A.realp[NOVER2] = A.imagp[0];   // move real Nyquist term to where it belongs
A.imagp[NOVER2] = 0.0;          // this is zero
A.imagp[0] = 0.0;               // make this a true zero

オーディオ データでは、DC はゼロであるべきですが (たとえば、振幅の平均はゼロです)、小さなウィンドウではそうではない場合があります。私はそれを放っておく。最大ビンを見つけるために必要な以上のことをしています (位相ボコーダーに関するコメントは正しいです)。ハムウィンドウを使用すると、精度が低下します。実際のデータの末尾に多数(4x) のゼロを埋め込むと、はるかに良い結果が得られます。幸運を。

于 2012-02-28T01:04:13.867 に答える
0

FFTだけでなく、FFTの後に位相ボコーダーを使用して、推定されたビン周波数を調整しているようです。位相ゲインと制限に応じて、位相ボコーダーの周波数調整により、推定された周波数が FFT ビン幅の外に十分に引き出される可能性があります。これが発生している場合、より狭いビン (より長い FFT) を使用しても役に立ちません。ピーク周波数を FFT 周波数ビンの外に引き出すかどうかを確認するために、サニティ チェックを実行することをお勧めします。または、位相ボコーダーを取り出して、FFT だけでより適切な結果が返されるかどうかを確認してみてください。

于 2011-04-01T01:01:35.710 に答える