ios - AVAudioPCMBuffer 的频谱图在 Swift 中使用 Accelerate 框架

标签 ios swift audio accelerate-framework avaudioengine

我正在尝试从 AVAudioPCMBuffer 生成频谱图在 swift 。我在 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?
})

我的问题是

  1. 如何转换 buffer.floatChannelDataUnsafePointer<DSPComplex>传递给 vDSP_ctoz功能?有没有不同/更好的方法来做到这一点甚至可能绕过 vDSP_ctoz
  2. 如果缓冲区包含来自多个 channel 的音频,这会有所不同吗?缓冲区音频 channel 数据交错或不交错时有何不同?
  3. 如何转换 fft 中的索引阵列到以赫兹为单位的频率?
  4. 我还有什么地方做错了吗?

更新

感谢大家的建议。我最终按照接受的答案中的建议填充了复杂数组。当我绘制这些值并在音叉上播放 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然后数组为每个 channel 保存单独的样本数组。

为了计算震级,我使用了这个:

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

最佳答案

  1. Hacky 方式:您可以只转换一个 float 组。真实值和图像值相继变化。
  2. 这取决于音频是否交错。如果它是交错的(大多数情况),左右声道在 STRIDE 2 的阵列中
  3. 在您的案例中,最低频率是 1024 个样本周期的频率。在 44100kHz 的情况下,它是 ~23ms,频谱的最低频率将为 1/(1024/44100) (~43Hz)。下一个频率将是这个频率的两倍 (~86Hz) 等等。

关于ios - AVAudioPCMBuffer 的频谱图在 Swift 中使用 Accelerate 框架,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32891012/

相关文章:

ios - 如何使用 swift 4 在 iOS 11 上播放声音?我在哪里放置 mp3 文件?

ios - 核心剧情: Is a sliced (little split from the rest) like pie chart possible?

ios - PickerView 在 DidSelectRow 之后消失

ios - UIView - 带有持续时间的动画似乎忽略了持续时间

swift - 检测 SKSpriteNode 是否在底部离开屏幕并调用函数

ios - 必须提供 secret 或公钥 - IOS/Swift + Socket.io-JWT

java - 将AWS Polly提供的ogg流保存到wav文件中

java - 使用 RTP 在 JMF 中实现播放器

ios - 未显示 UIViewController 时,属性返回 nil

android - 如何使用 ListView 改进应用程序的设置选项界面?