如何在SDL2音频流数据上执行实时FFT
问题描述:
我正在尝试使用SDL2和FFTW3在C++中创建音乐可视化工具。 我的目标是加载.wav音频文件,然后同时播放音频并使用SDL2回叫功能执行实时快速傅立叶变换。 我想获取频谱数据,以便日后可以实现图形可视化。如何在SDL2音频流数据上执行实时FFT
我按照SDL YouTube指南加载.wav并使用回调函数播放音频,但我不明白如何对此数据执行FFT。我遵循另一个使用FFTW和SDL的C指南来产生类似的效果,但我仍然不确定如何实际执行它。
Uint8* sampData;
SDL_AudioSpec wavSpec;
Uint8* wavStart;
Uint32 wavLength;
SDL_AudioDeviceID aDevice;
struct AudioData {
Uint8* filePosition;
Uint32 fileLength;
};
void PlayAudioCallback(void* userData, Uint8* stream, int streamLength) {
AudioData* audio = (AudioData*)userData;
sampData = new Uint8;
if (audio->fileLength == 0) {
return;
}
Uint32 length = (Uint32)streamLength;
length = (length > audio->fileLength ? audio->fileLength : length);
SDL_memcpy(stream, audio->filePosition, length);
// HERE is where i'd like to implement the FFT on 'stream' data
// but i don't know how to implement this using FFTW
audio->filePosition += length;
audio->fileLength -= length;
}
int main() {
SDL_Init(SDL_INIT_AUDIO);
// Load .wav file
if (SDL_LoadWAV(FILE_PATH, &wavSpec, &wavStart, &wavLength) == NULL) {
cerr << "Couldnt load file: " << FILE_PATH << endl;
getchar();
}
cout << "Loaded " << FILE_PATH << endl;
AudioData audio;
audio.filePosition = wavStart;
audio.fileLength = wavLength;
wavSpec.callback = PlayAudioCallback;
wavSpec.userdata = &audio;
// Open audio playback endpoint
aDevice = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (aDevice == 0) {
cerr << "Audio Device connection failed: " << SDL_GetError() << endl;
getchar();
}
// Play audio on playback endpoint
SDL_PauseAudioDevice(aDevice, 0);
// Do nothing while there's still data to be played
while (audio.fileLength > 0) {
SDL_Delay(100);
}
}
从我使用NumPy的为.wav数据解压到一个NumPy的阵列以往的经验,在发送前内置NumPy的-FFT功能,但我什么与SDL流数据做是无能我在这里。
答
你想要的是短期的FFT。您可以在执行FFT之前从流中收集样本的缓冲区,并将窗函数应用于样本。然后您收集另一个缓冲区,保留来自第一个缓冲区的一些样本并追加新的样本。重复,直到所有的数据已被处理。由于您的输入数据是实数,因此FFT是对称的,因此您只需要第一个N/2 + 1复杂输出分档。这些代表来自直流的频率。到Fs/2。以他们的大小和情节。对每个FFT重复一次。
几乎正确。所描述的解决方案的问题是窗口函数的应用是破坏性的。当你说“保留第一个缓冲区的一些样本”时,你会保留窗口样本。事实上,在重叠较小的情况下,您可以将每个窗口边缘的最大滤波部分保留在窗口函数几乎为零的位置。如果使用重叠窗口,最好在复制样本时应用窗口函数。即'auto windowed = buffer * windowFunction'而不是'buffer * = windowFunction'。 – MSalters
当然你是对的。我在提供细节时非常不透明,因为我试图在iPad键盘上输入此信息。不过,它是一个重要的细节。谢谢。 – sizzzzlerz