Possible bug in Core Audio Handling

Hi,

I've recently had problems with sound using Core Audio -  I get glitches with frame sizes around 128 samples. Smaller frame sizes work better, and larger also ( but not always ). The problem doesn't always happen, but will always turn up if you switch the frame sizes around a few times. 

I can reproduce the problem in all version of JUCE from 3.0.0 up tp 3.0.4.  If I put the 2.1.8 version of juce_audio_basics and juce_audio_devices into a new JUCE version then the problem goes away.

What seems to be happening is that empty frames are being inserted into the input and the ouput stream at (apparently) random intervals. I have checked the methods in juce_MacCoreAudio.cpp and traced the problem back to the data read/write in the run() method of  the combiner class. I haven't yet worked out exactly how this works, so I'm currently no help with debugging. When I have some time I'll try and trace it further. Unfortunately I just upgraded my last Mac to Mavericks, so I can't test an earlier OS version

The problem also happens in the JUCE demo. To be sure I built a program with the absolute minimum that I need to play a sound and change framesize - no problem with 2.1.8 and glitches with 3.0.0 upwards. Just for the record, and I've attached the program.

Here's my basic info:

Computer: MacBook pro 15" (mid 2013) with 16Gb memory and 500Gb disk

OS : OSX Mavericks 10.9.2

JUCE 3.0.4

Audio : Intel HIgh Definition Audio

 

I've been trying to track this down and there is definitely a problem triggered by a change of framesize which leads to the outputfifo structure in the device wrapper become somehow inconsistent. The problem is worst with a buffer size of 128 samples. I have found two workarounds that work:

a) add a short delay in the reset method (100ms) after resetting both fifos

        void reset()
        {
            inputFifo.reset();
            outputFifo.reset();
            Thread::sleep (100);
        }

 

b) increasing the fifo size to 6x the buffer size, but that's probably a bad idea.

I still haven't quite worked out why this works, but I think there might be a problem in the AbstractFifo implementation. If you look at the reset function, it is possibe that after validEnd is set to 0 a thread calling prepareToRead or prepareToWrite is called. As soon as the thread returns to the reset method the validStart will be set to 0, which leaves the buffer inconsistent. 

Anyway,  I'm going to test this on another Mac to see if it's a timing sensitive thing

Hmm, I don't really know why that'd make a difference, and would be skeptical about adding a sleep like that.. keep me posted with any other clues though.

Jules,

still trying to work out what's causing this, but there does seem to be a bug in the AbstractFifo class. The getFreeSpace method returns the number of available slots in the buffer, but since you're using the empty slot method to distinguish between full and empty buffers, this value is 1 too high. Same goes for getTotalSize, although that's more depending on whether you mean this to be available space or total buffer size allocated.

Hi Jules,

another clue. I've been focussing on the AudioDeviceCombiner::pushOutputData method. The buffer is large enough for three frames and the method does 3 retries to get data. I've been running at a sampling rate of 48kHz at various frame sizes. The biggest problem is with a 128 sample frame, which equates to 2.7ms. The pushOutputData method waits at the end of each retry, but since the wait is trunctated it will have waited 3 x 2 = 6ms after 3 tries, rather than 3 x 2.7 = 8.1ms. If I increase the retries to 5 which gives 5 x 2 = 10ms my problem goes away. With 4 retries at 8.0ms I still have a problem.  In fact the problem is also visible but less intense with a 224 sample buffer (4.7ms) where the rounding error after 3 tries is 2.1ms. Larger buffers don't seem to show the problem,  possibly because the relative rounding error is small.

Sorry, I only just noticed this post..

Thanks for the clues! I'm happy to increase the number of tries to 5, I can't think of any problems with that. I guess the same could be done with the readInput method too.