Спектрограмма от AVAudioPCMBuffer с использованием инфраструктуры Accelerate в Swift

Я пытаюсь создать спектрограмму из AVAudioPCMBuffer в Swift. Я устанавливаю кран на AVAudioMixerNode и получаю обратный вызов с аудиобуфером. Я хотел бы преобразовать сигнал в буфере в словарь [Float:Float], где ключ представляет частоту, а значение представляет величину звука на соответствующей частоте.

Я пытался использовать платформу Apple Accelerate, но результаты, которые я получаю, кажутся сомнительными. Я уверен, что дело в том, как я преобразовываю сигнал.

Я просмотрел эту запись в блоге среди прочего для справки.

Вот что у меня есть:

self.audioEngine.mainMixerNode.installTapOnBus(0, bufferSize: 1024, format: nil, block: { buffer, when in
    let bufferSize: Int = Int(buffer.frameLength)

    // Set up the transform
    let log2n = UInt(round(log2(Double(bufferSize))))
    let fftSetup = vDSP_create_fftsetup(log2n, Int32(kFFTRadix2))

    // Create the complex split value to hold the output of the transform
    var realp = [Float](count: bufferSize/2, repeatedValue: 0)
    var imagp = [Float](count: bufferSize/2, repeatedValue: 0)
    var output = DSPSplitComplex(realp: &realp, imagp: &imagp)

    // Now I need to convert the signal from the buffer to complex value, this is what I'm struggling to grasp.
    // The complexValue should be UnsafePointer<DSPComplex>. How do I generate it from the buffer's floatChannelData?
    vDSP_ctoz(complexValue, 2, &output, 1, UInt(bufferSize / 2))

    // Do the fast Fournier forward transform
    vDSP_fft_zrip(fftSetup, &output, 1, log2n, Int32(FFT_FORWARD))

    // Convert the complex output to magnitude
    var fft = [Float](count:Int(bufferSize / 2), repeatedValue:0.0)
    vDSP_zvmags(&output, 1, &fft, 1, vDSP_length(bufferSize / 2))

    // Release the setup
    vDSP_destroy_fftsetup(fftsetup)

    // TODO: Convert fft to [Float:Float] dictionary of frequency vs magnitude. How?
})

Мои вопросы

  1. Как преобразовать buffer.floatChannelData в UnsafePointer<DSPComplex> для перехода к функции vDSP_ctoz? Есть ли другой/лучший способ сделать это, возможно, даже в обход vDSP_ctoz?
  2. Отличается ли это, если буфер содержит звук с нескольких каналов? Как это отличается, когда данные буферного аудиоканала чередуются или не чередуются?
  3. Как преобразовать индексы в массиве fft в частоты в Гц?
  4. Что-то еще я могу делать неправильно?

Обновлять

Спасибо всем за предложения. В итоге я заполнил сложный массив, как это было предложено в принятом ответе. Когда я наношу значения и играю тон 440 Гц на камертоне, он регистрируется именно там, где должен.

Вот код для заполнения массива:

var channelSamples: [[DSPComplex]] = []
for var i=0; i<channelCount; ++i {
    channelSamples.append([])
    let firstSample = buffer.format.interleaved ? i : i*bufferSize
    for var j=firstSample; j<bufferSize; j+=buffer.stride*2 {
        channelSamples[i].append(DSPComplex(real: buffer.floatChannelData.memory[j], imag: buffer.floatChannelData.memory[j+buffer.stride]))
    }
}

Затем массив channelSamples содержит отдельный массив сэмплов для каждого канала.

Для расчета величины я использовал это:

var spectrum = [Float]()
for var i=0; i<bufferSize/2; ++i {
    let imag = out.imagp[i]
    let real = out.realp[i]
    let magnitude = sqrt(pow(real,2)+pow(imag,2))
    spectrum.append(magnitude)
}

person Jakub    schedule 01.10.2015    source источник
comment
Эй, просто обнаружив ваш вопрос о переполнении стека, я должен сказать: спасибо! Вы, несомненно, сэкономили мне много часов исследований. Я все еще разбираюсь в том, как работает этот ответ и все такое, но я хотел выразить некоторую признательность, поскольку он кажется довольно неоткрытым (или, возможно, просто не имеет отношения к большинству людей)   -  person sova    schedule 17.08.2016
comment
Этот вопрос довольно старый, но что такое переменная out во второй части и как вы ее получили?   -  person Logan    schedule 03.05.2017
comment
@Logan: переменная out является экземпляром DSPSplitComplex. Он содержит комплексное число, где действительная и мнимая части хранятся в отдельных массивах. Он заполняется функцией БПФ.   -  person Jakub    schedule 04.05.2017
comment
@Jakub, спасибо, я смог понять, как заставить это работать. Вы сэкономили мне кучу времени! Вот голосование за!   -  person Logan    schedule 04.05.2017


Ответы (2)


  1. Хакерский способ: вы можете просто создать массив с плавающей запятой. Где реалы и имаги идут одно за другим.
  2. Это зависит от того, чередуется ли звук или нет. Если он чередуется (в большинстве случаев), левый и правый каналы находятся в массиве с STRIDE 2.
  3. Самая низкая частота в вашем случае - это частота периода 1024 выборки. В случае 44100кГц это ~23мс, самая низкая частота спектра будет 1/(1024/44100) (~43Гц). Следующая частота будет в два раза больше этой (~86 Гц) и так далее.
person user1232690    schedule 01.10.2015
comment
Спасибо @user1232690. Заполнение сложного массива таким образом работает хорошо. В интересах других я опубликую решение в исходном сообщении. - person Jakub; 02.10.2015
comment
Кстати, for var i=0; i<bufferSize/2; ++i можно было бы оптимизировать с помощью чего-то вроде этого vDSP_vsmul(realp, 1, &scalar, &(complexValues) + 0, 2, (UInt)(bufferSize/2)) и vDSP_vsmul(imagp, 1, &scalar, &(complexValues) + 1, 2, (UInt)(bufferSize/2)), где скаляр равен 1,0 с плавающей запятой. - person user1232690; 02.10.2015

4: Вы установили обработчик обратного вызова на аудиошину. Вероятно, это выполняется с приоритетом потока в реальном времени и часто. Вы не должны делать ничего, что может привести к блокировке (это, скорее всего, приведет к инверсии приоритета и глючному звуку):

  1. Выделите память (realp, imagp - [Float](.....) является сокращением от Array[float] - и, вероятно, выделено в куче`. Предварительно выделите эти

  2. Вызов длительных операций, таких как vDSP_create_fftsetup(), который также выделяет память и инициализирует ее. Опять же, вы можете выделить это один раз вне вашей функции.

person marko    schedule 01.10.2015
comment
Команда CoreAudio была довольно прохладной на WWDC в этом году по вопросу о быстром для аудиокода. Они рекомендовали традиционный подход C++ или C. - person marko; 02.10.2015