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:如何获取当前音频输入的频率? 的相关文章

  • 如何将本机库链接到 IntelliJ 中的 jar?

    我正在尝试在 IntelliJ 中设置 OpenCV 但是我一直在弄清楚如何告诉 IntelliJ 在哪里可以找到本机库位置 在 Eclipse 中 添加 jar 后 您可以在 Build Config 屏幕中设置 Native 库的位置
  • Junit:如何测试从属性文件读取属性的方法

    嗨 我有课ReadProperty其中有一个方法ReadPropertyFile返回类型的Myclass从属性文件读取参数值并返回Myclass目的 我需要帮助来测试ReadPropertyFile方法与JUnit 如果可能的话使用模拟文件
  • Pig Udf 显示结果

    我是 Pig 的新手 我用 Java 编写了一个 udf 并且包含了一个 System out println 其中的声明 我必须知道在 Pig 中运行时该语句在哪里打印 假设你的UDF 扩展了 EvalFunc 您可以使用从返回的 Log
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • 如何更改javaFX中按钮的图像?

    我正在使用javaFX 我制作了一个按钮并为此设置了图像 代码是 Image playI new Image file c Users Farhad Desktop icons play2 jpg ImageView iv1 new Ima
  • 谷歌应用程序引擎会话

    什么是java应用程序引擎 默认会话超时 如果我们将会话超时设置为非常非常长的时间 会不会产生不良影响 因为谷歌应用程序引擎会话默认情况下仅存储在数据存储中 就像facebook一样 每次访问该页面时 会话仍然永远存在 默认会话超时设置为
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • 检测并缩短字符串中的所有网址

    假设我有一条字符串消息 您应该将 file zip 上传到http google com extremelylonglink zip http google com extremelylonglink zip not https stack
  • 内部类的构造函数引用在运行时失败并出现VerifyError

    我正在使用 lambda 为内部类构造函数创建供应商ctx gt new SpectatorSwitcher ctx IntelliJ建议我将其更改为SpectatorSwitcher new反而 SpectatorSwitcher 是我正
  • 如何在用户输入数据后重新运行java代码

    嘿 我有一个基本的java 应用程序 显示人们是成年人还是青少年等 我从java开始 在用户输入年龄和字符串后我找不到如何制作它它们被归类为 我希望它重新运行整个过程 以便其他人可以尝试 的节目 我一直在考虑做一个循环 但这对我来说没有用
  • 矩形函数的数值傅里叶变换

    本文的目的是通过一个众所周知的分析傅里叶变换示例来正确理解 Python 或 Matlab 上的数值傅里叶变换 为此 我选择矩形函数 这里报告了它的解析表达式及其傅立叶变换https en wikipedia org wiki Rectan
  • tomcat 中受密码保护的应用程序

    我正在使用 JSP Servlet 开发一个Web应用程序 并且我使用了Tomcat 7 0 33 as a web container 所以我的要求是tomcat中的每个应用程序都会password像受保护的manager applica
  • java for windows 中的文件图标叠加

    我正在尝试像 Tortoise SVN 或 Dropbox 一样在文件和文件夹上实现图标叠加 我在网上查了很多资料 但没有找到Java的解决方案 Can anyone help me with this 很抱歉确认您的担忧 但这无法在 Ja
  • 关键字“table”附近的语法不正确,无法提取结果集

    我使用 SQL Server 创建了一个项目 其中包含以下文件 UserDAO java public class UserDAO private static SessionFactory sessionFactory static se
  • java.io.Serialized 在 C/C++ 中的等价物是什么?

    C C 的等价物是什么java io Serialized https docs oracle com javase 7 docs api java io Serializable html 有对序列化库的引用 用 C 序列化数据结构 ht
  • 如何使用 jUnit 将测试用例添加到套件中?

    我有 2 个测试类 都扩展了TestCase 每个类都包含一堆针对我的程序运行的单独测试 如何将这两个类 以及它们拥有的所有测试 作为同一套件的一部分执行 我正在使用 jUnit 4 8 在 jUnit4 中你有这样的东西 RunWith
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • Eclipse 启动时崩溃;退出代码=13

    I am trying to work with Eclipse Helios on my x64 machine Im pretty sure now that this problem could occur with any ecli
  • 使用反射覆盖最终静态字段是否有限制?

    在我的一些单元测试中 我在最终静态字段上的反射中遇到了奇怪的行为 下面是说明我的问题的示例 我有一个基本的 Singleton 类 其中包含一个 Integer public class BasicHolder private static
  • 如何将双精度/浮点四舍五入为二进制精度?

    我正在编写对浮点数执行计算的代码的测试 不出所料 结果很少是准确的 我想在计算结果和预期结果之间设置一个容差 我已经证实 在实践中 使用双精度 在对最后两位有效小数进行四舍五入后 结果始终是正确的 但是usually四舍五入最后一位小数后

随机推荐