Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60d48a0682 | |||
| dee6322751 | |||
| 5e3048a7bf | |||
| ac7aabf746 | |||
| ce30f0de57 | |||
| a58269fcdb | |||
| 3ecba8c8b4 | |||
| c98b673064 | |||
| 13fc64baa2 | |||
| 5e4b500785 | |||
| 693c5ed059 | |||
| a15405b6c5 | |||
| ce6f2b9512 | |||
| 63bb19747f | |||
| 0a6e1d4534 |
@@ -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";
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user