构建 FFT 音频的频谱包络

信息处理 频谱
2022-02-09 15:13:09

我试图弄清楚如何创建一个良好的信号频谱包络。

基本上,我目前正在对音频的窗口部分应用 FFT 并生成频谱幅度的条形图样式表示。

然而,除此之外,我真的很想在顶部覆盖一个好的光谱包络。不幸的是,我似乎无法想出一个好的解决方案。

我尝试了一个简单的峰值选择算法,在该算法中我找到了频谱中的每个峰值(我实际上只是寻找当前值大于任一侧存储桶中的值的点)。这会返回一组峰值,然后我使用 catmull-rom 插值在峰值上绘制线。

正如您在以下屏幕截图中看到的那样,这合理地遵循了频谱:

合理的信封

然而它并不理想。例如,在该图像中,您可以看到它如何不遵循左侧的光谱。

缩小时:

太锯齿

您会看到信封非常参差不齐。它并不可怕,但这真的是构建光谱包络的最佳方式吗?

有没有更好的方法来挑选理想的峰,使其真正包含光谱?如果是这样,任何人都可以指出我的算法吗?

catmull-rom 插值也是最好的方法吗?谁能建议一种更好的插值方法并告诉我如何实现它?

问题与语言无关,但我用 C++ 编写。

编辑:好的,我找到了自动回归建模的实现。

然后我实现了如下方法:

    std::vector< float > coefficients( 3 );
    AutoRegression( &mSpecBuffer.front(), kFFTSizeDiv2, 3, &coefficients.front(), MAXENTROPY );

    mPeakBuffer.clear();

    mPeakBuffer.push_back( Peak( 0, mSpecBuffer[0] ) );
    mPeakBuffer.push_back( Peak( 1, mSpecBuffer[1] ) );
    mPeakBuffer.push_back( Peak( 2, mSpecBuffer[2] ) );

    int x       = 3;
    int xMax    = kFFTSizeDiv2;
    while( x < xMax )
    {
        const float k3  = (mPeakBuffer.end() - 3)->peakHeight;
        const float k2  = (mPeakBuffer.end() - 2)->peakHeight;
        const float k1  = (mPeakBuffer.end() - 1)->peakHeight;

        const float k   = (k1 * coefficients[0]) + (k2 * coefficients[1]) + (k3 * coefficients[2]);

        mPeakBuffer.push_back( Peak( x, k ) );
        x++;
    }

这给了我以下结果:

自回归包络

在我看来,这看起来好多了。

缩小后它看起来仍然非常好:

自回归包络(缩小)

所以我只想检查一下,这是否正确?如果是这样,我将沿着这条道路进一步工作:)

3个回答

通常用于恢复音频信号的频谱包络的​​两种技术:

  • 低阶 AR 建模。估计您的 AR 系数,然后绘制估计的全极点滤波器的频率响应幅度。

  • 倒谱分析。取 FFT 输出幅度的对数。取 FFT,截断只保留第一个系数,取逆 FFT,取指数。这相当于在频谱上应用低通滤波器(具有对数尺度幅度)。

如果我没看错的话,这似乎更像是一个风格问题,而不是任何真正基于理论的东西。目前还不清楚你想要什么。您希望您的用户可以通过查看显示来提取关于音频信号的哪些信息?如果您感兴趣的信号确实具有“锯齿状”频谱(并且您的示例确实如此),那么绘制“锯齿状”包络不会有问题。

您确实在第一张图片中指出了问题,您的插值包络并不严格大于或等于 FFT 中的所有 bin。我认为您可以通过调整用于在样条插值中选择控制点的算法来解决此问题。您可以在上向样条曲线插入一个控制点,而不是只选择出现峰值的控制点也就是说,对于每个 bin,如果它大于其左邻居,则将其作为样条曲线上的控制点。BnBn>Bn1

那么,在第一幅图像中,前 5 个 bin 都将是控制点,并且内插包络将穿过每个 bin 的实际值,这可能会提供更好看的基础光谱“包络”。

在我看来,您的目标是获得一条始终严格高于频谱的线。这与许多倾向于具有能量守恒特性的信号处理方法背道而驰 - 使用这些方法,降低分辨率会将频谱峰值扩展到相邻的 FFT 箱(例如箱 n 中 1.0 的峰值将扩散到箱 n 处的 0.1- 1,在 bin n 处为 0.8,在 bin n+1 处为 0.1)。当然,“hacks”、后处理等,当然可以根据你的视觉标准给出一些可以接受的东西,问题是从信号处理的角度来看它们是没有意义的。

我会建议一种稍微不同的方法,它是:b/ 有一个关于你正在绘制的内容的“沟通计划”,以及它对用户的用处。

首先,您可以传达您正在绘制一个低分辨率表示,它较少强调单个频谱峰值,而更多地强调整体频率分布 - 这将是耳朵中声音的“足迹”,与音高无关。为此,您可以使用:

  • 低阶 FFT(比如用于大 FFT 的大小的平方根),sinc 插值以适合您的全尺寸。
  • 听觉频谱。获取主 FFT 的输出,对以听力临界频带为中心的重叠三角形窗口上的能量求和,通过扩展函数进行卷积,然后从 Bark 尺度转换回线性频率尺度。有关快速介绍,请参阅Tristan Jehan 博士的第 3 章。

其次,你可以利用时间信息;并传达您正在绘制的是在过去几秒钟的“上下文”背景上绘制的当前频谱 - 允许用户注意到声音的变化(这种可视化在设置多频段压缩器时实际上很有用)。

  • 低通滤波频谱: background[n] = 0.9 * background[n] + 0.1 * current_fft_frame[n] 或...
  • 过去分析帧中每个 FFT bin 的最大值。n

请注意,最后一个选项是我列表中唯一一个保证它始终大于当前帧频谱的选项。