https://github.com/syedhali/EZAudio
I've been having success using this audio library, but now I'd like to increase the resolution of the microphone data that's read in, so that the FFT resolution, or frequency bin size goes down to 10Hz. To do that, I need a buffersize of 8820 instead of 512. Is the buffersize of the microphone and FFT windowing size separable? I can't see a way to separate it.
How do I set up the audio stream description, so that it can calculate the FFT with a larger window?
Any help would be much appreciated.
The FFT size and the audio buffer size should be completely independent. You can just save multiple audio input buffers (perhaps in a circular FIFO or queue), without processing them until you have enough samples for your desired length FFT.
Saving audio buffers this way also allows you to FFT overlapped frames for more time resolution.
Having browsed the source of the linked project, it appears that the audio callback passes a buffer size that is the preferred buffer size of the microphone device. I would recommend you buffer up the desired number of samples before calling the FFT. The following code is modified from FFTViewController.m in EZAudioFFTExample:
#pragma mark - EZMicrophoneDelegate
-(void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels {
dispatch_async(dispatch_get_main_queue(), ^{
// Update time domain plot
[self.audioPlotTime updateBuffer:buffer[0]
withBufferSize:bufferSize];
// Setup the FFT if it's not already setup
if( !_isFFTSetup ){
[self createFFTWithBufferSize:bufferSize withAudioData:buffer[0]];
_isFFTSetup = YES;
}
int samplesRemaining = bufferSize;
while (samplesRemaining > 0)
{
int samplestoCopy = max(bufferSize, FFTLEN - _fftBufIndex);
memcpy(_fftBuf, buffer[0], samplesToCopy*sizeof(float));
_fftBufIndex += samplesToCopy;
samplesRemaining -= samplesToCopy;
if (_fftBufIndex == FFTLEN)
{
_fftBufIndex = 0;
[self updateFFTWithBufferSize:FFTLEN withAudioData:_fftBuf];
}
}
});
}
In the modified program, FFTLEN
a value that you defined, _fftBuf
is an array of floats that you allocate and it needs to hold FFTLEN
elements, and _fftBufIndex
is an integer to track the write position into the array.
On a separate note, I would recommend you make a copy of the buffer parameter before calling the async delegate. The reason I say this is because looking at the source for EZMicrophone it looks like it's recycling the buffer so you'll have a race condition.
Thanks Jaket for suggestion. Buffer is the way to go and here is my working implementation of that same function now with adjustable FFT window:
-(void)microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels {
dispatch_async(dispatch_get_main_queue(),^{
[self.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize];
// Decibel Calculation.
float one = 1.0;
float meanVal = 0.0;
float tiny = 0.1;
vDSP_vsq(buffer[0], 1, buffer[0], 1, bufferSize);
vDSP_meanv(buffer[0], 1, &meanVal, bufferSize);
vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0);
// Exponential moving average to dB level to only get continous sounds.
float currentdb = 1.0 - (fabs(meanVal)/100);
if (lastdbValue == INFINITY || lastdbValue == -INFINITY || isnan(lastdbValue)) {
lastdbValue = 0.0;
}
dbValue = ((1.0 - tiny)*lastdbValue) + tiny*currentdb;
lastdbValue = dbValue;
// NSLog(@"dbval: %f",dbValue);
//
// Setup the FFT if it's not already setup
int samplestoCopy = fmin(bufferSize, FFTLEN - _fftBufIndex);
for ( size_t i = 0; i < samplestoCopy; i++ ) {
_fftBuf[_fftBufIndex+i] = buffer[0][i];
}
_fftBufIndex += samplestoCopy;
_samplesRemaining -= samplestoCopy;
if (_fftBufIndex == FFTLEN) {
if( !_isFFTSetup ){
[self createFFTWithBufferSize:FFTLEN withAudioData:_fftBuf];
_isFFTSetup = YES;
}
[self updateFFTWithBufferSize:FFTLEN withAudioData:_fftBuf];
_fftBufIndex = 0;
_samplesRemaining = FFTLEN;
}
});
}