1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-30 22:29:56 +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.
@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.
*/

View File

@ -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<CSAudioQueueDelegate> 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];
}

View File

@ -30,6 +30,7 @@
#import "NSBundle+DataResource.h"
#import "NSData+StdVector.h"
#include <cassert>
#include <atomic>
#include <bitset>
#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 {
[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];
}
}