AudioSampleBuffer realtime-problem


#1

In my audioCallback I use the AudioSampleBuffer class a lot to reference already existing audio data (so it’s already allocated), using AudioSampleBuffer (float** dataToReferTo, int numChannels, int numSamples).

Upon deletion, AudioSampleBuffer deletes a HeapBlock which again leads to a call of free() (and ofcourse its constructor also malloc’ed). Not exactly good if you have a AudioSampleBuffer in your audioCallback. Here it ruined all performances.
Since JUCE relies heavily on AudioSampleBuffer, this can cause heavy potential problems.

[code] ILSequencer.exe!free(void * pUserData=0x00000000) Line 49 + 0xb bytes C++
ILSequencer.exe!juce::HeapBlock<char,1>::~HeapBlock<char,1>() Line 119 + 0xb bytes C++

ILSequencer.exe!juce::AudioSampleBuffer::~AudioSampleBuffer() Line 147 + 0x19 bytes C++
ILSequencer.exe!MainController::audioDeviceIOCallback(const float * * inputChannelData=0x06bc5ec8, int numInputChannels=0x00000002, float * * outputChannelData=0x06bc6e30, int numOutputChannels=0x00000002, int numSamples=0x000001c0) Line 624 + 0x1e bytes C++[/code]


#2

Ok, well don’t create one in your audio thread then!

Just create a big enough buffer in your prepareToPlay method, and re-use it!


#3

OK, well maybe add a notice that this class should never be used for realtime. Because in former versions of JUCE this was not a problem.


#4

And btw, aren’t you doing exactly the same thing as myself in GraphRenderingOps::ProcessBufferOp::perform() ?

[code]void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray & sharedMidiBuffers, const int numSamples)
{
for (int i = totalChans; --i >= 0;)
channels[i] = sharedBufferChans.getSampleData (audioChannelsToUse.getUnchecked (i), 0);

    AudioSampleBuffer buffer (channels, totalChans, numSamples); // <------------ will result in free()/malloc()

    processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse));
}[/code]

#5

Erm… no, it’s always been the same. The class has to allocate memory, so it has to call malloc, there was never a version that didn’t do so. In fact the older versions used to make more calls to malloc than the current one does.

No. Try stepping through the code and see what really happens in there.


#6

Why would AudioSampleBuffer need to allocate memory when used with the constructor AudioSampleBuffer (float** dataToReferTo, int numChannels, int numSamples) ?? I’ve been clearly saying that I am only referencing pre-allocated memory, and not letting AudioSampleBuffer allocate anything!


#7

Ah, sorry then! I have to skim these forum posts pretty quickly, you know!

Just looking at your first stack trace again, the “free” call is actually freeing a null pointer, so surely the standard library won’t invoke a lock if you pass it a null pointer… would it??


#8

You’re right, calling free(0) should actually not take too much time… That’s strange… Could it have something to do with the leak detector?


#9

Yes, it definitely could!


#10

…hang on - you do mean the Visual studio leak detector, right? Not my juce leak detector, which certainly won’t have any connection with this.


#11

I meant any leak detector. Anyway, I guess problem solved. It’s not your fault, so sorry for the hassle.


#12

Last question: Is there any way to alert when calls to free/malloc/new/delete are made in the audio thread? It would help a lot if that would be possible.


#13

There’s no easy way to do that, I guess you could create your own DLL to intercept them, check the thread ID, etc. but it’d be messy.


#14

I use something like that

[code]
static juce::Thread::ThreadID gpAudioThreadId = NULL;

#include
void *operator new(size_t size) throw(std::bad_alloc)
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
void *p = malloc(size);

if (!p)
{
throw std::bad_alloc();
}

return p;
}

void operator delete(void *p) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
free§;
}

void *operator new(size_t size, const std::nothrow_t &) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
return malloc(size);
}

void operator delete(void *p, const std::nothrow_t &) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
free§;
}

void *operator new[](size_t size) throw(std::bad_alloc)
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
void *p = malloc(size);

if (!p)
{
throw std::bad_alloc();
}

return p;
}

void operator delete[](void *p) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
free§;
}

void *operator new[](size_t size, const std::nothrow_t &) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
return malloc(size);
}

void operator delete[](void *p, const std::nothrow_t &) throw()
{
assert(gpAudioThreadId == NULL || gpAudioThreadId != juce::Thread::getCurrentThreadId());
free§;
}[/code]

and assign
gpAudioThreadId = juce::Thread::getCurrentThreadId();
at the top level of the audio callback

HTH