Slower sample reading with AudioFormatReader instead of libsndfile

Hi to all,

I’m new to the Juce framework and I’m trying to integrate it in one of my Qt project to replace some part that is done with some other libraries.
At the moment I would like to use the AudioFormatReader to read a wave file in chunk to create some visualization instead of the libsndfile library I’m currently using, but I’m noticing a really big gap in performance between the two library, and I’m trying to understand why.

To contextualize the situation I’m currently reading a stereo wav file in chunk of around 5113 frames into a multidimensional float/double array that I will process after.

The read done with libsndfile is done in this way:

double chunk = new double[ 2(region_end_frame - region_start_frame)];
if (sf_seek(sndFile, region_start_frame, SEEK_SET) == -1) {
perror(“seek error in AudioUtil::peakForRegion function\n”);
return this->regionPeak;
}

if(sf_readf_double(sndFile, chunk, (region_end_frame - region_start_frame) ) == 0) {
return this->regionPeak;
}

sndFile is the resource handle to the opened wave file by the libsndfile library.
This code is executed in around 25/26 microseconds.
The libsnd is the version 1.1.0 and is static linked by cmake in the project.

The one done with AudioFormatReader is done in this way:

int frameCount = region_end_frame - region_start_frame;
float** audioBuffer = new float*[audioReader->numChannels];
for(uint i = 0; i < audioReader->numChannels; ++i) {
audioBuffer[i] = new float[frameCount];
}

audioReader->read(audioBuffer, audioReader->numChannels, region_start_frame, frameCount);

The audioReader is the AudioFormatReader created using the AudioFormatManager::createReaderFor().
This code is executed in 330/350 microseconds.
The Juce framework is added as a submodule of my project in cmake (I’m using add_subdirectory to add the juce directory and target_link_libraries to include juce:: and juce::juce_audio_formats in the compilation)

As you can see there’s more then 10x difference in time reading the same amount of data from the same file on the same computer.
What can be the cause? Am I missing something? Maybe I’ve left some “debugging” option active including the Juce library with cmake that is slowing down the reading?
There’s a more efficient way to read samples in Juce that I’m missing?

Hope to have written enough information.
Thanks to all!
Cheers
Daniele

Welcome Daniele!

I presume you are comparing release builds? Just asking…

How many channels are there in the file? In your second snippet, you’re doing numChannels extra allocations compared to the first snippet, so if there are lots of channels, this may introduce significant overhead.

Thanks @daniel!

Well, that’s embarrassing… you’re right… I was working with debug builds so I can’t compare a static linked library with one that it’s compiled with the code… hope I’ve seen that before posting… shame on me :frowning:

I’ve tried now in release mode and I get around 48/50 microseconds for the AudioFormatReader vs the around 23 microseconds of the libsndfile, so it’s still around double of the time.

@reuk currently I’m using a stereo file so the numChannel is 2 in both the case (in the libsndfile snippet code there’s an if with only the mono and stereo case hardcoded… the snippet I’ve posted is the one for the stereo case) so they should be pretty similar in term of allocation time… or am I wrong?

Thanks again

Why are you allocating separately for each small chunk you are reading? Why not preallocate the memory outside that code and reuse it for each chunk read?

In the first snippet, you call new once, to allocate the region for the output samples.

In the second snippet, you call new three times: once to create an area for the channel pointers, and then once for each channel.

Each call to new might have a certain amount of overhead, so reducing the number of calls may improve performance. For example, in the second snippet, you could instead do something like this:

const auto frameCount = region_end_frame - region_start_frame;
std::vector<float> channelStorage (audioReader->numChannels * frameCount); // one allocation
std::vector<float*> channelPointers;
channelPointers.reserve (audioReader->numChannels); // one allocation

for (auto i = 0; i < audioReader->numChannels; ++i)
    channelPointers.push_back (channelStorage.data() + i * frameCount);

audioReader->read (channelPointers.data(), channelPointers.size(), region_start_frame, frameCount);

It’s generally a bad idea to call new directly, especially outside of a constructor - I’d recommend using a vector or similar instead to automatically manage the memory.

Thanks again @reuk,

Sincerely it’s the first time I see a solution like this, that use to vectors to recreate a two dimension array of float… I’ve still many thing to learn in c++ :smiley:

I’ve tried the code instead of the allocation I was doing with the the new, and I’ve gained a little bit of time… now it’s around to 40 microseconds for the execution and it’s more near to the libsndfile case!

Just to ask, if I’ve seen well the code of the read method I’m using (the float const buffer version) is calling the method with the int const buffer and it’s converting it to float then…
Does this conversion can impact on the performances?

@xenakios well, you’re right… that’s probably something I will do… To be honest I’ve found this code online and I’m rewriting many piece of it while I found better solution… Thanks for the suggestion!

If you enjoy what you are doing, keep doing it. But I would recommend not to get too hooked in optimising micro seconds. Often you are more testing the test case than predicting what will work best.

Like one framework might read metadata by default, the other only on demand. Or you measure different memory operations.
The picture might change as soon as you read longer passages, which might change the hot spots where time is spent.

Good luck