2016-01-15 01:33:22 +00:00
|
|
|
//
|
|
|
|
// AudioQueue.m
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 14/01/2016.
|
|
|
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
2016-06-15 12:07:25 +00:00
|
|
|
#import "CSAudioQueue.h"
|
2016-01-15 01:33:22 +00:00
|
|
|
@import AudioToolbox;
|
|
|
|
|
2016-06-17 01:41:17 +00:00
|
|
|
#define AudioQueueBufferMaxLength 8192
|
2016-10-24 01:17:00 +00:00
|
|
|
#define NumberOfStoredAudioQueueBuffer 16
|
2016-01-18 19:56:20 +00:00
|
|
|
|
2016-06-15 12:07:25 +00:00
|
|
|
@implementation CSAudioQueue
|
2016-01-15 01:33:22 +00:00
|
|
|
{
|
|
|
|
AudioQueueRef _audioQueue;
|
2016-10-24 01:17:00 +00:00
|
|
|
|
|
|
|
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
|
2016-01-15 01:33:22 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 11:30:00 +00:00
|
|
|
#pragma mark - AudioQueue callbacks
|
2016-01-15 01:33:22 +00:00
|
|
|
|
|
|
|
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
|
|
|
{
|
2016-10-24 00:33:59 +00:00
|
|
|
[self.delegate audioQueueIsRunningDry:self];
|
2016-10-24 01:17:00 +00:00
|
|
|
|
|
|
|
@synchronized(self)
|
|
|
|
{
|
|
|
|
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
|
|
|
{
|
|
|
|
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
|
|
|
|
{
|
|
|
|
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
|
|
|
_storedBuffers[c] = buffer;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-10 11:30:00 +00:00
|
|
|
AudioQueueFreeBuffer(_audioQueue, buffer);
|
2016-01-15 01:33:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void audioOutputCallback(
|
|
|
|
void *inUserData,
|
|
|
|
AudioQueueRef inAQ,
|
|
|
|
AudioQueueBufferRef inBuffer)
|
|
|
|
{
|
2016-06-15 12:07:25 +00:00
|
|
|
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
2016-01-15 01:33:22 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 11:30:00 +00:00
|
|
|
#pragma mark - Standard object lifecycle
|
|
|
|
|
2016-06-01 23:04:07 +00:00
|
|
|
- (instancetype)initWithSamplingRate:(Float64)samplingRate
|
2016-01-15 01:33:22 +00:00
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
|
|
|
|
if(self)
|
|
|
|
{
|
2016-06-01 23:04:07 +00:00
|
|
|
_samplingRate = samplingRate;
|
2016-01-18 19:56:20 +00:00
|
|
|
|
2016-10-10 11:30:00 +00:00
|
|
|
// determine preferred buffer sizes
|
|
|
|
_preferredBufferSize = AudioQueueBufferMaxLength;
|
2016-10-10 11:42:24 +00:00
|
|
|
while((Float64)_preferredBufferSize*100.0 > samplingRate) _preferredBufferSize >>= 1;
|
2016-06-17 01:35:22 +00:00
|
|
|
|
2016-01-15 01:33:22 +00:00
|
|
|
/*
|
2016-06-15 11:35:34 +00:00
|
|
|
Describe a mono 16bit stream of the requested sampling rate
|
2016-01-15 01:33:22 +00:00
|
|
|
*/
|
|
|
|
AudioStreamBasicDescription outputDescription;
|
|
|
|
|
2016-06-01 23:04:07 +00:00
|
|
|
outputDescription.mSampleRate = samplingRate;
|
2016-01-15 01:33:22 +00:00
|
|
|
|
|
|
|
outputDescription.mFormatID = kAudioFormatLinearPCM;
|
|
|
|
outputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
|
|
|
|
|
|
|
outputDescription.mBytesPerPacket = 2;
|
|
|
|
outputDescription.mFramesPerPacket = 1;
|
|
|
|
outputDescription.mBytesPerFrame = 2;
|
|
|
|
outputDescription.mChannelsPerFrame = 1;
|
|
|
|
outputDescription.mBitsPerChannel = 16;
|
|
|
|
|
|
|
|
outputDescription.mReserved = 0;
|
|
|
|
|
|
|
|
// create an audio output queue along those lines
|
|
|
|
if(!AudioQueueNewOutput(
|
2016-06-01 23:04:07 +00:00
|
|
|
&outputDescription,
|
|
|
|
audioOutputCallback,
|
|
|
|
(__bridge void *)(self),
|
|
|
|
NULL,
|
|
|
|
kCFRunLoopCommonModes,
|
|
|
|
0,
|
|
|
|
&_audioQueue))
|
2016-01-15 01:33:22 +00:00
|
|
|
{
|
|
|
|
AudioQueueStart(_audioQueue, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2016-06-01 23:04:07 +00:00
|
|
|
- (instancetype)init
|
|
|
|
{
|
|
|
|
return [self initWithSamplingRate:[[self class] preferredSamplingRate]];
|
|
|
|
}
|
|
|
|
|
2016-01-15 01:35:36 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
if(_audioQueue) AudioQueueDispose(_audioQueue, NO);
|
|
|
|
}
|
|
|
|
|
2016-10-10 11:30:00 +00:00
|
|
|
#pragma mark - Audio enqueuer
|
|
|
|
|
2016-01-18 19:50:19 +00:00
|
|
|
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples
|
2016-01-15 01:33:22 +00:00
|
|
|
{
|
2016-10-10 11:30:00 +00:00
|
|
|
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
2016-01-18 19:50:19 +00:00
|
|
|
|
2016-10-24 01:17:00 +00:00
|
|
|
@synchronized(self)
|
|
|
|
{
|
|
|
|
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
|
|
|
{
|
|
|
|
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
|
|
|
|
{
|
|
|
|
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
|
|
|
|
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
|
|
|
|
|
|
|
|
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
|
|
|
_storedBuffers[c] = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2016-10-10 11:30:00 +00:00
|
|
|
|
2016-10-24 01:17:00 +00:00
|
|
|
AudioQueueBufferRef newBuffer;
|
|
|
|
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
|
|
|
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
|
|
|
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
|
|
|
|
|
|
|
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
|
|
|
}
|
2016-01-15 01:33:22 +00:00
|
|
|
}
|
|
|
|
|
2016-06-15 11:35:34 +00:00
|
|
|
#pragma mark - Sampling Rate getters
|
|
|
|
|
2016-06-01 23:04:07 +00:00
|
|
|
+ (AudioDeviceID)defaultOutputDevice
|
|
|
|
{
|
|
|
|
AudioObjectPropertyAddress address;
|
|
|
|
address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
|
|
|
address.mScope = kAudioObjectPropertyScopeGlobal;
|
|
|
|
address.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
|
|
|
|
AudioDeviceID deviceID;
|
|
|
|
UInt32 size = sizeof(AudioDeviceID);
|
2016-06-15 11:35:34 +00:00
|
|
|
return AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &deviceID) ? 0 : deviceID;
|
2016-06-01 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (Float64)preferredSamplingRate
|
|
|
|
{
|
|
|
|
AudioObjectPropertyAddress address;
|
|
|
|
address.mSelector = kAudioDevicePropertyNominalSampleRate;
|
|
|
|
address.mScope = kAudioObjectPropertyScopeGlobal;
|
|
|
|
address.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
|
|
|
|
Float64 samplingRate;
|
|
|
|
UInt32 size = sizeof(Float64);
|
2016-06-15 11:35:34 +00:00
|
|
|
return AudioObjectGetPropertyData([self defaultOutputDevice], &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &samplingRate) ? 0.0 : samplingRate;
|
2016-06-01 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
2016-01-15 01:33:22 +00:00
|
|
|
@end
|