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 ответа
Недавно я работаю над проектом, который требует почти того же. Возможно, вам больше не нужна помощь, но я все равно выскажу свои мысли. Возможно, кому-то это понадобится в будущем.
- Я не уверен, работает ли функция short to double, я тоже не понимаю этот фрагмент кода. Это написано для байта, чтобы удвоить преобразование.
- В коде:
"double[] micBufferData = new double[bufferSizeInBytes];"
Я думаю размерmicBufferData
должно быть "bufferSizeInBytes / 2
", так как каждая выборка занимает два байта и размерmicBufferData
должен быть номер образца. - Алгоритмы БПФ действительно требуют размера окна БПФ, и это должно быть число, которое является степенью 2. Однако многие алгоритмы могут получить произвольное число в качестве входных данных, и это сделает все остальное. В документе эти алгоритмы должны иметь требования к вводу. В вашем случае размер массива Complex может быть вводом алгоритмов FFT. И я действительно не знаю деталей алгоритма FFT, но я думаю, что обратный алгоритм не нужен.
Чтобы использовать код, который вы дали наконец, вы должны сначала найти индекс пика в массиве примеров. Я использовал двойной массив в качестве ввода вместо 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 Гц.