我正在尝试使用Swift从一个AVAudioPCMBuffer
生成频谱图。我在AVAudioMixerNode
上安装了一个tap,然后通过音频缓冲回调接收信号。我想将缓冲区中的信号转换为[Float: Float]
字典,其中键表示频率,值表示相应频率上的音频幅度。
我尝试使用苹果的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?
})
我的问题是
- 如何将
buffer.floatChannelData
转换为UnsafePointer<DSPComplex>
,以便传递给vDSP_ctoz
函数?也许还有一种不同/更好的方法可以绕过vDSP_ctoz
吗? - 如果缓冲区包含来自多个通道的音频,是否会有所不同?当缓冲区音频通道数据交错和非交错时有什么不同?
- 如何将
fft
数组中的索引转换为 Hz 中的频率? - 我可能还做错了什么吗?
更新
感谢大家的建议。最终我按照被接受的答案所建议的方式填充了复数数组。当我在音叉上播放一个 440 Hz 的音调并绘制出值时,它正好显示在应该的位置。
以下是填充数组的代码:
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]))
}
}
< p > 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)
}
out
变量是 DSPSplitComplex 的一个实例。它保存了一个复数,其中实部和虚部分别存储在不同的数组中。它由 FFT 函数填充。 - Jakub