Compare commits

...

10 Commits

8 changed files with 485 additions and 147 deletions
+2 -2
View File
@@ -21,7 +21,6 @@
@implementation AppDelegate
-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSError* error;
@@ -32,8 +31,9 @@
self.window.backgroundColor = [UIColor whiteColor];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:STKAudioPlayerOptionFlushQueueOnSeek];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 0.1;
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds];
+1 -2
View File
@@ -107,11 +107,10 @@
statusLabel.textAlignment = NSTextAlignmentCenter;
meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)];
meter.backgroundColor = [UIColor greenColor];
[self addSubview:slider];
[self addSubview:playButton];
[self addSubview:playFromHTTPButton];
@@ -432,7 +432,7 @@
"$(SRCROOT)/../StreamingKit/StreamingKit",
);
INFOPLIST_FILE = "ExampleAppMac/ExampleAppMac-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.8;
MACOSX_DEPLOYMENT_TARGET = "";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -452,7 +452,7 @@
"$(SRCROOT)/../StreamingKit/StreamingKit",
);
INFOPLIST_FILE = "ExampleAppMac/ExampleAppMac-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.8;
MACOSX_DEPLOYMENT_TARGET = "";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
+3 -2
View File
@@ -40,10 +40,11 @@
[[self.window contentView] addSubview:playFromHTTPButton];
[[self.window contentView] addSubview:meter];
audioPlayer = [[STKAudioPlayer alloc] init];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = YES, .equalizerBandFrequencies = {0, 50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
audioPlayer.delegate = self;
audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 0.1;
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
}
+1 -1
View File
@@ -16,7 +16,7 @@ The primary motivation of this project was to decouple the input data sources fr
* Optimised for low CPU/battery usage (0% - 1% CPU usage when streaming).
* Optimised for linear data sources. Random access sources are required only for seeking.
* StreamingKit 0.2.0 uses the AudioUnit API rather than the slower AudioQueues API which allows real-time interception of the raw PCM data for features such as level metering, EQ, etc.
* Example apps iOS and Mac OSX provided.
* Example apps for iOS and Mac OSX provided.
## Installation
@@ -565,7 +565,7 @@
"DEBUG=1",
"$(inherited)",
);
MACOSX_DEPLOYMENT_TARGET = 10.8;
MACOSX_DEPLOYMENT_TARGET = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
};
@@ -582,7 +582,7 @@
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch";
MACOSX_DEPLOYMENT_TARGET = 10.8;
MACOSX_DEPLOYMENT_TARGET = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
};
+21 -3
View File
@@ -78,10 +78,22 @@ typedef enum
}
STKAudioPlayerErrorCode;
typedef enum
typedef struct
{
STKAudioPlayerOptionNone = 0,
STKAudioPlayerOptionFlushQueueOnSeek = 1
/// If YES then seeking a track will cause all pending items to be flushed from the queue
BOOL flushQueueOnSeek;
/// If YES then volume control will be enabled on iOS
BOOL enableVolumeMixer;
/// A pointer to a 0 terminated array of band frequencies (iOS 5.0 and later, OSX 10.9 and later)
Float32 equalizerBandFrequencies[24];
/// The size of the internal I/O read buffer. This data in this buffer is transient and does not need to be larger.
UInt32 readBufferSize;
/// The size of the decompressed buffer (Default is 10 seconds which uses about 1.7MB of RAM)
UInt32 bufferSizeInSeconds;
/// Number of seconds of decompressed audio is required before playback first starts for each item (Default is 0.5 seconds. Must be larger than bufferSizeInSeconds)
Float32 secondsRequiredToStartPlaying;
/// Number of seconds of decompressed audio required before playback resumes after a buffer underrun (Default is 5 seconds. Must be larger than bufferSizeinSeconds)
Float32 secondsRequiredToStartPlayingAfterBufferUnderun;
}
STKAudioPlayerOptions;
@@ -117,6 +129,9 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
@interface STKAudioPlayer : NSObject<STKDataSourceDelegate>
/// Gets or sets the volume (ranges 0 - 1.0).
/// On iOS the STKAudioPlayerOptionEnableMultichannelMixer option must be enabled for volume to work.
@property (readwrite) Float32 volume;
/// Gets or sets the player muted state
@property (readwrite) BOOL muted;
/// Gets the current item duration in seconds
@@ -239,4 +254,7 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
/// Return values are between -60 (low) and 0 (high).
-(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber;
/// Sets the gain value (from -96 low to +24 high) for an equalizer band (0 based index)
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex;
@end
+453 -133
View File
@@ -52,13 +52,51 @@
#define STK_LOWPASSFILTERTIMESLICE (0.0005)
#define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (10)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (0.1)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (1)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN (7.5)
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (2048)
#define STK_DEFAULT_READ_BUFFER_SIZE (64 * 1024)
#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048)
#define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]];
static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
{
if (options->bufferSizeInSeconds == 0)
{
options->bufferSizeInSeconds = STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS;
}
if (options->readBufferSize == 0)
{
options->readBufferSize = STK_DEFAULT_READ_BUFFER_SIZE;
}
if (options->secondsRequiredToStartPlaying == 0)
{
options->secondsRequiredToStartPlaying = MIN(STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING, options->bufferSizeInSeconds);
}
if (options->secondsRequiredToStartPlayingAfterBufferUnderun == 0)
{
options->secondsRequiredToStartPlayingAfterBufferUnderun = MIN(STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN, options->bufferSizeInSeconds);
}
}
#define CHECK_STATUS_AND_RETURN(call) \
if ((status = (call))) \
{ \
[self unexpectedError:STKAudioPlayerErrorAudioSystemError]; \
return;\
}
#define CHECK_STATUS_AND_RETURN_VALUE(call, value) \
if ((status = (call))) \
{ \
[self unexpectedError:STKAudioPlayerErrorAudioSystemError]; \
return value;\
}
typedef enum
{
STKAudioPlayerInternalStateInitialised = 0,
@@ -112,6 +150,12 @@ STKAudioPlayerInternalState;
#pragma mark STKAudioPlayer
static AudioComponentDescription mixerDescription;
static AudioComponentDescription nbandUnitDescription;
static AudioComponentDescription outputUnitDescription;
static AudioComponentDescription convertUnitDescription;
static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
@interface STKAudioPlayer()
{
BOOL muted;
@@ -120,12 +164,28 @@ STKAudioPlayerInternalState;
int readBufferSize;
STKAudioPlayerInternalState internalState;
Float32 volume;
Float32 peakPowerDb[2];
Float32 averagePowerDb[2];
BOOL meteringEnabled;
STKAudioPlayerOptions options;
AudioComponentInstance audioUnit;
AUGraph audioGraph;
AUNode eqNode;
AUNode mixerNode;
AUNode outputNode;
AUNode eqInputNode;
AUNode eqOutputNode;
AUNode mixerInputNode;
AUNode mixerOutputNode;
AudioComponentInstance eqUnit;
AudioComponentInstance mixerUnit;
AudioComponentInstance outputUnit;
UInt32 eqBandCount;
UInt32 framesRequiredToStartPlaying;
UInt32 framesRequiredToPlayAfterRebuffering;
@@ -137,6 +197,7 @@ STKAudioPlayerInternalState;
NSMutableArray* bufferingQueue;
OSSpinLock pcmBufferSpinLock;
OSSpinLock internalStateLock;
volatile UInt32 pcmBufferTotalFrameCount;
volatile UInt32 pcmBufferFrameStartIndex;
volatile UInt32 pcmBufferUsedFrameCount;
@@ -146,7 +207,6 @@ STKAudioPlayerInternalState;
AudioBufferList pcmAudioBufferList;
AudioConverterRef audioConverterRef;
AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription audioConverterAudioStreamBasicDescription;
BOOL discontinuous;
@@ -200,6 +260,60 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
@implementation STKAudioPlayer
+(void) initialize
{
convertUnitDescription = (AudioComponentDescription)
{
.componentManufacturer = kAudioUnitManufacturer_Apple,
.componentType = kAudioUnitType_FormatConverter,
.componentSubType = kAudioUnitSubType_AUConverter,
.componentFlags = 0,
.componentFlagsMask = 0
};
const int bytesPerSample = sizeof(AudioSampleType);
canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription)
{
.mSampleRate = 44100.00,
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked,
.mFramesPerPacket = 1,
.mChannelsPerFrame = 2,
.mBytesPerFrame = bytesPerSample * 2 /*channelsPerFrame*/,
.mBitsPerChannel = 8 * bytesPerSample,
.mBytesPerPacket = (bytesPerSample * 2)
};
outputUnitDescription = (AudioComponentDescription)
{
.componentType = kAudioUnitType_Output,
#if TARGET_OS_IPHONE
.componentSubType = kAudioUnitSubType_RemoteIO,
#else
.componentSubType = kAudioUnitSubType_DefaultOutput,
#endif
.componentFlags = 0,
.componentFlagsMask = 0,
.componentManufacturer = kAudioUnitManufacturer_Apple
};
mixerDescription = (AudioComponentDescription)
{
.componentType = kAudioUnitType_Mixer,
.componentSubType = kAudioUnitSubType_MultiChannelMixer,
.componentFlags = 0,
.componentFlagsMask = 0,
.componentManufacturer = kAudioUnitManufacturer_Apple
};
nbandUnitDescription = (AudioComponentDescription)
{
.componentType = kAudioUnitType_Effect,
.componentSubType = kAudioUnitSubType_NBandEQ,
.componentManufacturer=kAudioUnitManufacturer_Apple
};
}
-(STKAudioPlayerOptions) options
{
return options;
@@ -212,16 +326,14 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(void) setInternalState:(STKAudioPlayerInternalState)value
{
if (value == internalState)
{
return;
}
internalState = value;
[self setInternalState:value ifInState:NULL];
}
-(void) setInternalState:(STKAudioPlayerInternalState)value ifInState:(BOOL(^)(STKAudioPlayerInternalState))ifInState
{
STKAudioPlayerState newState;
switch (internalState)
switch (value)
{
case STKAudioPlayerInternalStateInitialised:
newState = STKAudioPlayerStateReady;
@@ -257,17 +369,44 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
break;
}
OSSpinLockLock(&internalStateLock);
if (value == internalState)
{
OSSpinLockUnlock(&internalStateLock);
return;
}
if (ifInState != NULL)
{
if (!ifInState(self->internalState))
{
OSSpinLockUnlock(&internalStateLock);
return;
}
}
internalState = value;
STKAudioPlayerState previousState = self.state;
if (newState != previousState)
{
self.state = newState;
OSSpinLockUnlock(&internalStateLock);
dispatch_async(dispatch_get_main_queue(), ^
{
[self.delegate audioPlayer:self stateChanged:self.state previousState:previousState];
});
}
else
{
OSSpinLockUnlock(&internalStateLock);
}
}
-(STKAudioPlayerStopReason) stopReason
@@ -295,45 +434,33 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(id) init
{
return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:STKAudioPlayerOptionNone];
return [self initWithOptions:(STKAudioPlayerOptions){}];
}
-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn
{
return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:optionsIn];
}
-(id) initWithReadBufferSize:(int)readBufferSizeIn andOptions:(STKAudioPlayerOptions)optionsIn
{
if (self = [super init])
{
options = optionsIn;
const int bytesPerSample = sizeof(AudioSampleType);
canonicalAudioStreamBasicDescription.mSampleRate = 44100.00;
canonicalAudioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM;
canonicalAudioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
canonicalAudioStreamBasicDescription.mFramesPerPacket = 1;
canonicalAudioStreamBasicDescription.mChannelsPerFrame = 2;
canonicalAudioStreamBasicDescription.mBytesPerFrame = bytesPerSample * canonicalAudioStreamBasicDescription.mChannelsPerFrame;
canonicalAudioStreamBasicDescription.mBitsPerChannel = 8 * bytesPerSample;
canonicalAudioStreamBasicDescription.mBytesPerPacket = canonicalAudioStreamBasicDescription.mBytesPerFrame * canonicalAudioStreamBasicDescription.mFramesPerPacket;
self->volume = 1.0;
PopulateOptionsWithDefault(&options);
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS;
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
pcmAudioBuffer = &pcmAudioBufferList.mBuffers[0];
pcmAudioBufferList.mNumberBuffers = 1;
pcmAudioBufferList.mBuffers[0].mDataByteSize = (canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS) * canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmAudioBufferList.mBuffers[0].mDataByteSize = (canonicalAudioStreamBasicDescription.mSampleRate * options.bufferSizeInSeconds) * canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmAudioBufferList.mBuffers[0].mData = (void*)calloc(pcmAudioBuffer->mDataByteSize, 1);
pcmAudioBufferList.mBuffers[0].mNumberChannels = 2;
pcmBufferFrameSizeInBytes = canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmBufferTotalFrameCount = pcmAudioBuffer->mDataByteSize / pcmBufferFrameSizeInBytes;
readBufferSize = readBufferSizeIn;
readBufferSize = options.readBufferSize;
readBuffer = calloc(sizeof(UInt8), readBufferSize);
pthread_mutexattr_t attr;
@@ -355,7 +482,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
bufferingQueue = [[NSMutableArray alloc] init];
[self resetPcmBuffers];
[self createAudioUnit];
[self createAudioGraph];
[self createPlaybackThread];
}
@@ -388,11 +515,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
AudioConverterDispose(audioConverterRef);
}
if (audioUnit)
if (outputUnit)
{
AudioComponentInstanceDispose(audioUnit);
AudioComponentInstanceDispose(outputUnit);
}
AUGraphClose(audioGraph);
pthread_mutex_destroy(&playerMutex);
pthread_mutex_destroy(&mainThreadSyncCallMutex);
pthread_cond_destroy(&playerThreadReadyCondition);
@@ -732,7 +861,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(Float64) currentTimeInFrames
{
if (audioUnit == nil)
if (audioGraph == nil)
{
return 0;
}
@@ -932,7 +1061,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
[self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:entry];
[self startAudioUnit];
[self startAudioGraph];
}
else
{
@@ -1111,7 +1241,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[currentlyReadingEntry.dataSource unregisterForEvents];
}
if (self->options & STKAudioPlayerOptionFlushQueueOnSeek)
if (self->options.flushQueueOnSeek)
{
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
[self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES clearQueue:YES];
@@ -1286,7 +1416,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
if (audioUnit)
if (audioGraph)
{
[self resetPcmBuffers];
}
@@ -1430,9 +1560,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.stateBeforePaused = self.internalState;
self.internalState = STKAudioPlayerInternalStatePaused;
if (audioUnit)
if (audioGraph)
{
error = AudioOutputUnitStop(audioUnit);
error = AUGraphStop(audioGraph);
if (error)
{
@@ -1465,9 +1595,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[self resetPcmBuffers];
}
if (audioUnit != nil)
if (audioGraph != nil)
{
error = AudioOutputUnitStart(audioUnit);
error = AUGraphStart(audioGraph);
if (error)
{
@@ -1722,88 +1852,213 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
}
}
-(void) createAudioUnit
-(void) createOutputUnit
{
pthread_mutex_lock(&playerMutex);
OSStatus status;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
#endif
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent component = AudioComponentFindNext(NULL, &desc);
status = AudioComponentInstanceNew(component, &audioUnit);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
OSStatus status;
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &outputUnitDescription, &outputNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, outputNode, &outputUnitDescription, &outputUnit));
#if TARGET_OS_IPHONE
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)));
#endif
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = OutputRenderCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callbackStruct, sizeof(callbackStruct));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
status = AudioUnitInitialize(audioUnit);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
pthread_mutex_unlock(&playerMutex);
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription)));
}
-(BOOL) startAudioUnit
-(void) createMixerUnit
{
OSStatus status;
if (!self.options.enableVolumeMixer)
{
return;
}
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &mixerDescription, &mixerNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, mixerNode, &mixerDescription, &mixerUnit));
UInt32 busCount = 1;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount)));
Float64 graphSampleRate = 44100.0;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &graphSampleRate, sizeof(graphSampleRate)));
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, 0, 1.0, 0));
}
-(void) createEqUnit
{
#if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
return;
#else
OSStatus status;
if (self->options.equalizerBandFrequencies[0] == 0)
{
return;
}
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &nbandUnitDescription, &eqNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, eqNode, NULL, &eqUnit));
while (self->options.equalizerBandFrequencies[eqBandCount] != 0)
{
eqBandCount++;
}
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(eqUnit, kAUNBandEQProperty_NumberOfBands, kAudioUnitScope_Global, 0, &eqBandCount, sizeof(eqBandCount)));
for (int i = 0; i < eqBandCount; i++)
{
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_Frequency + i, kAudioUnitScope_Global, 0, (AudioUnitParameterValue)self->options.equalizerBandFrequencies[i], 0));
}
for (int i = 0; i < eqBandCount; i++)
{
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_BypassBand + i, kAudioUnitScope_Global, 0, (AudioUnitParameterValue)0, 0));
}
#endif
}
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex
{
if (!eqUnit)
{
return;
}
OSStatus status;
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_Gain + bandIndex, kAudioUnitScope_Global, 0, gain, 0));
}
-(AUNode) createConverterNode:(AudioStreamBasicDescription)srcFormat desFormat:(AudioStreamBasicDescription)desFormat
{
OSStatus status;
AUNode convertNode;
AudioComponentInstance convertUnit;
CHECK_STATUS_AND_RETURN_VALUE(AUGraphAddNode(audioGraph, &convertUnitDescription, &convertNode), 0);
CHECK_STATUS_AND_RETURN_VALUE(status = AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat)), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &desFormat, sizeof(desFormat)), 0);
return convertNode;
}
-(void) connectNodes:(AUNode)srcNode desNode:(AUNode)desNode srcUnit:(AudioComponentInstance)srcUnit desUnit:(AudioComponentInstance)desUnit
{
OSStatus status;
AudioStreamBasicDescription srcFormat, desFormat;
UInt32 size = sizeof(AudioStreamBasicDescription);
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(srcUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &srcFormat, &size));
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desFormat, &size));
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
if (status)
{
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, mixerNode, 0));
}
else
{
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, desNode, 0));
}
}
-(void) setOutputCallbackForFirstNode:(AUNode)firstNode firstUnit:(AudioComponentInstance)firstUnit
{
OSStatus status;
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = OutputRenderCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(firstUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription));
if (status)
{
AudioStreamBasicDescription format;
UInt32 size = sizeof(format);
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(firstUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, &size));
AUNode converterNode = [self createConverterNode:canonicalAudioStreamBasicDescription desFormat:format];
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct));
status = AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0);
}
else
{
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, firstNode, 0, &callbackStruct));
}
}
-(void) createAudioGraph
{
OSStatus status;
NSMutableArray* nodes = [[NSMutableArray alloc] init];
NSMutableArray* units = [[NSMutableArray alloc] init];
CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph));
CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph));
[self createEqUnit];
[self createMixerUnit];
[self createOutputUnit];
if (eqNode)
{
[nodes addObject:@(eqNode)];
[units addObject:[NSValue valueWithPointer:eqUnit]];
}
if (mixerNode)
{
[nodes addObject:@(mixerNode)];
[units addObject:[NSValue valueWithPointer:mixerUnit]];
}
if (outputNode)
{
[nodes addObject:@(outputNode)];
[units addObject:[NSValue valueWithPointer:outputUnit]];
}
[self setOutputCallbackForFirstNode:(AUNode)[[nodes objectAtIndex:0] intValue] firstUnit:(AudioComponentInstance)[[units objectAtIndex:0] pointerValue]];
for (int i = 0; i < nodes.count - 1; i++)
{
AUNode srcNode = [[nodes objectAtIndex:i] intValue];
AUNode desNode = [[nodes objectAtIndex:i + 1] intValue];
AudioComponentInstance srcUnit = (AudioComponentInstance)[[units objectAtIndex:i] pointerValue];
AudioComponentInstance desUnit = (AudioComponentInstance)[[units objectAtIndex:i + 1] pointerValue];
[self connectNodes:srcNode desNode:desNode srcUnit:srcUnit desUnit:desUnit];
}
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
self.volume = self->volume;
}
-(BOOL) startAudioGraph
{
OSStatus status;
[self resetPcmBuffers];
status = AudioOutputUnitStart(audioUnit);
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
@@ -1812,6 +2067,20 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return NO;
}
if (isRunning)
{
return NO;
}
status = AUGraphStart(audioGraph);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return NO;
}
return YES;
}
@@ -1819,7 +2088,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
{
OSStatus status;
if (!audioUnit)
if (!audioGraph)
{
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
@@ -1827,17 +2096,28 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return;
}
status = AudioOutputUnitStop(audioUnit);
[self resetPcmBuffers];
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
}
else if (!isRunning)
{
return;
}
status = AUGraphStop(audioGraph);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
}
[self resetPcmBuffers];
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
}
@@ -2126,19 +2406,11 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
UInt32 frameSizeInBytes = audioPlayer->pcmBufferFrameSizeInBytes;
UInt32 used = audioPlayer->pcmBufferUsedFrameCount;
UInt32 start = audioPlayer->pcmBufferFrameStartIndex;
STKAudioPlayerInternalState state = audioPlayer->internalState;
UInt32 end = (audioPlayer->pcmBufferFrameStartIndex + audioPlayer->pcmBufferUsedFrameCount) % audioPlayer->pcmBufferTotalFrameCount;
BOOL signal = audioPlayer->waiting && used < audioPlayer->pcmBufferTotalFrameCount / 2;
NSArray* frameFilters = audioPlayer->frameFilters;
STKAudioPlayerInternalState state = audioPlayer.internalState;
if (state == STKAudioPlayerInternalStatePendingNext)
{
OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock);
return 0;
}
if (entry)
{
if (state == STKAudioPlayerInternalStateWaitingForData)
@@ -2158,9 +2430,23 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
}
else if (state == STKAudioPlayerInternalStateRebuffering)
{
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
if (audioPlayer->currentlyPlayingEntry->lastFrameQueued >= 0)
if (entry->lastFrameQueued >= 0)
{
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued - entry->framesQueued);
}
if (used < framesRequiredToStartPlaying)
{
waitForBuffer = YES;
}
}
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
{
int64_t framesRequiredToStartPlaying = inNumberFrames;
if (entry->lastFrameQueued >= 0)
{
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued - entry->framesQueued);
}
@@ -2176,7 +2462,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
UInt32 totalFramesCopied = 0;
if (used > 0 && !waitForBuffer && entry != nil)
if (used > 0 && !waitForBuffer && entry != nil && ((state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused))
{
if (state == STKAudioPlayerInternalStateWaitingForData)
{
@@ -2254,7 +2540,10 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock);
}
audioPlayer.internalState = STKAudioPlayerInternalStatePlaying;
[audioPlayer setInternalState:STKAudioPlayerInternalStatePlaying ifInState:^BOOL(STKAudioPlayerInternalState state)
{
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
if (totalFramesCopied < inNumberFrames)
@@ -2267,14 +2556,17 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{
// Buffering
audioPlayer.internalState = STKAudioPlayerInternalStateRebuffering;
[audioPlayer setInternalState:STKAudioPlayerInternalStateRebuffering ifInState:^BOOL(STKAudioPlayerInternalState state)
{
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
}
if (frameFilters)
{
NSUInteger count = frameFilters.count;
AudioStreamBasicDescription asbd = audioPlayer->canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription asbd = canonicalAudioStreamBasicDescription;
for (int i = 0; i < count; i++)
{
@@ -2636,4 +2928,32 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
pthread_mutex_unlock(&self->playerMutex);
}
#pragma mark Volume
-(void) setVolume:(Float32)value;
{
self->volume = value;
#if (TARGET_OS_IPHONE)
if (self->mixerNode)
{
AudioUnitSetParameter(self->mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, self->volume, 0);
}
#else
if (self->mixerNode)
{
AudioUnitSetParameter(self->mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, self->volume, 0);
}
else
{
AudioUnitSetParameter(outputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, kOutputBus, self->volume, 0);
}
#endif
}
-(Float32) volume
{
return self->volume;
}
@end