Java:如何获取当前音频输入的频率?

2024-01-10

我想分析麦克风输入的当前频率,以使 LED 与播放的音乐同步。我知道如何从麦克风捕获声音,但我不知道 FFT,这是我在寻找获取频率的解决方案时经常看到的。

我想测试一下某个频率的当前音量是否大于设定值。代码应该看起来像这样:

 if(frequency > value) { 
   LEDs on
 else {
   LEDs off
 }

我的问题是如何用Java实现FFT。为了更好地理解,here https://www.youtube.com/watch?v=u-8MUwiBIQ4是 YouTube 视频的链接,它很好地展示了我想要实现的目标。

整个代码:

public class Music {

    static AudioFormat format;
    static DataLine.Info info;

    public static void input() {
        format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);

        try {
            info = new DataLine.Info(TargetDataLine.class, format);
            final TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
            targetLine.open();

            AudioInputStream audioStream = new AudioInputStream(targetLine);

            byte[] buf = new byte[256]

            Thread targetThread = new Thread() {
                public void run() {
                    targetLine.start();
                    try {
                        audioStream.read(buf);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };

            targetThread.start();
    } catch (LineUnavailableException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

Edit:我尝试使用 MediaPlayer 的 JavaFX AudioSpectrumListener,只要我使用.mp3文件。问题是,我必须使用一个字节数组来存储麦克风输入。我针对这个问题问了另一个问题here https://stackoverflow.com/questions/54021239/java-how-to-use-microphone-input-as-input-for-the-javafx-media-player.


使用JavaFFT班级来自here https://github.com/hendriks73/jipes/blob/master/src/main/java/com/tagtraum/jipes/math/FFTFactory.java,你可以这样做:

import javax.sound.sampled.*;

public class AudioLED {

    private static final float NORMALIZATION_FACTOR_2_BYTES = Short.MAX_VALUE + 1.0f;

    public static void main(final String[] args) throws Exception {
        // use only 1 channel, to make this easier
        final AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 1, 2, 44100, false);
        final DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
        final TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
        targetLine.open();
        targetLine.start();
        final AudioInputStream audioStream = new AudioInputStream(targetLine);

        final byte[] buf = new byte[256]; // <--- increase this for higher frequency resolution
        final int numberOfSamples = buf.length / format.getFrameSize();
        final JavaFFT fft = new JavaFFT(numberOfSamples);
        while (true) {
            // in real impl, don't just ignore how many bytes you read
            audioStream.read(buf);
            // the stream represents each sample as two bytes -> decode
            final float[] samples = decode(buf, format);
            final float[][] transformed = fft.transform(samples);
            final float[] realPart = transformed[0];
            final float[] imaginaryPart = transformed[1];
            final double[] magnitudes = toMagnitudes(realPart, imaginaryPart);

            // do something with magnitudes...
        }
    }

    private static float[] decode(final byte[] buf, final AudioFormat format) {
        final float[] fbuf = new float[buf.length / format.getFrameSize()];
        for (int pos = 0; pos < buf.length; pos += format.getFrameSize()) {
            final int sample = format.isBigEndian()
                    ? byteToIntBigEndian(buf, pos, format.getFrameSize())
                    : byteToIntLittleEndian(buf, pos, format.getFrameSize());
            // normalize to [0,1] (not strictly necessary, but makes things easier)
            fbuf[pos / format.getFrameSize()] = sample / NORMALIZATION_FACTOR_2_BYTES;
        }
        return fbuf;
    }

    private static double[] toMagnitudes(final float[] realPart, final float[] imaginaryPart) {
        final double[] powers = new double[realPart.length / 2];
        for (int i = 0; i < powers.length; i++) {
            powers[i] = Math.sqrt(realPart[i] * realPart[i] + imaginaryPart[i] * imaginaryPart[i]);
        }
        return powers;
    }

    private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << 8 * (byteIndex);
        }
        return sample;
    }

    private static int byteToIntBigEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << (8 * (bytesPerSample - byteIndex - 1));
        }
        return sample;
    }

}

傅里叶变换有什么作用?

简而言之:PCM 信号在时域中对音频进行编码,而傅立叶变换信号在频域中对音频进行编码。这是什么意思?

在 PCM 中,每个值都编码一个幅度。您可以将其想象为以一定幅度来回摆动的扬声器薄膜。每秒对扬声器振膜的位置进行一定时间的采样(采样率)。在您的示例中,采样率为 44100 Hz,即每秒 44100 次。这是 CD 品质音频的典型速率。就您的目的而言,您可能不需要这么高的费率。

要从时域转换到频域,您需要获取一定数量的样本(假设N=1024)并使用快速傅里叶变换(FFT)对其进行变换。在有关傅里叶变换的入门读物中,您会看到很多有关连续情况的信息,但您需要注意的是离散情况(也称为discrete傅里叶变换,DTFT https://en.wikipedia.org/wiki/Discrete-time_Fourier_transform),因为我们处理的是数字信号,而不是模拟信号。

那么当你转变时会发生什么1024使用 DTFT 的样本(使用其快速实现 FFT)?通常,样本是real数字,不是complex数字。但DTFT的输出是complex。这就是为什么通常从一个输入数组获得两个输出数组的原因。一个数组用于real一部分和一个为假想部分。它们一起形成一组复数。该数组代表输入样本的频谱。频谱很复杂,因为它必须编码两个方面:幅度(幅度)和相位。想象一个具有振幅的正弦波1。正如您可能还记得,从数学角度来看,正弦波穿过原点(0, 0),而余弦波在 y 轴处切割(0, 1)。除了这种转变之外,两个波的振幅和形状都相同。这种转变称为phase。在您的上下文中,我们不关心相位,而只关心幅度/幅度,但您获得的复数对两者进行编码。转换这些复数之一(r, i)对于一个简单的幅度值(特定频率下的响度),您只需计算m=sqrt(r*r+i*i)。结果总是积极的。理解其工作原理和原理的一个简单方法是想象一个笛卡尔平面。对待(r,i)作为该平面上的向量。因为勾股定理 https://en.wikipedia.org/wiki/Pythagorean_theorem该向量距原点的长度只是m=sqrt(r*r+i*i).

现在我们有了震级。但它们与频率有何关系?每个幅度值对应于某个(线性间隔的)频率。首先要了解的是,FFT 的输出是对称的(在中点镜像)。所以对1024复数,只有第一个512我们感兴趣。涵盖哪些频率?因为奈奎斯特-香农采样定理 https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem采样的信号SR=44100 Hz不能包含大于频率的信息F=SR/2=22050 Hz(您可能会意识到这是人类听觉的上限,这就是为什么选择它用于 CD)。所以第一个512从 FFT 获得的复数值1024采样信号的样本44100 Hz覆盖频率0 Hz - 22050 Hz。每个所谓的频率仓涵盖2F/N = SR/N = 22050/512 Hz = 43 Hz(bin 的带宽)。

所以这个垃圾箱11025 Hz就在索引处512/2=256。幅度可能为m[256].

要使其在您的应用程序中发挥作用,您还需要了解一件事:1024的样本44100 Hz signal覆盖的时间很短,即 23ms。在这么短的时间内,您会看到突然的峰值。最好将其中的多个汇总起来1024在阈值化之前采样到一个值。或者,您也可以使用更长的 DTFT,例如1024*64然而,我建议不要将 DTFT 做得太长,因为它会造成很大的计算负担。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java:如何获取当前音频输入的频率? 的相关文章

  • Amazon Elasticache Redis 集群 - 无法获取端点

    我需要获取 Amazon Elasticache 中 Redis 集群的终端节点 以下代码适用于 Memcached 集群 但不适用于 Redis import com amazonaws auth AWSCredentials impor
  • 在此代码中,Runnable 未实例化。为什么?

    Runnable cannot instantiate public class Thread4 public static void main String args Thread t1 new Thread new Runnable R
  • ListView:防止视图回收

    我有一个使用回收视图的 ListView 我试图阻止视图被回收 所以我使用 setHasTransientState android support v4 view ViewCompatJB setHasTransientState Vie
  • Android CursorAdapter、ListView 和后台线程

    我一直在开发的这个应用程序有包含数兆字节数据的数据库可供筛选 许多活动只是列表视图 通过数据库中的各个级别的数据下降 直到到达 文档 即从数据库中提取并显示在手机上的 HTML 我遇到的问题是 其中一些活动需要能够通过捕获击键并重新运行带有
  • Spring @Validated 在服务层

    Hej 我想使用 Validated group Foo class 在执行方法之前验证参数的注释 如下所示 public void doFoo Foo Validated groups Foo class foo 当我将此方法放入 Spr
  • 探索java图像处理的好资源[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我是图像处理领域的新手 请推荐一些好的资源 书籍和网络链接 来学习 Java 中的图像处理 最适合隐写术分析 适合初学者和高级水平 我看过
  • 无法将 INode 类型值分配给 类型变量。为什么?

    我想知道为什么以下代码无法工作 public static
  • 如何停止使用扫描仪从标准输入读取多行?

    我正在做一个 JAVA 作业 应该处理多行输入 指令显示 输入是从标准输入读取的 给出了示例输入的示例 one 1 two 2 three 3 我不明白上面的示例输入 从标准输入读取 是什么意思 这是我编写的一个测试程序 它可以消除我的困惑
  • Java - toString 到 Color

    我一整天都在努力解决这个问题 基本上我做了一个 for 循环 将条目添加到数组列表中 其中一项是 颜色 变量 我已经用过random nextInt为颜色构造函数的红色 绿色和蓝色部分创建新值 我还设置了一个toString方法 这样我就可
  • 为本地@ExceptionHandler编写JUnit测试

    我有以下控制器 class Controller ResponseStatus HttpStatus OK RequestMapping value verifyCert method RequestMethod GET public vo
  • 欧拉项目 45

    我还不是一名熟练的程序员 但我认为这是一个有趣的问题 我想我应该尝试一下 三角形 五边形 六边形 数字由以下生成 公式 三角形 T n n n 1 2 1 3 6 10 15 五边形 P n n 3n 1 2 1 5 12 22 35 六角
  • 使用 equals 方法比较两个对象,Java

    我有一个对象数组 我想将它们与目标对象进行比较 我想返回与目标对象完全匹配的对象的数量 这是我的计数方法 public int countMatchingGhosts Ghost target int count 0 for int i 0
  • 如何以编程方式播放 16 位 pcm 数组 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有一个包含 16 位 pcm 值的短 数组 我希望能够在不添加任何标题 也不将任何文件保存到内存的情况下播放它 我知道我可能需要一个提供
  • JPA 的 Hibernate 查询提示

    我一直在尝试为所有可以通过设置的提示找到一个明确的资源Query setHint String Object JPA 中的方法调用 但我一无所获 有人知道一个好的参考吗 See 3 4 1 7 查询提示 http docs jboss or
  • 如何从 Trie 中检索给定长度的随机单词

    我有一个简单的 Trie 用来存储大约 80k 长度为 2 15 的单词 它非常适合检查字符串是否是单词 但是 现在我需要一种获取给定长度的随机单词的方法 换句话说 我需要 getRandomWord 5 来返回 5 个字母的单词 所有 5
  • Java 中通用方法参数的 getClass()

    以下 Java 方法无法编译
  • Javac 版本 1.7 无法为目标 1.7 构建

    我试图在 Linux Mint 系统上使用 Sun Java JDK 1 7 0 17 编译 Java 代码 但遇到了这个问题 javac version target 1 7 javac 1 7 0 17 javac invalid ta
  • 一个类中有多个具有相同参数类型的方法

    我知道 至少已经有了关于这个主题的一个问题 https stackoverflow com questions 5561436 can two java methods have same name with different retur
  • 如何让JComboBox中的内容居中显示?

    目前我有这个JComboBox 我怎样才能将其中的内容居中 String strs new String 15158133110 15158133124 15158133458 JComboBox com new JComboBox str
  • Swing:创建可拖动组件...?

    我在网上搜索了可拖动 Swing 组件的示例 但我发现示例不完整或不起作用 我需要的是一个摇摆组件那可以是dragged通过鼠标 在另一个组件内 被拖拽的时候 应该已经 改变它的位置 而不仅仅是 跳 到目的地 我很欣赏无需非标准 API 即

随机推荐