Использование Apple FFT и Accelerate Framework

Кто-нибудь еще использовал Apple FFT для iPhone-приложения или знал, где я могу найти пример приложения о том, как его использовать? Я знаю, что Apple имеет несколько примеров кода, но я не уверен, как реализовать его в реальном проекте.

Ответ 1

Я только что получил код FFT для проекта iPhone:

  • создать новый проект
  • удалить все файлы, кроме main.m и xxx_info.plist
  • чтобы перейти к настройкам проекта и найти pch и остановить его от попытки загрузить .pch(видя, что мы только что удалили его).
  • скопируйте пример кода поверх всего, что у вас есть в main.m
  • удалите строку, содержащую #include Carbon. Углерод для OSX.
  • удалить все фреймворки и добавить ускорение рамки

Вам также может потребоваться удалить запись из info.plist, которая сообщает проекту загрузить xib, но я на 90% уверен, что вам не нужно беспокоиться об этом.

ПРИМЕЧАНИЕ. Выходы программы на консоль, результаты выдаются как 0.000, а не ошибка - это очень очень быстро

Этот код действительно тупо неясен; это щедро прокомментировано, но комментарии на самом деле не облегчают жизнь.

В сущности, это:

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

FFT на n реальных поплавках, а затем назад, чтобы вернуться туда, где мы начали. ip стоит на месте, а это означает, что A переписывается Это причина всей этой специальной упаковки malarkey - чтобы мы могли выдавить возвращаемое значение в то же пространство, что и значение отправки.

Чтобы дать некоторую перспективу (например, как в: почему мы должны использовать эту функцию в первую очередь?), предположим, что мы хотим выполнить определение высоты тона на микрофонном входе, и мы настроили его так, чтобы какой-то обратный вызов срабатывает каждый раз, когда микрофон получает 1024 поплавков. Предположим, что частота дискретизации микрофона составляла 44,1 кГц, поэтому ~ 44 кадра/сек.

Итак, наше временное окно - это то, что длительность 1024 образцов составляет, например, 1/44 с.

Итак, мы собирали A с 1024 поплавками из микрофона, устанавливали log2n = 10 (2 ^ 10 = 1024), предварительно вычисляли некоторые катушки (setupReal) и:

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

Теперь A будет содержать n/2 комплексных чисел. Они представляют собой n/2 частотных бункера:

  • bin [1].idealFreq = 44Hz - т.е. самая низкая частота, которую мы можем надежно обнаружить, - это ОДНА полная волна в этом окне, то есть волна 44 Гц.

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

  • и др.

  • bin [512].idealFreq = 512 * 44Hz. Самая высокая частота, которую мы можем обнаружить (так называемая частота Найквиста), - это где каждая пара точек представляет собой волну, т.е. 512 полных волн внутри окна, т.е. 512 * 44 Гц или: n/2 * bin [1].idealFreq

  • На самом деле есть дополнительный Bin, Bin [0], который часто называют "DC Offset". Так получилось, что Bin [0] и Bin [n/2] всегда будут иметь сложную компоненту 0, поэтому A [0].realp используется для хранения Bin [0] и A [0].imagp используется для хранения Bin [ п /2]

И величина каждого комплексного числа - это количество энергии, вибрирующей вокруг этой частоты.

Итак, как вы можете видеть, это был бы не очень хороший датчик высоты тона, так как он не имеет почти достаточно тонкой детализации. Существует хитроумный трюк Извлечение точных частот из БПФ с использованием изменения фазы между кадрами, чтобы получить точную частоту для данного бункера.

Хорошо, Теперь на код:

Обратите внимание на 'ip' в vDSP_fft_zrip, = 'in place', то есть на выходе перезаписывается A ('r' означает, что он принимает реальные входы)

Посмотрите документацию на vDSP_fft_zrip,

Реальные данные хранятся в сплит-комплексе формы с нечетными значениями, хранящимися на воображаемая сторона расколотого комплекса формы и даже реалов, хранящихся на реальная сторона.

это, наверное, самое трудное для понимания. Мы используем тот же контейнер (& A) весь процесс. поэтому вначале мы хотим заполнить его n действительными числами. после БПФ он будет удерживать n/2 комплексных чисел. мы тогда бросаем это в обратное преобразование и, надеюсь, выберем наши исходные n действительных чисел.

теперь структура A - его установка для комплексных значений. Таким образом, vDSP должен стандартизировать, как упаковывать в него реальные числа.

поэтому сначала мы порождаем n действительных чисел: 1, 2,..., n

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

Затем мы упаковываем их в как n/2 complex #s:

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

Вам действительно нужно посмотреть, как распределяется A, чтобы получить это, возможно, посмотрите COMPLEX_SPLIT в документации.

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

Далее мы делаем предварительный расчет.


Быстрый класс DSP для maths bods: Теория Фурье занимает много времени, чтобы окунуться в голову (я смотрел на нее в течение нескольких лет)

Цисоид:

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

то есть. точка на единичной окружности в комплексной плоскости.

Когда вы умножаете комплексные числа, углы добавляют. Поэтому z ^ k будет продолжать прыгать вокруг единичного круга; z ^ k можно найти под углом k.theta​​p >

  • Выберем z1 = 0 + 1i, т.е. на четверть оборота от вещественной оси, и заметим, что z1 ^ 2 z1 ^ 3 z1 ^ 4 дают еще один четверть оборота, так что z1 ^ 4 = 1

  • Выберите z2 = -1, т.е. полуоборот. также z2 ^ 4 = 1, но z2 завершило в этой точке 2 цикла (z2 ^ 2 также = 1). Таким образом, вы можете думать о z1 как о фундаментальной частоте и z2 как о первой гармонике

  • Аналогично, z3 = точка "три четверти оборота", т.е. -i завершает ровно 3 цикла, но на самом деле движение вперед 3/4 каждый раз совпадает с обратным назад 1/4 каждый раз

то есть. z3 - это просто z1, но в противоположном направлении - он называется сглаживанием

z2 - самая высокая значимая частота, так как мы выбрали 4 образца для полной волны.

  • z0 = 1 + 0i, z0 ^ (ничего) = 1, это смещение по постоянному току

Вы можете выразить любой 4-точечный сигнал как линейную комбинацию z0 z1 и z2 т.е. вы проецируете его на эти базисные векторы

но я слышу, что вы спрашиваете: "Что значит проецировать сигнал на цисоид?"

Вы можете думать об этом так: Игла вращается вокруг цисоида, поэтому на образце k игла указывает направление k.theta, а длина - сигнал [k]. Сигнал, который точно соответствует частоте цизоидов, будет выпирать результирующую форму в определенном направлении. Поэтому, если вы добавите все вклады, вы получите сильный результирующий вектор. Если частота почти совпадает, чем выпуклость будет меньше и будет медленно перемещаться по кругу. Для сигнала, который не соответствует частоте, вклады отменяют друг друга.

http://complextoreal.com/tutorials/tutorial-4-fourier-analysis-made-easy-part-1/ поможет вам получить интуитивное понимание.

Но суть такова; если мы выбрали проект 1024 образцов на {z0,..., z512}, мы бы предварительно вычислили z0 через z512 и , что этот шаг предварительного расчета.


Обратите внимание, что если вы делаете это в реальном коде, вы, вероятно, захотите сделать это один раз, когда приложение загрузится и вызовет функцию дополнительного доступа один раз, когда он уйдет. НЕ делайте это много раз - это дорого.

// let 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);

Стоит отметить, что если мы установим log2n на, например, 8, вы можете выбросить эти предварительно просчитанные значения в любую функцию fft, которая использует разрешение <= 2 ^ 8. Таким образом (если вы не хотите, чтобы оптимизация памяти была максимальной), просто создайте один набор для максимального разрешения, который вам понадобится, и используйте его для всего.

Теперь фактические преобразования, используя материал, который мы только что рассчитали:

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

В этот момент A будет содержать n/2 комплексных числа, только первый из них фактически представляет собой два действительных числа (смещение по постоянному току, Nyquist #), маскирующееся как комплексное число. Обзор документации объясняет эту упаковку. Это довольно аккуратно - в основном это позволяет (сложные) результаты преобразования быть упакованы в тот же объем памяти, что и (реальные, но странно упакованные) входы.

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

и обратно... нам все равно придется распаковать наш оригинальный массив из A., тогда мы сравним только, чтобы проверить, что мы вернулись именно с того, с чего мы начали, отпустите наши предварительно рассчитанные бобины и сделали!

Но подождите! перед распаковкой необходимо выполнить одно последнее:

// 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);

Ответ 2

Вот пример реального мира: фрагмент С++, который использует процедуры ускорения vdSP fft для выполнения автокорреляции на входе аудиоустройства Remote IO. Использование этой структуры довольно сложно, но документация не так уж плоха.

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);
    }
}

Ответ 3

Пока я скажу, что Apple FFT Framework быстро... Вам нужно знать, как работает FFT, чтобы получить точное определение тона (например, вычисление разности фаз на каждом последующем FFT, чтобы найти точный шаг, а не шаг самого доминирующего бункера).

Я не знаю, помогла ли это, но я загрузил свой объект Pitch Detector из своего приложения-тюнера (musicianskit.com/developer.php). Для загрузки также есть пример проекта xCode 4 (так что вы можете увидеть, как работает реализация).

Я работаю над загрузкой примера реализации FFT - так что следите за обновлениями, и я обновлю это, когда это произойдет.

Счастливое кодирование!