diff --git a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj index 7a1605b..044fdfc 100644 --- a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj +++ b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj @@ -418,6 +418,7 @@ ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch"; INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist"; diff --git a/ExampleApp/ExampleApp/AppDelegate.m b/ExampleApp/ExampleApp/AppDelegate.m index f338498..77a518f 100644 --- a/ExampleApp/ExampleApp/AppDelegate.m +++ b/ExampleApp/ExampleApp/AppDelegate.m @@ -27,19 +27,21 @@ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; [[AVAudioSession sharedInstance] setActive:YES error:&error]; - + + Float32 bufferLength = 0.1; + AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength); + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; - audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = YES, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }]; + audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }]; audioPlayer.meteringEnabled = YES; audioPlayer.volume = 1; - AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds]; + AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer]; audioPlayerView.delegate = self; - audioPlayerView.audioPlayer = audioPlayer; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; [self becomeFirstResponder]; diff --git a/ExampleApp/ExampleApp/AudioPlayerView.h b/ExampleApp/ExampleApp/AudioPlayerView.h index 92d06cb..b91952e 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.h +++ b/ExampleApp/ExampleApp/AudioPlayerView.h @@ -51,6 +51,7 @@ UILabel* label; UILabel* statusLabel; UISlider* slider; + UISwitch* enableEqSwitch; UISwitch* repeatSwitch; UIButton* muteButton; UIButton* playButton; @@ -65,4 +66,6 @@ @property (readwrite, retain) STKAudioPlayer* audioPlayer; @property (readwrite, unsafe_unretained) id delegate; +- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayer; + @end diff --git a/ExampleApp/ExampleApp/AudioPlayerView.m b/ExampleApp/ExampleApp/AudioPlayerView.m index 61dcf8f..3e65b6c 100644 --- a/ExampleApp/ExampleApp/AudioPlayerView.m +++ b/ExampleApp/ExampleApp/AudioPlayerView.m @@ -47,12 +47,14 @@ @implementation AudioPlayerView @synthesize audioPlayer, delegate; -- (id)initWithFrame:(CGRect)frame +- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayerIn { self = [super initWithFrame:frame]; if (self) { + self.audioPlayer = audioPlayerIn; + CGSize size = CGSizeMake(220, 50); playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; @@ -97,7 +99,12 @@ size = CGSizeMake(80, 50); - repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 180, size.width, size.height)]; + repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(30, frame.size.height * 0.15 + 180, size.width, size.height)]; + + enableEqSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(320 - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)]; + enableEqSwitch.on = audioPlayer.equalizerEnabled; + + [enableEqSwitch addTarget:self action:@selector(onEnableEqSwitch) forControlEvents:UIControlEventAllTouchEvents]; label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 10, frame.size.width, 25)]; @@ -123,6 +130,7 @@ [self addSubview:stopButton]; [self addSubview:meter]; [self addSubview:muteButton]; + [self addSubview:enableEqSwitch]; [self setupTimer]; [self updateControls]; @@ -131,6 +139,11 @@ return self; } +-(void) onEnableEqSwitch +{ + audioPlayer.equalizerEnabled = self->enableEqSwitch.on; +} + -(void) sliderChanged { if (!audioPlayer) diff --git a/ExampleAppMac/ExampleAppMac/AppDelegate.m b/ExampleAppMac/ExampleAppMac/AppDelegate.m index 27672d0..c18b120 100644 --- a/ExampleAppMac/ExampleAppMac/AppDelegate.m +++ b/ExampleAppMac/ExampleAppMac/AppDelegate.m @@ -40,14 +40,22 @@ [[self.window contentView] addSubview:playFromHTTPButton]; [[self.window contentView] addSubview:meter]; - audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = NO, .equalizerBandFrequencies = {0, 50, 100, 200, 400, 800, 1600, 2600, 16000} } ]; + audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} } ]; audioPlayer.delegate = self; audioPlayer.meteringEnabled = YES; audioPlayer.volume = 0.1; + [self performSelector:@selector(test) withObject:nil afterDelay:4]; + [self performSelector:@selector(test) withObject:nil afterDelay:8]; + [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES]; } +-(void) test +{ + audioPlayer.equalizerEnabled = !audioPlayer.equalizerEnabled; +} + -(void) playFromHTTP { [audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.h b/StreamingKit/StreamingKit/STKAudioPlayer.h index 8c58712..c926070 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.h +++ b/StreamingKit/StreamingKit/STKAudioPlayer.h @@ -140,6 +140,8 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn @property (readonly) double progress; /// Enables or disables peak and average decibel meteting @property (readwrite) BOOL meteringEnabled; +/// Enables or disables the EQ +@property (readwrite) BOOL equalizerEnabled; /// Returns an array of STKFrameFilterEntry objects representing the filters currently in use @property (readonly) NSArray* frameFilters; /// Returns the items pending to be played (includes buffering and upcoming items but does not include the current item) diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.m b/StreamingKit/StreamingKit/STKAudioPlayer.m index 1dc759e..8c6d66f 100644 --- a/StreamingKit/StreamingKit/STKAudioPlayer.m +++ b/StreamingKit/StreamingKit/STKAudioPlayer.m @@ -171,8 +171,12 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription; Float32 averagePowerDb[2]; BOOL meteringEnabled; + BOOL equalizerOn; + BOOL equalizerEnabled; STKAudioPlayerOptions options; + NSMutableArray* converterNodes; + AUGraph audioGraph; AUNode eqNode; AUNode mixerNode; @@ -446,6 +450,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn options = optionsIn; self->volume = 1.0; + self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0; PopulateOptionsWithDefault(&options); @@ -522,7 +527,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn AudioComponentInstanceDispose(outputUnit); } - AUGraphClose(audioGraph); + if (audioGraph) + { + AUGraphUninitialize(audioGraph); + AUGraphClose(audioGraph); + } pthread_mutex_destroy(&playerMutex); pthread_mutex_destroy(&mainThreadSyncCallMutex); @@ -1955,6 +1964,8 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl 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); CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)), 0); + + [converterNodes addObject:@(convertNode)]; return convertNode; } @@ -1962,24 +1973,33 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl -(void) connectNodes:(AUNode)srcNode desNode:(AUNode)desNode srcUnit:(AudioComponentInstance)srcUnit desUnit:(AudioComponentInstance)desUnit { OSStatus status; + BOOL addConverter = NO; 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)); + addConverter = memcmp(&srcFormat, &desFormat, sizeof(srcFormat)) != 0; - if (status) + if (addConverter) { - AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat]; - - CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, mixerNode, 0)); + status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat)); + + addConverter = status != 0; } - else - { + + if (addConverter) + { + AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat]; + + CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, convertNode, 0)); + CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, desNode, 0)); + } + else + { CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, desNode, 0)); - } + } } -(void) setOutputCallbackForFirstNode:(AUNode)firstNode firstUnit:(AudioComponentInstance)firstUnit @@ -2014,8 +2034,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl -(void) createAudioGraph { OSStatus status; - NSMutableArray* nodes = [[NSMutableArray alloc] init]; - NSMutableArray* units = [[NSMutableArray alloc] init]; + converterNodes = [[NSMutableArray alloc] init]; CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph)); CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph)); @@ -2024,11 +2043,46 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl [self createMixerUnit]; [self createOutputUnit]; + [self connectGraph]; + + CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph)); + + self.volume = self->volume; +} + +-(void) connectGraph +{ + OSStatus status; + NSMutableArray* nodes = [[NSMutableArray alloc] init]; + NSMutableArray* units = [[NSMutableArray alloc] init]; + + AUGraphClearConnections(audioGraph); + + for (NSNumber* converterNode in converterNodes) + { + status = AUGraphRemoveNode(audioGraph, (AUNode)converterNode.intValue); + } + + [converterNodes removeAllObjects]; + if (eqNode) { - [nodes addObject:@(eqNode)]; - [units addObject:[NSValue valueWithPointer:eqUnit]]; + if (self->equalizerEnabled) + { + [nodes addObject:@(eqNode)]; + [units addObject:[NSValue valueWithPointer:eqUnit]]; + + self->equalizerOn = YES; + } + else + { + self->equalizerOn = NO; + } } + else + { + self->equalizerOn = NO; + } if (mixerNode) { @@ -2041,7 +2095,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl [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++) @@ -2053,10 +2107,6 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl [self connectNodes:srcNode desNode:desNode srcUnit:srcUnit desUnit:desUnit]; } - - CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph)); - - self.volume = self->volume; } -(BOOL) startAudioGraph @@ -2585,6 +2635,17 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* entry->filter(asbd.mChannelsPerFrame, asbd.mBytesPerFrame, inNumberFrames, ioData->mBuffers[0].mData); } } + + if (audioPlayer->equalizerEnabled != audioPlayer->equalizerOn) + { + Boolean isUpdated; + + [audioPlayer connectGraph]; + + AUGraphUpdate(audioPlayer->audioGraph, &isUpdated); + + isUpdated = isUpdated; + } if (entry == nil) { @@ -2966,4 +3027,15 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* return self->volume; } +-(BOOL) equalizerEnabled +{ + return self->equalizerEnabled; +} + +-(void) setEqualizerEnabled:(BOOL)value +{ + self->equalizerEnabled = value; +} + + @end