java频谱计算的工具类

直接上代码,逻辑还算比较清楚

public class SpectrumHelper {

    /**
     * 计算频谱
     * @param data 采样数据
     * @param samplingInterval 采样间隔,以秒为单位
     * @param needFillZero 是否需要补零
     * @param flyLen 参与傅里叶变换的长度
     */
    public static SpectrumBean caculateSpectrum(double[] data, double samplingInterval, boolean needFillZero, int flyLen){
        if(data == null || data.length == 0){
            return null;
        }
        int dataLen = data.length;
        double[] partakeFLYArr = null;//参与傅里叶变换的数组
        if(needFillZero){ //需要补零
            if(flyLen < dataLen){
                return null;
            }
            partakeFLYArr = new double[flyLen];
            System.arraycopy(data, 0, partakeFLYArr, 0, dataLen);
            for(int i = dataLen; i < flyLen; i++){
                partakeFLYArr[i] = 0;
            }
        }else{
            partakeFLYArr = data;
            flyLen = dataLen;
        }
        //进行傅里叶变换,使用的是commons-math包里面的快速傅里叶算法
        FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
        Complex[] complexArr = fft.transform(partakeFLYArr, TransformType.FORWARD);

        SpectrumBean spectrumBean = new SpectrumBean();
        double[] amplitude = new double[flyLen / 2];

        //计算振幅,除以的都是采样点的长度,而不是傅里叶变化的长度
        Complex complex = complexArr[0];
        amplitude[0] = Math.sqrt(Math.abs(complex.getReal()) * Math.abs(complex.getReal())
                + Math.abs(complex.getImaginary()) * Math.abs(complex.getImaginary())) * 1.0 / dataLen;
        for(int i = 1; i < flyLen / 2; i++){
            complex = complexArr[i];
            amplitude[i] = Math.sqrt(Math.abs(complex.getReal()) * Math.abs(complex.getReal())
                    + Math.abs(complex.getImaginary()) * Math.abs(complex.getImaginary())) * 2.0 / dataLen;
        }
        spectrumBean.setAmplitude(amplitude);//设置振幅

        //计算频率分辨率,除以的是傅里叶变换的长度,而不是采样点的长度
        double Fs = 1.0 / samplingInterval;//采样频率
        spectrumBean.setFrequencyResolution(Fs / flyLen);//设置频率分辨率

        return spectrumBean;
    }

}

SpectrumBean类表示的是频谱计算的结果,类定义如下:

/**
 * 用于保存频谱计算的结果
 */
public class SpectrumBean {

    /**
     * 频率分辨率
     */
    private double frequencyResolution;

    /**
     * 保存振幅信息,长度是参与傅里叶变换的长度,不是采样点的长度
     */
    private double[] amplitude;

    public double getFrequencyResolution() {
        return frequencyResolution;
    }

    public void setFrequencyResolution(double frequencyResolution) {
        this.frequencyResolution = frequencyResolution;
    }

    public double[] getAmplitude() {
        return amplitude;
    }

    public void setAmplitude(double[] amplitude) {
        this.amplitude = amplitude;
    }
}

 

测试的代码如下:

   /**
     * 正常的输出波形,正常的傅里叶变换,没有补零的操作
     * 频率分辨率 = 采样频率 / 参与傅里叶变换的点数,注意是参与傅里叶变换的点数而非采样点数,因为可能补零
     * 傅里叶变换结果的频率计算,第n个点的频率 = 频率分辨率 * (n - 1)
     * 所谓频谱,横轴是频率,纵轴是振幅,也就是频率振幅谱
     */
    @Test
    public void test3(){
        //模拟采样
        int Fs = 100;//采样频率
        int N = 256;//采样点数
        double[] wave = generateWaveNormal(Fs, N);
        log(Arrays.toString(wave));

        //对采样结果做频谱分析
        SpectrumBean spectrumBean = SpectrumHelper.caculateSpectrum(wave, 1.0f / Fs, false, 0);
        /*for(int i = 0; i < spectrumBean.getAmplitude().length; i++){
            log(spectrumBean.getAmplitude()[i]);
        }*/
        log(spectrumBean.getFrequencyResolution());

    }


    /**
     * 后面补零
     * 频率分辨率 = 采样频率 / 参与傅里叶变换的点数,注意是参与傅里叶变换的点数而非采样点数,
     * 在补零的情况下,参与傅里叶变换的点数是大于采样点数
     * 傅里叶变换结果的频率计算,第n个点的频率 = 频率分辨率 * (n - 1)
     * 所谓频谱,横轴是频率,纵轴是振幅,也就是频率振幅谱
     */
    @Test
    public void test4(){
        //模拟采样
        int Fs = 100;//采样频率
        int N = 256;//采样点数
        int WANT_N = 1024;//参与傅里叶变换的点数,实际上只采了256个点,后面的都补零了
        double[] wave = generateWaveNormal(Fs, N);
        log(Arrays.toString(wave));

        //对采样结果做频谱分析
        SpectrumBean spectrumBean = SpectrumHelper.caculateSpectrum(wave, 1.0 / Fs, true, WANT_N);
        /*for(int i = 0; i < spectrumBean.getAmplitude().length; i++){
            log(spectrumBean.getAmplitude()[i]);
        }*/
        log(spectrumBean.getFrequencyResolution());

    }

    
    private void log(Object obj){
        System.out.println(obj);
    }

    
    /**
     * 生成一段模拟信号,模拟采样
     * @param fs 采样频率
     * @param n 采样点数
     * 模拟的这段信号包括的频率有10hz, 15hz, 20hz, 26.5hz,这段信号包括直流信号
     * 注意:根据采样定理,采样频率必须大于信号频率的2倍
     * 因此,传递过来的采样频率务必要大于26.5 * 2
     */
    private double[] generateWaveNormal(float fs, int n) {
        double pi = Math.PI;
        double[] res = new double[n];
        float interval = 1 / fs;//采样间隔,单位是s
        for (int i = 0; i < n; i++) {
            double t = interval * i;
            res[i] = 3 + 1 * Math.cos(2 * pi * 10 * t) + 2 * Math.sin(2 * pi * 15 * t + Math.toRadians(30))
                    + 3 * Math.cos(2 * pi * 20 * t + Math.toRadians(-30)) + 4 * Math.sin(2 * pi * 26.5 * t + Math.toRadians(60));
        }
        return res;
    }

 

测试的结果打印到控制台,然后复制到excel种,使用excel画图

不补零的图如下:

补零的图如下:

 

结果分析

横坐标频率计算,比如第n个点的频率 = (n -1) * 频率分辨率,频率分辨率的计算见上面

纵坐标表示的是振幅。

频谱图上的信号和原始信号是能正好对应上的。

使用jdk16编译 import java.awt.Graphics; import java.awt.GridLayout; import java.io.File; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import javax.swing.GroupLayout; import javax.swing.JFrame; /** * */ /** * @author Administrator * */ public class Musicline extends JFrame implements Runnable { private byte[] audioData = null; private int intBytes = 0; private byte[] ml = new byte[1]; private int[] drawl = null; /** Creates new form Musicline */ public Musicline() { initComponents(); Graphics g; g = this.getGraphics(); } public void paint(Graphics g) { g.clearRect(0, 0, 900, 900); // System.out.print(drawl.length); if (audioData != null) { drawl = new int[audioData.length]; for (int i = 0; i < audioData.length; i++) { ml[0] = audioData[i]; // String s=new String(ml); drawl[i] = Math.abs((int) ml[0]); } System.out.println(drawl[0]); for (int i = 0; i < drawl.length - 1; i++) { g.drawLine(i * this.getWidth() / 256, drawl[i] + 100, (i + 1) * this.getWidth() / 256, drawl[i + 1] + 100); } } } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ public void run() { // TODO Auto-generated method stub while (intBytes != -1) { try { synchronized (this) { this.wait(10); } } catch (InterruptedException ex) { ex.printStackTrace(); } repaint(); } } public void play() { try { AudioInputStream ais = AudioSystem.getAudioInputStream(new File( "F:/perl/key2.wav"));// 获得音频输入流 ais = AudioSystem.getAudioInputStream( AudioFormat.Encoding.PCM_SIGNED, ais); AudioFormat baseFormat = ais.getFormat();// 指定声音流中特定数据安排 System.out.println("baseFormat=" + baseFormat); DataLine.Info info = new DataLine.Info(SourceDataLine.class, baseFormat); System.out.println("info=" + info);
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值