【问题标题】:AudioRecorder | Interpreting FFT data for Spectrum Analyzer录音机 |解释频谱分析仪的 FFT 数据
【发布时间】:2018-09-24 13:47:31
【问题描述】:

我正在构建一个需要能够显示实时频谱分析仪的应用。这是我在 iOS 上成功制作的版本:

我正在使用 Wendykierp JTransforms 库来执行 FFT 计算,并设法捕获音频数据并执行 FFT 函数。见下文:

short sData[] = new short[BufferElements2Rec];
int result = audioRecord.read(sData, 0, BufferElements2Rec);

try
{
    //Initiate FFT
    DoubleFFT_1D fft = new DoubleFFT_1D(sData.length);

    //Convert sample data from short[] to double[]
    double[] fftSamples = new double[sData.length];
    for (int i = 0; i < sData.length; i++) {
        //IMPORTANT: We cannot simply cast the short value to double.
        //As a double is only 2 bytes (values -32768 to 32768)
        //We must divide by 32768 before we cast to Double.
        fftSamples[i] = (double) sData[i] / 32768;
    }

    //Perform fft calcs
    fft.realForward(fftSamples);

    //TODO - Convert FFT data into 20 "bands"

} Catch (Exception e)
{

}

在 iOS 中,我使用了一个库 (Tempi-FFT),它内置了计算幅度、频率和提供任何给定数量波段的平均数据的功能(我使用了 20 个波段,如图所示多于)。看来我对这个库没有那么奢侈,我需要自己计算。

寻找有关如何对 FFT 计算返回的数据进行交互的任何好的示例或教程。这是我收到的一些示例数据:

-11387.0, 183.0, -384.9121475854448, -224.66315714636642, -638.0173005872095, -236.2318653974911, -1137.1498541119106, -437.71599514435786, 1954.683405957685, -2142.742125980924 ...

寻找有关如何解释此数据的简单说明。我看过的其他一些问题要么无法理解,要么没有提供有关如何确定给定频段数量的信息:

Power Spectral Density from jTransforms DoubleFFT_1D

How to develop a Spectrum Analyser from a realtime audio?

【问题讨论】:

  • FFT 绝对不是一件简单的事情。第一个链接 (stackoverflow.com/questions/5010261/…) 提供了如何解释您的输出的最简单的解释。给我一点,我会针对您的问题发布具体答案。
  • 发布了答案。希望对你有用!

标签: android fft audiorecord


【解决方案1】:

您的问题可以分为两部分:找出所有频率的幅度(解释输出)和将频率平均到频带中


找出所有频率的大小:

我不会深入了解快速傅里叶变换/离散傅里叶变换的复杂性(如果您想获得基本的了解,请参阅 this video),但要知道每个输出都有实部和虚部.

realForward 函数的文档描述了虚部和实部在输出数组中的位置(我假设您的样本大小是偶数):

a[2*k] = Re[k], 0 <= k < n / 2
a[2*k+1] = Im[k], 0 < k < n / 2
a[1] = Re[n/2] 

a 等同于您的fftSamples,这意味着我们可以将这个文档翻译成如下代码(我已将ReIm 分别更改为realPartimaginaryPart):

int n = fftSamples.length;

double[] realPart = new double[n / 2];
double[] imaginaryPart = new double[n / 2];

for(int k = 0; k < n / 2; k++) {
    realPart[k] = fftSamples[k * 2];
    imaginaryPart[k] = fftSamples[k * 2 + 1];
}

realPart[n / 2] = fftSamples[1];

现在我们有了每个频率的实部和虚部。我们可以将它们绘制在 x-y 坐标平面上,使用实部作为 x 值,虚部作为 y 值。这创建了一个三角形,三角形斜边的长度就是频率的大小。我们可以使用勾股定理来得到这个量级:

double[] spectrum = new double[n / 2];

for(int k = 1; k < n / 2; k++) {
    spectrum[k] = Math.sqrt(Math.pow(realPart[k], 2) + Math.pow(imaginaryPart[k], 2));
}

spectrum[0] = realPart[0];

请注意,频谱的第 0 个索引没有虚部。这是信号的DC component(我们不会使用它)。

现在,我们有一个数组,其中包含您频谱中每个频率的幅度(如果您的采样频率为 44100Hz,这意味着您现在有一个频率幅度在 0Hz 和 44100Hz 之间的数组,如果您有 441 个值在你的数组中,那么每个索引值代表一个 100Hz 的步长。)


将频率平均到频带中:

既然我们已将 FFT 输出转换为我们可以使用的数据,我们可以继续讨论您问题的第二部分:找出不同频段的平均值。这个比较简单。我们只需要将数组分成不同的波段并找到每个波段的平均值。这可以这样概括:

int NUM_BANDS = 20; //This can be any positive integer.
double[] bands = new double[NUM_BANDS];
int samplesPerBand = (n / 2) / NUM_BANDS;

for(int i = 0; i < NUM_BANDS; i++) {
    //Add up each part
    double total;
    for(int j = samplesPerBand * i ; j < samplesPerBand * (i+1); j++) {
        total += spectrum[j];
    }
    //Take average
    bands[i] = total / samplesPerBand;
}


最终代码:

就是这样!您现在有一个名为bands 的数组,其中包含每个频带的平均幅度。上面的代码故意没有优化以显示每个步骤的工作原理。这是一个缩短和优化的版本:

int numFrequencies = fftSamples.length / 2;

double[] spectrum = new double[numFrequencies];

for(int k = 1; k < numFrequencies; k++) {
    spectrum[k] = Math.sqrt(Math.pow(fftSamples[k*2], 2) + Math.pow(fftSamples[k*2+1], 2));
}

spectrum[0] = fftSamples[0];

int NUM_BANDS = 20; //This can be any positive integer.
double[] bands = new double[NUM_BANDS];
int samplesPerBand = numFrequencies / NUM_BANDS;

for(int i = 0; i < NUM_BANDS; i++) {
    //Add up each part
    double total;
    for(int j = samplesPerBand * i ; j < samplesPerBand * (i+1); j++) {
        total += spectrum[j];
    }
    //Take average
    bands[i] = total / samplesPerBand;
}

//Use bands in view!

这是一个很长的答案,我还没有测试代码(尽管我确实计划这样做)。如果您发现任何错误,请随时发表评论。

【讨论】:

  • 超级有用的答案,谢谢。此外,我还做错了其他事情。对于其他想要做类似事情的人 - 为了 FFT 计算的目的,我将值从 short 转换为 double 的方式是不正确的。必须考虑到 short 是 2 个字节(范围从 -32768 到 32768)这一事实,因此必须在转换为 double 之前将 short 除以 32768。如果你不这样做,你会从 FFT 计算中得到意外的行为/输出。
  • 是的,这会导致一些问题。 :)
猜你喜欢
  • 1970-01-01
  • 2020-03-04
  • 2013-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多