AudioRecorder |为频谱分析仪解释FFT数据

时间:2018-09-24 13:47:31

标签: android fft audiorecord

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

Sample Spectral Analyser

我正在使用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?

1 个答案:

答案 0 :(得分: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更改为{{1} }和realPart):

imaginaryPart

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

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];

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

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


将频率平均划分为频带:

现在,我们已经将FFT输出转换为可以使用的数据,我们可以继续进行问题的第二部分:查找不同频段的平均值。这是相对简单的。我们只需要将数组拆分为不同的频段,然后找到每个频段的平均值即可。可以这样概括:

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];


最终密码:

就是这样!现在,您有了一个名为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

这是一个很长的答案,而且我尚未测试代码(尽管我确实打算这样做)。如果发现任何错误,请随时发表评论。