在Android中使用JTransforms进行由PCM数据转换的FFT

5

我已经尝试了一段时间,但是我不知道我在这里应该做什么。

我正在将PCM音频数据读入一个audioData数组中:

 recorder.read(audioData,0,bufferSize);     //read the PCM audio data into the audioData array

我希望使用Piotr Wendykier的JTransform库对我的PCM数据执行FFT,以获取频率。

import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;

目前我有这个:

       DoubleFFT_1D fft = new DoubleFFT_1D(1024); // 1024 is size of array

for (int i = 0; i < 1023; i++) {
           a[i]= audioData[i];               
           if (audioData[i] != 0)
           Log.v(TAG, "audiodata=" + audioData[i] + " fft= " + a[i]);
       }
       fft.complexForward(a);

我不理解如何使用这个,能有人给我指点一下吗?之后我需要进行任何计算吗?
我确定我的想法偏离了正确方向,非常感谢任何帮助!
Ben

抱歉,我应该说一下complexForward的作用:计算复杂数据的1D正向DFT,并将结果存储在a中。复数以两个double值的序列形式存储:实部和虚部,即输入数组的大小必须大于或等于2*n。输入数据的物理布局必须如下所示:a [2k] = Re[k], a [2k+1] = Im[k],其中0<=k<n。 - Ben Taliadoros
给我任何例子... - NagarjunaReddy
4个回答

10

如果你只是想找到输入波形中单个正弦频率的频率,则需要找到具有最大幅度的FFT峰值,其中:

Magnitude = sqrt(re*re + im*im)

这个最大幅值峰的索引i将告诉您正弦信号的近似频率:

Frequency = Fs * i / N

其中:

Fs = sample rate (Hz)
i = index of peak
N = number of points in FFT (1024 in this case)

这听起来非常像我正在尝试做的事情。但是我不确定如何从变换中获取实部和虚部值。另外,我不确定是否应该使用DoubleFFT_1D类?而不是DoubleFFT_2D类、FloatFFT_1D类等。 - Ben Taliadoros
1
你只需要一个一维FFT来处理音频(二维FFT通常用于二维数据,如图像等)。根据你在上面的问题中所说,实部和虚部是交错的,因此bin i的实部位于索引2i处,虚部位于索引2i+1处。 - Paul R
谢谢Paul,太好了,有几件事我不明白... 我要像这样传递audioData数组吗fft.complexForward(audioData);?如果我这么做了,它是否会操作数组?它没有返回类型。谢谢 - Ben Taliadoros
2
我不熟悉这个特定的FFT,但是听起来像是所谓的“原地”实现,因此您将时域数据传入并用频域结果覆盖它。对于输入,您将实部(2 * i)设置为样本数据值,将虚部(2 * i + 1)设置为0。 - Paul R
那我在将音频数据数组传递给FFT之前,应该将每个索引都加倍吗?例如(2 * i)? - Ben Taliadoros
是的,当将音频数据复制到FFT时,您可以执行例如fft[2*i] = pcm[i]; fft[2*i+1] = 0;的操作。 - Paul R

6

我花了几个小时才使这个工作起来,现在我提供一个完整的Java实现:

import org.jtransforms.fft.DoubleFFT_1D;

public class FrequencyScanner {
    private double[] window;

    public FrequencyScanner() {
        window = null;
    }

    /** extract the dominant frequency from 16bit PCM data.
     * @param sampleData an array containing the raw 16bit PCM data.
     * @param sampleRate the sample rate (in HZ) of sampleData
     * @return an approximation of the dominant frequency in sampleData
     */
    public double extractFrequency(short[] sampleData, int sampleRate) {
        /* sampleData + zero padding */
        DoubleFFT_1D fft = new DoubleFFT_1D(sampleData.length + 24 * sampleData.length);
        double[] a = new double[(sampleData.length + 24 * sampleData.length) * 2];

        System.arraycopy(applyWindow(sampleData), 0, a, 0, sampleData.length);
        fft.realForward(a);

        /* find the peak magnitude and it's index */
        double maxMag = Double.NEGATIVE_INFINITY;
        int maxInd = -1;

        for(int i = 0; i < a.length / 2; ++i) {
            double re  = a[2*i];
            double im  = a[2*i+1];
            double mag = Math.sqrt(re * re + im * im);

            if(mag > maxMag) {
                maxMag = mag;
                maxInd = i;
            }
        }

        /* calculate the frequency */
        return (double)sampleRate * maxInd / (a.length / 2);
    }

    /** build a Hamming window filter for samples of a given size
     * See http://www.labbookpages.co.uk/audio/firWindowing.html#windows
     * @param size the sample size for which the filter will be created
     */
    private void buildHammWindow(int size) {
        if(window != null && window.length == size) {
            return;
        }
        window = new double[size];
        for(int i = 0; i < size; ++i) {
            window[i] = .54 - .46 * Math.cos(2 * Math.PI * i / (size - 1.0));
        }
    }

    /** apply a Hamming window filter to raw input data
     * @param input an array containing unfiltered input data
     * @return a double array containing the filtered data
     */
    private double[] applyWindow(short[] input) {
        double[] res = new double[input.length];

        buildHammWindow(input.length);
        for(int i = 0; i < input.length; ++i) {
            res[i] = (double)input[i] * window[i];
        }
        return res;
    }
}
FrequencyScanner将返回所提供样本数据中主导频率的近似值。它对输入应用汉明窗口,以允许从音频流中传入任意样本。在进行FFT变换之前,通过内部零填充样本数据来实现精度。虽然我知道有更好、更复杂的方法来做这个,但是填充方法对于我的个人需求足够了。
我使用220hz和440hz的参考音源创建原始16位PCM样本进行测试,结果匹配。

我认为你应该调用realforwardfull来计算FFT结果。Realforward对结果有不同的解释。如果你查看API文档,就会知道。 - Foreverniu

2

是的,你需要使用realForward函数而不是complexForward,因为你传递给它的是一个实数数组而不是复数数组(来源)。

编辑:

或者你可以获取实部并执行复杂到复杂fft,就像这样:

double[] in = new double[N];
read ...
double[] fft = new double[N * 2];

for(int i = 0; i < ffsize; ++i)
{
  fft[2*i] = mic[i];
  fft[2*i+1] = 0.0;
}
fft1d.complexForward(fft);

我尝试并将结果与Matlab进行比较,但是没有得到相同的结果...(幅度)


1
如果你正在寻找音频输入(1D,实数数据)的FFT,那么你不应该使用1D REAL Fft吗?

这是我将要尝试的东西。 - Ben Taliadoros

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