Ah, now that I've found the source to TPCircularBuffer I see my mistake—it's not a circularly linked list of buckets, but a circular linear buffer of audio data. Which means my whole increment/decrement idea is not gonna fly.
So forget all that stuff. Let's restate your actual problem:
- Your decoding thread is generating audio data slower than your render callback thread wants to fill the AudioBuffers.
- You _must_ fill every available AudioBuffer completely, either wi audio data or silence.
- You _must not_ block the render callback thread. Not with synchronization primitives, nor with sleep().
The obvious solution is to buffer up a bunch of decoded audio at the beginning, outputting silence from your render callback until you reach a certain watermark. This introduces latency, and is the common solution to this problem.
If your decoder is so slow that it the render callback drains all the available data in the circular buffer, you'll have to generate silence until it catches up. Your code already contains logic to do that, but either it's buggy, or not _every_ render callback is writing zeroes.
Also, you really need to remove those NSLogs from your render callback. They take a global lock.
--Kyle Sluder On May 3, 2014, at 1:34 PM, Kyle Sluder < email@hidden> wrote: On May 3, 2014, at 1:17 PM, Douglas Carmichael < email@hidden> wrote:
Kyle:
Would I need to pass the semaphore down to the rendering callback along with the TPCircularBuffer?
How would I do that?
Perhaps I should have avoided the term semaphore. You don't want to actually use a real semaphore because you must not block the render callback thread. The render callback is run on a real time thread by the operating system on a fixed schedule. If there's no useful work for it to do, it must generate the requisite amount of silence or else you will get audio glitching.
The idea I was trying to explain by analogy is to atomically and locklessly increment and decrement a global integer variable that counts the number of free buffers in the ring. Because it's a global variable, there’s no object that needs to be passed around. Both threads just take the address of the global variable.
The tricky part is in using the correct atomic primitives in the right order to ensure you don't enqueue or use a buffer without properly updating the counter. And that I can't help you with.
--Kyle Sluder
Still unclear on how you would get it down to the callback, though.
--Douglas On May 3, 2014, at 2:38 PM, Kyle Sluder < email@hidden> wrote: On May 3, 2014, at 10:08 AM, Douglas Carmichael < email@hidden> wrote:
Kevin:
How would I pass the NSCondition down to the rendering callback along with the TPCircularBuffer?
Don’t. NSCondition isn’t useful to you here. It probably just calls sleep() internally. Plus, you should not be sending Objective-C messages from realtime threads, as they may block on malloc().
I'm certain there are existing implementations out there that show how to deal with over/under-runs. Alas, I’m on my phone and don’t have quick access to any. To my memory, the general idea is that since both your decoding and rendering threads know how many buffers are being generated, you can keep a “free buffer” counter synchronized between them (essentially a lockless semaphore). If the semaphore count drops to zero, the render thread generates silence. If the semaphore count reaches the number of buffers, the decoding thread yields to the scheduler. (Sleeping by some multiple of the duration of audio contained in a sample buffer seems like a reasonable idea.)
--Kyle Sluder
—Douglas On May 1, 2014, at 8:49 PM, Kevin Dixon < email@hidden> wrote: Your output callback is called in a more or less "real time" sense. The callback is invoked by the OS as needed to satisfy the requirements of the output device.
See https://developer.apple.com/library/mac/documentation/musicaudio/Conceptual/AudioUnitProgrammingGuide/AudioUnitDevelopmentFundamentals/AudioUnitDevelopmentFundamentals.html
and read the bit about "Pull Model"
libxmp, or other similar decoding type of software typically generate data faster than real time. On the flip side, they are normally too expensive to call on a realtime thread as well, not to mention have buffer sizes different than your generator.
Basically your "main" method is already running on a separate thread.
You need to accurately detect when the buffer is getting full, and then sleep before generating the next block. You can more or less count on that your buffer will be drained in real time, so if you have a 2 second buffer, then you should sleep for less than 2 seconds. Your margin of safety may vary depending on hardware.
Remember that usleep sleeps AT LEAST for the amount of time you specify. You should consider using a proper thread synchronization mechanism. Have a look at NSCondition, which has the "waitUntilDate:" method
-Kevin
|