Получить частоту Гц из аудиопотока на iPhone

Каков наилучший способ получить значение частоты Гц из аудиопотока (музыки) на iOS? Каковы самые лучшие и простые фреймворки, предоставляемые Apple для этого. Заранее спасибо.

3 ответа

Решение

Такие вопросы часто задают здесь, на SO. (Я ответил на аналогичный здесь), поэтому я написал небольшое руководство с кодом, который вы можете использовать даже в коммерческих и закрытых приложениях. Это не обязательно ЛУЧШИЙ способ, но это способ, который понимают многие люди. Вам нужно будет изменить его в зависимости от того, что вы подразумеваете под "средним значением Гц каждого короткого музыкального сегмента". Вы имеете в виду, например, основной шаг или частотный центроид.

Возможно, вы захотите использовать Apple FFT в ускоренной среде, как это предлагается в другом ответе.

Надеюсь, поможет.

http://blog.bjornroche.com/2012/07/frequency-detection-using-fft-aka-pitch.html

Вот некоторый код, который я использую для выполнения FFT в iOS с использованием Accelerate Framework, что делает его довольно быстрым.

//keep all internal stuff inside this struct
    typedef struct FFTHelperRef {
        FFTSetup fftSetup; // Accelerate opaque type that contains setup information for a given FFT transform.
        COMPLEX_SPLIT complexA; // Accelerate type for complex number
        Float32 *outFFTData; // Your fft output data
        Float32 *invertedCheckData; // This thing is to verify correctness of output. Compare it with input.
    } FFTHelperRef;

// first - инициализируйте ваш FFTHelperRef с помощью этой функции.

FFTHelperRef * FFTHelperCreate(long numberOfSamples) {

    FFTHelperRef *helperRef = (FFTHelperRef*) malloc(sizeof(FFTHelperRef));
    vDSP_Length log2n = log2f(numberOfSamples);    
    helperRef->fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
    int nOver2 = numberOfSamples/2;
    helperRef->complexA.realp = (Float32*) malloc(nOver2*sizeof(Float32) );
    helperRef->complexA.imagp = (Float32*) malloc(nOver2*sizeof(Float32) );

    helperRef->outFFTData = (Float32 *) malloc(nOver2*sizeof(Float32) );
    memset(helperRef->outFFTData, 0, nOver2*sizeof(Float32) );

    helperRef->invertedCheckData = (Float32*) malloc(numberOfSamples*sizeof(Float32) );

    return  helperRef;
}

// передаем инициализированный FFTHelperRef, данные и размер данных здесь. Вернуть данные FFT с размером numSamples/2.

Float32 * computeFFT(FFTHelperRef *fftHelperRef, Float32 *timeDomainData, long numSamples) {
    vDSP_Length log2n = log2f(numSamples);
    Float32 mFFTNormFactor = 1.0/(2*numSamples);

    //Convert float array of reals samples to COMPLEX_SPLIT array A
    vDSP_ctoz((COMPLEX*)timeDomainData, 2, &(fftHelperRef->complexA), 1, numSamples/2);

    //Perform FFT using fftSetup and A
    //Results are returned in A
    vDSP_fft_zrip(fftHelperRef->fftSetup, &(fftHelperRef->complexA), 1, log2n, FFT_FORWARD);

    //scale fft 
    vDSP_vsmul(fftHelperRef->complexA.realp, 1, &mFFTNormFactor, fftHelperRef->complexA.realp, 1, numSamples/2);
    vDSP_vsmul(fftHelperRef->complexA.imagp, 1, &mFFTNormFactor, fftHelperRef->complexA.imagp, 1, numSamples/2);

    vDSP_zvmags(&(fftHelperRef->complexA), 1, fftHelperRef->outFFTData, 1, numSamples/2);

    //to check everything =============================
    vDSP_fft_zrip(fftHelperRef->fftSetup, &(fftHelperRef->complexA), 1, log2n, FFT_INVERSE);
    vDSP_ztoc( &(fftHelperRef->complexA), 1, (COMPLEX *) fftHelperRef->invertedCheckData , 2, numSamples/2);
    //=================================================    

    return fftHelperRef->outFFTData;
}

Используйте это так:

  1. Инициализируйте его: FFTHelperCreate (TimeDomainDataLenght);

  2. Передайте данные временной области Float32, получите данные частотной области по возвращении: Float32 * fftData = computeFFT (fftHelper, buffer, frameSize);

Теперь у вас есть массив, где индексы = частоты, значения = величина (квадрат величины?). Согласно теореме Найквиста, ваша максимально возможная частота в этом массиве составляет половину вашей частоты дискретизации. То есть, если ваша частота дискретизации = 44100, максимальная частота, которую вы можете кодировать, составляет 22050 Гц.

Итак, найдите максимальную частоту Nyquist для вашей частоты дискретизации: const Float32 NyquistMaxFreq = SAMPLE_RATE / 2.0;

Найти Hz легко: Float32 hz = ((Float32) someIndex / (Float32) fftDataSize) * NyquistMaxFreq;(fftDataSize = frameSize / 2.0)

Это работает для меня. Если я сгенерирую определенную частоту в Audacity и воспроизведу ее - этот код обнаружит правильную (самую сильную, для этого также нужно найти max в fftData).

(есть небольшое несоответствие примерно в 1-2%. Не уверен, почему это происходит. Если кто-то может объяснить мне, почему - это было бы очень ценно.)

РЕДАКТИРОВАТЬ:

Это несоответствие происходит, потому что части, которые я использую для FFT, слишком малы. Использование больших порций данных во временной области (16384 кадра) решает проблему. Этот вопрос объясняет это: Невозможно получить правильное значение частоты на iphone

РЕДАКТИРОВАТЬ: Вот пример проекта: https://github.com/krafter/DetectingAudioFrequency

Apple не предоставляет рамки для оценки частоты или высоты тона. Тем не менее, платформа iOS Accelerate действительно включает в себя процедуры для БПФ и автокорреляции, которые могут использоваться в качестве компонентов более сложных алгоритмов распознавания или оценки частоты и основного тона.

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

Другие вопросы по тегам