diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h index f91011618..eeae21eb9 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h @@ -36,9 +36,8 @@ Enqueues a buffer for playback. @param buffer A pointer to the data that comprises the buffer. - @param lengthInSamples The length of the buffer, in samples. */ -- (void)enqueueAudioBuffer:(nonnull const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples; +- (void)enqueueAudioBuffer:(nonnull const int16_t *)buffer; /// @returns The sampling rate at which this queue is playing audio. @property (nonatomic, readonly) Float64 samplingRate; @@ -58,6 +57,11 @@ */ @property (nonatomic, readonly) NSUInteger preferredBufferSize; +/*! + Sets the size of buffers to be posted, in samplrs. +*/ +@property (nonatomic) NSUInteger bufferSize; + /*! @returns @C YES if this queue is running low or is completely exhausted of new audio buffers. */ diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index b66a5e371..1e0e6fd89 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -18,11 +18,18 @@ #define IsDry(x) (x) < 2 +#define MaximumBacklog 4 +#define NumBuffers (MaximumBacklog + 1) + @implementation CSAudioQueue { AudioQueueRef _audioQueue; + NSLock *_deallocLock; NSLock *_queueLock; + atomic_int _enqueuedBuffers; + AudioQueueBufferRef _buffers[NumBuffers]; + int _bufferWritePointer; } #pragma mark - Status @@ -73,19 +80,19 @@ 0, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { + (void)inBuffer; + CSAudioQueue *queue = weakSelf; if(!queue) { return; } if([queue->_deallocLock tryLock]) { - [queue->_queueLock lock]; - OSSGuard(AudioQueueFreeBuffer(inAQ, inBuffer)); - [queue->_queueLock unlock]; - const int buffers = atomic_fetch_add(&queue->_enqueuedBuffers, -1) - 1; if(!buffers) { - OSSGuard(AudioQueuePause(queue->_audioQueue)); + [queue->_queueLock lock]; + OSSGuard(AudioQueuePause(inAQ)); + [queue->_queueLock unlock]; } id delegate = queue.delegate; @@ -105,43 +112,66 @@ - (void)dealloc { [_deallocLock lock]; - if(_audioQueue) { - OSSGuard(AudioQueueDispose(_audioQueue, true)); - _audioQueue = NULL; - } + if(_audioQueue) { + OSSGuard(AudioQueueDispose(_audioQueue, true)); + _audioQueue = NULL; + } - // nil out the dealloc lock before entering the critical section such - // that it becomes impossible for anyone else to acquire. - NSLock *deallocLock = _deallocLock; - _deallocLock = nil; + for(size_t c = 0; c < NumBuffers; c++) { + if(_buffers[c]) { + OSSGuard(AudioQueueFreeBuffer(_audioQueue, _buffers[c])); + _buffers[c] = NULL; + } + } + + // nil out the dealloc lock before entering the critical section such + // that it becomes impossible for anyone else to acquire. + NSLock *deallocLock = _deallocLock; + _deallocLock = nil; [deallocLock unlock]; } #pragma mark - Audio enqueuer -- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { - size_t bufferBytes = lengthInSamples * sizeof(int16_t); +- (void)setBufferSize:(NSUInteger)bufferSize { + _bufferSize = bufferSize; - // Don't enqueue more than 4 buffers ahead of now, to ensure not too much latency accrues. - if(atomic_load_explicit(&_enqueuedBuffers, memory_order_relaxed) == 4) { + // Allocate future audio buffers. + [_queueLock lock]; + const size_t bufferBytes = self.bufferSize * sizeof(int16_t); + for(size_t c = 0; c < NumBuffers; c++) { + if(_buffers[c]) { + OSSGuard(AudioQueueFreeBuffer(_audioQueue, _buffers[c])); + } + + OSSGuard(AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &_buffers[c])); + _buffers[c]->mAudioDataByteSize = (UInt32)bufferBytes; + } + [_queueLock unlock]; +} + +- (void)enqueueAudioBuffer:(const int16_t *)buffer { + const size_t bufferBytes = self.bufferSize * sizeof(int16_t); + + // Don't enqueue more than the allowed number of future buffers, + // to ensure not too much latency accrues. + if(atomic_load_explicit(&_enqueuedBuffers, memory_order_relaxed) == MaximumBacklog) { return; } const int enqueuedBuffers = atomic_fetch_add(&_enqueuedBuffers, 1) + 1; + const int targetBuffer = _bufferWritePointer; + _bufferWritePointer = (_bufferWritePointer + 1) % NumBuffers; + memcpy(_buffers[targetBuffer]->mAudioData, buffer, bufferBytes); + [_queueLock lock]; + OSSGuard(AudioQueueEnqueueBuffer(_audioQueue, _buffers[targetBuffer], 0, NULL)); - AudioQueueBufferRef newBuffer; - OSSGuard(AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer)); - memcpy(newBuffer->mAudioData, buffer, bufferBytes); - newBuffer->mAudioDataByteSize = (UInt32)bufferBytes; - - OSSGuard(AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL)); - - // Start the queue if it isn't started yet, and there are now some packets waiting. - if(enqueuedBuffers > 1) { - OSSGuard(AudioQueueStart(_audioQueue, NULL)); - } - + // Starting is a no-op if the queue is already playing, but it may not have been started + // yet, or may have been paused due to a pipeline failure if the producer is running slowly. + if(enqueuedBuffers > 1) { + OSSGuard(AudioQueueStart(_audioQueue, NULL)); + } [_queueLock unlock]; } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index d38bb7b39..dd2323fef 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -30,6 +30,7 @@ #import "NSBundle+DataResource.h" #import "NSData+StdVector.h" +#include #include #include #include @@ -175,7 +176,8 @@ struct ActivityObserver: public Activity::Observer { } - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { - [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; + assert(NSUInteger(length) == self.audioQueue.bufferSize); + [self.audioQueue enqueueAudioBuffer:samples]; } - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker { @@ -231,6 +233,7 @@ struct ActivityObserver: public Activity::Observer { - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo { @synchronized(self) { + self.audioQueue.bufferSize = bufferSize; [self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize stereo:stereo]; } }