Compare commits

...

15 Commits

Author SHA1 Message Date
Thong Nguyen 60d48a0682 Added the ability to dynamically remove or add EQ unit to save battery when EQ unit is not needed 2014-02-11 19:24:41 +00:00
Thong Nguyen dee6322751 Set default kAudioUnitProperty_MaximumFramesPerSlice on all audio units to 4096 to fix playback problems on lockscreen 2014-02-10 23:14:32 +00:00
Thong Nguyen 5e3048a7bf Merge branch 'nbandeq' 2014-02-10 14:15:37 +00:00
Thong Nguyen ac7aabf746 Removed EQ support for OSX < 10.9 2014-02-10 14:14:59 +00:00
Thong Nguyen ce30f0de57 New AudioGraph creation code mostly done. EQ working on iOS and OSX 2014-02-10 13:25:17 +00:00
Thong Nguyen a58269fcdb Got EQ working (iOS5 and OSX 10.9). Changed OSX projects to use 10.9 SDK 2014-02-09 23:32:49 +00:00
Thong Nguyen 3ecba8c8b4 Some more EQ work 2014-02-07 18:30:54 +00:00
Thong Nguyen c98b673064 Refactoring how the graph and nodes are created 2014-02-07 09:43:47 +00:00
Thong Nguyen 13fc64baa2 Merge pull request #77 from AndrewKosovich/master
Fixed crash in the STKHTTPDataSource.
2014-02-06 17:20:17 +00:00
Andrew Kosovich 5e4b500785 Fixed crash in the STKHTTPDataSource. 2014-02-06 18:33:03 +02:00
Thong Nguyen 693c5ed059 Started adding EQ 2014-02-05 20:22:29 +00:00
Thong Nguyen a15405b6c5 Fixed audio thread may set the state to playing even in certain conditions even if the player has been user-paused or stopped (race condition) 2014-02-05 19:05:47 +00:00
Thong Nguyen ce6f2b9512 Fixed audio thread may set the state to playing even in certain conditions even if the player has been user-paused or stopped (race condition) 2014-02-05 18:05:47 +00:00
Thong Nguyen 63bb19747f Changed STKAudioPlayerOptions to be a struct rather than enum. Most values that can be tweaked can be provided in constructor. Fixed deadlock if createAudioGraph fails 2014-02-05 14:18:37 +00:00
Thong Nguyen 0a6e1d4534 Added volume support via volume property using mixer on iOS and mixer or standard output unit volume property on OSX 2014-02-04 16:46:30 +00:00
11 changed files with 613 additions and 156 deletions
@@ -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";
+8 -5
View File
@@ -21,24 +21,27 @@
@implementation AppDelegate
-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSError* error;
[[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:STKAudioPlayerOptionFlushQueueOnSeek];
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];
+3
View File
@@ -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<AudioPlayerViewDelegate> delegate;
- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayer;
@end
+16 -4
View File
@@ -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)];
@@ -107,11 +114,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];
@@ -124,6 +130,7 @@
[self addSubview:stopButton];
[self addSubview:meter];
[self addSubview:muteButton];
[self addSubview:enableEqSwitch];
[self setupTimer];
[self updateControls];
@@ -132,6 +139,11 @@
return self;
}
-(void) onEnableEqSwitch
{
audioPlayer.equalizerEnabled = self->enableEqSwitch.on;
}
-(void) sliderChanged
{
if (!audioPlayer)
@@ -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;
+11 -2
View File
@@ -40,13 +40,22 @@
[[self.window contentView] addSubview:playFromHTTPButton];
[[self.window contentView] addSubview:meter];
audioPlayer = [[STKAudioPlayer alloc] init];
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"];
+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;
};
+23 -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
@@ -125,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)
@@ -239,4 +256,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
+534 -132
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,14 @@ STKAudioPlayerInternalState;
#pragma mark STKAudioPlayer
static UInt32 maxFramesPerSlice = 4096;
static AudioComponentDescription mixerDescription;
static AudioComponentDescription nbandUnitDescription;
static AudioComponentDescription outputUnitDescription;
static AudioComponentDescription convertUnitDescription;
static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
@interface STKAudioPlayer()
{
BOOL muted;
@@ -120,12 +166,32 @@ STKAudioPlayerInternalState;
int readBufferSize;
STKAudioPlayerInternalState internalState;
Float32 volume;
Float32 peakPowerDb[2];
Float32 averagePowerDb[2];
BOOL meteringEnabled;
BOOL equalizerOn;
BOOL equalizerEnabled;
STKAudioPlayerOptions options;
AudioComponentInstance audioUnit;
NSMutableArray* converterNodes;
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 +203,7 @@ STKAudioPlayerInternalState;
NSMutableArray* bufferingQueue;
OSSpinLock pcmBufferSpinLock;
OSSpinLock internalStateLock;
volatile UInt32 pcmBufferTotalFrameCount;
volatile UInt32 pcmBufferFrameStartIndex;
volatile UInt32 pcmBufferUsedFrameCount;
@@ -146,7 +213,6 @@ STKAudioPlayerInternalState;
AudioBufferList pcmAudioBufferList;
AudioConverterRef audioConverterRef;
AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription audioConverterAudioStreamBasicDescription;
BOOL discontinuous;
@@ -200,6 +266,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 +332,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 +375,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 +440,34 @@ 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;
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 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 +489,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
bufferingQueue = [[NSMutableArray alloc] init];
[self resetPcmBuffers];
[self createAudioUnit];
[self createAudioGraph];
[self createPlaybackThread];
}
@@ -388,11 +522,17 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
AudioConverterDispose(audioConverterRef);
}
if (audioUnit)
if (outputUnit)
{
AudioComponentInstanceDispose(audioUnit);
AudioComponentInstanceDispose(outputUnit);
}
if (audioGraph)
{
AUGraphUninitialize(audioGraph);
AUGraphClose(audioGraph);
}
pthread_mutex_destroy(&playerMutex);
pthread_mutex_destroy(&mainThreadSyncCallMutex);
pthread_cond_destroy(&playerThreadReadyCondition);
@@ -732,7 +872,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(Float64) currentTimeInFrames
{
if (audioUnit == nil)
if (audioGraph == nil)
{
return 0;
}
@@ -932,7 +1072,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
[self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:entry];
[self startAudioUnit];
[self startAudioGraph];
}
else
{
@@ -1111,7 +1252,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 +1427,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
if (audioUnit)
if (audioGraph)
{
[self resetPcmBuffers];
}
@@ -1430,9 +1571,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 +1606,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 +1863,262 @@ 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)));
flag = 0;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &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));
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
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));
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(eqUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
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);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)), 0);
[converterNodes addObject:@(convertNode)];
return convertNode;
}
-(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));
addConverter = memcmp(&srcFormat, &desFormat, sizeof(srcFormat)) != 0;
if (addConverter)
{
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
addConverter = status != 0;
}
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
{
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;
converterNodes = [[NSMutableArray alloc] init];
CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph));
CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph));
[self createEqUnit];
[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)
{
if (self->equalizerEnabled)
{
[nodes addObject:@(eqNode)];
[units addObject:[NSValue valueWithPointer:eqUnit]];
self->equalizerOn = YES;
}
else
{
self->equalizerOn = NO;
}
}
else
{
self->equalizerOn = NO;
}
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];
}
}
-(BOOL) startAudioGraph
{
OSStatus status;
[self resetPcmBuffers];
Boolean isRunning;
status = AudioOutputUnitStart(audioUnit);
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
@@ -1812,6 +2127,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 +2148,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
{
OSStatus status;
if (!audioUnit)
if (!audioGraph)
{
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
@@ -1827,17 +2156,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 +2466,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 +2490,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 +2522,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 +2600,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 +2616,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++)
{
@@ -2283,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)
{
@@ -2636,4 +2999,43 @@ 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;
}
-(BOOL) equalizerEnabled
{
return self->equalizerEnabled;
}
-(void) setEqualizerEnabled:(BOOL)value
{
self->equalizerEnabled = value;
}
@end
+12 -5
View File
@@ -138,15 +138,22 @@
-(void) dataAvailable
{
if (stream == NULL) {
return;
}
if (self.httpStatusCode == 0)
{
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
CFRelease(response);
if (response)
{
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
CFRelease(response);
}
if (self.httpStatusCode == 200)
{