關於FFT分析音頻的小歸納


本文部分知識從以下文章學習:

https://zhuanlan.zhihu.com/p/19763358 傅里葉變換的知識

https://www.cnblogs.com/RabbitHu/p/FFT.html FFT的知識

 

最近工作上在做關於音樂游戲的內容,其中需要分析音頻找節奏點(或者說是重音點)。

學習了一系列相關知識后,了解到一段音樂的波形圖可以分解成不同頻率的波形圖,也就是由時域到頻域的轉換。

借用其他博主的圖就比較容易理解了,如下所示。

波從時域到頻域的轉換可以通過傅里葉變換實現,關於傅里葉變換的知識可以從最上面的鏈接學習或者自行查找(傅里葉真厲害!!!)。

計算機處理的音頻在時域上是離散的數據,我們可以使用離散傅里葉變換DFT(傅里葉變換在時域和頻域上都呈離散的形式)獲得頻域上的離散數據。

快速傅立葉變換FFT是DFT的快速算法,其核心思路就是分治法的DFT,具體推導可以查看上面的第二個鏈接。

FFT 代碼如下:

     static void BitReverse(Complex[] cpData, int n)
        {
            Complex temp;
            int lim = 0;
            while ((1 << lim) < n) lim++;
            for (int i = 0; i < n; i++)
            {
                int t = 0;
                for (int j = 0; j < lim; j++)
                {

                    if (((i >> j) & 1) != 0)
                        t |= (1 << (lim - j - 1));
                }
                if (i < t)
                {
                    temp = cpData[i];
                    cpData[i] = cpData[t];
                    cpData[t] = temp;
                } // i < t 防止交換兩次
            }
        }

        static void FFT1(Complex[] cpData, bool forward)
        {
            var n = cpData.Length;
 
            BitReverse(cpData, n);//位反轉

            Complex[] omg = new Complex[n];
            for (int i = 0; i < n; i++)
            {
                omg[i] = new Complex((float)Math.Cos(2 * Math.PI * i / n), (float)Math.Sin(2 * Math.PI * i / n));
            }
             Complex temp ;
            for (int step = 2; step <= n; step *= 2)
            {
                int m = step / 2;
                for (int j = 0; j < n; j += step)
                    for (int i = 0; i < m; i++)
                    {//蝶形運算
                        if(forward)
                            temp = omg[n / step * i] * cpData[j + i + m];
                        else
                            temp = omg[n / step * i].Conjugate() * cpData[j + i + m];
                        cpData[j + i + m] = cpData[j + i] - temp;
                        cpData[j + i] = cpData[j + i] + temp;
                    }
            }
        }

Complex是封裝的復數類,偷懶不是自己寫的,來自這位老哥https://blog.csdn.net/u011583927/article/details/46974341

這個FFT,new了好多對象,效率不是很高。。。

再貼一個直接把復數的實部虛部輪流放在一個數組里直接算的,能快一些。

     static void Reverse(float[] data, int n)
        {
                                                           
            int j = 0, k = 0;                                                        
            var top = n / 2;                                                   
            while (true)
            {
                                                 
                var t = data[j + 2];
                data[j + 2] = data[k + n];
                data[k + n] = t;
                t = data[j + 3];
                data[j + 3] = data[k + n + 1];
                data[k + n + 1] = t;
                if (j > k)
                {                                                                           
                    t = data[j];
                    data[j] = data[k];
                    data[k] = t;
                    t = data[j + 1];
                    data[j + 1] = data[k + 1];
                    data[k + 1] = t;
                                                                             
                    t = data[j + n + 2];
                    data[j + n + 2] = data[k + n + 2];
                    data[k + n + 2] = t;
                    t = data[j + n + 3];
                    data[j + n + 3] = data[k + n + 3];
                    data[k + n + 3] = t;
                }
                                                                                   
                k += 4;
                if (k >= n)
                    break;
                                                                                 
                var h = top;
                while (j >= h)
                {
                    j -= h;
                    h /= 2;
                }
                j += h;
            }                                                                            
        }
        static void FFT2(float[] data, bool forward)
        {
            var n = data.Length;            
            n /= 2;

            Reverse(data, n);


            float sign = forward ? 1 : -1;
            var mmax = 1;
            while (n > mmax)
            {
                var istep = 2 * mmax;
                var theta = sign * (float)Math.PI / mmax;
                float wr = 1, wi = 0;
                var wpr = (float)Math.Cos(theta);
                var wpi = (float)Math.Sin(theta);
                for (var m = 0; m < istep; m += 2)
                {
                    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;
                    }
                    var t = wr;                                                              
                    wr = wr * wpr - wi * wpi;
                    wi = wi * wpr + t * wpi;
                }
                mmax = istep;
            }

        }

     static void Main(string[] args)
        {

            int n =1024*512;
            float[] data = new float[2 * n];
            for (int i = 0; i < n; i++)
            {
                data[2 * i] = i;
                data[2 * i + 1] = 0;
            }

            Complex[] cpData = new Complex[n];
            for (int i = 0; i < n; i++)
            {
                cpData[i] = new Complex(data[2 * i], data[2 * i + 1]);
            }

            long s = DateTime.Now.Ticks;
            FFT1(cpData, true);
            Console.WriteLine("time:" + (DateTime.Now.Ticks - s) / 10000);
            
            s = DateTime.Now.Ticks;
            FFT2(data, true);
            Console.WriteLine("time:" + (DateTime.Now.Ticks - s) / 10000);

            Console.Read();
        }

 速度上還是差挺多的。。。

 

好了獲得頻率數據之后的流程就不再那么燒腦了(都怪自己早早把傅里葉變換還給課本了)。。。

找節奏點的邏輯大概如下(代碼有點多就不貼了):

1.根據采樣率依次獲取數據,每次通過FFT得到一組復數數組。

2.計算出復數的模長,可以表示此頻率下的聲音大小,可以把一定范圍的聲音累加起來,可以用來表示低音、中音、高音。

3.對比每一幀的數據變化就可以判斷出節奏點(聲音變化大,可以表示是一個節奏點)。

其實能得到頻域的值,針對不同的功能,大家后面就可以自由發揮了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM