Android аудио FFT для отображения основной частоты

Некоторое время я работал над проектом Android, который отображает основную частоту входного сигнала (в качестве тюнера). Я успешно реализовал класс AudioRecord и получаю от него данные. Однако мне трудно выполнить БПФ для этих данных, чтобы получить основную частоту входного сигнала. Я просматривал этот пост здесь и использую FFT в Java и сложный класс.

Я успешно использовал функцию FFT, найденную в FFT в Java, но я не уверен, что получаю правильные результаты. Для величины БПФ (sqrt [rere + im im]) я получаю значения, которые начинаются с высокого уровня, около 15000 Гц, а затем медленно уменьшаются до примерно 300 Гц. Не кажется правильным.

Кроме того, что касается исходных данных с микрофона, данные кажутся нормальными, за исключением того, что первые 50 значений или около того всегда являются цифрой 3, если я не нажму кнопку настройки еще раз, пока еще в приложении, а затем я только получаю 15. Это нормально?

Вот немного моего кода.

Прежде всего, я конвертирую короткие данные (полученные из микрофона) в двойные, используя следующий код из поста, который я просматривал. Этот фрагмент кода я не совсем понимаю, но я думаю, что это работает.

//Conversion from short to double
double[] micBufferData = new double[bufferSizeInBytes];//size may need to change
final int bytesPerSample = 2; // As it is 16bit PCM
final double amplification = 1.0; // choose a number as you like
for (int index = 0, floatIndex = 0; index < bufferSizeInBytes - bytesPerSample + 1; index += bytesPerSample, floatIndex++) {
    double sample = 0;
    for (int b = 0; b < bytesPerSample; b++) {
        int v = audioData[index + b];
        if (b < bytesPerSample - 1 || bytesPerSample == 1) {
            v &= 0xFF;
        }
        sample += v << (b * 8);
    }
    double sample32 = amplification * (sample / 32768.0);
    micBufferData[floatIndex] = sample32;
}

Затем код продолжается следующим образом:

//Create Complex array for use in FFT
Complex[] fftTempArray = new Complex[bufferSizeInBytes];
for (int i=0; i<bufferSizeInBytes; i++)
{
    fftTempArray[i] = new Complex(micBufferData[i], 0);
}

//Obtain array of FFT data
final Complex[] fftArray = FFT.fft(fftTempArray);
final Complex[] fftInverse = FFT.ifft(fftTempArray);

//Create an array of magnitude of fftArray
double[] magnitude = new double[fftArray.length];
for (int i=0; i<fftArray.length; i++){
    magnitude[i]= fftArray[i].abs();
}


fft.setTextColor(Color.GREEN);
fft.setText("fftArray is "+ fftArray[500] +" and fftTempArray is "+fftTempArray[500] + " and fftInverse is "+fftInverse[500]+" and audioData is "+audioData[500]+ " and magnitude is "+ magnitude[1] + ", "+magnitude[500]+", "+magnitude[1000]+" Good job!");
for(int i = 2; i < samples; i++){
    fft.append(" " + magnitude[i] + " Hz");
}

Этот последний бит просто чтобы проверить, какие ценности я получаю (и чтобы держать меня в здравом уме!). В сообщении, упомянутом выше, говорится о необходимости частоты дискретизации и дается следующий код:

private double ComputeFrequency(int arrayIndex) {
    return ((1.0 * sampleRate) / (1.0 * fftOutWindowSize)) * arrayIndex;
}

Как мне реализовать этот код? Я действительно не понимаю, откуда fftOutWindowSize и arrayIndex?

Любая помощь очень ценится!

Dustin

3 ответа

Недавно я работаю над проектом, который требует почти того же. Возможно, вам больше не нужна помощь, но я все равно выскажу свои мысли. Возможно, кому-то это понадобится в будущем.

  1. Я не уверен, работает ли функция short to double, я тоже не понимаю этот фрагмент кода. Это написано для байта, чтобы удвоить преобразование.
  2. В коде: "double[] micBufferData = new double[bufferSizeInBytes];" Я думаю размер micBufferData должно быть "bufferSizeInBytes / 2", так как каждая выборка занимает два байта и размер micBufferData должен быть номер образца.
  3. Алгоритмы БПФ действительно требуют размера окна БПФ, и это должно быть число, которое является степенью 2. Однако многие алгоритмы могут получить произвольное число в качестве входных данных, и это сделает все остальное. В документе эти алгоритмы должны иметь требования к вводу. В вашем случае размер массива Complex может быть вводом алгоритмов FFT. И я действительно не знаю деталей алгоритма FFT, но я думаю, что обратный алгоритм не нужен.
  4. Чтобы использовать код, который вы дали наконец, вы должны сначала найти индекс пика в массиве примеров. Я использовал двойной массив в качестве ввода вместо Complex, поэтому в моем случае это что-то вроде: double maxVal = -1;int maxIndex = -1;

    for( int j=0; j < mFftSize / 2; ++j ) {
        double v = fftResult[2*j] * fftResult[2*j] + fftResult[2*j+1] * fftResult[2*j+1];
        if( v > maxVal ) {
            maxVal = v;
            maxIndex = j;
        }
    }
    

    2 * j - действительная часть, а 2*j+1 - мнимая часть. maxIndex является индексом пиковой величины, которую вы хотите (более подробно здесь), и используйте его в качестве входных данных для ComputeFrequency функция. Возвращаемое значение - это частота выбранного массива выборок.

Надеюсь, это может кому-то помочь.

Вы должны выбрать размер окна FFT в зависимости от вашего разрешения по времени и частоте, а не просто использовать размер аудио буфера при создании временного массива FFT.

Индекс массива - это ваш int i, используемый в вашем выражении печати magnitude[i].

Основная частота основного тона для музыки часто отличается от пиковой величины БПФ, поэтому вы можете исследовать некоторые алгоритмы оценки основного тона.

Я подозреваю, что странные результаты вы получаете, потому что вам может понадобиться распаковать БПФ. Как это сделать, будет зависеть от используемой вами библиотеки (см. Здесь документацию о том, как она упакована в GSL, например). Упаковка может означать, что реальные и мнимые компоненты не находятся в тех позициях в массиве, которые вы ожидаете.

Что касается ваших других вопросов о размере и разрешении окна, если вы создаете тюнер, я бы предложил попробовать размер окна около 20 мс (например, 1024 сэмпла при 44,1 кГц). Для тюнера вам нужно довольно высокое разрешение, чтобы вы могли попробовать заполнение нулями с коэффициентом 8 или 16, что даст вам разрешение 3-6 Гц.

Другие вопросы по тегам