Используя класс 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 Гц, то есть 44100 раз в секунду.Это типичный показатель качества звука CD.Для ваших целей вам, вероятно, не нужна такая высокая скорость.
Для преобразования из временной области в частотную область вы берете определенное количество выборок (скажем, N=1024
) и преобразуете их, используябыстрое преобразование Фурье (БПФ).В учебниках по преобразованию Фурье вы увидите много информации о непрерывном случае, но на что нужно обратить внимание, это на дискретный случай (также называемый дискретное преобразование Фурье, 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)
.
Теперь у нас есть величины.Но как они связаны с частотами?Каждое из значений величины соответствует определенной (линейно разнесенной) частоте.Первое, что нужно понять, это то, что выходной сигнал БПФ является симметричным (отражается в средней точке).Так что из 1024
комплексных чисел, только первые 512
представляют интерес для нас.И какие частоты это охватывает?Из-за теоремы Найквиста-Шеннона сигнал, дискретизированный с помощью SR=44100 Hz
, не может содержать информацию о частотах, превышающих F=SR/2=22050 Hz
(вы можете понять, что это верхняя граница человеческого слуха, поэтомувыбрал для дисков).Таким образом, первые 512
комплексные значения, которые вы получаете из БПФ для 1024
выборок сигнала, дискретизированного в 44100 Hz
, охватывают частоты 0 Hz - 22050 Hz
.Каждая так называемая ячейка частоты охватывает 2F/N = SR/N = 22050/512 Hz = 43 Hz
(полоса пропускания ячейки).
Таким образом, ячейка для 11025 Hz
имеет право на индекс 512/2=256
.Величина может быть в m[256]
.
Чтобы это работало в вашем приложении, вам нужно понять еще одну вещь: 1024
выборки из 44100 Hz signal
охватывают очень короткий промежуток времени, то есть 23 мс.За это короткое время вы увидите внезапные пики.Лучше объединить несколько этих 1024
выборок в одно значение перед установкой порога.В качестве альтернативы вы также можете использовать более длинный DTFT, например, 1024*64
, однако я не советую делать DTFT очень длинным, поскольку это создает большую вычислительную нагрузку.