Compare commits

...

18 Commits

Author SHA1 Message Date
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 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
Thong Nguyen d49e2849eb Removed unnecessary synthesized properties. Made STKFrameFilterEntry public readonly. Removed AudioUnit framework requirement for iOS n podspec 2014-02-03 17:23:53 +00:00
Thong Nguyen f725ed0b2c Added additional queue/play methods 2014-02-03 13:50:15 +00:00
Thong Nguyen 55a96578b4 Updated README 2014-02-03 13:37:10 +00:00
Thong Nguyen 16855e81dc Updated README 2014-02-03 13:33:40 +00:00
Thong Nguyen fed674d774 Updated README 2014-02-02 23:51:44 +00:00
Thong Nguyen c6e1b113dc Updated README 2014-02-02 15:33:15 +00:00
Thong Nguyen 43a9f8b26f Updated README 2014-02-02 15:30:34 +00:00
Thong Nguyen db5b9d92ed Updated README 2014-02-02 15:24:36 +00:00
11 changed files with 645 additions and 189 deletions
@@ -24,6 +24,8 @@
A111596C188D6C8100641365 /* sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = A111596B188D6C8100641365 /* sample.m4a */; };
A111596F188D6DB100641365 /* SampleQueueId.m in Sources */ = {isa = PBXBuildFile; fileRef = A111596E188D6DB100641365 /* SampleQueueId.m */; };
A142571D189079BE005F0129 /* airplane.aac in Resources */ = {isa = PBXBuildFile; fileRef = A142571C18907861005F0129 /* airplane.aac */; };
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */; };
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F5E491189EB3F20070B03F /* AVFoundation.framework */; };
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */; };
/* End PBXBuildFile section */
@@ -61,7 +63,10 @@
A111596D188D6DB100641365 /* SampleQueueId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleQueueId.h; sourceTree = "<group>"; };
A111596E188D6DB100641365 /* SampleQueueId.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SampleQueueId.m; sourceTree = "<group>"; };
A142571C18907861005F0129 /* airplane.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = airplane.aac; sourceTree = "<group>"; };
A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
A1F5E491189EB3F20070B03F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -69,6 +74,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */,
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */,
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */,
A1115964188D691500641365 /* libStreamingKit.a in Frameworks */,
A1115937188D686000641365 /* CoreGraphics.framework in Frameworks */,
@@ -112,6 +119,9 @@
A1115933188D686000641365 /* Frameworks */ = {
isa = PBXGroup;
children = (
A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */,
A1F5E491189EB3F20070B03F /* AVFoundation.framework */,
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */,
A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */,
A1115963188D691500641365 /* libStreamingKit.a */,
A1115934188D686000641365 /* Foundation.framework */,
@@ -412,6 +422,7 @@
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
LLVM_LTO = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -428,6 +439,7 @@
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
LLVM_LTO = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
+3 -3
View File
@@ -21,19 +21,19 @@
@implementation AppDelegate
-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSError* error;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
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 = 0.1;
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds];
+2 -3
View File
@@ -107,11 +107,10 @@
statusLabel.textAlignment = NSTextAlignmentCenter;
meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)];
meter.backgroundColor = [UIColor greenColor];
[self addSubview:slider];
[self addSubview:playButton];
[self addSubview:playFromHTTPButton];
@@ -146,7 +145,7 @@
-(void) setupTimer
{
timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(tick) userInfo:nil repeats:YES];
timer = [NSTimer timerWithTimeInterval:0.001 target:self selector:@selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
A17FFB6818A002BC00BAA7FF /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499ED189E793700E2A2E2 /* AudioUnit.framework */; };
A1A499A5189E765800E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499A4189E765800E2A2E2 /* Cocoa.framework */; };
A1A499AF189E765800E2A2E2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1A499AD189E765800E2A2E2 /* InfoPlist.strings */; };
A1A499B1189E765800E2A2E2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A499B0189E765800E2A2E2 /* main.m */; };
@@ -26,7 +27,6 @@
A1A499F9189E7A3500E2A2E2 /* libStreamingKitMac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */; };
A1A499FA189E7A5600E2A2E2 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */; };
A1A499FC189E7A6D00E2A2E2 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */; };
A1A499FD189E7BFC00E2A2E2 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A499ED189E793700E2A2E2 /* AudioUnit.framework */; };
A1A49A01189E82EC00E2A2E2 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */; };
/* End PBXBuildFile section */
@@ -41,6 +41,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
A17FFB6618A002AD00BAA7FF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
A1A499A1189E765800E2A2E2 /* ExampleAppMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleAppMac.app; sourceTree = BUILT_PRODUCTS_DIR; };
A1A499A4189E765800E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
A1A499A7189E765800E2A2E2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@@ -60,12 +61,12 @@
A1A499CA189E765800E2A2E2 /* ExampleAppMacTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleAppMacTests-Info.plist"; sourceTree = "<group>"; };
A1A499CC189E765800E2A2E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A1A499CE189E765800E2A2E2 /* ExampleAppMacTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAppMacTests.m; sourceTree = "<group>"; };
A1A499EA189E76BD00E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = file; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = "<group>"; };
A1A499EA189E76BD00E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = "<group>"; };
A1A499ED189E793700E2A2E2 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
A1A499EF189E793D00E2A2E2 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
A1A499F1189E799400E2A2E2 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
A1A499F4189E79CB00E2A2E2 /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; };
A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = file; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = "<group>"; };
A1A499F8189E7A3500E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libStreamingKitMac.a; path = ../StreamingKit/build/Debug/libStreamingKitMac.a; sourceTree = "<group>"; };
A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
A1A499FE189E82DD00E2A2E2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
@@ -76,8 +77,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A17FFB6818A002BC00BAA7FF /* AudioUnit.framework in Frameworks */,
A1A49A01189E82EC00E2A2E2 /* QuartzCore.framework in Frameworks */,
A1A499FD189E7BFC00E2A2E2 /* AudioUnit.framework in Frameworks */,
A1A499FC189E7A6D00E2A2E2 /* SystemConfiguration.framework in Frameworks */,
A1A499FA189E7A5600E2A2E2 /* AudioToolbox.framework in Frameworks */,
A1A499F9189E7A3500E2A2E2 /* libStreamingKitMac.a in Frameworks */,
@@ -124,6 +125,7 @@
A1A499A3189E765800E2A2E2 /* Frameworks */ = {
isa = PBXGroup;
children = (
A17FFB6618A002AD00BAA7FF /* AVFoundation.framework */,
A1A49A00189E82EC00E2A2E2 /* QuartzCore.framework */,
A1A499FE189E82DD00E2A2E2 /* CoreGraphics.framework */,
A1A499FB189E7A6D00E2A2E2 /* SystemConfiguration.framework */,
@@ -430,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;
@@ -450,7 +452,7 @@
"$(SRCROOT)/../StreamingKit/StreamingKit",
);
INFOPLIST_FILE = "ExampleAppMac/ExampleAppMac-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.8;
MACOSX_DEPLOYMENT_TARGET = "";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
+3 -2
View File
@@ -40,10 +40,11 @@
[[self.window contentView] addSubview:playFromHTTPButton];
[[self.window contentView] addSubview:meter];
audioPlayer = [[STKAudioPlayer alloc] init];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = YES, .equalizerBandFrequencies = {0, 50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
audioPlayer.delegate = self;
audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 0.1;
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
}
+37 -14
View File
@@ -1,41 +1,64 @@
## StreamingKit
StreamingKit (formally Audjustable) is an audio streaming library for iOS and OSX. StreamingKit uses CoreAudio to decompress and playback audio whilst providing a clean and simple object-oriented API.
StreamingKit (formally Audjustable) is an audio playback and streaming library for iOS and Mac OSX. StreamingKit uses CoreAudio to decompress and playback audio (using hardware or software codecs) whilst providing a clean and simple object-oriented API.
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP streaming, encryption/decryption, auto-recovery, dynamic-buffering. Along the way other features such as gapless playback were added.
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP streaming, encryption/decryption, auto-recovery, dynamic-buffering. StreamingKit is the only streaming and playback library that supports dead-easy [gapless playback](https://github.com/tumtumtum/StreamingKit/wiki/Gapless-playback) between audio files of differing formats.
## Main Features
* Simple OOP API
* Easy to read source
* Mostly asynchronous API
* Buffered and gapless playback
* Easy to implement audio data sources (Local, HTTP, Auto Recovering HTTP DataSources are provided)
* Free OSS.
* Simple API.
* Easy to read source.
* Carefully multi-threaded to provide a responsive API that won't block your UI thread nor starve the audio buffers.
* Buffered and gapless playback between all format types.
* Easy to implement audio data sources (Local, HTTP, AutoRecoveryingHTTP DataSources are provided).
* Easy to extend DataSource to support adaptive buffering, encryption, etc.
* Optimised for low CPU/battery usage
* 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 for iOS and Mac OSX provided.
## Installation
StreamingKit is also available as a [Cocoapod](http://cocoapods.org/?q=StreamingKit) and a static lib. You can also simply manually copy all the source files located inside StreamingKit/StreamingKit/* into your project.
StreamingKit is available as a [Cocoapod](http://cocoapods.org/?q=StreamingKit). You can also simply copy all the source files located inside StreamingKit/StreamingKit/* into your Xcode project.
## Example
There are two main classes. The `STKDataSource` class which is the abstract base class for the various compressed audio data sources. The `STKAudioPlayer` class manages and renders audio from a queue DataSources. By default `STKAudioPlayer` will automatically parse URLs and create the appropriate data source internally.
### Play an MP3 over HTTP
```objective-c
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
audioPlayer.delegate = self;
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
```
### Gapless playback
```objective-c
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/airplane.aac"];
```
### Intercept PCM data just before its played
```objective-c
[audioPlayer appendFrameFilterWithName:@"MyCustomFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames)
{
...
}];
````
## More
More documentation is available on the project [wiki](https://github.com/tumtumtum/StreamingKit/wiki)
More documentation is available on the project [Wiki](https://github.com/tumtumtum/StreamingKit/wiki/_pages)
### Authors and Contributors
Copyright (c) 2012-2014, Thong Nguyen (@tumtumtum)
Copyright (c) 2012-2014, Thong Nguyen ([@tumtumtum](http://www.twitter.com/tumtumtum))
+1 -1
View File
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.source_files = 'StreamingKit/StreamingKit/*.{h,m}'
s.ios.deployment_target = '4.3'
s.ios.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit'
s.ios.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox'
s.osx.deployment_target = '10.7'
s.osx.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit'
end
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "StreamingKit"
s.version = "0.1.18"
s.version = "0.1.19"
s.summary = "A fast and extensible audio streamer for iOS and OSX with support for gapless playback and custom (non-HTTP) sources."
s.homepage = "https://github.com/tumtumtum/StreamingKit/"
s.license = 'MIT'
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.source_files = 'StreamingKit/StreamingKit/*.{h,m}'
s.ios.deployment_target = '4.3'
s.ios.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit'
s.ios.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox'
s.osx.deployment_target = '10.7'
s.osx.frameworks = 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', 'AudioToolbox', 'AudioUnit'
end
@@ -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;
};
+56 -9
View File
@@ -78,13 +78,32 @@ 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;
typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames);
@interface STKFrameFilterEntry : NSObject
@property (readonly) NSString* name;
@property (readonly) STKFrameFilter filter;
@end
@class STKAudioPlayer;
@protocol STKAudioPlayerDelegate <NSObject>
@@ -108,10 +127,11 @@ STKAudioPlayerOptions;
@end
typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames);
@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
@@ -149,16 +169,40 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
/// Initializes a new STKAudioPlayer with the given options
-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn;
/// Plays an item from the given URL (all pending queued items are removed)
/// Plays an item from the given URL string (all pending queued items are removed).
/// The NSString is used as the queue item ID
-(void) play:(NSString*)urlString;
/// Plays an item from the given URL (all pending queued items are removed)
-(void) playWithURL:(NSURL*)url;
-(void) play:(NSString*)urlString withQueueItemID:(NSObject*)queueItemId;
/// Plays an item from the given URL (all pending queued items are removed)
/// The NSURL is used as the queue item ID
-(void) playURL:(NSURL*)url;
/// Plays an item from the given URL (all pending queued items are removed)
-(void) playURL:(NSURL*)url withQueueItemID:(NSObject*)queueItemId;
/// Plays the given item (all pending queued items are removed)
-(void) playWithDataSource:(STKDataSource*)dataSource;
/// The STKDataSource is used as the queue item ID
-(void) playDataSource:(STKDataSource*)dataSource;
/// Queues a DataSource with te given Item ID for playback
/// Plays the given item (all pending queued items are removed)
-(void) playDataSource:(STKDataSource*)dataSource withQueueItemID:(NSObject*)queueItemId;
/// Queues the URL string for playback and uses the NSString as the queueItemID
-(void) queue:(NSString*)urlString;
/// Queues the URL string for playback with the given queueItemID
-(void) queue:(NSString*)urlString withQueueItemId:(NSObject*)queueItemId;
/// Queues the URL for playback and uses the NSURL as the queueItemID
-(void) queueURL:(NSURL*)url;
/// Queues the URL for playback with the given queueItemID
-(void) queueURL:(NSURL*)url withQueueItemId:(NSObject*)queueItemId;
/// Queues a DataSource with the given queueItemId
-(void) queueDataSource:(STKDataSource*)dataSource withQueueItemId:(NSObject*)queueItemId;
/// Plays the given item (all pending queued items are removed)
@@ -210,4 +254,7 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
/// Return values are between -60 (low) and 0 (high).
-(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber;
/// Sets the gain value (from -96 low to +24 high) for an equalizer band (0 based index)
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex;
@end
+519 -147
View File
@@ -42,6 +42,8 @@
#import "libkern/OSAtomic.h"
#import <float.h>
#pragma mark Defines
#define kOutputBus 0
#define kInputBus 1
@@ -50,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,
@@ -75,10 +115,12 @@ typedef enum
}
STKAudioPlayerInternalState;
@interface STKFrameFilterEntry : NSObject
#pragma mark STKFrameFilterEntry
@interface STKFrameFilterEntry()
{
@public
const NSString* name;
NSString* name;
STKFrameFilter filter;
}
@end
@@ -94,21 +136,56 @@ STKAudioPlayerInternalState;
return self;
}
-(NSString*) name
{
return self->name;
}
-(STKFrameFilter) filter
{
return self->filter;
}
@end
#pragma mark STKAudioPlayer
static AudioComponentDescription mixerDescription;
static AudioComponentDescription nbandUnitDescription;
static AudioComponentDescription outputUnitDescription;
static AudioComponentDescription convertUnitDescription;
static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
@interface STKAudioPlayer()
{
BOOL muted;
UInt8* readBuffer;
int readBufferSize;
STKAudioPlayerInternalState internalState;
Float32 volume;
Float32 peakPowerDb[2];
Float32 averagePowerDb[2];
BOOL meteringEnabled;
STKAudioPlayerOptions options;
AudioComponentInstance audioUnit;
AUGraph audioGraph;
AUNode eqNode;
AUNode mixerNode;
AUNode outputNode;
AUNode eqInputNode;
AUNode eqOutputNode;
AUNode mixerInputNode;
AUNode mixerOutputNode;
AudioComponentInstance eqUnit;
AudioComponentInstance mixerUnit;
AudioComponentInstance outputUnit;
UInt32 eqBandCount;
UInt32 framesRequiredToStartPlaying;
UInt32 framesRequiredToPlayAfterRebuffering;
@@ -120,6 +197,7 @@ STKAudioPlayerInternalState;
NSMutableArray* bufferingQueue;
OSSpinLock pcmBufferSpinLock;
OSSpinLock internalStateLock;
volatile UInt32 pcmBufferTotalFrameCount;
volatile UInt32 pcmBufferFrameStartIndex;
volatile UInt32 pcmBufferUsedFrameCount;
@@ -129,7 +207,6 @@ STKAudioPlayerInternalState;
AudioBufferList pcmAudioBufferList;
AudioConverterRef audioConverterRef;
AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription audioConverterAudioStreamBasicDescription;
BOOL discontinuous;
@@ -182,9 +259,61 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
@implementation STKAudioPlayer
@synthesize delegate, internalState, state;
+(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;
@@ -197,16 +326,14 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(void) setInternalState:(STKAudioPlayerInternalState)value
{
if (value == internalState)
{
return;
}
internalState = value;
[self setInternalState:value ifInState:NULL];
}
-(void) setInternalState:(STKAudioPlayerInternalState)value ifInState:(BOOL(^)(STKAudioPlayerInternalState))ifInState
{
STKAudioPlayerState newState;
switch (internalState)
switch (value)
{
case STKAudioPlayerInternalStateInitialised:
newState = STKAudioPlayerStateReady;
@@ -242,17 +369,44 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
break;
}
OSSpinLockLock(&internalStateLock);
if (value == internalState)
{
OSSpinLockUnlock(&internalStateLock);
return;
}
if (ifInState != NULL)
{
if (!ifInState(self->internalState))
{
OSSpinLockUnlock(&internalStateLock);
return;
}
}
internalState = value;
STKAudioPlayerState previousState = self.state;
if (newState != previousState)
{
self.state = newState;
OSSpinLockUnlock(&internalStateLock);
dispatch_async(dispatch_get_main_queue(), ^
{
[self.delegate audioPlayer:self stateChanged:self.state previousState:previousState];
});
}
else
{
OSSpinLockUnlock(&internalStateLock);
}
}
-(STKAudioPlayerStopReason) stopReason
@@ -264,61 +418,49 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
{
if ([NSThread currentThread].isMainThread)
{
if ([self->delegate respondsToSelector:@selector(audioPlayer:logInfo:)])
if ([self.delegate respondsToSelector:@selector(audioPlayer:logInfo:)])
{
[self->delegate audioPlayer:self logInfo:line];
[self.delegate audioPlayer:self logInfo:line];
}
}
else
{
if ([self->delegate respondsToSelector:@selector(audioPlayer:logInfo:)])
if ([self.delegate respondsToSelector:@selector(audioPlayer:logInfo:)])
{
[self->delegate audioPlayer:self logInfo:line];
[self.delegate audioPlayer:self logInfo:line];
}
}
}
-(id) init
{
return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:STKAudioPlayerOptionNone];
return [self initWithOptions:(STKAudioPlayerOptions){}];
}
-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn
{
return [self initWithReadBufferSize:STK_DEFAULT_READ_BUFFER_SIZE andOptions:optionsIn];
}
-(id) initWithReadBufferSize:(int)readBufferSizeIn andOptions:(STKAudioPlayerOptions)optionsIn
{
if (self = [super init])
{
options = optionsIn;
const int bytesPerSample = sizeof(AudioSampleType);
canonicalAudioStreamBasicDescription.mSampleRate = 44100.00;
canonicalAudioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM;
canonicalAudioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
canonicalAudioStreamBasicDescription.mFramesPerPacket = 1;
canonicalAudioStreamBasicDescription.mChannelsPerFrame = 2;
canonicalAudioStreamBasicDescription.mBytesPerFrame = bytesPerSample * canonicalAudioStreamBasicDescription.mChannelsPerFrame;
canonicalAudioStreamBasicDescription.mBitsPerChannel = 8 * bytesPerSample;
canonicalAudioStreamBasicDescription.mBytesPerPacket = canonicalAudioStreamBasicDescription.mBytesPerFrame * canonicalAudioStreamBasicDescription.mFramesPerPacket;
self->volume = 1.0;
PopulateOptionsWithDefault(&options);
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS;
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
pcmAudioBuffer = &pcmAudioBufferList.mBuffers[0];
pcmAudioBufferList.mNumberBuffers = 1;
pcmAudioBufferList.mBuffers[0].mDataByteSize = (canonicalAudioStreamBasicDescription.mSampleRate * STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS) * canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmAudioBufferList.mBuffers[0].mDataByteSize = (canonicalAudioStreamBasicDescription.mSampleRate * options.bufferSizeInSeconds) * canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmAudioBufferList.mBuffers[0].mData = (void*)calloc(pcmAudioBuffer->mDataByteSize, 1);
pcmAudioBufferList.mBuffers[0].mNumberChannels = 2;
pcmBufferFrameSizeInBytes = canonicalAudioStreamBasicDescription.mBytesPerFrame;
pcmBufferTotalFrameCount = pcmAudioBuffer->mDataByteSize / pcmBufferFrameSizeInBytes;
readBufferSize = readBufferSizeIn;
readBufferSize = options.readBufferSize;
readBuffer = calloc(sizeof(UInt8), readBufferSize);
pthread_mutexattr_t attr;
@@ -340,7 +482,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
bufferingQueue = [[NSMutableArray alloc] init];
[self resetPcmBuffers];
[self createAudioUnit];
[self createAudioGraph];
[self createPlaybackThread];
}
@@ -373,11 +515,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
AudioConverterDispose(audioConverterRef);
}
if (audioUnit)
if (outputUnit)
{
AudioComponentInstanceDispose(audioUnit);
AudioComponentInstanceDispose(outputUnit);
}
AUGraphClose(audioGraph);
pthread_mutex_destroy(&playerMutex);
pthread_mutex_destroy(&mainThreadSyncCallMutex);
pthread_cond_destroy(&playerThreadReadyCondition);
@@ -481,20 +625,35 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
-(void) play:(NSString*)urlString
{
[self play:urlString withQueueItemID:urlString];
}
-(void) play:(NSString*)urlString withQueueItemID:(NSObject*)queueItemId
{
NSURL* url = [NSURL URLWithString:urlString];
[self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:urlString];
[self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:queueItemId];
}
-(void) playWithURL:(NSURL*)url
-(void) playURL:(NSURL*)url
{
[self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:url];
[self playURL:url withQueueItemID:url];
}
-(void) playWithDataSource:(STKDataSource*)dataSource
-(void) playURL:(NSURL*)url withQueueItemID:(NSObject*)queueItemId
{
[self setDataSource:dataSource withQueueItemId:dataSource];
[self setDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:queueItemId];
}
-(void) playDataSource:(STKDataSource*)dataSource
{
[self playDataSource:dataSource withQueueItemID:dataSource];
}
-(void) playDataSource:(STKDataSource*)dataSource withQueueItemID:(NSObject *)queueItemId
{
[self setDataSource:dataSource withQueueItemId:queueItemId];
}
-(void) setDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId
@@ -516,6 +675,26 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[self wakeupPlaybackThread];
}
-(void) queue:(NSString*)urlString
{
return [self queueURL:[NSURL URLWithString:urlString] withQueueItemId:urlString];
}
-(void) queue:(NSString*)urlString withQueueItemId:(NSObject*)queueItemId
{
[self queueURL:[NSURL URLWithString:urlString] withQueueItemId:queueItemId];
}
-(void) queueURL:(NSURL*)url
{
[self queueURL:url withQueueItemId:url];
}
-(void) queueURL:(NSURL*)url withQueueItemId:(NSObject*)queueItemId
{
[self queueDataSource:[STKAudioPlayer dataSourceFromURL:url] withQueueItemId:queueItemId];
}
-(void) queueDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId
{
pthread_mutex_lock(&playerMutex);
@@ -682,7 +861,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(Float64) currentTimeInFrames
{
if (audioUnit == nil)
if (audioGraph == nil)
{
return 0;
}
@@ -882,7 +1061,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
[self processFinishPlayingIfAnyAndPlayingNext:currentlyPlayingEntry withNext:entry];
[self startAudioUnit];
[self startAudioGraph];
}
else
{
@@ -1061,7 +1241,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[currentlyReadingEntry.dataSource unregisterForEvents];
}
if (self->options & STKAudioPlayerOptionFlushQueueOnSeek)
if (self->options.flushQueueOnSeek)
{
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
[self setCurrentlyReadingEntry:currentlyPlayingEntry andStartPlaying:YES clearQueue:YES];
@@ -1236,7 +1416,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
if (audioUnit)
if (audioGraph)
{
[self resetPcmBuffers];
}
@@ -1380,9 +1560,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.stateBeforePaused = self.internalState;
self.internalState = STKAudioPlayerInternalStatePaused;
if (audioUnit)
if (audioGraph)
{
error = AudioOutputUnitStop(audioUnit);
error = AUGraphStop(audioGraph);
if (error)
{
@@ -1415,9 +1595,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[self resetPcmBuffers];
}
if (audioUnit != nil)
if (audioGraph != nil)
{
error = AudioOutputUnitStart(audioUnit);
error = AUGraphStart(audioGraph);
if (error)
{
@@ -1672,88 +1852,213 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
}
}
-(void) createAudioUnit
-(void) createOutputUnit
{
pthread_mutex_lock(&playerMutex);
OSStatus status;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
#endif
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent component = AudioComponentFindNext(NULL, &desc);
status = AudioComponentInstanceNew(component, &audioUnit);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
OSStatus status;
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &outputUnitDescription, &outputNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, outputNode, &outputUnitDescription, &outputUnit));
#if TARGET_OS_IPHONE
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)));
#endif
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = OutputRenderCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callbackStruct, sizeof(callbackStruct));
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
status = AudioUnitInitialize(audioUnit);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return;
}
pthread_mutex_unlock(&playerMutex);
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription)));
}
-(BOOL) startAudioUnit
-(void) createMixerUnit
{
OSStatus status;
if (!self.options.enableVolumeMixer)
{
return;
}
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &mixerDescription, &mixerNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, mixerNode, &mixerDescription, &mixerUnit));
UInt32 busCount = 1;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount)));
Float64 graphSampleRate = 44100.0;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &graphSampleRate, sizeof(graphSampleRate)));
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, 0, 1.0, 0));
}
-(void) createEqUnit
{
#if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
return;
#else
OSStatus status;
if (self->options.equalizerBandFrequencies[0] == 0)
{
return;
}
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &nbandUnitDescription, &eqNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, eqNode, NULL, &eqUnit));
while (self->options.equalizerBandFrequencies[eqBandCount] != 0)
{
eqBandCount++;
}
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(eqUnit, kAUNBandEQProperty_NumberOfBands, kAudioUnitScope_Global, 0, &eqBandCount, sizeof(eqBandCount)));
for (int i = 0; i < eqBandCount; i++)
{
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_Frequency + i, kAudioUnitScope_Global, 0, (AudioUnitParameterValue)self->options.equalizerBandFrequencies[i], 0));
}
for (int i = 0; i < eqBandCount; i++)
{
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_BypassBand + i, kAudioUnitScope_Global, 0, (AudioUnitParameterValue)0, 0));
}
#endif
}
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex
{
if (!eqUnit)
{
return;
}
OSStatus status;
CHECK_STATUS_AND_RETURN(AudioUnitSetParameter(eqUnit, kAUNBandEQParam_Gain + bandIndex, kAudioUnitScope_Global, 0, gain, 0));
}
-(AUNode) createConverterNode:(AudioStreamBasicDescription)srcFormat desFormat:(AudioStreamBasicDescription)desFormat
{
OSStatus status;
AUNode convertNode;
AudioComponentInstance convertUnit;
CHECK_STATUS_AND_RETURN_VALUE(AUGraphAddNode(audioGraph, &convertUnitDescription, &convertNode), 0);
CHECK_STATUS_AND_RETURN_VALUE(status = AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat)), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &desFormat, sizeof(desFormat)), 0);
return convertNode;
}
-(void) connectNodes:(AUNode)srcNode desNode:(AUNode)desNode srcUnit:(AudioComponentInstance)srcUnit desUnit:(AudioComponentInstance)desUnit
{
OSStatus status;
AudioStreamBasicDescription srcFormat, desFormat;
UInt32 size = sizeof(AudioStreamBasicDescription);
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(srcUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &srcFormat, &size));
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desFormat, &size));
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
if (status)
{
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, mixerNode, 0));
}
else
{
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, desNode, 0));
}
}
-(void) setOutputCallbackForFirstNode:(AUNode)firstNode firstUnit:(AudioComponentInstance)firstUnit
{
OSStatus status;
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = OutputRenderCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(firstUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription));
if (status)
{
AudioStreamBasicDescription format;
UInt32 size = sizeof(format);
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(firstUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, &size));
AUNode converterNode = [self createConverterNode:canonicalAudioStreamBasicDescription desFormat:format];
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct));
status = AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0);
}
else
{
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, firstNode, 0, &callbackStruct));
}
}
-(void) createAudioGraph
{
OSStatus status;
NSMutableArray* nodes = [[NSMutableArray alloc] init];
NSMutableArray* units = [[NSMutableArray alloc] init];
CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph));
CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph));
[self createEqUnit];
[self createMixerUnit];
[self createOutputUnit];
if (eqNode)
{
[nodes addObject:@(eqNode)];
[units addObject:[NSValue valueWithPointer:eqUnit]];
}
if (mixerNode)
{
[nodes addObject:@(mixerNode)];
[units addObject:[NSValue valueWithPointer:mixerUnit]];
}
if (outputNode)
{
[nodes addObject:@(outputNode)];
[units addObject:[NSValue valueWithPointer:outputUnit]];
}
[self setOutputCallbackForFirstNode:(AUNode)[[nodes objectAtIndex:0] intValue] firstUnit:(AudioComponentInstance)[[units objectAtIndex:0] pointerValue]];
for (int i = 0; i < nodes.count - 1; i++)
{
AUNode srcNode = [[nodes objectAtIndex:i] intValue];
AUNode desNode = [[nodes objectAtIndex:i + 1] intValue];
AudioComponentInstance srcUnit = (AudioComponentInstance)[[units objectAtIndex:i] pointerValue];
AudioComponentInstance desUnit = (AudioComponentInstance)[[units objectAtIndex:i + 1] pointerValue];
[self connectNodes:srcNode desNode:desNode srcUnit:srcUnit desUnit:desUnit];
}
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
self.volume = self->volume;
}
-(BOOL) startAudioGraph
{
OSStatus status;
[self resetPcmBuffers];
status = AudioOutputUnitStart(audioUnit);
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
@@ -1762,6 +2067,20 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return NO;
}
if (isRunning)
{
return NO;
}
status = AUGraphStart(audioGraph);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return NO;
}
return YES;
}
@@ -1769,7 +2088,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
{
OSStatus status;
if (!audioUnit)
if (!audioGraph)
{
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
@@ -1777,17 +2096,28 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return;
}
status = AudioOutputUnitStop(audioUnit);
[self resetPcmBuffers];
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
}
else if (!isRunning)
{
return;
}
status = AUGraphStop(audioGraph);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
}
[self resetPcmBuffers];
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
}
@@ -2076,19 +2406,11 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
UInt32 frameSizeInBytes = audioPlayer->pcmBufferFrameSizeInBytes;
UInt32 used = audioPlayer->pcmBufferUsedFrameCount;
UInt32 start = audioPlayer->pcmBufferFrameStartIndex;
STKAudioPlayerInternalState state = audioPlayer->internalState;
UInt32 end = (audioPlayer->pcmBufferFrameStartIndex + audioPlayer->pcmBufferUsedFrameCount) % audioPlayer->pcmBufferTotalFrameCount;
BOOL signal = audioPlayer->waiting && used < audioPlayer->pcmBufferTotalFrameCount / 2;
NSArray* frameFilters = audioPlayer->frameFilters;
STKAudioPlayerInternalState state = audioPlayer.internalState;
if (state == STKAudioPlayerInternalStatePendingNext)
{
OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock);
return 0;
}
if (entry)
{
if (state == STKAudioPlayerInternalStateWaitingForData)
@@ -2108,9 +2430,23 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
}
else if (state == STKAudioPlayerInternalStateRebuffering)
{
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
if (audioPlayer->currentlyPlayingEntry->lastFrameQueued >= 0)
if (entry->lastFrameQueued >= 0)
{
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued - entry->framesQueued);
}
if (used < framesRequiredToStartPlaying)
{
waitForBuffer = YES;
}
}
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
{
int64_t framesRequiredToStartPlaying = inNumberFrames;
if (entry->lastFrameQueued >= 0)
{
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued - entry->framesQueued);
}
@@ -2126,7 +2462,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
UInt32 totalFramesCopied = 0;
if (used > 0 && !waitForBuffer && entry != nil)
if (used > 0 && !waitForBuffer && entry != nil && ((state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused))
{
if (state == STKAudioPlayerInternalStateWaitingForData)
{
@@ -2204,7 +2540,10 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
OSSpinLockUnlock(&audioPlayer->pcmBufferSpinLock);
}
audioPlayer.internalState = STKAudioPlayerInternalStatePlaying;
[audioPlayer setInternalState:STKAudioPlayerInternalStatePlaying ifInState:^BOOL(STKAudioPlayerInternalState state)
{
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
if (totalFramesCopied < inNumberFrames)
@@ -2217,14 +2556,17 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{
// Buffering
audioPlayer.internalState = STKAudioPlayerInternalStateRebuffering;
[audioPlayer setInternalState:STKAudioPlayerInternalStateRebuffering ifInState:^BOOL(STKAudioPlayerInternalState state)
{
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
}
if (frameFilters)
{
NSUInteger count = frameFilters.count;
AudioStreamBasicDescription asbd = audioPlayer->canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription asbd = canonicalAudioStreamBasicDescription;
for (int i = 0; i < count; i++)
{
@@ -2478,6 +2820,8 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
}
}
#pragma mark Frame Filters
-(NSArray*) frameFilters
{
return frameFilters;
@@ -2584,4 +2928,32 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
pthread_mutex_unlock(&self->playerMutex);
}
#pragma mark Volume
-(void) setVolume:(Float32)value;
{
self->volume = value;
#if (TARGET_OS_IPHONE)
if (self->mixerNode)
{
AudioUnitSetParameter(self->mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, self->volume, 0);
}
#else
if (self->mixerNode)
{
AudioUnitSetParameter(self->mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, self->volume, 0);
}
else
{
AudioUnitSetParameter(outputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, kOutputBus, self->volume, 0);
}
#endif
}
-(Float32) volume
{
return self->volume;
}
@end