Jan*_*nik 3 java audio fft frequency javasound
我想分析麦克风输入的当前频率,以将我的 LED 与播放的音乐同步。我知道如何从麦克风捕获声音,但我不知道 FFT,我在寻找获取频率的解决方案时经常看到它。
我想测试某个频率的当前音量是否大于设定值。代码应该是这样的:
if(frequency > value) {
LEDs on
else {
LEDs off
}
Run Code Online (Sandbox Code Playgroud)
我的问题是如何在 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();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:我尝试使用 MediaPlayer 的 JavaFX AudioSpectrumListener,只要我使用.mp3文件,它就可以很好地工作。问题是,我必须使用一个字节数组来存储麦克风输入。我在这里问了这个问题的另一个问题。
hen*_*rik 15
使用此处的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;
}
}
Run Code Online (Sandbox Code Playgroud)
用非常简单的术语来说:PCM 信号在时域中对音频进行编码,而傅立叶变换信号在频域中对音频进行编码。这是什么意思?
在 PCM 中,每个值都编码一个幅度。你可以把它想象成一个扬声器的膜片,它以一定的幅度来回摆动。扬声器振膜的位置每秒采样一定时间(采样率)。在您的示例中,采样率为 44100 Hz,即每秒 44100 次。这是 CD 质量音频的典型速率。出于您的目的,您可能不需要这么高的费率。
要从时域转换到频域,您需要获取一定数量的样本(假设为N=1024)并使用快速傅立叶变换 (FFT) 对其进行转换。在关于傅立叶变换的入门中,您会看到很多关于连续情况的信息,但您需要注意的是离散情况(也称为离散傅立叶变换,DTFT),因为我们处理的是数字信号,而不是模拟信号信号。
那么当您1024使用 DTFT(使用其快速实现 FFT)转换样本时会发生什么?通常,样本是实数,而不是复数。但是 DTFT 的输出是复杂的。这就是为什么您通常从一个输入数组获得两个输出数组的原因。一组用于实部,一组用于虚部。它们一起形成一组复数。此数组表示输入样本的频谱。频谱很复杂,因为它必须编码两个方面:幅度(幅度)和相位。想象一个振幅为 的正弦波1。您可能还记得数学中的一个正弦波穿过原点(0, 0),而余弦波在 处切割 y 轴(0, 1)。除了这种偏移,两种波的振幅和形状都相同。这种转变称为阶段。在您的上下文中,我们不关心相位,而只关心幅度/幅度,但是您得到的复数对两者进行编码。要将这些复数之一转换(r, i)为简单的幅度值(在某个频率下有多响),您只需计算m=sqrt(r*r+i*i). 结果总是积极的。理解为什么以及如何工作的一个简单方法是想象一个笛卡尔平面。将其(r,i)视为该平面上的向量。由于勾股定理,该向量从原点的长度为m=sqrt(r*r+i*i)。
现在我们有了量级。但是它们与频率有什么关系呢?每个幅度值对应于某个(线性间隔)频率。首先要理解的是,FFT 的输出是对称的(镜像在中点)。所以在1024复数中,只有第一个512是我们感兴趣的。它涵盖哪些频率?由于Nyquist-Shannon 采样定理,采样的信号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 for11025 Hz就在 index 处512/2=256。幅度可能在m[256]。
要将其应用于您的应用程序,您还需要了解一件事:在非常短的时间内(即 23 毫秒)覆盖1024样本44100 Hz signal。在这么短的时间内,您会看到突然的峰值。最好1024在阈值处理之前将这些样本中的多个聚合为一个值。或者,您也可以使用更长的 DTFT,例如1024*64,但是,我建议不要使 DTFT 过长,因为它会产生很大的计算负担。