对 pcm 数据应用 FFT 并转换为频谱图

Posted

技术标签:

【中文标题】对 pcm 数据应用 FFT 并转换为频谱图【英文标题】:Apply FFT on pcm data and convert to a spectrogram 【发布时间】:2013-07-02 01:33:49 【问题描述】:

我目前在 VS2012 中使用 Metro 应用程序。我有一个 c# 代码,可以记录用户的声音并将其保存到 wav 文件中(16 位、44.1kHz、单声道)。我已将 PCM 处理为仅包含值介于 -1 和 1 之间的双数组数据,如下所示。

下一步是我想要做的就是对双数组数据应用 FFT 并将其转换为频谱图。我想知道是否有任何 FFT 算法可以在不使用任何库的情况下接收双数组。

我还想知道是否有任何方法可以使用 Metro 将此数据(应用 FFT 后)转换为频谱图(稍后用于与另一个频谱图进行比较)。

我的双数组值的一部分示例,我想用它对其应用 FFT 以获取频率并直观地显示它。

这是处理我的 PCM 数据的代码:

public static Double[] prepare(String wavePath)

    Double[] data;
    byte[] wave;
    byte[] sR = new byte[4];
    System.IO.FileStream WaveFile = System.IO.File.OpenRead(wavePath);
    wave = new byte[WaveFile.Length];
    data = new Double[(wave.Length - 44) / 4];//shifting the headers out of the PCM data;
    WaveFile.Read(wave, 0, Convert.ToInt32(WaveFile.Length));//read the wave file into the wave variable
    /***********Converting and PCM accounting***************/
    for (int i = 0; i < data.Length; i++)
    
        data[i] = BitConverter.ToInt16(wave, i * 2) / 32768.0;
    
    //65536.0.0=2^n,       n=bits per sample;

    return data;

已编辑 这是我的输出:

-3.0517578125E-05  
-3.0517578125E-05  
-3.0517578125E-05  
-3.0517578125E-05  
-6.103515625E-05  
-9.1552734375E-05  
-6.103515625E-05  
-6.103515625E-05  
-6.103515625E-05  
-6.103515625E-05  
-9.1552734375E-05  
-6.103515625E-05  
-9.1552734375E-05  
-6.103515625E-05  
-9.1552734375E-05  
-6.103515625E-05  

【问题讨论】:

你知道为什么每一个数字都是0吗?看起来很奇怪...... 我不确定。对我来说也有点奇怪。也许如果我提出我用来处理我的 pcm 数据的代码,你可以为我指出来 您将 i 增加 2 并写入 data[i],因此您只写入每隔一个字段。你需要将 i 加一。例如i++ 并使用 BitConverter.ToInt16(wave, i*2) 读出字节。而且我还想知道为什么您在读取数据时不考虑 PCM 标头的偏移量?也许“我”应该从 44 开始? How to generate the audio spectrum using fft in C++? 的可能重复项 @thalm 嗨,我已经编辑了代码。您可以忽略我删除的部分,其中 i = 28 是为了识别采样率。我设法得到 -1 和 1 之间的值,如我的输出所示。但是,我在开始时有一些值,其间有很多 0。这是一个问题还是应该这样做? 【参考方案1】:

如果您在 Google 上搜索 c# FFT,您会找到可能的代码示例。这个看起来不错,我找到了here。有趣的是,您可以使用您的数据,每秒钟都带有 0 作为 FFT 表的输入,如果您愿意,它需要复数。

始终确保您的数据包具有 2 个样本计数的幂。

    /// <summary>                                                                                            
    /// Compute the forward or inverse Fourier Transform of data, with data                                  
    /// containing complex valued data as alternating real and imaginary                                     
    /// parts. The length must be a power of 2. This method caches values                                    
    /// and should be slightly faster on than the FFT method for repeated uses.                              
    /// It is also slightly more accurate. Data is transformed in place.                                     
    /// </summary>                                                                                           
    /// <param name="data">The complex data stored as alternating real                                       
    /// and imaginary parts</param>                                                                          
    /// <param name="forward">true for a forward transform, false for                                        
    /// inverse transform</param>                                                                            
    public void TableFFT(double[] data, bool forward)                                                        
                                                                                                            
        var n = data.Length;                                                                                 
        // checks n is a power of 2 in 2's complement format                                                 
        if ((n & (n - 1)) != 0)                                                                              
            throw new ArgumentException(                                                                     
                "data length " + n + " in FFT is not a power of 2"                                           
                );                                                                                           
        n /= 2;    // n is the number of samples                                                             

        Reverse(data, n); // bit index data reversal                                                         

        // make table if needed                                                                              
        if ((cosTable == null) || (cosTable.Length != n))                                                    
            Initialize(n);                                                                                   

        // do transform: so single point transforms, then doubles, etc.                                      
        double sign = forward ? B : -B;                                                                      
        var mmax = 1;                                                                                        
        var tptr = 0;                                                                                        
        while (n > mmax)                                                                                     
                                                                                                            
            var istep = 2 * mmax;                                                                            
            for (var m = 0; m < istep; m += 2)                                                               
                                                                                                            
                var wr = cosTable[tptr];                                                                     
                var wi = sign * sinTable[tptr++];                                                            
                for (var k = m; k < 2 * n; k += 2 * istep)                                                   
                                                                                                            
                    var j = k + istep;                                                                       
                    var tempr = wr * data[j] - wi * data[j + 1];                                             
                    var tempi = wi * data[j] + wr * data[j + 1];                                             
                    data[j] = data[k] - tempr;                                                               
                    data[j + 1] = data[k + 1] - tempi;                                                       
                    data[k] = data[k] + tempr;                                                               
                    data[k + 1] = data[k + 1] + tempi;                                                       
                                                                                                            
                                                                                                            
            mmax = istep;                                                                                    
                                                                                                            


        // perform data scaling as needed                                                                    
        Scale(data, n, forward);                                                                             
      

    /// <summary>                                                                                            
    /// Compute the forward or inverse Fourier Transform of data, with                                       
    /// data containing real valued data only. The output is complex                                         
    /// valued after the first two entries, stored in alternating real                                       
    /// and imaginary parts. The first two returned entries are the real                                     
    /// parts of the first and last value from the conjugate symmetric                                       
    /// output, which are necessarily real. The length must be a power                                       
    /// of 2.                                                                                                
    /// </summary>                                                                                           
    /// <param name="data">The complex data stored as alternating real                                       
    /// and imaginary parts</param>                                                                          
    /// <param name="forward">true for a forward transform, false for                                        
    /// inverse transform</param>                                                                            
    public void RealFFT(double[] data, bool forward)                                                         
                                                                                                            

        var n = data.Length; // # of real inputs, 1/2 the complex length                                     
        // checks n is a power of 2 in 2's complement format                                                 
        if ((n & (n - 1)) != 0)                                                                              
            throw new ArgumentException(                                                                     
                "data length " + n + " in FFT is not a power of 2"                                           
                );                                                                                           

        var sign = -1.0; // assume inverse FFT, this controls how algebra below works                        
        if (forward)                                                                                         
         // do packed FFT. This can be changed to FFT to save memory                                        
            TableFFT(data, true);                                                                            
            sign = 1.0;                                                                                      
            // scaling - divide by scaling for N/2, then mult by scaling for N                               
            if (A != 1)                                                                                      
                                                                                                            
                var scale = Math.Pow(2.0, (A - 1) / 2.0);                                                    
                for (var i = 0; i < data.Length; ++i)                                                        
                    data[i] *= scale;                                                                        
                                                                                                            
                                                                                                            

        var theta = B * sign * 2 * Math.PI / n;                                                              
        var wpr = Math.Cos(theta);                                                                           
        var wpi = Math.Sin(theta);                                                                           
        var wjr = wpr;                                                                                       
        var wji = wpi;                                                                                       

        for (var j = 1; j <= n/4; ++j)                                                                       
                                                                                                            
            var k = n / 2 - j;                                                                               
            var tkr = data[2 * k];    // real and imaginary parts of t_k  = t_(n/2 - j)                      
            var tki = data[2 * k + 1];                                                                       
            var tjr = data[2 * j];    // real and imaginary parts of t_j                                     
            var tji = data[2 * j + 1];                                                                       

            var a = (tjr - tkr) * wji;                                                                       
            var b = (tji + tki) * wjr;                                                                       
            var c = (tjr - tkr) * wjr;                                                                       
            var d = (tji + tki) * wji;                                                                       
            var e = (tjr + tkr);                                                                             
            var f = (tji - tki);                                                                             

            // compute entry y[j]                                                                            
            data[2 * j] = 0.5 * (e + sign * (a + b));                                                        
            data[2 * j + 1] = 0.5 * (f + sign * (d - c));                                                    

            // compute entry y[k]                                                                            
            data[2 * k] = 0.5 * (e - sign * (b + a));                                                        
            data[2 * k + 1] = 0.5 * (sign * (d - c) - f);                                                    

            var temp = wjr;                                                                                  
            // todo - allow more accurate version here? make option?                                         
            wjr = wjr * wpr - wji * wpi;                                                                     
            wji = temp * wpi + wji * wpr;                                                                    
                                                                                                            

        if (forward)                                                                                         
                                                                                                            
            // compute final y0 and y_N/2, store in data[0], data[1]                                       
            var temp = data[0];                                                                              
            data[0] += data[1];                                                                              
            data[1] = temp - data[1];                                                                        
                                                                                                            
        else                                                                                                 
                                                                                                            
            var temp = data[0]; // unpack the y0 and y_N/2, then invert FFT                                
            data[0] = 0.5 * (temp + data[1]);                                                                
            data[1] = 0.5 * (temp - data[1]);                                                                
            // do packed inverse (table based) FFT. This can be changed to regular inverse FFT to save memory
            TableFFT(data, false);                                                                           
            // scaling - divide by scaling for N, then mult by scaling for N/2                               
            //if (A != -1) // todo - off by factor of 2? this works, but something seems weird               
                                                                                                            
                var scale = Math.Pow(2.0, -(A + 1) / 2.0)*2;                                                 
                for (var i = 0; i < data.Length; ++i)                                                        
                    data[i] *= scale;                                                                        
                                                                                                            
                                                                                                            
                                                                                                            

    /// <summary>                                                                                            
    /// Determine how scaling works on the forward and inverse transforms.                                   
    /// For size N=2^n transforms, the forward transform gets divided by                                     
    /// N^((1-a)/2) and the inverse gets divided by N^((1+a)/2). Common                                      
    /// values for (A,B) are                                                                                 
    ///     ( 0, 1)  - default                                                                               
    ///     (-1, 1)  - data processing                                                                       
    ///     ( 1,-1)  - signal processing                                                                     
    /// Usual values for A are 1, 0, or -1                                                                   
    /// </summary>                                                                                           
    public int A  get; set;                                                                                

    /// <summary>                                                                                            
    /// Determine how phase works on the forward and inverse transforms.                                     
    /// For size N=2^n transforms, the forward transform uses an                                             
    /// exp(B*2*pi/N) term and the inverse uses an exp(-B*2*pi/N) term.                                      
    /// Common values for (A,B) are                                                                          
    ///     ( 0, 1)  - default                                                                               
    ///     (-1, 1)  - data processing                                                                       
    ///     ( 1,-1)  - signal processing                                                                     
    /// Abs(B) should be relatively prime to N.                                                              
    /// Setting B=-1 effectively corresponds to conjugating both input and                                   
    /// output data.                                                                                         
    /// Usual values for B are 1 or -1.                                                                      
    /// </summary>                                                                                           
    public int B  get; set;  

    /// <summary>                                                                                            
    /// Scale data using n samples for forward and inverse transforms as needed                              
    /// </summary>                                                                                           
    /// <param name="data"></param>                                                                          
    /// <param name="n"></param>                                                                             
    /// <param name="forward"></param>                                                                       
    void Scale(double[] data, int n, bool forward)                                                           
                                                                                                            
        // forward scaling if needed                                                                         
        if ((forward) && (A != 1))                                                                           
                                                                                                            
            var scale = Math.Pow(n, (A - 1) / 2.0);                                                          
            for (var i = 0; i < data.Length; ++i)                                                            
                data[i] *= scale;                                                                            
                                                                                                            

        // inverse scaling if needed                                                                         
        if ((!forward) && (A != -1))                                                                         
                                                                                                            
            var scale = Math.Pow(n, -(A + 1) / 2.0);                                                         
            for (var i = 0; i < data.Length; ++i)                                                            
                data[i] *= scale;                                                                            
                                                                                                            
    

【讨论】:

以上是关于对 pcm 数据应用 FFT 并转换为频谱图的主要内容,如果未能解决你的问题,请参考以下文章

matlab 作出信号频谱图

wav音频文件解析读取 定点转浮点分析 幅值提取(C语言实现)

在将数据输入 FFT 用于音频频谱分析仪之前,使用 python 将 wav 文件转换为 csv 文件 [关闭]

对12bit的AD建模后matlab写程序对其进行FFT,计算SNR并输出频谱图,程序该怎么写???急

获取 WAV 文件的 PCM 值

使用 QWT 和 FFTW 绘制频谱图