NAudio FFT的结果给出了所有频率上的强度(C#)

7
我已经实现了NAudio的wasapi回路录音和数据的FFT。大部分数据都是正常的,但偶尔(10秒至数分钟间隔)几乎所有频率都会显示振幅。

The error image

基本上,图片从右向左滚动,时间和频率按对数比例尺从最低频率开始。线条代表误差。据我所知,这些线条不应该存在。
我获取音频缓冲区并将样本发送到聚合器(应用汉明窗口),它实现了NAudio FFT。在任何方式修改数据之前,我已经检查了数据(FFT结果)(图片不是来自原始FFT输出,但是分贝缩放),证实FFT结果正在产生这些线条。我还可以指出,该图片使用LockBits进行了修改,因此我认为我在逻辑上有问题,但这就是为什么我检查FFT输出数据显示相同问题的原因。
好吧,我可能错了,问题可能出现在我说它不存在的地方,但似乎它源于FFT或缓冲区数据(数据本身或样本的聚合)。不知何故,我怀疑缓冲区本身会出现这种损坏情况。
如果有人有任何想法造成这个问题,我将非常感激!
更新
因此,我决定绘制整个FFT结果范围而不是其中一半。它显示了一些奇怪的东西。我不确定FFT,但我认为傅里叶变换应该给出一个关于中心对称的结果。这显然不是这种情况。

这张图片是线性比例尺的,因此图片的正中间是FFT结果的中心点。底部是第一个数据,而顶部是最后一个。

wholefft

我正在播放一个10kHz正弦波,这给出了两条水平线,但顶部的部分超出了我的理解。另外,似乎线条在图片的底部四分之一处镜像,这也让我感到奇怪。
更新2:所以我将FFT大小从4096增加到8192,然后再次尝试。这是我调整正弦频率后的输出结果。

picture3

结果似乎被镜像两次。一次在中间,然后在上下半部分再次出现。巨大的线条现在已经消失了...看起来现在线条只出现在下半部分。
通过使用不同的FFT长度进行进一步测试后,似乎这些线是完全随机的。
更新3
我已经进行了许多测试。我最近添加的东西是样本重叠,这样我就可以在下一个FFT的开始处重复使用样本数组的后半部分。在Hamming和Hann窗口上,它给我带来了巨大的强度(就像我发布的第二张图片),但是在BlackmannHarris上没有。禁用重叠会消除每个窗口函数上最大的错误。即使使用BH窗口,仍然会保留类似于顶部图片中的较小错误。我仍然不知道为什么会出现那些线条。
我的当前形式允许控制要使用哪个窗口函数(前面提到的三个中的哪个)、重叠(开/关)和多种不同的绘图选项。这使我能够比较所有影响方面的效果。
我将进一步调查(我相当确定我在某个时候犯了一个错误),但好的建议更加受欢迎!

你是否应用了任何窗口函数? - Mark Heath
是的,我正在使用汉明窗口。忘了提到这一点。 - Typhus
你能加上你目前所使用的代码吗?我正在尝试将FFT与Naudio配合使用,但没有成功。 - Pat
我需要将相当多的代码变得易读(因为我有不注释代码的坏习惯),但我会尽力而为。 - Typhus
顺便说一下,我终于解决了我的问题。我在处理包含数据的数组时犯了一个愚蠢的错误。除非至少查看代码,否则没有人能知道这个错误。 - Typhus
1个回答

15

问题出在我处理数据数组的方式上。现在运行得像魅力一样。

代码(去除了多余部分,可能会有错误):

// Other inputs are also usable. Just look through the NAudio library.
private IWaveIn waveIn; 
private static int fftLength = 8192; // NAudio fft wants powers of two!

// There might be a sample aggregator in NAudio somewhere but I made a variation for my needs
private SampleAggregator sampleAggregator = new SampleAggregator(fftLength);

public Main()
{
    sampleAggregator.FftCalculated += new EventHandler<FftEventArgs>(FftCalculated);
    sampleAggregator.PerformFFT = true;

    // Here you decide what you want to use as the waveIn.
    // There are many options in NAudio and you can use other streams/files.
    // Note that the code varies for each different source.
    waveIn = new WasapiLoopbackCapture(); 

    waveIn.DataAvailable += OnDataAvailable;

    waveIn.StartRecording();
}

void OnDataAvailable(object sender, WaveInEventArgs e)
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new EventHandler<WaveInEventArgs>(OnDataAvailable), sender, e);
    }
    else
    {
        byte[] buffer = e.Buffer;
        int bytesRecorded = e.BytesRecorded;
        int bufferIncrement = waveIn.WaveFormat.BlockAlign;

        for (int index = 0; index < bytesRecorded; index += bufferIncrement)
        {
            float sample32 = BitConverter.ToSingle(buffer, index);
            sampleAggregator.Add(sample32);
        }
    }
}

void FftCalculated(object sender, FftEventArgs e)
{
    // Do something with e.result!
}

还有示例聚合器类:

using NAudio.Dsp; // The Complex and FFT are here!

class SampleAggregator
{
    // FFT
    public event EventHandler<FftEventArgs> FftCalculated;
    public bool PerformFFT { get; set; }

    // This Complex is NAudio's own! 
    private Complex[] fftBuffer;
    private FftEventArgs fftArgs;
    private int fftPos;
    private int fftLength;
    private int m;

    public SampleAggregator(int fftLength)
    {
        if (!IsPowerOfTwo(fftLength))
        {
            throw new ArgumentException("FFT Length must be a power of two");
        }
        this.m = (int)Math.Log(fftLength, 2.0);
        this.fftLength = fftLength;
        this.fftBuffer = new Complex[fftLength];
        this.fftArgs = new FftEventArgs(fftBuffer);
    }

    bool IsPowerOfTwo(int x)
    {
        return (x & (x - 1)) == 0;
    }

    public void Add(float value)
    {
        if (PerformFFT && FftCalculated != null)
        {
            // Remember the window function! There are many others as well.
            fftBuffer[fftPos].X = (float)(value * FastFourierTransform.HammingWindow(fftPos, fftLength));
            fftBuffer[fftPos].Y = 0; // This is always zero with audio.
            fftPos++;
            if (fftPos >= fftLength)
            {
                fftPos = 0;
                FastFourierTransform.FFT(true, m, fftBuffer);
                FftCalculated(this, fftArgs);
            }
        }
    }
}

public class FftEventArgs : EventArgs
{
    [DebuggerStepThrough]
    public FftEventArgs(Complex[] result)
    {
        this.Result = result;
    }
    public Complex[] Result { get; private set; }
}

就这样了,我想。虽然可能有遗漏的地方。 希望能对你有所帮助!


我尝试使用FFT数据制作频谱分析仪可视化,但似乎效果不佳。有没有关于如何使用16个条计算峰值的想法/示例? - blez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接