Java: Как узнать текущую частоту аудиовхода? - PullRequest
0 голосов
/ 01 января 2019

Я хочу проанализировать текущую частоту входа микрофона, чтобы синхронизировать мои светодиоды с воспроизводимой музыкой.Я знаю, как захватить звук с микрофона, но я не знаю о БПФ, которое я часто видел при поиске решения для получения частоты.

Я хочу проверить, превышает ли текущий объем определенной частоты заданное значение.Код должен выглядеть примерно так:

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

Моя проблема заключается в том, как реализовать FFT в Java.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Код:

Весь код: *1008* *1009*. Редактировать: Я пытался использовать JavaFX AudioSpectrumListener MediaPlayer, который работает очень хорошо, пока я использую файл .mp3.Проблема в том, что мне нужно использовать байтовый массив, в котором я храню вход микрофона.Я задал еще один вопрос по этой проблеме здесь .

Ответы [ 2 ]

0 голосов
/ 02 января 2019

Я думаю, что у Хендрика есть базовый план, но я слышу вашу боль от понимания процесса его достижения!

Я предполагаю, что вы получаете свой байтовый массив через 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, в которых показаны алгоритмы для этого преобразования.

Возможно, вам также придется выполнить процесс редактирования, где пример кода считывается из AudioInputStream (как в примере) против TargetDataLine, но я думаю, что если это создает проблему, существуют также вопросы StackOverflow, которые могут помочь с этим.

Для FFTFactory , рекомендованного Хендриком, я подозреваю, что использование метода transform сбудет достаточно float [] для ввода.Но я еще не разбирался в деталях и не пытался запустить это сам.(Это выглядит многообещающе. Я подозреваю, что поиск может также обнаружить другие библиотеки FFT с более полной документацией. Я помню, что что-то было возможно, возможно, из MIT. Я, вероятно, всего на пару шагов впереди вас технически.)

Inлюбое событие, в точке выше, где происходит преобразование, вы можете добавить во входной массив для transform (), пока оно не заполнится, и на этой итерации вызовите метод transform ().

Интерпретация выходных данных изметод может быть лучше всего реализован в отдельном потоке.Я думаю, передать результаты FFT-вызова или передать сам вызов transform () через какую-то слабую связь.(Вы знакомы с этим термином и многопоточным кодированием?)

Важные сведения о том, как Java кодирует звук и звуковые форматы, можно найти в учебных пособиях, которые непосредственно предшествуют приведенному выше.

Другойотличный ресурс, если вы хотите лучше понять, как интерпретировать результаты БПФ, можно бесплатно загрузить: « Руководство для ученых и инженеров по DSP »

0 голосов
/ 02 января 2019

Используя класс 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 очень длинным, поскольку это создает большую вычислительную нагрузку.

...