web-dev-qa-db-fra.com

Utilisation de Apple FFT et Accelerate Framework

Quelqu'un at-il utilisé le Apple FFT pour une application iPhone ou savoir où trouver un exemple d'application pour savoir comment l'utiliser? Je sais que Apple a un exemple de code publié, mais je ne sais pas vraiment comment l'implémenter dans un projet réel.

69
Ian Oswald

Je viens de faire travailler le code FFT pour un projet iPhone:

  • créer un nouveau projet
  • supprimer tous les fichiers à l'exception de main.m et xxx_info.plist
  • aller dans les paramètres du projet et rechercher pch et l'empêcher d'essayer de charger un .pch (vu que nous venons de le supprimer)
  • copier coller l'exemple de code sur tout ce que vous avez dans main.m
  • supprimez la ligne contenant # Carbon. Le carbone est pour OSX.
  • supprimer tous les cadres et ajouter un cadre accéléré

Vous devrez peut-être également supprimer une entrée de info.plist qui indique au projet de charger un fichier xib, mais je suis sûr à 90% que vous n'avez pas besoin de vous en préoccuper.

REMARQUE: programmez les sorties vers la console, les résultats sont de 0,000, ce n'est pas une erreur - c'est juste très très rapide

Ce code est vraiment bêtement obscur; il est généreusement commenté, mais les commentaires ne facilitent pas vraiment la vie.

Fondamentalement, au cœur de celui-ci se trouve:

vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE);

FFT sur n vrais flotteurs, puis inversez pour revenir à notre point de départ. ip signifie in-place, ce qui signifie que & A est écrasé C'est la raison de tout ce malarkey d'emballage spécial - afin que nous puissions écraser la valeur de retour dans le même espace que la valeur d'envoi.

Pour donner une certaine perspective (comme, comme dans: pourquoi utiliserions-nous cette fonction en premier lieu?), Disons que nous voulons effectuer une détection de hauteur sur l'entrée du microphone, et nous l'avons configurée de sorte qu'un rappel soit déclenché à chaque fois le microphone reçoit 1024 flotteurs. Supposons que la fréquence d'échantillonnage du microphone soit de 44,1 kHz, ce qui correspond à environ 44 images/s.

Ainsi, notre fenêtre temporelle est quelle que soit la durée de 1024 échantillons, soit 1/44 s.

Donc, nous emballerions A avec 1024 flottants du micro, définirions log2n = 10 (2 ^ 10 = 1024), précalculerions quelques bobines (setupReal) et:

vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);

Maintenant, A contiendra n/2 nombres complexes. Ceux-ci représentent n/2 tranches de fréquence:

  • bin [1] .idealFreq = 44Hz - ie La fréquence la plus basse que nous pouvons détecter de manière fiable est UNE onde complète dans cette fenêtre, c'est-à-dire une onde 44Hz.

  • bin [2] .idealFreq = 2 * 44Hz

  • etc.

  • bin [512] .idealFreq = 512 * 44Hz - La fréquence la plus élevée que nous pouvons détecter (connue sous le nom de fréquence de Nyquist) est celle où chaque paire de points représente une onde, soit 512 ondes complètes dans la fenêtre, soit 512 * 44Hz, ou: n/2 * bin [1] .idealFreq

  • En fait, il existe un Bin supplémentaire, Bin [0] qui est souvent appelé "DC Offset". Il se trouve que Bin [0] et Bin [n/2] auront toujours un composant complexe 0, donc A [0] .realp est utilisé pour stocker Bin [0] et A [0] .imagp est utilisé pour stocker Bin [ n/2]

Et la grandeur de chaque nombre complexe est la quantité d'énergie qui vibre autour de cette fréquence.

Donc, comme vous pouvez le voir, ce ne serait pas un très bon détecteur de hauteur car il n'a pas une granularité assez fine. Il y a une astuce astucieuse Extraire des fréquences précises des bacs FFT en utilisant le changement de phase entre les trames pour obtenir la fréquence précise pour un bac donné.

Ok, passons maintenant au code:

Notez le 'ip' dans vDSP_fft_zrip, = 'in place' c'est-à-dire que la sortie écrase A ('r' signifie qu'il faut de vraies entrées)

Regardez la documentation sur vDSP_fft_zrip,

Les données réelles sont stockées sous une forme complexe divisée, avec des réels impairs stockés du côté imaginaire de la forme complexe divisée et même des réels stockés du côté réel.

c'est probablement la chose la plus difficile à comprendre. Nous utilisons le même conteneur (& A) tout au long du processus. donc au début nous voulons le remplir avec n nombres réels. après la FFT, il contiendra n/2 nombres complexes. nous jetons ensuite cela dans la transformée inverse, et nous espérons que nous sortirons nos n réels réels.

maintenant la structure de A sa configuration pour des valeurs complexes. Le vDSP doit donc normaliser la manière de regrouper des nombres réels.

donc on génère d'abord n ​​nombres réels: 1, 2, ..., n

for (i = 0; i < n; i++)
    originalReal[i] = (float) (i + 1);

Ensuite, nous les emballons dans A en tant que n/2 complexes:

// 1. masquerades n real #s as n/2 complex #s = {1+2i, 3+4i, ...}
// 2. splits to 
//   A.realP = {1,3,...} (n/2 elts)
//   A.compP = {2,4,...} (n/2 elts)
//
vDSP_ctoz(
          (COMPLEX *) originalReal, 
          2,                            // stride 2, as each complex # is 2 floats
          &A, 
          1,                            // stride 1 in A.realP & .compP
          nOver2);                      // n/2 elts

Vous auriez vraiment besoin de regarder comment A est alloué pour l'obtenir, peut-être recherchez COMPLEX_SPLIT dans la documentation.

A.realp = (float *) malloc(nOver2 * sizeof(float));
A.imagp = (float *) malloc(nOver2 * sizeof(float));

Ensuite, nous faisons un pré-calcul.


Classe DSP rapide pour les maths bods: La théorie de Fourier prend beaucoup de temps à comprendre (je la regarde de temps en temps depuis plusieurs années maintenant)

Un cisoïde est:

z = exp(i.theta) = cos(theta) + i.sin(theta)

c'est-à-dire. un point sur le cercle unité dans le plan complexe.

Lorsque vous multipliez des nombres complexes, les angles s'ajoutent. Ainsi, z ^ k continuera de sauter autour du cercle unitaire; z ^ k peut être trouvé sous un angle k.theta

  • Choisissez z1 = 0 + 1i, c'est-à-dire un quart de tour par rapport à l'axe réel, et notez que z1 ^ 2 z1 ^ 3 z1 ^ 4 donnent chacun un autre quart de tour pour que z1 ^ 4 = 1

  • Choisissez z2 = -1, c'est-à-dire un demi-tour. z2 ^ 4 = 1 également, mais z2 a effectué 2 cycles à ce stade (z2 ^ 2 est également = 1). Vous pourriez donc penser à z1 comme fréquence fondamentale et z2 comme première harmonique

  • De même z3 = le point "trois quarts de révolution" c'est-à-dire que -i termine exactement 3 cycles, mais en fait aller de l'avant 3/4 à chaque fois équivaut à reculer de 1/4 à chaque fois

c'est-à-dire. z3 est juste z1 mais dans la direction opposée - Cela s'appelle un alias

z2 est la fréquence significative la plus élevée, car nous avons choisi 4 échantillons pour contenir une onde complète.

  • z0 = 1 + 0i, z0 ^ (n'importe quoi) = 1, c'est DC offset

Vous pouvez exprimer n'importe quel signal à 4 points sous la forme d'une combinaison linéaire de z0 z1 et z2 c'est-à-dire. Vous le projetez sur ces vecteurs de base

mais je vous entends demander "qu'est-ce que cela signifie de projeter un signal sur un cisoïde?"

Vous pouvez penser de cette façon: l'aiguille tourne autour du cisoïde, donc à l'échantillon k, l'aiguille pointe dans la direction k.theta, et la longueur est le signal [k]. Un signal qui correspond exactement à la fréquence du cisoïde bombera la forme résultante dans une certaine direction. Donc, si vous additionnez toutes les contributions, vous obtiendrez un vecteur résultant solide. Si la fréquence correspond presque, le renflement sera plus petit et se déplacera lentement autour du cercle. Pour un signal qui ne correspond pas à la fréquence, les contributions s'annulent.

http://complextoreal.com/tutorials/tutorial-4-fourier-analysis-made-easy-part-1/ vous aidera à obtenir une compréhension intuitive.

Mais l'essentiel est; si nous avons choisi de projeter 1024 échantillons sur {z0, ..., z512}, nous aurions précalculé z0 à z512, et c'est ce que cette étape de précalcul est.


Notez que si vous faites cela dans du code réel, vous voudrez probablement le faire une fois lorsque l'application se charge et appeler la fonction de libération complémentaire une fois qu'elle se ferme. NE le faites PAS beaucoup de fois - c'est cher.

// let's say log2n = 8, so n=2^8=256 samples, or 'harmonics' or 'terms'
// if we pre-calculate the 256th roots of unity (of which there are 256) 
// that will save us time later.
//
// Note that this call creates an array which will need to be released 
// later to avoid leaking
setupReal = vDSP_create_fftsetup(log2n, FFT_RADIX2);

Il convient de noter que si nous définissons log2n par exemple à 8, vous pouvez lancer ces valeurs précalculées dans n'importe quelle fonction fft qui utilise une résolution <= 2 ^ 8. Donc (à moins que vous ne vouliez une optimisation de la mémoire ultime), créez simplement un ensemble pour la résolution la plus élevée dont vous aurez besoin et utilisez-le pour tout.

Maintenant, les transformations réelles, en utilisant les éléments que nous venons de précalculer:

vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);

À ce stade, A contiendra n/2 nombres complexes, seul le premier est en fait deux nombres réels (décalage DC, Nyquist #) se faisant passer pour un nombre complexe. L'aperçu de la documentation explique cet emballage. C'est assez soigné - fondamentalement, il permet aux résultats (complexes) de la transformation d'être compressés dans la même empreinte mémoire que les entrées (réelles, mais étrangement compressées).

vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE);

et encore une fois ... nous aurons encore besoin de déballer notre tableau d'origine de A. puis nous comparons juste pour vérifier que nous avons récupéré exactement ce avec quoi nous avons commencé, libérer nos bobines précalculées et c'est fait!

Mais attendez! avant de déballer, il y a une dernière chose à faire:

// Need to see the documentation for this one...
// in order to optimise, different routines return values 
// that need to be scaled by different amounts in order to 
// be correct as per the math
// In this case...
scale = (float) 1.0 / (2 * n);

vDSP_vsmul(A.realp, 1, &scale, A.realp, 1, nOver2);
vDSP_vsmul(A.imagp, 1, &scale, A.imagp, 1, nOver2);
134
P i

Voici un exemple concret: un extrait de c ++ qui utilise les routines vDSP fft d'Accelerate pour effectuer une autocorrélation sur l'entrée de l'unité audio Remote IO. L'utilisation de ce framework est assez compliquée, mais la documentation n'est pas trop mauvais.

OSStatus DSPCore::initialize (double _sampleRate, uint16_t _bufferSize) {
    sampleRate = _sampleRate;
    bufferSize = _bufferSize;
    peakIndex = 0;
    frequency = 0.f;
    uint32_t maxFrames = getMaxFramesPerSlice();
    displayData = (float*)malloc(maxFrames*sizeof(float));
    bzero(displayData, maxFrames*sizeof(float));
    log2n = log2f(maxFrames);
    n = 1 << log2n;
    assert(n == maxFrames);
    nOver2 = maxFrames/2;
    A.realp = (float*)malloc(nOver2 * sizeof(float));
    A.imagp = (float*)malloc(nOver2 * sizeof(float));
    FFTSetup fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);

    return noErr;
}

void DSPCore::Render(uint32_t numFrames, AudioBufferList *ioData) {

    bufferSize = numFrames;
    float ln = log2f(numFrames);

    //vDSP autocorrelation

    //convert real input to even-odd
    vDSP_ctoz((COMPLEX*)ioData->mBuffers[0].mData, 2, &A, 1, numFrames/2);
    memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
    //fft
    vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_FORWARD);

    // Absolute square (equivalent to mag^2)
    vDSP_zvmags(&A, 1, A.realp, 1, numFrames/2);
    bzero(A.imagp, (numFrames/2) * sizeof(float));    

    // Inverse FFT
    vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_INVERSE);

    //convert complex split to real
    vDSP_ztoc(&A, 1, (COMPLEX*)displayData, 2, numFrames/2);

    // Normalize
    float scale = 1.f/displayData[0];
    vDSP_vsmul(displayData, 1, &scale, displayData, 1, numFrames);

    // Naive peak-pick: find the first local maximum
    peakIndex = 0;
    for (size_t ii=1; ii < numFrames-1; ++ii) {
        if ((displayData[ii] > displayData[ii-1]) && (displayData[ii] > displayData[ii+1])) {
            peakIndex = ii;
            break;
        }
    }

    // Calculate frequency
    frequency = sampleRate / peakIndex + quadInterpolate(&displayData[peakIndex-1]);

    bufferSize = numFrames;

    for (int ii=0; ii<ioData->mNumberBuffers; ++ii) {
        bzero(ioData->mBuffers[ii].mData, ioData->mBuffers[ii].mDataByteSize);
    }
}
26
Art Gillespie

Bien que je dise que le cadre FFT d'Apple est rapide ... Vous devez savoir comment fonctionne une FFT afin d'obtenir une détection précise de la hauteur (c'est-à-dire calculer la différence de phase sur chaque FFT successive afin de trouver la hauteur exacte, pas la hauteur de la la plupart dominent bin).

Je ne sais pas si cela peut être utile, mais j'ai téléchargé mon objet Pitch Detector depuis mon application tuner (musicianskit.com/developer.php). Il existe également un exemple de projet xCode 4 à télécharger (afin que vous puissiez voir comment fonctionne l'implémentation).

Je travaille sur le téléchargement d'un exemple d'implémentation FFT - alors restez à l'écoute et je le mettrai à jour une fois que cela se produira.

Bon codage!

13
Kpmurphy91

Voici un autre exemple du monde réel: https://github.com/krafter/DetectingAudioFrequency

4
krafter