当前位置: 首页 > 知识库问答 >
问题:

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

姬庆
2023-03-14

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

我想测试特定频率的当前音量是否大于设定值。代码应该如下所示:

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

我的问题是如何在Java中实现FFT。为了更好地理解,这里有一个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();
    }

}

编辑:我尝试使用MediaPlayer的JavaFX AudioSpectrumListener,只要我使用. mp3文件,它的工作原理就非常好。问题是,我必须使用一个字节数组来存储麦克风输入。我在这里针对这个问题又问了一个问题。

共有3个答案

施永贞
2023-03-14

虽然其他答案提供了大量有用的信息,并很好地解释了相关概念,但如果您想快速获得Java的工作解决方案,那么jAudio提供了一个非常易于使用的FFT类,它将为您完成所有工作。这个类的所有依赖函数都可以在这里找到。

在这种情况下,可以忽略假想输入(因为音频信号只是实值),因此所需的输入只是一个样本数组(类型为double)。例如,如果您的样本是16位整数,您可以使用以下方法轻松地将short样本转换为double

short shortSample = ...
double sample = (double) shortSample / Short.MAX_VALUE;

对于一个完全可以工作的代码片段,请看一看我自己实现的代码,该代码改编自Hendrik的回答,或者看下面的代码片段:

double[] samples = getSamples(NUMBER_OF_SAMPLES); // implement this function to get samples from your source

FFT fft = new FFT(samples, null, false, false); // optionally set last parameter to true if you want Hamming window

double[] magnitudes = fft.getMagnitudeSpectrum();
double[] bins = leftFft.getBinLabels(sampleRate); // the sample rate used is required for frequency bins

// get the loudest occurring frequency within typical human hearing range
int maxIndex = 0;
double max = Double.NEGATIVE_INFINITY;
for (int i = 0; i < magnitudes.length; i++) {
  // ignore frequencies outside human hearing range
  if (bins[i] < 20 || bins[i] > 20000) {
    continue;
  }
  if (magnitudes[i] > max) {
    maxIndex = i;
    max = magnitudes[i];
  }
}

// loudest frequency of all previous samples now easy to obtain
double frequency = bins[maxIndex];
包谭三
2023-03-14

我认为亨德里克有基本的计划,但我听到了你对理解到达那里的过程的痛苦!

我假设您是通过TargetDataLine获取字节数组,它返回字节。将字节转换为浮点数需要一些操作,这取决于AudioFormat。典型的格式是每秒44100帧,16位编码(两个字节形成一个数据点)和立体声。这意味着4个字节组成一个由左值和右值组成的单帧。

显示如何读取和处理传入的单个字节流的示例代码可以在使用文件和格式转换器的java音频教程中找到。向下滚动至“读取声音文件”部分中的第一个“代码段”。将传入数据转换为浮动的关键点出现在标记如下的位置:

// Here, do something useful with the audio data that's 
// now in the audioBytes array...

此时,您可以将两个字节(假设为16位编码)追加到一个短字节中,并将值缩放为标准化浮点数(范围从-1到1)。有几个StackOverflow问题显示了执行此转换的算法

您可能还需要经历一个过程编辑,其中示例代码从音频输入流(如示例所示)与目标数据线读取,但我认为如果这造成了问题,也有一些堆栈溢出问题可以帮助解决。

对于hendrik推荐的FFTFactory,我怀疑使用变换方法,只需输入一个浮动[]就足够了。但是我还没有深入细节,也没有试着自己运行这个。(看起来很有希望。我怀疑搜索也可能发现其他具有更完整留档的FFT库。我记得麻省理工学院可能有一些东西。从技术上来说,我可能只比你领先几步。)

在任何情况下,在上面发生转换的地方,您都可以将transform()添加到输入数组中,直到它已满,然后在该迭代中调用transform()方法。

解释方法的输出最好在单独的线程上完成。我在想,通过某种松散耦合来传递FFT调用的结果,或者传递变换()调用本身。(你熟悉这个术语和多线程编码吗?)

关于Java如何编码声音和声音格式的重要见解,可以在上面链接的教程之前的教程中找到。

另一个很好的资源,如果你想更好地理解如何解释FFT结果,可以免费下载:“科学家和工程师DSP指南”

艾浩穰
2023-03-14

从这里使用JavaFFT类,可以执行以下操作:

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)进行转换。在关于傅里叶变换的初级读本中,你会看到很多关于连续情况的信息,但是你需要注意的是离散情况(也称为离散傅里叶变换,DTFT),因为我们处理的是数字信号,而不是模拟信号。

那么,当您使用DTFT(使用其快速实现FFT)变换1024个样本时会发生什么呢?通常,样本是实数,而不是复数。但DTFT的输出是复杂的。这就是为什么通常从一个输入数组中获得两个输出数组。一个数组表示实部,一个数组表示虚部。它们一起构成一个复数数组。此数组表示输入样本的频谱。频谱是复杂的,因为它必须编码两个方面:幅度和相位。想象一个振幅1的正弦波。正如您可能还记得的那样,正弦波穿过原点(0,0),而余弦波在(0,1)处切割y轴。除此之外,两种波的振幅和形状都是相同的。这种转变称为相位。在您的上下文中,我们不关心相位,只关心振幅/幅度,但您得到的复数对两者进行编码。要将其中一个复数(r,i)转换为一个简单的幅值(在特定频率下的音量),只需计算m=sqrt(r*ri*i)。结果总是积极的。理解其原因和工作原理的一个简单方法是想象一个笛卡尔平面。将(r,i)视为该平面上的向量。由于毕达哥拉斯定理,向量从原点的长度正好是m=sqrt(r*ri*i)

现在我们有了震级。但是它们与频率有什么关系呢?每个幅值对应于特定(线性间隔)频率。首先要了解的是FFT的输出是对称的(在中点镜像)。因此,在1024复数中,我们只感兴趣的是第一个512。这包括哪些频率?由于奈奎斯特-香农采样定理,以SR=44100 Hz采样的信号不能包含大于F=SR/2=22050 Hz的频率信息(您可能会意识到这是人类听力的上限,这就是为什么选择它作为CD的原因)。因此,对于在44100 Hz下采样的信号的1024样本,从FFT中获得的第一个512复数值涵盖了频率0 Hz-22050 Hz。每个所谓的频率单元包括2F/N=SR/N=22050/512 Hz=43 Hz(单元带宽)。

因此,11025 Hz的bin就在索引512/2=256处。震级可能在m[256]处。

要在应用程序中实现这一点,您还需要了解一件事:102444100 Hz信号的采样时间非常短,即23ms。在这短短的一段时间里,你会看到突如其来的高峰。在阈值化之前,最好将这些1024样本的倍数聚合为一个值。或者,您也可以使用更长的DTFT,例如1024*64,但是,我建议不要将DTFT设置得太长,因为它会造成很大的计算负担。

 类似资料:
  • 想获取 mp3 或者 wav 文件的音调信息, 那个可以量化的音调 输入一段音频 输出量化的音调, 跟随着时间, 1 秒一个, 3,3,9,2,10,13.....

  • 我的音乐应用程序从外部应用程序启动,使用意向数据作为音乐文件。 我有mp3音频URI,类似这样 file:///storage/emulated/0/Music/Tamil/I(2014)/Ennodu Nee Irundhaal。mp3 如何从媒体获取音频详细信息。标题,媒体。相册,媒体_身份证件

  • 问题内容: 我正在开发一个必须处理音频文件的应用程序。使用mp3文件时,我不确定如何处理数据(我感兴趣的数据是音频字节,代表我们所听到的)。 如果我使用的是wav文件,我知道我有一个44字节的标头,然后是数据。关于mp3,我已经读到它们是由帧组成的,每个帧都包含标题和音频数据。是否可以从mp3文件中获取所有音频数据? 我正在使用Java(我已经添加了MP3SPI,Jlayer和Tritonus),

  • electron如何获取到桌面音频? 比方说 直播时电脑上播放视频 能不能获取到视频的音频 electorn官方提供的desktopCapturer API也是基于webtrc底层实现的。只能获取麦克风的音频。

  • 问题内容: 我正在做一个使用HTML和Javascript的项目,它将与本地文件一起在本地运行。我需要通过输入选择一个文件,获取文件信息,然后决定是否将其添加到列表中并进行复制。如果我决定使用它,则必须将其放在队列中以备后用。否则,我将丢弃并选择另一个文件。 我面临的问题是我无法找到一种仅通过在输入中选择视频持续时间来获得视频持续时间的方法。 我进行了很多搜索,但没有找到任何方法来获取持续时间。在

  • YouTube频道可以包含多个“相关”频道的列表。例如,音乐频道 音乐频道:http://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ 我想以上链接渠道(类别)明智的数据 音乐频道相关频道:http://www . YouTube . com/Channel/UC-9-kytw 8 zkzndhqj 6 fg pwq/Channels