1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-02 20:30:00 +00:00

Merge pull request #1064 from TomHarte/FewerAudioAllocations

macOS: perform audio buffer allocations ahead of time.
This commit is contained in:
Thomas Harte 2022-07-14 14:58:51 -04:00 committed by GitHub
commit 8310b40812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 32 deletions

View File

@ -36,9 +36,8 @@
Enqueues a buffer for playback. Enqueues a buffer for playback.
@param buffer A pointer to the data that comprises the buffer. @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. /// @returns The sampling rate at which this queue is playing audio.
@property (nonatomic, readonly) Float64 samplingRate; @property (nonatomic, readonly) Float64 samplingRate;
@ -58,6 +57,11 @@
*/ */
@property (nonatomic, readonly) NSUInteger preferredBufferSize; @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. @returns @C YES if this queue is running low or is completely exhausted of new audio buffers.
*/ */

View File

@ -18,11 +18,18 @@
#define IsDry(x) (x) < 2 #define IsDry(x) (x) < 2
#define MaximumBacklog 4
#define NumBuffers (MaximumBacklog + 1)
@implementation CSAudioQueue { @implementation CSAudioQueue {
AudioQueueRef _audioQueue; AudioQueueRef _audioQueue;
NSLock *_deallocLock; NSLock *_deallocLock;
NSLock *_queueLock; NSLock *_queueLock;
atomic_int _enqueuedBuffers; atomic_int _enqueuedBuffers;
AudioQueueBufferRef _buffers[NumBuffers];
int _bufferWritePointer;
} }
#pragma mark - Status #pragma mark - Status
@ -73,19 +80,19 @@
0, 0,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { ^(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
(void)inBuffer;
CSAudioQueue *queue = weakSelf; CSAudioQueue *queue = weakSelf;
if(!queue) { if(!queue) {
return; return;
} }
if([queue->_deallocLock tryLock]) { if([queue->_deallocLock tryLock]) {
[queue->_queueLock lock];
OSSGuard(AudioQueueFreeBuffer(inAQ, inBuffer));
[queue->_queueLock unlock];
const int buffers = atomic_fetch_add(&queue->_enqueuedBuffers, -1) - 1; const int buffers = atomic_fetch_add(&queue->_enqueuedBuffers, -1) - 1;
if(!buffers) { if(!buffers) {
OSSGuard(AudioQueuePause(queue->_audioQueue)); [queue->_queueLock lock];
OSSGuard(AudioQueuePause(inAQ));
[queue->_queueLock unlock];
} }
id<CSAudioQueueDelegate> delegate = queue.delegate; id<CSAudioQueueDelegate> delegate = queue.delegate;
@ -105,43 +112,66 @@
- (void)dealloc { - (void)dealloc {
[_deallocLock lock]; [_deallocLock lock];
if(_audioQueue) { if(_audioQueue) {
OSSGuard(AudioQueueDispose(_audioQueue, true)); OSSGuard(AudioQueueDispose(_audioQueue, true));
_audioQueue = NULL; _audioQueue = NULL;
} }
// nil out the dealloc lock before entering the critical section such for(size_t c = 0; c < NumBuffers; c++) {
// that it becomes impossible for anyone else to acquire. if(_buffers[c]) {
NSLock *deallocLock = _deallocLock; OSSGuard(AudioQueueFreeBuffer(_audioQueue, _buffers[c]));
_deallocLock = nil; _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]; [deallocLock unlock];
} }
#pragma mark - Audio enqueuer #pragma mark - Audio enqueuer
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { - (void)setBufferSize:(NSUInteger)bufferSize {
size_t bufferBytes = lengthInSamples * sizeof(int16_t); _bufferSize = bufferSize;
// Don't enqueue more than 4 buffers ahead of now, to ensure not too much latency accrues. // Allocate future audio buffers.
if(atomic_load_explicit(&_enqueuedBuffers, memory_order_relaxed) == 4) { [_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; return;
} }
const int enqueuedBuffers = atomic_fetch_add(&_enqueuedBuffers, 1) + 1; 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]; [_queueLock lock];
OSSGuard(AudioQueueEnqueueBuffer(_audioQueue, _buffers[targetBuffer], 0, NULL));
AudioQueueBufferRef newBuffer; // Starting is a no-op if the queue is already playing, but it may not have been started
OSSGuard(AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer)); // yet, or may have been paused due to a pipeline failure if the producer is running slowly.
memcpy(newBuffer->mAudioData, buffer, bufferBytes); if(enqueuedBuffers > 1) {
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes; OSSGuard(AudioQueueStart(_audioQueue, NULL));
}
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));
}
[_queueLock unlock]; [_queueLock unlock];
} }

View File

@ -30,6 +30,7 @@
#import "NSBundle+DataResource.h" #import "NSBundle+DataResource.h"
#import "NSData+StdVector.h" #import "NSData+StdVector.h"
#include <cassert>
#include <atomic> #include <atomic>
#include <bitset> #include <bitset>
#include <codecvt> #include <codecvt>
@ -175,7 +176,8 @@ struct ActivityObserver: public Activity::Observer {
} }
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { - (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 { - (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 { - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo {
@synchronized(self) { @synchronized(self) {
self.audioQueue.bufferSize = bufferSize;
[self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize stereo:stereo]; [self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize stereo:stereo];
} }
} }