草庐IT

对音频数据进行fft转换显示声压级图

宝帅哥 2023-04-11 原文

目录

一、取得音频数据

二、将取得数据列表转换成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));
        }

有关对音频数据进行fft转换显示声压级图的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  5. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

    我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  8. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  9. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  10. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

    所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

随机推荐