如何在Java中从PCM数据中获取频率-FFT

5
由于某种原因,频率发生了位移。
 391 hz => 1162
 440 hz => 2196
 493 hz => 2454

我正在使用这些值

 final int audioFrames= 1024;
 final float sampleRate= 44100.0f;
 final int bitsPerRecord= 16;
 final int channels= 1;
 final boolean bigEndian = true;
 final boolean signed= true;

 byteData= new byte[audioFrames * 2];  //two bytes per audio frame, 16 bits
 dData= new double[audioFrames * 2];  // real & imaginary

这是我如何准备数据并将其转换为双精度数的方法:

format = new AudioFormat(sampleRate, bitsPerRecord, channels, signed, bigEndian);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); 
microphone = (TargetDataLine) AudioSystem.getLine(info);
microphone.open(format);
microphone.start();
int numBytesRead =  microphone.read(byteData, 0, byteData.length);

读取数据后,将其从16位大端有符号转换为双精度。
 public void byteToDouble(){
    ByteBuffer buf= ByteBuffer.wrap(byteData);
    buf.order(ByteOrder.BIG_ENDIAN);
    int i=0;
    while(buf.remaining()>1){
        short s = buf.getShort();
        dData[ 2 * i ] = (double) s / 32768.0; //real 
        dData[ 2 * i + 1] = 0.0;    // imag
        ++i;
    }
}

最后,运行FFT并查找频率:

 public void findFrequency(){

    double frequency;

            DoubleFFT_1D fft= new DoubleFFT_1D(audioFrames); 
/* edu/emory/mathcs/jtransforms/fft/DoubleFFT_1D.java */

    fft.complexForward(dData); // do the magic so we can find peak      
    for(int i = 0; i < audioFrames; i++){
        re[i] = dData[i*2];
        im[i] = dData[(i*2)+1];
        mag[i] = Math.sqrt((re[i] * re[i]) + (im[i]*im[i]));
    }

    double peak = -1.0;
    int peakIn=-1;
    for(int i = 0; i < audioFrames; i++){
        if(peak < mag[i]){
            peakIn=i;
            peak= mag[i];
        }
    }
    frequency = (sampleRate * (double)peakIn) / (double)audioFrames;
    System.out.print("Peak: "+peakIn+", Frequency: "+frequency+"\n");
}

除了您没有使用窗函数以外,它看起来还不错,因此您的频谱将会被模糊。尝试绘制 mag [] 并查看频谱是否合理,即在低阶 bin 的某个地方是否存在单个大峰值。 - Paul R
嗨,保罗,看起来我的问题是频率分bin不够准确,所以,例如如果声音是440 Hz,但我更接近的频率bin是445 Hz,它就不会使用它,而是会使用一个谐波,比如1320,可能在频率bin中。有没有办法提高这些bin的准确性,而不必读取大量样本? - Jose Hidalgo
对于某些乐器,泛音实际上比基频更响亮 - 你可能需要查看各种不同的音高检测方法,而不仅仅是寻找FFT中最大的单个峰值,因为正如你所发现的那样,这种方法并不可靠。此外,请注意您的频率分辨率非常粗糙,44100/1024 = 每个bin约为40 Hz。 - Paul R
你找到答案了吗?请告诉我,我也遇到了同样的问题。 - Ranjithkumar
2个回答

0
首先,如果你要录制的音频很长,你需要将其分块进行FFT计算,最好在每个块上应用窗函数后再执行FFT。FFT只能计算出一个基本频率,所以如果频率多次变化,你需要在多个位置上进行FFT计算。
滑动窗口也可以提高准确性。这意味着你会取一个块,稍微移动一下,然后再取另一个块,使得这些块有重叠部分。你可以根据需要自由调整滑动距离和每个块的大小。
此外,仅仅使用FFT可能会产生错误结果。你可以对FFT生成的功率谱进行更多的分析,例如倒谱分析或谐波产品谱分析,以更准确地估计音高。

0

你可以在FFT结果的频率间进行插值(抛物线或Sinc插值),以获得更准确的频率估计。但你可能会遇到一个更大的问题:你的频率源可能产生(或被剪切以产生)一些非常强的奇次谐波或泛音,掩盖了FFT结果幅度中的任何基本正弦波。因此,你应该尝试使用音高检测/估计算法,而不仅仅是尝试寻找(可能缺失的)FFT峰值。


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