Crash in CoreAudio due to changing number of channels


#1

I have a Motu 828 MK3 with two ADAT in/out ports. Adat supports 8 audio channels at up to 48khz, and 4 channels for higher sample rates.
This means that if you change the samplerate from 48 kHz to 96 kHz, the amount of in & output channels changes to be 2 x 4 = 8 channels less.
When I build the Demo and go to Audio::Settings, open my device for input and output and enable all available channels, changing the samplerate causes a crash in juce_mac_CoreAudio.cpp (around line 742, downloaded from the website):

for (int i = numOutputChans; --i >= 0;)
{
                const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i);
                const float* src = tempOutputBuffers [i];
                float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData)
                                + info.dataOffsetSamples;

The index i is out of bounds when accessing outputchannelInfo. It seems that outputChannelInfo is updated with the correct number of new channels, but numOutChans is not.

I have to check if this is also the case for ASIO.


#2

Could anyone please have a look, this is a clear error and probably not a hard one to fix for you guys. Crashes in the current Demo of JUCE so it’s easy to reproduce, of course if you have a Adat card laying around.


#3

I don’t have an ADAT device to hand, but I think I’ve spotted how to fix things.

Could you please insert

updateDetailsFromDevice();

after the stop (false); line in reopen

//==============================================================================
String reopen (const BigInteger& inputChannels,
               const BigInteger& outputChannels,
               double newSampleRate, int bufferSizeSamples)
{
    String error;
    callbacksAllowed = false;
    stopTimer();

    stop (false);

    // Add updateDetailsFromDevice(); here

and let me know if this solves the issue?


#4

Hi T0m,

Unfortunately not. I have created a fix of my own, but I’m not sure this is a good one.
In void audioCallback (const AudioBufferList* inInputData, AudioBufferList* outOutputData), before the input and output channel data copy loops, I’m doing

numInputChans = jmin(inputChannelInfo.size(), numInputChans);

and

numOutputChans = jmin(outputChannelInfo.size(), numOutputChans);

Please let me know if this is a suitable solution. I will test the same device on ASIO as well.


#5

That looks like a practical fix, but I don’t like the fact that things numOutputChans will be incorrect in other parts of the code.

When you change the sample rate a call to updateDetailsFromDevice must occur, as this is the only mechanism that can alter outputChannelInfo. Could you put a breakpoint here to find out where this call originates from? Once we know this, we need to ensure that the device is reopened (or similar) afterwards so that we configure the correct number of channels.


#6

I understand that this is not the most practical way.
For the record: it’s not outputChannelInfo that is incorrect, this gets updated in the updateDetailsFromDevice call following a sample rate change (coming from ::open -> ::reopen).

numOutputChans is the variable that has the wrong (outdated) value. Since this is used as a size for indexing outputChannelInfo, this will go out of bounds.
I’ve noticed that the outChanNames array does have the right size, but activeOutputChansdoes not.

It seems to me that not all variable’s are updated correctly when the ‘updateDetailsFromDevice’ call happens.


#7

Why doesn’t the fix I suggest work?

What I was expecting to happen was something along the lines of:

  • The number of channels reduces from 8 to 4
  • reopen is called
  • updateDetailsFromDevice is called (where we inserted it)
    – This correctly updates outChanNames so that it has 4 entries

Then this block of code immediately after

activeOutputChans = outputChannels;
activeOutputChans.setRange (outChanNames.size(),
                            activeOutputChans.getHighestBit() + 1 - outChanNames.size(),
                            false);

changes activeOutputChans from 1111111100000... to 11110000... (or possibly a mix of 1s and 0s in place of the block of 1s, depending on your current config). Then, finally, we do

numOutputChans = activeOutputChans.countNumberOfSetBits();

which should be <= 4.

However, one of these assumptions is wrong. Which is it?


#8

Hello Tom,

I could have sworn I managed to reproduce the crash with your solution of adding updateDetailsFromDevice() immediately after stop(false) in the reopen call, but now It’s seems to be working fine! The version of juce that we’re using still crashes with this addition, but I guess this is just a matter of updating to the recent stable version…
Thank you!


#9

I’ll push a fix to the develop branch later today, once I’ve tested it more thoroughly .


#10

https://github.com/WeAreROLI/JUCE/commit/fec19eeadebd896c077dd3355fcd93acdf1e37b5