目录

一、取得音频数据

二、将取得数据列表转换成fft所需形式

三、fft转换

四、频谱绘制

五、转换成对应的dB


       

用于将wav音频数据通过fft算法转换成频域,最后计算其对应的dB、Hz。安照操作成功显示,有问题留言讨论。

一、取得音频数据

取得方式:wav文件、麦克风采集数据(一般是麦克风采集实时显示频谱)

(1)从wav文件中取出,参考以下文章。

C#解析wav_宝帅哥的博客-CSDN博客_c# wav一、wav文件保存格式RIFF区块ChunkID:以'RIFF'为标识('RIFF' (0x52494646))CHUNKSize:是整个文件的长度减去ID和Size的长度Format: Type是WAVE(0x57415645)表示后面需要两个子块:Format区块和Data区块FORMAT区块Subchunk1ID: 以'fmt '(0x666D7420)为标识Subchunk1Size: 表示该区块数据的长度(不包含ID和Size的长度)AudioFormat: 表.https://blog.csdn.net/CSDN_15987/article/details/124194878?spm=1001.2014.3001.5502

         如果需要从wav文件取得数据,最好是将wav文件存储格式了解一下(如采样率、文件位数、声道数目)。都是对后面音频数据处理有帮助理解的。

(2)如果是采集到的数据,需要知道采样率、位数、声道数目。

二、将取得数据列表转换成fft所需形式

 定义一个结构体保存一个点的音频数据

public struct Complex
{
    public float real;//实部
    public float imag;//虚部
}

1、取得数据一般形式(位数24bit、声道1):

25 4a f9 25 4b fb 35 a8 de 35 a9 09 42 d8 60 

这里是15个字节,由于这是24位的音频数据。所以这里是5个点(fft需要取点)的数据。

2、将数据保存到结构体

        private void dataimprot(ref Complex[] buf,int len,int count)
        {
            int data=0;
            for (int i = 0;i<len;i++)
            {
                data = wavFFT.wavdata[i * 6 + 0 + count * Len*6];
                data += wavFFT.wavdata[i * 6 + 1 + count * Len*6]<< 8;
                data += wavFFT.wavdata[i * 6 + 2 + count * Len*6]<< 16;
                data = data << 8;//添加符号位
                data = data >> 8;
                //if (data >= Math.Pow(2, 23))
                //{
                //    data = (data | (-1 << 24));
                //}
                buf[i].real = (float)(data/Math.Pow(2,23));//加权,大小0~1
                buf[i].imag = 0;
            }
        }

添加符号位两种方式都可。16位的音频数据可以直接转成short类型。如果声道不止一个,取数的时候就要注意取相同声道的数据进行fft转换(可以查看wav文件格式了解数据保存形式)。

三、fft转换

转换前须知

(1)取点,就是一次fft转换多少个数据。需要为2的指数个。

(2)float pi = 3.14159265358979f;

        static void CoutData(ref Complex[] x, int len)//用于输出转化后的数据
        {
            int i, j;
            int cnt = len / 8;
            for (i = 0; i < cnt; i++)
            {
                for (j = 0; j < 8; j++)
                {
                    Console.Write( x[i * 8 + j].real + " "+x[i * 8 + j].imag+" |");
                }
                Console.WriteLine("\n");
            }
        }

        int BitReverse(int src, int size)
        {
            int tmp = src;
            int des = 0;
            int i;

            for (i = size - 1; i >= 0; i--)
            {
                des = ((tmp & 0x3) << (i * 2)) | des;
                tmp = tmp >> 2;
            }
            return des;
        }

        void reverse_idx(ref Complex[] im, int log4_N)
        {
            int i;
            int N = 1 << (log4_N * 2);

            Complex[] temp = new Complex[N];

            for (i = 0; i < N; i++)
            {
                int idx;
                idx = BitReverse(i, log4_N);
                temp[idx].real = im[i].real;
                temp[idx].imag = im[i].imag;
            }

            for (i = 0; i < N; i++)
            {
                im[i].real = temp[i].real;
                im[i].imag = temp[i].imag;
            }
        }

        void fft_ifft_4_common(ref Complex[] im,ref Complex[] win, int log4_N, int reverse)
        {
            int N = (1 << log4_N * 2);
            int i, j, k;
            int span = 1;
            int n = N >> 2;
            int widx;
            Complex temp1 = new Complex();
            Complex temp2 = new Complex();
            Complex temp3 = new Complex();
            Complex temp4 = new Complex();
            int idx1, idx2, idx3, idx4;

            for (i = 0; i < log4_N; i++)
            {
                for (j = 0; j < n; j++)
                {
                    widx = 0;

                    idx1 = j * span * 4;
                    idx2 = idx1 + span;
                    idx3 = idx2 + span;
                    idx4 = idx3 + span;
                    for (k = 0; k < span; k++)
                    {

                        temp1.real = im[idx1 +k].real;
                        temp1.imag = im[idx1 +k].imag;
                        temp2.real = win[widx].real * im[idx2 +k].real - win[widx].imag * im[idx2 +k].imag;
                        temp2.imag = win[widx].imag * im[idx2 +k].real + win[widx].real * im[idx2 +k].imag;
                        temp3.real = win[widx * 2].real * im[idx3 +k].real - win[widx * 2].imag * im[idx3 +k].imag;
                        temp3.imag = win[widx * 2].imag * im[idx3 +k].real + win[widx * 2].real * im[idx3 +k].imag;
                        temp4.real = win[widx * 3].real * im[idx4 +k].real - win[widx * 3].imag * im[idx4 +k].imag;
                        temp4.imag = win[widx * 3].imag * im[idx4 +k].real + win[widx * 3].real * im[idx4 +k].imag;

                        im[idx1 +k].real = temp1.real + temp3.real;
                        im[idx1 +k].imag = temp1.imag + temp3.imag;
                        im[idx2 +k].real = temp1.real - temp3.real;
                        im[idx2 +k].imag = temp1.imag - temp3.imag;
                        im[idx3 +k].real = temp2.real + temp4.real;
                        im[idx3 +k].imag = temp2.imag + temp4.imag;
                        im[idx4 +k].real = temp2.real - temp4.real;
                        im[idx4 +k].imag = temp2.imag - temp4.imag;

                        temp1.real = im[idx1 +k].real + im[idx3 +k].real;
                        temp1.imag = im[idx1 +k].imag + im[idx3 +k].imag;
                        if (reverse == 0)
                        {
                            temp2.real = im[idx2 +k].real + im[idx4 +k].imag;
                            temp2.imag = im[idx2 +k].imag - im[idx4 +k].real;
                        }
                        else
                        {
                            temp2.real = im[idx2 +k].real - im[idx4 +k].imag;
                            temp2.imag = im[idx2 +k].imag + im[idx4 +k].real;
                        }
                        temp3.real = im[idx1 +k].real - im[idx3 +k].real;
                        temp3.imag = im[idx1 +k].imag - im[idx3 +k].imag;

                        if (reverse == 0)
                        {
                            temp4.real = im[idx2 +k].real - im[idx4 +k].imag;
                            temp4.imag = im[idx2 +k].imag + im[idx4 +k].real;
                        }
                        else
                        {
                            temp4.real = im[idx2 +k].real + im[idx4 +k].imag;
                            temp4.imag = im[idx2 +k].imag - im[idx4 +k].real;
                        }

                        im[idx1 +k].real = temp1.real;
                        im[idx1 +k].imag = temp1.imag;
                        im[idx2 +k].real = temp2.real;
                        im[idx2 +k].imag = temp2.imag;
                        im[idx3 +k].real = temp3.real;
                        im[idx3 +k].imag = temp3.imag;
                        im[idx4 +k].real = temp4.real;
                        im[idx4 +k].imag = temp4.imag;

                        widx += n;
                    }
                }
                n >>= 2;
                span <<= 2;
            }
        }

        /**
         * @brief
         *
         * @param in :complex number input
         * @param log4_N :64=3,256=4,1024=5
         */
        void fft4(ref Complex[] im, int log4_N)
        {
            int N = 1 << (log4_N * 2);
            int i;
            Complex[] win = new Complex[N];


            reverse_idx(ref im, log4_N);

            for (i = 0; i < (3 * N / 4 - 2); i++)
            {
                win[i].real = (float)Math.Cos(2 * pi * i / (float)N);
                win[i].imag = -(float)Math.Sin(2 * pi * i / (float)N);
            }

            fft_ifft_4_common(ref im,ref win, log4_N, 0);

        }

假如1024个数据进行转换。转换成功后,得到的数据就是1024数据(实部、虚部)。

四、频谱绘制

频率计算公式:Fn = (n-1)*Fs/N

Fs为采样频率;

N取点数;

n对应的点

解析:fft转换后,每个点对应一个频率。取点的点越多,频率分辨率越高。

1、简单绘制频谱(控件1个button,1个pictureBox1)

添加button点击事件,点击一次绘制一组数据(pictureBox1尽量大一点)

        private void button1_Click(object sender, EventArgs e)
        {
            Complex[] buf = new Complex[1024];
            //CreateInput(ref buf, Len);
            dataimprot(ref buf, Len, count++);//数据输入

            fft4(ref buf,(int)Math.Log(Len,4));//fft转换

            Bitmap bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            Graphics g = Graphics.FromImage(bitmap);
            Pen pen = new Pen(Color.Red);
            Pen pen1 = new Pen(Color.Black,2);
            double pr = 0;//保存上组数据
            g.Clear(Color.White);

            //项目根目录,将数据保存到data文件里
            string path = Application.StartupPath +"/data.txt";//创建一个文件的路径
            FileStream fs;
            if (!File.Exists(path))//检查文件是否存在
            {
                fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite);
            }
            else
            {
                fs = new FileStream(path, FileMode.Append);
            }
            StreamWriter sw = new StreamWriter(fs);
            g.DrawLine(pen1, new Point(0, 40), new Point(pictureBox1.Width, 40));
            for (int i = 0; i < buf.Length; i++)
            {
                double data = Math.Pow((buf[i].real * buf[i].real + buf[i].imag * buf[i].imag), 0.5);//求复数模

                sw.Write(Math.Round(data, 2).ToString() + " ");//将数据写入文件
                
                hz = (i+1) * wavFFT.SamplesPerSec / 1024;//绘制不考虑频率(横坐标)
                if (i  < pictureBox1.Width && i != 0)
                {
                    try
                    {
                        g.DrawLine(pen, new PointF(i, (float)pr + 40), new PointF((i + 1), (float)data + 40));//简略的画一下数据
                    }
                    catch (Exception ex)
                    {
                        //Console.WriteLine(ex.ToString());
                    }
                }
                pr = data;//保存上一次数据
                
            }
            bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
            pictureBox1.Image = bitmap;
            sw.Flush();
            sw.Close();

        }

五、转换成对应的dB

dB转换公式:double data = 20 * Math.Log10( data/ (20 * Math.Pow(10, -6)) );//计算声压级

 Hz与频谱对应的一样。只需要在fft转换数据取模后叫上,绘制的则是对应的声压级图了。

注意:如果发现声压级图和音频分析软件(如Adobe Audition)声压级图形状不一样。则可能是因为信号泄漏,可以去看看汉明窗的作用,不想看就在fft转换之前对数据进行以下处理。

            
            for (int i = 0; i < Len; i++)
            {
                buf[i].real = (float)(buf[i].real * HammingWindow(i, Len));
            }
            //fft转化

        //汉明窗
        public static double HammingWindow(int n, int frameSize)
        {
            return 0.54 - 0.46 * Math.Cos(Math.PI * 2.0 * (double)n / (double)(frameSize - 1));
        }

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐