安卓SDK中的FFT库

38

我正在进行Android项目的开发。我需要使用FFT算法来处理Android加速度计数据。是否在Android SDK中有FFT库可用?

7个回答

42
你可以使用这个类,它的速度足够快来进行实时音频分析。
public class FFT {

  int n, m;

  // Lookup tables. Only need to recompute when size of FFT changes.
  double[] cos;
  double[] sin;

  public FFT(int n) {
      this.n = n;
      this.m = (int) (Math.log(n) / Math.log(2));

      // Make sure n is a power of 2
      if (n != (1 << m))
          throw new RuntimeException("FFT length must be power of 2");

      // precompute tables
      cos = new double[n / 2];
      sin = new double[n / 2];

      for (int i = 0; i < n / 2; i++) {
          cos[i] = Math.cos(-2 * Math.PI * i / n);
          sin[i] = Math.sin(-2 * Math.PI * i / n);
      }

  }

  public void fft(double[] x, double[] y) {
      int i, j, k, n1, n2, a;
      double c, s, t1, t2;

      // Bit-reverse
      j = 0;
      n2 = n / 2;
      for (i = 1; i < n - 1; i++) {
          n1 = n2;
          while (j >= n1) {
              j = j - n1;
              n1 = n1 / 2;
          }
          j = j + n1;

          if (i < j) {
              t1 = x[i];
              x[i] = x[j];
              x[j] = t1;
              t1 = y[i];
              y[i] = y[j];
              y[j] = t1;
          }
      }

      // FFT
      n1 = 0;
      n2 = 1;

      for (i = 0; i < m; i++) {
          n1 = n2;
          n2 = n2 + n2;
          a = 0;

          for (j = 0; j < n1; j++) {
              c = cos[a];
              s = sin[a];
              a += 1 << (m - i - 1);

              for (k = j; k < n; k = k + n2) {
                  t1 = c * x[k + n1] - s * y[k + n1];
                  t2 = s * x[k + n1] + c * y[k + n1];
                  x[k + n1] = x[k] - t1;
                  y[k + n1] = y[k] - t2;
                  x[k] = x[k] + t1;
                  y[k] = y[k] + t2;
              }
          }
      }
  }
}

警告:此代码似乎派生自这里,并带有GPLv2许可证。


1
@ishan: https://zh.wikipedia.org/wiki/Cooley-Tukey_FFT_algorithm - endolith
1
fft函数中的x和y参数是什么?我知道输入样本应该放在x数组中,但y有什么用途? - Pompair
1
@Pompair 看起来 y 数组是输出表。 - Damian Walczak
37
我们这里有一个代表“如何不编写代码”的标志性例子。单字符变量、无用的注释、对实际正在发生的事情没有任何解释。 - durilka
2
最终回答数组y的含义:它是通常作为FFT复数输入的虚部。对于实数输入,必须在每次fft()调用之前将数组y填充为0。关于许可证的最后说明:此代码与1960年代中期Cooley/Tukey算法的标准实现几乎完全相同(例如,在《C语言数字计算》中发布的four1.c)。 - Hartmut Pfitzinger
显示剩余11条评论

14

使用位于https://www.ee.columbia.edu/~ronw/code/MEAPsoft/doc/html/FFT_8java-source.html的类:

简要说明:调用fft()函数,提供幅度数据x和零数组y,函数返回后,第一个答案将为a[0]=x[0]^2+y[0]^2。

完整说明:FFT是一个复杂的变换,它需要N个复数并产生N个复数。因此,x[0]是第一个数字的实部,y[0]是复数部分。这个函数在原地计算,所以当函数返回时,x和y将具有变换的实部和复数部分。

一个典型的用途是计算音频的功率谱。您的音频样本只有实部,您的复数部分为0。要计算功率谱,您需要添加实部和复数部分的平方P[0]=x[0]^2+y[0]^2。

还要注意,傅里叶变换在应用于实数时,结果呈对称形式(x[0]==x[x.lenth-1])。x[x.length / 2]处的数据具有频率为f=0Hz的数据。x[0]==x[x.length-1]具有与采样率相等的频率数据(例如,如果您的采样速率为44000Hz,则f[0]表示22kHz)。

完整过程:

  1. 创建一个512个样本的零数组p[n]
  2. 收集1024个音频样本,将它们写入x
  3. 对所有n设置y[n]=0
  4. 计算fft(x,y)
  5. 对于所有n=0到512,计算p[n]+=x[n+512]^2+y[n+512]^2
  6. 进行第2步以获取另一个批次(在50个批次之后进入下一步)
  • 绘制p
  • 转到1
  • 然后根据您的口味调整固定数字。

    数字512定义了采样窗口,我不会解释。只需避免将其减少太多。

    数字1024必须始终是上一个数字的两倍。

    数字50定义了更新速率。如果您的采样率为每秒44000个样本,则更新速率为:R=44000/1024/50 = 0.85秒。


    8

    5

    使用这个class(EricLarch的答案来源之一)。

    用法说明

    此函数将用FFT输出替换您的输入数组。

    输入

    • N = 数据点数(输入数组的大小,必须是2的幂)
    • X = 要转换的数据的实部
    • Y = 要转换的数据的虚部

    例如,如果您的输入为 (1+8i, 2+3j, 7-i, -10-3i)

    • N = 4
    • X = (1, 2, 7, -10)
    • Y = (8, 3, -1, -3)

    输出

    • X = FFT输出的实部
    • Y = FFT输出的虚部
    要获得经典的FFT图形,您需要计算实部和虚部的幅度。
    类似以下方式:
    ````
    public double[] fftCalculator(double[] re, double[] im) {
        if (re.length != im.length) return null;
        FFT fft = new FFT(re.length);
        fft.fft(re, im);
        double[] fftMag = new double[re.length];
        for (int i = 0; i < re.length; i++) {
           fftMag[i] = Math.pow(re[i], 2) + Math.pow(im[i], 2);
        }
        return fftMag;
    }
    

    此外,如果您的原始输入是幅度与时间相关的,请参阅此StackOverflow答案以了解如何获取频率。

    你能帮我吗?...我该怎样在我的项目中实现它? - Ravindra Kushwaha

    2
    是的,这里有一个JTransforms,它在github上维护这里并且作为Maven插件这里可用。
    使用方法:
    compile group: 'com.github.wendykierp', name: 'JTransforms', version: '3.1'
    

    但是,对于更新的Gradle版本,您需要使用类似以下的内容:
    dependencies {
        ... 
        implementation 'com.github.wendykierp:JTransforms:3.1'
    }
    

    1

    @J Wang 您的输出幅值似乎比您链接的线程中给出的答案更好,但仍然是幅值平方...一个复数的幅值

    z = a + ib
    

    被计算为。
    |z|=sqrt(a^2+b^2)
    

    链接中的答案建议对于纯实数输入,输出应使用a2a,因为值为
    a_(i+N/2) = -a_(i),
    

    使用b_(i) = a_(i+N/2)表示他们表格中的复数部分在输出表格的第二部分。

    即对于实数输入表格,输出表格的后一半是实数的共轭...

    所以z = a-ia得到一个大小。

    |z|=sqrt(2a^2) = sqrt(2)a
    

    因此值得注意的是缩放因子...我建议查阅书籍或维基百科以确保。


    0

    不幸的是,顶部答案仅适用于大小为2的幂的数组,这非常有限。

    我使用了Jtransforms库,它完美地工作,你可以将其与Matlab使用的函数进行比较。

    这里是我的代码,带有注释引用如何转换任何信号并获取频率幅度的Matlab(https://la.mathworks.com/help/matlab/ref/fft.html

    首先,在build.gradle(app)中添加以下内容

    implementation 'com.github.wendykierp:JTransforms:3.1'
    

    这是用于转换简单正弦波的代码,非常出色。
        double Fs = 8000;
        double T = 1/Fs;
        int L = 1600;
    
        double freq = 338;
    
        double sinValue_re_im[] = new double[L*2]; // because FFT takes an array where its positions alternate between real and imaginary
        for( int i = 0; i < L; i++)
        {
            sinValue_re_im[2*i] = Math.sin( 2*Math.PI*freq*(i * T) ); // real part
            sinValue_re_im[2*i+1] = 0; //imaginary part
        }
    
        // matlab
        // tf = fft(y1);
    
        DoubleFFT_1D fft = new DoubleFFT_1D(L);
        fft.complexForward(sinValue_re_im);
        double[] tf = sinValue_re_im.clone();
    
        // matlab
        // P2 = abs(tf/L);
        double[] P2 = new double[L];
        for(int i=0; i<L; i++){
    
            double re = tf[2*i]/L;
            double im = tf[2*i+1]/L;
            P2[i] = sqrt(re*re+im*im);
        }
    
        // P1 = P2(1:L/2+1);
        double[] P1 = new double[L/2]; // single-sided: the second half of P2 has the same values as the first half
        System.arraycopy(P2, 0, P1, 0, L/2);
        // P1(2:end-1) = 2*P1(2:end-1);
        System.arraycopy(P1, 1, P1, 1, L/2-2);
        for(int i=1; i<P1.length-1; i++){
            P1[i] = 2*P1[i];
        }
        // f = Fs*(0:(L/2))/L;
        double[] f = new double[L/2 + 1];
        for(int i=0; i<L/2+1;i++){
            f[i] = Fs*((double) i)/L;
        }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接