Swift 中使用加速框架的 AVAudioPCMBuffer 的频谱图
Spectrogram from AVAudioPCMBuffer using Accelerate framework in Swift
我正在尝试从 Swift 中的 AVAudioPCMBuffer
生成频谱图。我在 AVAudioMixerNode
上安装了一个水龙头,并收到了带有音频缓冲区的回调。我想将缓冲区中的信号转换为 [Float:Float]
字典,其中键表示频率,值表示相应频率上音频的幅度。
我尝试使用 Apple 的 Accelerate 框架,但我得到的结果似乎很可疑。我确定这只是我转换信号的方式。
我查看了 this blog post 作为参考。
这是我的:
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
函数?有没有 different/better 方法甚至可以绕过 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]))
}
}
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)
}
- Hacky 方式:您可以只转换一个浮点数组。 reals 和 imag 值一个接一个地变化。
- 这取决于音频是否交错。如果它是交错的(大多数情况),左右声道在 STRIDE 2
的数组中
- 在您的案例中,最低频率是 1024 个样本周期的频率。在 44100kHz 的情况下,它是 ~23ms,频谱的最低频率将为 1/(1024/44100) (~43Hz)。下一个频率将是这个频率的两倍 (~86Hz) 等等。
4:您已经在音频总线上安装了回调处理程序。这很可能 运行 具有实时线程优先级并且很频繁。你不应该做任何有可能阻塞的事情(它可能会导致优先级倒置和音频故障):
分配内存(realp
、imagp
- [Float](.....)
对于 Array[float]
是 shorthand - 并且可能分配在堆上` .预分配这些
调用诸如 vDSP_create_fftsetup()
之类的冗长操作 - 它还会分配内存并对其进行初始化。同样,您可以在函数之外分配一次。
我正在尝试从 Swift 中的 AVAudioPCMBuffer
生成频谱图。我在 AVAudioMixerNode
上安装了一个水龙头,并收到了带有音频缓冲区的回调。我想将缓冲区中的信号转换为 [Float:Float]
字典,其中键表示频率,值表示相应频率上音频的幅度。
我尝试使用 Apple 的 Accelerate 框架,但我得到的结果似乎很可疑。我确定这只是我转换信号的方式。
我查看了 this blog post 作为参考。
这是我的:
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
函数?有没有 different/better 方法甚至可以绕过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]))
}
}
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)
}
- Hacky 方式:您可以只转换一个浮点数组。 reals 和 imag 值一个接一个地变化。
- 这取决于音频是否交错。如果它是交错的(大多数情况),左右声道在 STRIDE 2 的数组中
- 在您的案例中,最低频率是 1024 个样本周期的频率。在 44100kHz 的情况下,它是 ~23ms,频谱的最低频率将为 1/(1024/44100) (~43Hz)。下一个频率将是这个频率的两倍 (~86Hz) 等等。
4:您已经在音频总线上安装了回调处理程序。这很可能 运行 具有实时线程优先级并且很频繁。你不应该做任何有可能阻塞的事情(它可能会导致优先级倒置和音频故障):
分配内存(
realp
、imagp
-[Float](.....)
对于Array[float]
是 shorthand - 并且可能分配在堆上` .预分配这些调用诸如
vDSP_create_fftsetup()
之类的冗长操作 - 它还会分配内存并对其进行初始化。同样,您可以在函数之外分配一次。