本文部分知识从以下文章学习:

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 = ;
while (( << lim) < n) lim++;
for (int i = ; i < n; i++)
{
int t = ;
for (int j = ; j < lim; j++)
{ if (((i >> j) & ) != )
t |= ( << (lim - j - ));
}
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 = ; i < n; i++)
{
omg[i] = new Complex((float)Math.Cos( * Math.PI * i / n), (float)Math.Sin( * Math.PI * i / n));
}
Complex temp ;
for (int step = ; step <= n; step *= )
{
int m = step / ;
for (int j = ; j < n; j += step)
for (int i = ; 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 = , k = ;
var top = n / ;
while (true)
{ var t = data[j + ];
data[j + ] = data[k + n];
data[k + n] = t;
t = data[j + ];
data[j + ] = data[k + n + ];
data[k + n + ] = t;
if (j > k)
{
t = data[j];
data[j] = data[k];
data[k] = t;
t = data[j + ];
data[j + ] = data[k + ];
data[k + ] = t; t = data[j + n + ];
data[j + n + ] = data[k + n + ];
data[k + n + ] = t;
t = data[j + n + ];
data[j + n + ] = data[k + n + ];
data[k + n + ] = t;
} k += ;
if (k >= n)
break; var h = top;
while (j >= h)
{
j -= h;
h /= ;
}
j += h;
}
}
static void FFT2(float[] data, bool forward)
{
var n = data.Length;
n /= ; Reverse(data, n); float sign = forward ? : -;
var mmax = ;
while (n > mmax)
{
var istep = * mmax;
var theta = sign * (float)Math.PI / mmax;
float wr = , wi = ;
var wpr = (float)Math.Cos(theta);
var wpi = (float)Math.Sin(theta);
for (var m = ; m < istep; m += )
{
for (var k = m; k < * n; k += * istep)
{
var j = k + istep;
var tempr = wr * data[j] - wi * data[j + ];
var tempi = wi * data[j] + wr * data[j + ];
data[j] = data[k] - tempr;
data[j + ] = data[k + ] - tempi;
data[k] = data[k] + tempr;
data[k + ] = data[k + ] + tempi;
}
var t = wr;
wr = wr * wpr - wi * wpi;
wi = wi * wpr + t * wpi;
}
mmax = istep;
} }
     static void Main(string[] args)
{ int n =*;
float[] data = new float[ * n];
for (int i = ; i < n; i++)
{
data[ * i] = i;
data[ * i + ] = ;
} Complex[] cpData = new Complex[n];
for (int i = ; i < n; i++)
{
cpData[i] = new Complex(data[ * i], data[ * i + ]);
} long s = DateTime.Now.Ticks;
FFT1(cpData, true);
Console.WriteLine("time:" + (DateTime.Now.Ticks - s) / ); s = DateTime.Now.Ticks;
FFT2(data, true);
Console.WriteLine("time:" + (DateTime.Now.Ticks - s) / ); Console.Read();
}

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

好了获得频率数据之后的流程就不再那么烧脑了(都怪自己早早把傅里叶变换还给课本了)。。。

找节奏点的逻辑大概如下(代码有点多就不贴了):

1.根据采样率依次获取数据,每次通过FFT得到一组复数数组。

2.计算出复数的模长,可以表示此频率下的声音大小,可以把一定范围的声音累加起来,可以用来表示低音、中音、高音。

3.对比每一帧的数据变化就可以判断出节奏点(声音变化大,可以表示是一个节奏点)。

其实能得到频域的值,针对不同的功能,大家后面就可以自由发挥了。

关于FFT分析音频的学习的更多相关文章

  1. I2S音频总线学习

    IIS音频总线学习(一)数字音频技术 一.声音的基本概念 声音是通过一定介质传播的连续的波. 图1 声波 重要指标: 振幅:音量的大小 周期:重复出现的时间间隔 频率:指信号每秒钟变化的次数 声音按频 ...

  2. LINUX内核分析第一周学习总结——计算机是如何工作的

    LINUX内核分析第一周学习总结——计算机是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/ ...

  3. LINUX内核分析第二周学习总结——操作系统是如何工作的

    LINUX内核分析第二周学习总结——操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  4. LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”

    LINUX内核分析第四周学习总结--扒开系统调用的"三层皮" 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC ...

  5. linux内核分析第四周学习笔记

    linux内核分析第四周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  6. Linux内核分析第二周学习笔记

    linux内核分析第二周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  7. linux内核分析第一周学习笔记

    linux内核分析第一周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  8. Linux内核分析第一周学习博客 --- 通过反汇编方式学习计算机工作过程

    Linux内核分析第一周学习博客 通过反汇编方式学习计算机工作过程 总结: 通过这次对一个简单C程序的反汇编学习,我了解到计算机在实际工作工程中要涉及大量的跳转指针操作.计算机通常是顺序执行一条一条的 ...

  9. Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码

    Linux内核分析第二周学习博客 本周,通过实现一个简单的操作系统内核,我大致了解了操作系统运行的过程. 实验主要步骤如下: 代码分析: void my_process(void) { int i = ...

随机推荐

  1. kubernetes实战篇之docker镜像的打包与加载

    系列目录 前面我们讲到了使用nexus搭建docker镜像仓库,操作还是有点复杂的,可能有的童鞋仅仅是想尝试kubernetes功能,并不想在搭建仓库上花费过多时间,但是又想在不同的主机之间传递镜像. ...

  2. sql-实现select取行号、分组后在分组内排序、每个分组中的前n条数据

    表结构设计: 实现select取行号 sql局部变量的2种方式 set @name='cm3333f'; select @id:=1; 区别:set 可以用=号赋值,而select 不行,必须使用:= ...

  3. spark 源码分析之十二 -- Spark内置RPC机制剖析之八Spark RPC总结

    在spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv中,剖析了NettyRpcEnv的创建过程. Dispatcher.NettyStreamManager.T ...

  4. ArrayList的add方法实现

    ArrayList的底层是由数组实现,所以所有的操作都是围绕数组展开,要想理解add方法,就得先了解数组的增加,所以我们先实现一个数组的add,数组的添加可以从尾部增加或者其他位置插入, 如果在数组的 ...

  5. Kafka Eagle V1.3.4更新预览

    1.概述 Kafka Eagle是一款开源的Kafka集群监控系统,源代码托管在Github.目前Kafka Eagle已更新到V1.3.4版本,域名已经统一更新为http://www.kafka-e ...

  6. PCB 板边倒圆角的实现方法(基本算法一)

    PCB外形是直角时外形时,通常工程制作时,外是直角或尖角的地方倒圆角,主要是为了防止板边容易划伤板且容易扎伤人 所以当客户没有特殊要求时,PCB外形是直角时一般会默认倒角0.5mm圆角(如下图所示) ...

  7. 使用http3访问服务

    用到的包:import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response; 主要代码: try { //创建OkH ...

  8. mybatis基础配置

    我这个写的比较简略,是自己短时间记录的可能只适合自己看,新手或者不懂的建议看看下面大神这篇: https://www.cnblogs.com/homejim/p/9613205.html 1.MyBa ...

  9. [Spring+SpringMVC+Mybatis]框架学习笔记(六):事务

    第7讲 事务 7.1 事务的概念 事务是一系列作为一个逻辑单元来执行的操作集合. 它是数据库维护数据一致性的单位,它讲数据库从一个一致状态,转变为新的另外一个一致状态.说的简单一点就是:如果一组处理步 ...

  10. 【DFS练习】【最大的蛋糕块】-C++

    这道题目是一个基本的dfs模板(?)下面日常贴一波dfs的基本模板: void dfs()//参数用来表示状态 { if(到达终点状态) { ...//根据题意添加 return; } if(越界或者 ...