Compare commits

..

72 Commits

Author SHA1 Message Date
Anton 6b6a5ad434 STKAutoRecoveringHTTPDataSource http errors handling fix 2016-09-22 16:07:06 +03:00
Anton 7def768397 Merge branch 'master' of https://github.com/tumtumtum/StreamingKit
# Conflicts:
#	StreamingKit/StreamingKit/STKAudioPlayer.h
#	StreamingKit/StreamingKit/STKAudioPlayer.m
#	StreamingKit/StreamingKit/STKQueueEntry.m
2016-06-15 00:35:31 +03:00
Николай Ашанин 532bac1e07 buffer size in seconds to float
decibel commented
2016-02-09 04:41:35 +03:00
Николай Ашанин 15a9f6d395 aef to stk 2016-02-08 19:09:59 +03:00
Николай Ашанин d13282d84e add float converter 2016-02-08 18:56:16 +03:00
Николай Ашанин dfe6c73621 add vizualizer for player buffers 2016-02-08 00:21:29 +03:00
Thong Nguyen 2d251d5150 Updated podspec 2016-01-04 11:06:47 +00:00
Thong Nguyen 2043330287 Removed commented out bad code 2016-01-04 11:05:20 +00:00
Thong Nguyen 3cb6349c97 Merge pull request #209 from corprew/patch-1
spelling fix
2015-12-19 23:14:54 +00:00
Thong Nguyen b51035a267 Merge branch 'richardgroves-patch-1' 2015-12-19 23:14:13 +00:00
Thong Nguyen 0d0280b631 Fix conflicts with richardgroves-patch-1 2015-12-19 23:14:02 +00:00
Thong Nguyen f9f8199015 Merge branch 'NOUSguide-master' 2015-12-19 23:11:33 +00:00
Thong Nguyen d9a6ba7248 Fixed conflicts 2015-12-19 23:11:25 +00:00
Thong Nguyen 6264442f58 Merge pull request #247 from kwillick/master
Swift compatibilty improvements
2015-12-19 23:09:30 +00:00
Kipp Hickman 830ed0f3db Added nullablity tags for better Swift compatibility. 2015-12-18 14:04:31 -08:00
Kipp Hickman 6ef69bacf0 Changed init methods to return instancetype instead of id.
Using instancetype allows the compiler to perform more error
checking. This article provides an in-depth explanation:
http://nshipster.com/instancetype/.
2015-12-18 14:04:24 -08:00
Alexey Savin a84db95be6 Minor fixes 2015-12-14 11:37:51 +03:00
Thong Nguyen d8b77ae214 Fixed bug in progressInFrames 2015-12-11 01:06:53 +00:00
Thong Nguyen 499e54731d Merge pull request #175 from sergiou87/master
Paused Audio Player doesn't transit to Stopped state on stop
2015-12-11 01:02:14 +00:00
Thong Nguyen da71b04aaf Updated version number in podspec 2015-12-07 13:02:44 +00:00
Thong Nguyen 55a314b966 Fix int->double truncation when doing duration calculation 2015-12-07 13:00:53 +00:00
Thong Nguyen 8fa821a944 Fixed int->double truncation in duration calculation 2015-12-07 12:59:32 +00:00
Thong Nguyen 0f69b7ea76 Merge pull request #239 from reindernijhoff/master
Fixed memory leak
2015-12-06 14:00:49 +00:00
Reinder Nijhoff 4d0fccdd70 Fixed memory leak 2015-12-06 14:06:15 +01:00
Thong Nguyen 5909657368 Changed enums to NS_ENUM to better support Swift. Added launch images to remove warnings 2015-12-04 22:47:14 +00:00
Thong Nguyen 50bec46acc Fixed build issue with OSSTATUS_PRINTF_VALUE on 64bit 2015-12-03 17:25:30 +00:00
Thong Nguyen dec8b87498 Updated podspec version 2015-12-03 16:18:50 +00:00
Thong Nguyen f872de223d Added support for disabling buffers in options using STK_DISABLE_BUFFER. Changed bufferSizeInSeconds from a UInt32 to Float32 2015-12-03 16:18:06 +00:00
kampfgnu f84f1ef0bd use brackets to test for formatID 2015-11-26 11:43:41 +01:00
kampfgnu 3bc3a85df3 fix mono streams
by removing a line that is already removed in v 0.1.25, but not in master...
2015-11-26 11:41:07 +01:00
kampfgnu 520f98a6b3 fix reconnect to live streams
seeking to a non-zero offset works only if seek is supported (accept-ranges header)
2015-11-10 11:02:57 +01:00
Thong Nguyen 4f72249c94 Fixed License 2015-10-29 18:16:01 +00:00
Anton cbdb01f0a9 Prev commit fix 2015-10-14 19:46:35 +03:00
Anton 30815ae23a Add track extension overriding 2015-10-14 18:24:26 +03:00
Nikolai Ashanin 7960ffcace Update STKAudioPlayer.m
allow mono playing
2015-10-09 15:37:52 +03:00
Nikolai Ashanin 36a5f1cd22 Update STKAudioPlayer.m 2015-10-01 18:16:38 +03:00
Alexey Savin d52a2a39c2 Decrease PCM buffer size to prevent memory warnings 2015-09-29 18:42:18 +03:00
Nikolai Ashanin 6f14432449 Update STKAudioPlayer.m 2015-09-22 21:09:16 +03:00
Richard Groves 726ec86d77 Update STKAudioPlayer.m
The original line throws this warning in XCode - 
...../External Libraries/StreamingKit/StreamingKit/StreamingKit/STKAudioPlayer.m:880:61: Comparison of constant 'kAudioFormatLinearPCM' (1819304813) with boolean expression is always false

I'm guessing the precedence of ! vs == causes it to be 'mis-interpreted'.

Not quite sure of the logic where audio format != linear PCM implies a discontinuous stream, but...
2015-06-26 11:45:29 +01:00
Corprew Reed 15b0242305 spelling fix 2015-06-18 14:14:47 -07:00
Sergio Padrino de99ec9d7a Make sure the audio player changes to stopped state when it's stopped from paused state 2015-01-14 16:56:06 +01:00
Thong Nguyen 39f0d8bdfe Added CA_CANONICAL_DEPRECATED check 2014-12-29 18:14:39 +00:00
Thong Nguyen 8708e48395 Merge pull request #166 from danielgindi/master
NULLing eventsRunLoop may crash later
2014-12-29 18:10:15 +00:00
Daniel Cohen Gindi 94e6cbf41b NULLing eventsRunLoop may crash later 2014-12-29 20:05:21 +02:00
Thong Nguyen a5b6360b1c Merge pull request #152 from danielgindi/recording
Recording
2014-12-29 17:51:26 +00:00
Daniel Cohen Gindi 09e5602464 Allow recording a specific DataSource to AAC-LC 2014-12-29 19:43:29 +02:00
Thong Nguyen 4c7ce6c4ec Merge branch 'danielgindi-duration_and_progress' 2014-12-29 16:51:09 +00:00
Thong Nguyen 5784504e38 Merge branch 'duration_and_progress' of git://github.com/danielgindi/StreamingKit into danielgindi-duration_and_progress 2014-12-29 16:50:36 +00:00
Thong Nguyen e73df7fd86 Merge branch 'danielgindi-deprecations' 2014-12-29 16:49:00 +00:00
Thong Nguyen 9a4b4a617d Merged/fixed deprecations changes from danielgindi 2014-12-29 16:48:37 +00:00
Thong Nguyen 7d4d7b847e Merge pull request #153 from danielgindi/quality
Transcode using the same number of channels as the datasource
2014-12-29 16:41:23 +00:00
Daniel Cohen Gindi 61cb8a8a7b Transcode using the same number of channels as the datasource
We also need to do the same for sample rate, but then we need to restart the audio unit graph
2014-12-18 00:51:01 +02:00
Daniel Cohen Gindi 8e2e451e9c These SSL constants are deprecated since iOS 4.0 2014-12-17 17:31:54 +02:00
Daniel Cohen Gindi 88be5b33c6 Using AudioSampleType constant is deprecated in iOS 8
The equivalent is SInt16
2014-12-17 17:31:50 +02:00
Daniel Cohen Gindi 50a8b610c4 Provide a mechanism to hint for correct duration.
For cases where duration can't be calculated accurately
2014-12-17 17:31:20 +02:00
Thong Nguyen c1b3b5d8cc Merge pull request #141 from danielgindi/master
Allow using headers from submodule, framework-like
2014-11-10 20:28:53 +00:00
Daniel Cohen Gindi 477b1f175f Allow using headers from submodule, framework-like
Now when we use the xcodeproj as a submodule we can do stuff like:
#import <StreamingKit/STKAudioPlayer.h>
without setting up weird include paths.
2014-11-10 21:05:23 +02:00
Thong Nguyen 000930a295 Fixed seek not working with HTTP 2014-11-10 17:25:02 +00:00
Thong Nguyen 269f335ee4 Smallf ix to ExampleAppMac app 2014-11-08 20:56:55 +00:00
Thong Nguyen 162d964372 STKHTTPDataSource tidyup 2014-11-08 20:04:29 +00:00
Thong Nguyen ef7f42c97e AudioPlayerView layout jig 2014-11-08 19:52:54 +00:00
Thong Nguyen ea9e40b17a Added progress for live streams in AudioPlayerView 2014-11-08 19:45:26 +00:00
Thong Nguyen 9510d74e58 Added progress for live streams in AudioPlayerView 2014-11-08 19:43:51 +00:00
Thong Nguyen c7e90e4d2e Merge branch 'master' of https://github.com/tumtumtum/StreamingKit 2014-11-08 15:45:43 +00:00
Thong Nguyen cba7db8112 Added basic support for Icecast streams 2014-11-08 15:45:40 +00:00
Thong Nguyen 1333f7f025 Merge pull request #138 from danielgindi/master
Missing mime types
2014-10-31 09:36:23 +00:00
Daniel Cohen Gindi ff779b669b Missing mime types 2014-10-31 10:54:49 +02:00
Thong Nguyen b07270910b Fixed URL for HTTP pointing to local file. Oops 2014-09-11 15:29:32 +01:00
Thong Nguyen 4d9cea0a31 Fixed HE-AAC format being stripped 2014-08-20 15:49:36 +01:00
Thong Nguyen acdf65c7cb Merge pull request #128 from kwillick/master
Support adding http headers to a STKHTTPDataSource
2014-08-19 09:56:21 +01:00
Kipp Hickman ac951bfc7a Added ability to add http headers to a STKHTTPDataSource. 2014-08-15 12:00:49 -07:00
Thong Nguyen 8c64914314 Updated test URLs to use abstractpath.com 2014-06-10 16:29:30 +01:00
41 changed files with 1328 additions and 410 deletions

View File

@ -27,7 +27,6 @@
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */; }; A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */; };
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F5E491189EB3F20070B03F /* AVFoundation.framework */; }; A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F5E491189EB3F20070B03F /* AVFoundation.framework */; };
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */; }; A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */; };
A1F3410A1908185900CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341091908185900CA7755 /* Accelerate.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -66,7 +65,6 @@
A142571C18907861005F0129 /* airplane.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = airplane.aac; 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; }; 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; }; A1EBEE63188DE34500681B04 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
A1F341091908185900CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.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; }; A1F5E491189EB3F20070B03F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -76,7 +74,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A1F3410A1908185900CA7755 /* Accelerate.framework in Frameworks */,
A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */, A17FFB6918A002E400BAA7FF /* AVFoundation.framework in Frameworks */,
A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */, A17FFB6318A0028300BAA7FF /* AudioToolbox.framework in Frameworks */,
A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */, A1EBEE64188DE34500681B04 /* SystemConfiguration.framework in Frameworks */,
@ -122,7 +119,6 @@
A1115933188D686000641365 /* Frameworks */ = { A1115933188D686000641365 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A1F341091908185900CA7755 /* Accelerate.framework */,
A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */, A17FFB6218A0028300BAA7FF /* AudioToolbox.framework */,
A1F5E491189EB3F20070B03F /* AVFoundation.framework */, A1F5E491189EB3F20070B03F /* AVFoundation.framework */,
A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */, A1F5E48F189EB3CB0070B03F /* AudioUnit.framework */,
@ -234,7 +230,7 @@
A1115929188D686000641365 /* Project object */ = { A1115929188D686000641365 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0510; LastUpgradeCheck = 0710;
ORGANIZATIONNAME = "Thong Nguyen"; ORGANIZATIONNAME = "Thong Nguyen";
TargetAttributes = { TargetAttributes = {
A111594B188D686000641365 = { A111594B188D686000641365 = {
@ -350,6 +346,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@ -426,6 +423,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 6.0; IPHONEOS_DEPLOYMENT_TARGET = 6.0;
LLVM_LTO = YES; LLVM_LTO = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
}; };
@ -442,6 +440,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 6.0; IPHONEOS_DEPLOYMENT_TARGET = 6.0;
LLVM_LTO = YES; LLVM_LTO = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
}; };
@ -463,6 +462,7 @@
"$(inherited)", "$(inherited)",
); );
INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist"; INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUNDLE_LOADER)"; TEST_HOST = "$(BUNDLE_LOADER)";
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;
@ -481,6 +481,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch"; GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist"; INFOPLIST_FILE = "ExampleAppTests/ExampleAppTests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUNDLE_LOADER)"; TEST_HOST = "$(BUNDLE_LOADER)";
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;

View File

@ -32,13 +32,14 @@
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength); AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength);
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[UIViewController alloc] init];
self.window.backgroundColor = [UIColor whiteColor]; self.window.backgroundColor = [UIColor whiteColor];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }]; audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
audioPlayer.meteringEnabled = YES; audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 1; audioPlayer.volume = 1;
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer]; AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
audioPlayerView.delegate = self; audioPlayerView.delegate = self;
@ -46,9 +47,9 @@
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder]; [self becomeFirstResponder];
[self.window addSubview:audioPlayerView];
[self.window makeKeyAndVisible]; [self.window makeKeyAndVisible];
[self.window.rootViewController.view addSubview:audioPlayerView];
return YES; return YES;
} }
@ -60,13 +61,22 @@
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView
{ {
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; NSURL* url = [NSURL URLWithString:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
[audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]]; [audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
} }
-(void) audioPlayerViewPlayFromIcecastSelected:(AudioPlayerView *)audioPlayerView
{
NSURL* url = [NSURL URLWithString:@"http://shoutmedia.abc.net.au:10326"];
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
[audioPlayer setDataSource:dataSource withQueueItemId:[[SampleQueueId alloc] initWithUrl:url andCount:0]];
}
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView -(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView
{ {
NSString* path = [[NSBundle mainBundle] pathForResource:@"airplane" ofType:@"aac"]; NSString* path = [[NSBundle mainBundle] pathForResource:@"airplane" ofType:@"aac"];
@ -89,7 +99,7 @@
-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView -(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView
{ {
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/perfectly.wav"]; NSURL* url = [NSURL URLWithString:@"http://www.abstractpath.com/files/audiosamples/perfectly.wav"];
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url]; STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];

View File

@ -39,6 +39,7 @@
@protocol AudioPlayerViewDelegate<NSObject> @protocol AudioPlayerViewDelegate<NSObject>
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewPlayFromIcecastSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewQueueShortFileSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewPlayFromLocalFileSelected:(AudioPlayerView*)audioPlayerView;
-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView; -(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView;
@ -57,6 +58,7 @@
UIButton* playButton; UIButton* playButton;
UIButton* stopButton; UIButton* stopButton;
UIButton* playFromHTTPButton; UIButton* playFromHTTPButton;
UIButton* playFromIcecastButton;
UIButton* queueShortFileButton; UIButton* queueShortFileButton;
UIButton* queuePcmWaveFileFromHTTPButton; UIButton* queuePcmWaveFileFromHTTPButton;
UIButton* playFromLocalFileButton; UIButton* playFromLocalFileButton;

View File

@ -40,9 +40,6 @@
/// ///
@interface AudioPlayerView() @interface AudioPlayerView()
{
NSMutableArray* meters;
}
-(void) setupTimer; -(void) setupTimer;
-(void) updateControls; -(void) updateControls;
@end @end
@ -58,47 +55,50 @@
{ {
self.audioPlayer = audioPlayerIn; self.audioPlayer = audioPlayerIn;
self.audioPlayer.spectrumAnalyzerEnabled = YES;
CGSize size = CGSizeMake(220, 50); CGSize size = CGSizeMake(220, 50);
playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
playFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10, size.width, size.height); playFromHTTPButton.frame = CGRectMake((frame.size.width - size.width) / 2, frame.size.height * 0.10, size.width, size.height);
[playFromHTTPButton addTarget:self action:@selector(playFromHTTPButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [playFromHTTPButton addTarget:self action:@selector(playFromHTTPButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[playFromHTTPButton setTitle:@"Play from HTTP" forState:UIControlStateNormal]; [playFromHTTPButton setTitle:@"Play from HTTP" forState:UIControlStateNormal];
playFromIcecastButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
playFromIcecastButton.frame = CGRectMake((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 35, size.width, size.height);
[playFromIcecastButton addTarget:self action:@selector(playFromIcecasButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[playFromIcecastButton setTitle:@"Play from Icecast" forState:UIControlStateNormal];
playFromLocalFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; playFromLocalFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
playFromLocalFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 50, size.width, size.height); playFromLocalFileButton.frame = CGRectMake((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 70, size.width, size.height);
[playFromLocalFileButton addTarget:self action:@selector(playFromLocalFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [playFromLocalFileButton addTarget:self action:@selector(playFromLocalFileButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[playFromLocalFileButton setTitle:@"Play from Local File" forState:UIControlStateNormal]; [playFromLocalFileButton setTitle:@"Play from Local File" forState:UIControlStateNormal];
queueShortFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; queueShortFileButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
queueShortFileButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 100, size.width, size.height); queueShortFileButton.frame = CGRectMake((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 105, size.width, size.height);
[queueShortFileButton addTarget:self action:@selector(queueShortFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [queueShortFileButton addTarget:self action:@selector(queueShortFileButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[queueShortFileButton setTitle:@"Queue short file" forState:UIControlStateNormal]; [queueShortFileButton setTitle:@"Queue short file" forState:UIControlStateNormal];
queuePcmWaveFileFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; queuePcmWaveFileFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
queuePcmWaveFileFromHTTPButton.frame = CGRectMake((320 - size.width) / 2, frame.size.height * 0.10 + 150, size.width, size.height); queuePcmWaveFileFromHTTPButton.frame = CGRectMake((frame.size.width - size.width) / 2, frame.size.height * 0.10 + 140, size.width, size.height);
[queuePcmWaveFileFromHTTPButton addTarget:self action:@selector(queuePcmWaveFileButtonTouched) forControlEvents:UIControlEventTouchUpInside]; [queuePcmWaveFileFromHTTPButton addTarget:self action:@selector(queuePcmWaveFileButtonTouched) forControlEvents:UIControlEventTouchUpInside];
[queuePcmWaveFileFromHTTPButton setTitle:@"Queue PCM/WAVE from HTTP" forState:UIControlStateNormal]; [queuePcmWaveFileFromHTTPButton setTitle:@"Queue PCM/WAVE from HTTP" forState:UIControlStateNormal];
size = CGSizeMake(90, 40); size = CGSizeMake(90, 40);
playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
playButton.frame = CGRectMake(30, 380, size.width, size.height); playButton.frame = CGRectMake(30, 400, size.width, size.height);
[playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
stopButton.frame = CGRectMake((320 - size.width) - 30, 380, size.width, size.height); stopButton.frame = CGRectMake((frame.size.width - size.width) - 30, 400, size.width, size.height);
[stopButton addTarget:self action:@selector(stopButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [stopButton addTarget:self action:@selector(stopButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[stopButton setTitle:@"Stop" forState:UIControlStateNormal]; [stopButton setTitle:@"Stop" forState:UIControlStateNormal];
muteButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; muteButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
muteButton.frame = CGRectMake((320 - size.width) - 30, 410, size.width, size.height); muteButton.frame = CGRectMake((frame.size.width - size.width) - 30, 430, size.width, size.height);
[muteButton addTarget:self action:@selector(muteButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [muteButton addTarget:self action:@selector(muteButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[muteButton setTitle:@"Mute" forState:UIControlStateNormal]; [muteButton setTitle:@"Mute" forState:UIControlStateNormal];
slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, 280, 20)]; slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 320, queuePcmWaveFileFromHTTPButton.frame.origin.y + queuePcmWaveFileFromHTTPButton.frame.size.height + 20, 20)];
slider.continuous = YES; slider.continuous = YES;
[slider addTarget:self action:@selector(sliderChanged) forControlEvents:UIControlEventValueChanged]; [slider addTarget:self action:@selector(sliderChanged) forControlEvents:UIControlEventValueChanged];
@ -106,40 +106,27 @@
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(30, 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 = [[UISwitch alloc] initWithFrame:CGRectMake(frame.size.width - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)];
enableEqSwitch.on = audioPlayer.equalizerEnabled; enableEqSwitch.on = audioPlayer.equalizerEnabled;
[enableEqSwitch addTarget:self action:@selector(onEnableEqSwitch) forControlEvents:UIControlEventAllTouchEvents]; [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)]; label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 40, frame.size.width, 25)];
label.textAlignment = NSTextAlignmentCenter; label.textAlignment = NSTextAlignmentCenter;
statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + label.frame.size.height + 8, frame.size.width, 50)]; statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + label.frame.size.height + 50, frame.size.width, 50)];
statusLabel.textAlignment = NSTextAlignmentCenter; statusLabel.textAlignment = NSTextAlignmentCenter;
meters = [[NSMutableArray alloc] init];
meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)]; meter = [[UIView alloc] initWithFrame:CGRectMake(0, 450, 0, 20)];
meter.backgroundColor = [UIColor greenColor]; meter.backgroundColor = [UIColor greenColor];
for (int i = 0; i < 256; i++)
{
UIView* freqMeter = [[UIView alloc] initWithFrame:CGRectMake(i, self.bounds.size.height, 1, 0)];
freqMeter.backgroundColor = [UIColor blueColor];
[self addSubview:freqMeter];
[meters addObject:freqMeter];
[self sendSubviewToBack:freqMeter];
}
[self addSubview:slider]; [self addSubview:slider];
[self addSubview:playButton]; [self addSubview:playButton];
[self addSubview:playFromHTTPButton]; [self addSubview:playFromHTTPButton];
[self addSubview:playFromIcecastButton];
[self addSubview:playFromLocalFileButton]; [self addSubview:playFromLocalFileButton];
[self addSubview:queueShortFileButton]; [self addSubview:queueShortFileButton];
[self addSubview:queuePcmWaveFileFromHTTPButton]; [self addSubview:queuePcmWaveFileFromHTTPButton];
@ -193,6 +180,17 @@
return; return;
} }
if (audioPlayer.currentlyPlayingQueueItemId == nil)
{
slider.value = 0;
slider.minimumValue = 0;
slider.maximumValue = 0;
label.text = @"";
return;
}
if (audioPlayer.duration != 0) if (audioPlayer.duration != 0)
{ {
slider.minimumValue = 0; slider.minimumValue = 0;
@ -207,23 +205,14 @@
slider.minimumValue = 0; slider.minimumValue = 0;
slider.maximumValue = 0; slider.maximumValue = 0;
label.text = @""; label.text = [NSString stringWithFormat:@"Live stream %@", [self formatTimeFromSeconds:audioPlayer.progress]];
} }
statusLabel.text = audioPlayer.state == STKAudioPlayerStateBuffering ? @"buffering" : @""; statusLabel.text = audioPlayer.state == STKAudioPlayerStateBuffering ? @"buffering" : @"";
CGFloat newWidth = 320 * (([audioPlayer testPowerWithIndex:100] + 96) / 96); CGFloat newWidth = 320 * (([audioPlayer averagePowerInDecibelsForChannel:1] + 60) / 60);
meter.frame = CGRectMake(0, 460, newWidth, 20); meter.frame = CGRectMake(0, 460, newWidth, 20);
for (int i = 0; i < 256; i++)
{
UIView* freqMeter = [meters objectAtIndex:i];
CGFloat height = 200 * (([audioPlayer testPowerWithIndex:i] + 96) / 96);
freqMeter.frame = CGRectMake(freqMeter.frame.origin.x, self.bounds.size.height - height, 1, height);
}
} }
-(void) playFromHTTPButtonTouched -(void) playFromHTTPButtonTouched
@ -231,6 +220,11 @@
[self.delegate audioPlayerViewPlayFromHTTPSelected:self]; [self.delegate audioPlayerViewPlayFromHTTPSelected:self];
} }
-(void) playFromIcecasButtonTouched
{
[self.delegate audioPlayerViewPlayFromIcecastSelected:self];
}
-(void) playFromLocalFileButtonTouched -(void) playFromLocalFileButtonTouched
{ {
[self.delegate audioPlayerViewPlayFromLocalFileSelected:self]; [self.delegate audioPlayerViewPlayFromLocalFileSelected:self];

View File

@ -9,7 +9,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>

View File

@ -5,16 +5,31 @@
"size" : "29x29", "size" : "29x29",
"scale" : "2x" "scale" : "2x"
}, },
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "40x40", "size" : "40x40",
"scale" : "2x" "scale" : "2x"
}, },
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "60x60", "size" : "60x60",
"scale" : "2x" "scale" : "2x"
}, },
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "29x29", "size" : "29x29",

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,5 +1,31 @@
{ {
"images" : [ "images" : [
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "736h",
"filename" : "TX6sV.png",
"minimum-system-version" : "8.0",
"orientation" : "portrait",
"scale" : "3x"
},
{
"orientation" : "landscape",
"idiom" : "iphone",
"extent" : "full-screen",
"minimum-system-version" : "8.0",
"subtype" : "736h",
"scale" : "3x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "667h",
"filename" : "dBEHd.png",
"minimum-system-version" : "8.0",
"orientation" : "portrait",
"scale" : "2x"
},
{ {
"orientation" : "portrait", "orientation" : "portrait",
"idiom" : "iphone", "idiom" : "iphone",
@ -8,11 +34,12 @@
"scale" : "2x" "scale" : "2x"
}, },
{ {
"orientation" : "portrait", "extent" : "full-screen",
"idiom" : "iphone", "idiom" : "iphone",
"subtype" : "retina4", "subtype" : "retina4",
"extent" : "full-screen", "filename" : "TX6sV-2.png",
"minimum-system-version" : "7.0", "minimum-system-version" : "7.0",
"orientation" : "portrait",
"scale" : "2x" "scale" : "2x"
}, },
{ {
@ -42,6 +69,26 @@
"extent" : "full-screen", "extent" : "full-screen",
"minimum-system-version" : "7.0", "minimum-system-version" : "7.0",
"scale" : "2x" "scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "TX6sV-1.png",
"extent" : "full-screen",
"subtype" : "retina4",
"scale" : "2x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>

View File

@ -58,7 +58,7 @@
-(void) playFromHTTP -(void) playFromHTTP
{ {
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; [audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
} }
-(void) tick:(NSTimer*)timer -(void) tick:(NSTimer*)timer
@ -72,7 +72,7 @@
CGFloat meterWidth = 0; CGFloat meterWidth = 0;
if (audioPlayer.duration != 0) if (audioPlayer.currentlyPlayingQueueItemId != nil)
{ {
slider.minValue = 0; slider.minValue = 0;
slider.maxValue = audioPlayer.duration; slider.maxValue = audioPlayer.duration;

View File

@ -4,7 +4,7 @@
Inspired by Matt Gallagher's AudioStreamer: Inspired by Matt Gallagher's AudioStreamer:
https://github.com/mattgallagher/AudioStreamer https://github.com/mattgallagher/AudioStreamer
Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. Copyright (c) 2015 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
@ -15,12 +15,12 @@
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software 3. All advertising materials mentioning features or use of this software
must display the following acknowledgement: must display the following acknowledgement:
This product includes software developed by the <organization>. This product includes software developed by Thong Nguyen.
4. Neither the name of the <organization> nor the 4. Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THONG NGUYEN ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY

View File

@ -2,7 +2,7 @@
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. 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. 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. 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 progressive download based 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 ## Main Features
@ -11,7 +11,7 @@ The primary motivation of this project was to decouple the input data sources fr
* Easy to read source. * Easy to read source.
* Carefully multi-threaded to provide a responsive API that won't block your UI thread nor starve the audio buffers. * 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. * Buffered and gapless playback between all format types.
* Easy to implement audio data sources (Local, HTTP, AutoRecoveryingHTTP DataSources are provided). * Easy to implement audio data sources (Local, HTTP, AutoRecoveringHTTP DataSources are provided).
* Easy to extend DataSource to support adaptive buffering, encryption, etc. * Easy to extend DataSource to support adaptive buffering, encryption, etc.
* Optimised for low CPU/battery usage (0% - 1% CPU usage when streaming). * 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. * Optimised for linear data sources. Random access sources are required only for seeking.
@ -34,7 +34,7 @@ There are two main classes. The `STKDataSource` class which is the abstract bas
```objective-c ```objective-c
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init]; STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; [audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
``` ```
### Gapless playback ### Gapless playback
@ -42,8 +42,8 @@ STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
```objective-c ```objective-c
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init]; STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"]; [audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/airplane.aac"]; [audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/airplane.aac"];
``` ```

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "StreamingKit" s.name = "StreamingKit"
s.version = "0.1.19" s.version = "0.1.29"
s.summary = "A fast and extensible audio streamer for iOS and OSX with support for gapless playback and custom (non-HTTP) sources." 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.homepage = "https://github.com/tumtumtum/StreamingKit/"
s.license = 'MIT' s.license = 'MIT'

View File

@ -10,29 +10,29 @@
<string>StreamingKit</string> <string>StreamingKit</string>
<key>IDESourceControlProjectOriginsDictionary</key> <key>IDESourceControlProjectOriginsDictionary</key>
<dict> <dict>
<key>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</key> <key>3E9414865BAE5433092B9D136FFC1F054EA505C2</key>
<string>https://github.com/tumtumtum/StreamingKit.git</string> <string>https://github.com/tumtumtum/StreamingKit.git</string>
</dict> </dict>
<key>IDESourceControlProjectPath</key> <key>IDESourceControlProjectPath</key>
<string>StreamingKit.xcworkspace</string> <string>StreamingKit.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key> <key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict> <dict>
<key>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</key> <key>3E9414865BAE5433092B9D136FFC1F054EA505C2</key>
<string>..</string> <string>..</string>
</dict> </dict>
<key>IDESourceControlProjectURL</key> <key>IDESourceControlProjectURL</key>
<string>https://github.com/tumtumtum/StreamingKit.git</string> <string>https://github.com/tumtumtum/StreamingKit.git</string>
<key>IDESourceControlProjectVersion</key> <key>IDESourceControlProjectVersion</key>
<integer>110</integer> <integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key> <key>IDESourceControlProjectWCCIdentifier</key>
<string>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</string> <string>3E9414865BAE5433092B9D136FFC1F054EA505C2</string>
<key>IDESourceControlProjectWCConfigurations</key> <key>IDESourceControlProjectWCConfigurations</key>
<array> <array>
<dict> <dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key> <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string> <string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key> <key>IDESourceControlWCCIdentifierKey</key>
<string>DD310C30-B3D0-4BD7-9565-9F29F09CC4F8</string> <string>3E9414865BAE5433092B9D136FFC1F054EA505C2</string>
<key>IDESourceControlWCCName</key> <key>IDESourceControlWCCName</key>
<string>StreamingKit</string> <string>StreamingKit</string>
</dict> </dict>

View File

@ -7,6 +7,16 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5B949CD21A1140E4005675A0 /* STKAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F1188D5E550010896F /* STKAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD31A1140E4005675A0 /* STKAutoRecoveringHTTPDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD41A1140E4005675A0 /* STKCoreFoundationDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F5188D5E550010896F /* STKCoreFoundationDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD51A1140E4005675A0 /* STKDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F7188D5E550010896F /* STKDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD61A1140E4005675A0 /* STKDataSourceWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4F9188D5E550010896F /* STKDataSourceWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD71A1140E4005675A0 /* STKHTTPDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4FB188D5E550010896F /* STKHTTPDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD81A1140E4005675A0 /* STKLocalFileDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B949CD91A1140E4005675A0 /* STKQueueEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = A1BF65D0189A6582004DD08C /* STKQueueEntry.h */; settings = {ATTRIBUTES = (Public, ); }; };
95F80F4E1C68EE2300DB24B3 /* STKFloatConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 95F80F4C1C68EE2300DB24B3 /* STKFloatConverter.h */; };
95F80F4F1C68EE2300DB24B3 /* STKFloatConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F80F4D1C68EE2300DB24B3 /* STKFloatConverter.m */; };
A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; }; A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; };
A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49974189E744500E2A2E2 /* StreamingKitMac.m */; }; A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49974189E744500E2A2E2 /* StreamingKitMac.m */; };
A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; }; A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; };
@ -37,8 +47,6 @@
A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FA188D5E550010896F /* STKDataSourceWrapper.m */; }; A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FA188D5E550010896F /* STKDataSourceWrapper.m */; };
A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FC188D5E550010896F /* STKHTTPDataSource.m */; }; A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FC188D5E550010896F /* STKHTTPDataSource.m */; };
A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */; }; A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */; };
A1F341041908183300CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341031908183300CA7755 /* Accelerate.framework */; };
A1F341081908183A00CA7755 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F341071908183A00CA7755 /* Accelerate.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -85,6 +93,8 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
95F80F4C1C68EE2300DB24B3 /* STKFloatConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKFloatConverter.h; sourceTree = "<group>"; };
95F80F4D1C68EE2300DB24B3 /* STKFloatConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKFloatConverter.m; sourceTree = "<group>"; };
A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStreamingKitMac.a; sourceTree = BUILT_PRODUCTS_DIR; }; A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStreamingKitMac.a; sourceTree = BUILT_PRODUCTS_DIR; };
A1A4996A189E744400E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; A1A4996A189E744400E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; };
A1A4996D189E744500E2A2E2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A1A4996D189E744500E2A2E2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@ -127,8 +137,6 @@
A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKLocalFileDataSource.h; sourceTree = "<group>"; }; A1E7C4FD188D5E550010896F /* STKLocalFileDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKLocalFileDataSource.h; sourceTree = "<group>"; };
A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKLocalFileDataSource.m; sourceTree = "<group>"; }; A1E7C4FE188D5E550010896F /* STKLocalFileDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKLocalFileDataSource.m; sourceTree = "<group>"; };
A1E7C507188D62D20010896F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; A1E7C507188D62D20010896F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
A1F341031908183300CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
A1F341071908183A00CA7755 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -136,7 +144,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A1F341081908183A00CA7755 /* Accelerate.framework in Frameworks */,
A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */, A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -155,7 +162,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A1F341041908183300CA7755 /* Accelerate.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -254,8 +260,6 @@
A1E7C4CA188D57F50010896F /* Frameworks */ = { A1E7C4CA188D57F50010896F /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A1F341071908183A00CA7755 /* Accelerate.framework */,
A1F341031908183300CA7755 /* Accelerate.framework */,
A1A499F6189E79EA00E2A2E2 /* AudioToolbox.framework */, A1A499F6189E79EA00E2A2E2 /* AudioToolbox.framework */,
A1C9767618981BFE0057F881 /* AudioUnit.framework */, A1C9767618981BFE0057F881 /* AudioUnit.framework */,
A1E7C507188D62D20010896F /* UIKit.framework */, A1E7C507188D62D20010896F /* UIKit.framework */,
@ -270,6 +274,8 @@
A1E7C4CD188D57F50010896F /* StreamingKit */ = { A1E7C4CD188D57F50010896F /* StreamingKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
95F80F4C1C68EE2300DB24B3 /* STKFloatConverter.h */,
95F80F4D1C68EE2300DB24B3 /* STKFloatConverter.m */,
A1E7C4F1188D5E550010896F /* STKAudioPlayer.h */, A1E7C4F1188D5E550010896F /* STKAudioPlayer.h */,
A1E7C4F2188D5E550010896F /* STKAudioPlayer.m */, A1E7C4F2188D5E550010896F /* STKAudioPlayer.m */,
A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */, A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */,
@ -322,6 +328,22 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */ /* Begin PBXHeadersBuildPhase section */
5B949CD11A1140CF005675A0 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
5B949CD21A1140E4005675A0 /* STKAudioPlayer.h in Headers */,
5B949CD31A1140E4005675A0 /* STKAutoRecoveringHTTPDataSource.h in Headers */,
5B949CD41A1140E4005675A0 /* STKCoreFoundationDataSource.h in Headers */,
5B949CD51A1140E4005675A0 /* STKDataSource.h in Headers */,
5B949CD61A1140E4005675A0 /* STKDataSourceWrapper.h in Headers */,
95F80F4E1C68EE2300DB24B3 /* STKFloatConverter.h in Headers */,
5B949CD71A1140E4005675A0 /* STKHTTPDataSource.h in Headers */,
5B949CD81A1140E4005675A0 /* STKLocalFileDataSource.h in Headers */,
5B949CD91A1140E4005675A0 /* STKQueueEntry.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
A1A49967189E744400E2A2E2 /* Headers */ = { A1A49967189E744400E2A2E2 /* Headers */ = {
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -374,6 +396,7 @@
A1E7C4C4188D57F50010896F /* Sources */, A1E7C4C4188D57F50010896F /* Sources */,
A1E7C4C5188D57F50010896F /* Frameworks */, A1E7C4C5188D57F50010896F /* Frameworks */,
A1E7C4C6188D57F50010896F /* CopyFiles */, A1E7C4C6188D57F50010896F /* CopyFiles */,
5B949CD11A1140CF005675A0 /* Headers */,
); );
buildRules = ( buildRules = (
); );
@ -409,7 +432,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
CLASSPREFIX = STK; CLASSPREFIX = STK;
LastUpgradeCheck = 0510; LastUpgradeCheck = 0710;
ORGANIZATIONNAME = "Thong Nguyen"; ORGANIZATIONNAME = "Thong Nguyen";
}; };
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */; buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
@ -509,6 +532,7 @@
A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */, A1E7C505188D5E550010896F /* STKLocalFileDataSource.m in Sources */,
A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */, A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */,
A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */, A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */,
95F80F4F1C68EE2300DB24B3 /* STKFloatConverter.m in Sources */,
A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */, A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */,
A1E7C502188D5E550010896F /* STKDataSource.m in Sources */, A1E7C502188D5E550010896F /* STKDataSource.m in Sources */,
A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */, A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */,
@ -562,6 +586,7 @@
A1A49988189E744500E2A2E2 /* Debug */ = { A1A49988189E744500E2A2E2 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)", "$(DEVELOPER_FRAMEWORKS_DIR)",
@ -575,6 +600,7 @@
); );
MACOSX_DEPLOYMENT_TARGET = ""; MACOSX_DEPLOYMENT_TARGET = "";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
SDKROOT = macosx; SDKROOT = macosx;
}; };
name = Debug; name = Debug;
@ -582,6 +608,7 @@
A1A49989189E744500E2A2E2 /* Release */ = { A1A49989189E744500E2A2E2 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -592,6 +619,7 @@
GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch";
MACOSX_DEPLOYMENT_TARGET = ""; MACOSX_DEPLOYMENT_TARGET = "";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
SDKROOT = macosx; SDKROOT = macosx;
}; };
name = Release; name = Release;
@ -613,6 +641,7 @@
); );
INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist"; INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.8; MACOSX_DEPLOYMENT_TARGET = 10.8;
PRODUCT_BUNDLE_IDENTIFIER = "com.abstractpath.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;
@ -633,6 +662,7 @@
GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch"; GCC_PREFIX_HEADER = "StreamingKitMac/StreamingKitMac-Prefix.pch";
INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist"; INFOPLIST_FILE = "StreamingKitMacTests/StreamingKitMacTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.8; MACOSX_DEPLOYMENT_TARGET = 10.8;
PRODUCT_BUNDLE_IDENTIFIER = "com.abstractpath.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;
@ -656,6 +686,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@ -719,6 +750,7 @@
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch"; GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
}; };
name = Debug; name = Debug;
@ -735,6 +767,7 @@
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch"; GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = include/StreamingKit;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
}; };
name = Release; name = Release;
@ -754,6 +787,7 @@
"$(inherited)", "$(inherited)",
); );
INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist"; INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;
}; };
@ -770,6 +804,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch"; GCC_PREFIX_HEADER = "StreamingKit/StreamingKit-Prefix.pch";
INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist"; INFOPLIST_FILE = "StreamingKitTests/StreamingKitTests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "abstractpath.com.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = xctest; WRAPPER_EXTENSION = xctest;
}; };

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -8,10 +8,14 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSMutableArray (STKAudioPlayer) @interface NSMutableArray (STKAudioPlayer)
-(void) enqueue:(id)obj; -(void) enqueue:(id)obj;
-(void) skipQueue:(id)obj; -(void) skipQueue:(id)obj;
-(void) skipQueueWithQueue:(NSMutableArray*)queue; -(void) skipQueueWithQueue:(NSMutableArray*)queue;
-(id) dequeue; -(nullable id) dequeue;
-(id) peek; -(nullable id) peek;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -44,7 +44,9 @@
#include "UIKit/UIApplication.h" #include "UIKit/UIApplication.h"
#endif #endif
typedef enum NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSInteger, STKAudioPlayerState)
{ {
STKAudioPlayerStateReady, STKAudioPlayerStateReady,
STKAudioPlayerStateRunning = 1, STKAudioPlayerStateRunning = 1,
@ -54,10 +56,9 @@ typedef enum
STKAudioPlayerStateStopped = (1 << 4), STKAudioPlayerStateStopped = (1 << 4),
STKAudioPlayerStateError = (1 << 5), STKAudioPlayerStateError = (1 << 5),
STKAudioPlayerStateDisposed = (1 << 6) STKAudioPlayerStateDisposed = (1 << 6)
} };
STKAudioPlayerState;
typedef enum typedef NS_ENUM(NSInteger, STKAudioPlayerStopReason)
{ {
STKAudioPlayerStopReasonNone = 0, STKAudioPlayerStopReasonNone = 0,
STKAudioPlayerStopReasonEof, STKAudioPlayerStopReasonEof,
@ -65,10 +66,9 @@ typedef enum
STKAudioPlayerStopReasonPendingNext, STKAudioPlayerStopReasonPendingNext,
STKAudioPlayerStopReasonDisposed, STKAudioPlayerStopReasonDisposed,
STKAudioPlayerStopReasonError = 0xffff STKAudioPlayerStopReasonError = 0xffff
} };
STKAudioPlayerStopReason;
typedef enum typedef NS_ENUM(NSInteger, STKAudioPlayerErrorCode)
{ {
STKAudioPlayerErrorNone = 0, STKAudioPlayerErrorNone = 0,
STKAudioPlayerErrorDataSource, STKAudioPlayerErrorDataSource,
@ -77,9 +77,13 @@ typedef enum
STKAudioPlayerErrorCodecError, STKAudioPlayerErrorCodecError,
STKAudioPlayerErrorDataNotFound, STKAudioPlayerErrorDataNotFound,
STKAudioPlayerErrorOther = 0xffff STKAudioPlayerErrorOther = 0xffff
} };
STKAudioPlayerErrorCode;
///
/// Options to initiailise the Audioplayer with.
/// By default if you set buffer size or seconds to 0, the non-zero default will be used
/// If you would like to disable the buffer option completely set to STK_DISABLE_BUFFER
///
typedef struct typedef struct
{ {
/// If YES then seeking a track will cause all pending items to be flushed from the queue /// If YES then seeking a track will cause all pending items to be flushed from the queue
@ -91,7 +95,7 @@ typedef struct
/// The size of the internal I/O read buffer. This data in this buffer is transient and does not need to be larger. /// 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; UInt32 readBufferSize;
/// The size of the decompressed buffer (Default is 10 seconds which uses about 1.7MB of RAM) /// The size of the decompressed buffer (Default is 10 seconds which uses about 1.7MB of RAM)
UInt32 bufferSizeInSeconds; float 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) /// 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; Float32 secondsRequiredToStartPlaying;
/// Seconds after a seek is performed before data needs to come in (after which the state will change to playing/buffering) /// Seconds after a seek is performed before data needs to come in (after which the state will change to playing/buffering)
@ -101,7 +105,9 @@ typedef struct
} }
STKAudioPlayerOptions; STKAudioPlayerOptions;
typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames); #define STK_DISABLE_BUFFER (0xffffffff)
typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, float* frames);
@interface STKFrameFilterEntry : NSObject @interface STKFrameFilterEntry : NSObject
@property (readonly) NSString* name; @property (readonly) NSString* name;
@ -129,6 +135,8 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
/// Raised when items queued items are cleared (usually because of a call to play, setDataSource or stop) /// Raised when items queued items are cleared (usually because of a call to play, setDataSource or stop)
-(void) audioPlayer:(STKAudioPlayer*)audioPlayer didCancelQueuedItems:(NSArray*)queuedItems; -(void) audioPlayer:(STKAudioPlayer*)audioPlayer didCancelQueuedItems:(NSArray*)queuedItems;
-(void) plotGraphWithBuffer:(float*)buffer andLength:(UInt32)count;
@end @end
@interface STKAudioPlayer : NSObject<STKDataSourceDelegate> @interface STKAudioPlayer : NSObject<STKDataSourceDelegate>
@ -146,16 +154,14 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
@property (readwrite) BOOL meteringEnabled; @property (readwrite) BOOL meteringEnabled;
/// Enables or disables the EQ /// Enables or disables the EQ
@property (readwrite) BOOL equalizerEnabled; @property (readwrite) BOOL equalizerEnabled;
/// Enables or disables the spectrum analyzer (fft)
@property (readwrite) BOOL spectrumAnalyzerEnabled;
/// Returns an array of STKFrameFilterEntry objects representing the filters currently in use /// Returns an array of STKFrameFilterEntry objects representing the filters currently in use
@property (readonly) NSArray* frameFilters; @property (readonly, nullable) NSArray* frameFilters;
/// Returns the items pending to be played (includes buffering and upcoming items but does not include the current item) /// Returns the items pending to be played (includes buffering and upcoming items but does not include the current item)
@property (readonly) NSArray* pendingQueue; @property (readonly) NSArray* pendingQueue;
/// The number of items pending to be played (includes buffering and upcoming items but does not include the current item) /// The number of items pending to be played (includes buffering and upcoming items but does not include the current item)
@property (readonly) NSUInteger pendingQueueCount; @property (readonly) NSUInteger pendingQueueCount;
/// Gets the most recently queued item that is still pending to play /// Gets the most recently queued item that is still pending to play
@property (readonly) NSObject* mostRecentlyQueuedStillPendingItem; @property (readonly, nullable) NSObject* mostRecentlyQueuedStillPendingItem;
/// Gets the current state of the player /// Gets the current state of the player
@property (readwrite) STKAudioPlayerState state; @property (readwrite) STKAudioPlayerState state;
/// Gets the options provided to the player on startup /// Gets the options provided to the player on startup
@ -172,10 +178,10 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
+(STKDataSource*) dataSourceFromURL:(NSURL*)url; +(STKDataSource*) dataSourceFromURL:(NSURL*)url;
/// Initializes a new STKAudioPlayer with the default options /// Initializes a new STKAudioPlayer with the default options
-(id) init; -(instancetype) init;
/// Initializes a new STKAudioPlayer with the given options /// Initializes a new STKAudioPlayer with the given options
-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn; -(instancetype) initWithOptions:(STKAudioPlayerOptions)optionsIn;
/// Plays an item from the given URL string (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 /// The NSString is used as the queue item ID
@ -252,19 +258,19 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
/// Appends a frame filter with the given name and filter block just after the filter with the given name. /// Appends a frame filter with the given name and filter block just after the filter with the given name.
/// If the given name is nil, the filter will be inserted at the beginning of the filter change /// If the given name is nil, the filter will be inserted at the beginning of the filter change
-(void) addFrameFilterWithName:(NSString*)name afterFilterWithName:(NSString*)afterFilterWithName block:(STKFrameFilter)block; -(void) addFrameFilterWithName:(NSString*)name afterFilterWithName:(nullable NSString*)afterFilterWithName block:(STKFrameFilter)block;
/// Reads the peak power in decibals for the given channel (0 or 1). /// Reads the peak power in decibals for the given channel (0 or 1).
/// Return values are between -60 (low) and 0 (high). /// Return values are between -60 (low) and 0 (high).
-(float) peakPowerInDecibelsForChannel:(NSUInteger)channelNumber; //-(float) peakPowerInDecibelsForChannel:(NSUInteger)channelNumber;
/// Reads the average power in decibals for the given channel (0 or 1) /// Reads the average power in decibals for the given channel (0 or 1)
/// Return values are between -60 (low) and 0 (high). /// Return values are between -60 (low) and 0 (high).
-(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber; //-(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber;
/// Sets the gain value (from -96 low to +24 high) for an equalizer band (0 based index) /// Sets the gain value (from -96 low to +24 high) for an equalizer band (0 based index)
-(void) setGain:(float)gain forEqualizerBand:(int)bandIndex; -(void) setGain:(float)gain forEqualizerBand:(int)bandIndex;
-(float) testPowerWithIndex:(int)index;
@end @end
NS_ASSUME_NONNULL_END

600
StreamingKit/StreamingKit/STKAudioPlayer.m Normal file → Executable file
View File

@ -40,8 +40,8 @@
#import "STKQueueEntry.h" #import "STKQueueEntry.h"
#import "NSMutableArray+STKAudioPlayer.h" #import "NSMutableArray+STKAudioPlayer.h"
#import "libkern/OSAtomic.h" #import "libkern/OSAtomic.h"
#include <Accelerate/Accelerate.h>
#import <float.h> #import <float.h>
#import "STKFloatConverter.h"
#ifndef DBL_MAX #ifndef DBL_MAX
#define DBL_MAX 1.7976931348623157e+308 #define DBL_MAX 1.7976931348623157e+308
@ -52,18 +52,21 @@
#define kOutputBus 0 #define kOutputBus 0
#define kInputBus 1 #define kInputBus 1
#define STK_DBMIN (-60) //#define STK_DBMIN (-60)
#define STK_DBOFFSET (-74.0) //#define STK_DBOFFSET (-74.0)
#define STK_LOWPASSFILTERTIMESLICE (0.0005) #define STK_LOWPASSFILTERTIMESLICE (0.0005)
#define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (10) #define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (0.1)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (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_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN (7.5)
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (4096) #define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (4096)
#define STK_DEFAULT_READ_BUFFER_SIZE (64 * 1024) #define STK_DEFAULT_READ_BUFFER_SIZE (128 * 1024)
#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048) #define STK_DEFAULT_PACKET_BUFFER_SIZE (2048)
#define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5) #define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5)
#define OSSTATUS_PRINTF_PLACEHOLDER @"%c%c%c%c"
#define OSSTATUS_PRINTF_VALUE(status) (char)(((status) >> 24) & 0xFF), (char)(((status) >> 16) & 0xFF), (char)(((status) >> 8) & 0xFF), (char)((status) & 0xFF)
#define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]]; #define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]];
static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options) static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
@ -94,6 +97,34 @@ static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
} }
} }
static void NormalizeDisabledBuffers(STKAudioPlayerOptions* options)
{
if (options->bufferSizeInSeconds == STK_DISABLE_BUFFER)
{
options->bufferSizeInSeconds = 0;
}
if (options->readBufferSize == STK_DISABLE_BUFFER)
{
options->readBufferSize = 0;
}
if (options->secondsRequiredToStartPlaying == STK_DISABLE_BUFFER)
{
options->secondsRequiredToStartPlaying = 0;
}
if (options->secondsRequiredToStartPlayingAfterBufferUnderun == STK_DISABLE_BUFFER)
{
options->secondsRequiredToStartPlayingAfterBufferUnderun = 0;
}
if (options->gracePeriodAfterSeekInSeconds == STK_DISABLE_BUFFER)
{
options->gracePeriodAfterSeekInSeconds = 0;
}
}
#define CHECK_STATUS_AND_REPORT(call) \ #define CHECK_STATUS_AND_REPORT(call) \
if ((status = (call))) \ if ((status = (call))) \
{ \ { \
@ -143,7 +174,7 @@ STKAudioPlayerInternalState;
@end @end
@implementation STKFrameFilterEntry @implementation STKFrameFilterEntry
-(id) initWithFilter:(STKFrameFilter)filterIn andName:(NSString*)nameIn -(instancetype) initWithFilter:(STKFrameFilter)filterIn andName:(NSString*)nameIn
{ {
if (self = [super init]) if (self = [super init])
{ {
@ -174,6 +205,7 @@ static AudioComponentDescription nbandUnitDescription;
static AudioComponentDescription outputUnitDescription; static AudioComponentDescription outputUnitDescription;
static AudioComponentDescription convertUnitDescription; static AudioComponentDescription convertUnitDescription;
static AudioStreamBasicDescription canonicalAudioStreamBasicDescription; static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
static AudioStreamBasicDescription recordAudioStreamBasicDescription;
@interface STKAudioPlayer() @interface STKAudioPlayer()
{ {
@ -190,7 +222,6 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
BOOL meteringEnabled; BOOL meteringEnabled;
BOOL equalizerOn; BOOL equalizerOn;
BOOL equalizerEnabled; BOOL equalizerEnabled;
BOOL spectrumAnalyzerEnabled;
STKAudioPlayerOptions options; STKAudioPlayerOptions options;
NSMutableArray* converterNodes; NSMutableArray* converterNodes;
@ -243,7 +274,16 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioFileStreamID audioFileStream; AudioFileStreamID audioFileStream;
NSConditionLock* threadStartedLock; NSConditionLock* threadStartedLock;
NSConditionLock* threadFinishedCondLock; NSConditionLock* threadFinishedCondLock;
AudioFileID recordAudioFileId;
UInt32 recordFilePacketPosition;
AudioConverterRef recordAudioConverterRef;
UInt32 recordOutputBufferSize;
UInt8 *recordOutputBuffer;
UInt32 recordPacketsPerBuffer;
UInt32 recordPacketSize;
AudioStreamPacketDescription *recordPacketDescriptions;
void(^stopBackBackgroundTaskBlock)(); void(^stopBackBackgroundTaskBlock)();
int32_t seekVersion; int32_t seekVersion;
@ -255,19 +295,14 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
pthread_mutex_t mainThreadSyncCallMutex; pthread_mutex_t mainThreadSyncCallMutex;
pthread_cond_t mainThreadSyncCallReadyCondition; pthread_cond_t mainThreadSyncCallReadyCondition;
float* window;
float* obtainedReal;
float* originalReal;
int fftStride;
FFTSetup setupReal;
DSPSplitComplex fftInput;
volatile BOOL waiting; volatile BOOL waiting;
volatile double requestedSeekTime; volatile double requestedSeekTime;
volatile BOOL disposeWasRequested; volatile BOOL disposeWasRequested;
volatile BOOL seekToTimeWasRequested; volatile BOOL seekToTimeWasRequested;
volatile STKAudioPlayerStopReason stopReason; volatile STKAudioPlayerStopReason stopReason;
float **_floatBuffers;
STKFloatConverter *_floatConverter;
} }
@property (readwrite) STKAudioPlayerInternalState internalState; @property (readwrite) STKAudioPlayerInternalState internalState;
@ -302,9 +337,15 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
.componentSubType = kAudioUnitSubType_AUConverter, .componentSubType = kAudioUnitSubType_AUConverter,
.componentFlags = 0, .componentFlags = 0,
.componentFlagsMask = 0 .componentFlagsMask = 0
}; };
#ifdef CA_CANONICAL_DEPRECATED
const int bytesPerSample = sizeof(SInt16);
#elif __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
const int bytesPerSample = sizeof(SInt16);
#else
const int bytesPerSample = sizeof(AudioSampleType); const int bytesPerSample = sizeof(AudioSampleType);
#endif
canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription) canonicalAudioStreamBasicDescription = (AudioStreamBasicDescription)
{ {
@ -317,7 +358,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
.mBitsPerChannel = 8 * bytesPerSample, .mBitsPerChannel = 8 * bytesPerSample,
.mBytesPerPacket = (bytesPerSample * 2) .mBytesPerPacket = (bytesPerSample * 2)
}; };
outputUnitDescription = (AudioComponentDescription) outputUnitDescription = (AudioComponentDescription)
{ {
.componentType = kAudioUnitType_Output, .componentType = kAudioUnitType_Output,
@ -467,12 +508,12 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
} }
} }
-(id) init -(instancetype) init
{ {
return [self initWithOptions:(STKAudioPlayerOptions){}]; return [self initWithOptions:(STKAudioPlayerOptions){}];
} }
-(id) initWithOptions:(STKAudioPlayerOptions)optionsIn -(instancetype) initWithOptions:(STKAudioPlayerOptions)optionsIn
{ {
if (self = [super init]) if (self = [super init])
{ {
@ -480,9 +521,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self->volume = 1.0; self->volume = 1.0;
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0; self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0;
self->spectrumAnalyzerEnabled = NO;
PopulateOptionsWithDefault(&options); PopulateOptionsWithDefault(&options);
NormalizeDisabledBuffers(&options);
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying; framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun; framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
@ -518,7 +559,21 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
upcomingQueue = [[NSMutableArray alloc] init]; upcomingQueue = [[NSMutableArray alloc] init];
bufferingQueue = [[NSMutableArray alloc] init]; bufferingQueue = [[NSMutableArray alloc] init];
//initialie the float converter
// Allocate the float buffers
_floatConverter = [[STKFloatConverter alloc] initWithSourceFormat:canonicalAudioStreamBasicDescription];
size_t sizeToAllocate = sizeof(float*) * canonicalAudioStreamBasicDescription.mChannelsPerFrame;
sizeToAllocate = MAX(8, sizeToAllocate);
_floatBuffers = (float**)malloc( sizeToAllocate );
UInt32 outputBufferSize = 32 * 1024; // 32 KB
for ( int i=0; i< canonicalAudioStreamBasicDescription.mChannelsPerFrame; i++ ) {
_floatBuffers[i] = (float*)malloc(outputBufferSize);
}
[self resetPcmBuffers]; [self resetPcmBuffers];
[self createAudioGraph]; [self createAudioGraph];
[self createPlaybackThread]; [self createPlaybackThread];
@ -549,6 +604,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
OSSpinLockUnlock(&currentEntryReferencesLock); OSSpinLockUnlock(&currentEntryReferencesLock);
} }
[self closeRecordAudioFile];
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed]; [self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed];
[self clearQueue]; [self clearQueue];
@ -589,9 +646,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
pthread_cond_destroy(&mainThreadSyncCallReadyCondition); pthread_cond_destroy(&mainThreadSyncCallReadyCondition);
free(readBuffer); free(readBuffer);
free(originalReal); free(pcmAudioBufferList.mBuffers[0].mData);
free(obtainedReal);
free(window);
} }
-(void) startSystemBackgroundTask -(void) startSystemBackgroundTask
@ -819,7 +874,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
pthread_mutex_lock(&playerMutex); pthread_mutex_lock(&playerMutex);
entryToUpdate->audioStreamBasicDescription = newBasicDescription; if (entryToUpdate->audioStreamBasicDescription.mFormatID == 0)
{
entryToUpdate->audioStreamBasicDescription = newBasicDescription;
}
entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate; entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate;
entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate; entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate;
@ -866,7 +925,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
} }
case kAudioFileStreamProperty_ReadyToProducePackets: case kAudioFileStreamProperty_ReadyToProducePackets:
{ {
if (!audioConverterAudioStreamBasicDescription.mFormatID == kAudioFormatLinearPCM) if (audioConverterAudioStreamBasicDescription.mFormatID != kAudioFormatLinearPCM)
{ {
discontinuous = YES; discontinuous = YES;
} }
@ -900,13 +959,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2) if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2)
{ {
//
// We've found HE-AAC, remember this to tell the audio queue
// when we construct it.
//
#if !TARGET_IPHONE_SIMULATOR
currentlyReadingEntry->audioStreamBasicDescription = pasbd; currentlyReadingEntry->audioStreamBasicDescription = pasbd;
#endif
break; break;
} }
} }
@ -1110,6 +1164,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]]; [currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]];
[currentlyReadingEntry.dataSource seekToOffset:0]; [currentlyReadingEntry.dataSource seekToOffset:0];
[self closeRecordAudioFile];
if (startPlaying) if (startPlaying)
{ {
if (clearQueue) if (clearQueue)
@ -1412,6 +1468,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
currentlyReadingEntry = nil; currentlyReadingEntry = nil;
OSSpinLockUnlock(&currentEntryReferencesLock); OSSpinLockUnlock(&currentEntryReferencesLock);
pthread_mutex_unlock(&playerMutex); pthread_mutex_unlock(&playerMutex);
[self closeRecordAudioFile];
self.internalState = STKAudioPlayerInternalStateDisposed; self.internalState = STKAudioPlayerInternalStateDisposed;
@ -1474,6 +1532,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
AudioConverterReset(audioConverterRef); AudioConverterReset(audioConverterRef);
} }
if (recordAudioConverterRef)
{
AudioConverterReset(recordAudioConverterRef);
}
[currentEntry reset]; [currentEntry reset];
[currentEntry.dataSource seekToOffset:seekByteOffset]; [currentEntry.dataSource seekToOffset:seekByteOffset];
@ -1592,7 +1655,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
} }
NSObject* queueItemId = currentlyReadingEntry.queueItemId; NSObject* queueItemId = currentlyReadingEntry.queueItemId;
[self closeRecordAudioFile];
[self dispatchSyncOnMainThread:^ [self dispatchSyncOnMainThread:^
{ {
[self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId];
@ -1698,10 +1763,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self->pcmBufferFrameStartIndex = 0; self->pcmBufferFrameStartIndex = 0;
self->pcmBufferUsedFrameCount = 0; self->pcmBufferUsedFrameCount = 0;
self->peakPowerDb[0] = STK_DBMIN; // self->peakPowerDb[0] = STK_DBMIN;
self->peakPowerDb[1] = STK_DBMIN; // self->peakPowerDb[1] = STK_DBMIN;
self->averagePowerDb[0] = STK_DBMIN; // self->averagePowerDb[0] = STK_DBMIN;
self->averagePowerDb[1] = STK_DBMIN; // self->averagePowerDb[1] = STK_DBMIN;
OSSpinLockUnlock(&pcmBufferSpinLock); OSSpinLockUnlock(&pcmBufferSpinLock);
} }
@ -1717,6 +1782,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
return; return;
} }
[self closeRecordAudioFile];
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonUserAction]; [self stopAudioUnitWithReason:STKAudioPlayerStopReasonUserAction];
[self resetPcmBuffers]; [self resetPcmBuffers];
@ -1811,6 +1878,35 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.muted = NO; self.muted = NO;
} }
-(void) closeRecordAudioFile
{
if (recordAudioFileId)
{
AudioFileClose(recordAudioFileId);
recordAudioFileId = NULL;
}
if (recordAudioConverterRef)
{
AudioConverterDispose(recordAudioConverterRef);
recordAudioConverterRef = nil;
}
if (recordOutputBuffer)
{
free(recordOutputBuffer);
recordOutputBuffer = NULL;
}
if (recordPacketDescriptions)
{
free(recordPacketDescriptions);
recordPacketDescriptions = NULL;
}
recordFilePacketPosition = 0;
}
-(void) dispose -(void) dispose
{ {
[self stop]; [self stop];
@ -1883,17 +1979,42 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
{ {
OSStatus status; OSStatus status;
Boolean writable; Boolean writable;
UInt32 cookieSize; UInt32 cookieSize = 0;
if (memcmp(asbd, &audioConverterAudioStreamBasicDescription, sizeof(AudioStreamBasicDescription)) == 0) if (memcmp(asbd, &audioConverterAudioStreamBasicDescription, sizeof(AudioStreamBasicDescription)) == 0)
{ {
AudioConverterReset(audioConverterRef); AudioConverterReset(audioConverterRef);
if (recordAudioConverterRef)
{
AudioConverterReset(recordAudioConverterRef);
}
return; return;
} }
[self destroyAudioConverter]; [self destroyAudioConverter];
BOOL isRecording = currentlyReadingEntry.dataSource.recordToFileUrl != nil;
if (isRecording)
{
recordAudioStreamBasicDescription = (AudioStreamBasicDescription)
{
.mFormatID = kAudioFormatMPEG4AAC,
.mFormatFlags = kMPEG4Object_AAC_LC,
.mChannelsPerFrame = canonicalAudioStreamBasicDescription.mChannelsPerFrame,
.mSampleRate = canonicalAudioStreamBasicDescription.mSampleRate,
};
UInt32 dataSize = sizeof(recordAudioStreamBasicDescription);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
0,
NULL,
&dataSize,
&recordAudioStreamBasicDescription);
}
AudioClassDescription classDesc; AudioClassDescription classDesc;
if (GetHardwareCodecClassDesc(asbd->mFormatID, &classDesc)) if (GetHardwareCodecClassDesc(asbd->mFormatID, &classDesc))
@ -1912,14 +2033,29 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return; return;
} }
} }
if (isRecording && !recordAudioConverterRef)
{
status = AudioConverterNew(&canonicalAudioStreamBasicDescription, &recordAudioStreamBasicDescription, &recordAudioConverterRef);
if (status)
{
NSLog(@"STKAudioPlayer failed to create a recording audio converter");
}
}
audioConverterAudioStreamBasicDescription = *asbd; audioConverterAudioStreamBasicDescription = *asbd;
status = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); if (self->currentlyReadingEntry.dataSource.audioFileTypeHint != kAudioFileAAC_ADTSType)
{
status = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
if (!status) if (status)
{ {
void* cookieData = alloca(cookieSize); return;
}
void* cookieData = alloca(cookieSize);
status = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); status = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);
@ -1932,9 +2068,87 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
if (status) if (status)
{ {
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return; return;
} }
} }
if (recordAudioConverterRef)
{
if (recordAudioFileId)
{
AudioFileClose(recordAudioFileId);
recordAudioFileId = NULL;
}
if (recordOutputBuffer)
{
free(recordOutputBuffer);
recordOutputBuffer = NULL;
}
if (recordPacketDescriptions)
{
free(recordPacketDescriptions);
recordPacketDescriptions = NULL;
}
recordOutputBufferSize = 32 * 1024;
recordPacketSize = canonicalAudioStreamBasicDescription.mBytesPerPacket;
if (recordPacketSize == 0)
{
UInt32 size = sizeof(recordPacketSize);
if (0 == AudioConverterGetProperty(recordAudioConverterRef, kAudioConverterPropertyMaximumOutputPacketSize, &size, &recordPacketSize))
{
if (recordPacketSize > recordOutputBufferSize)
{
recordOutputBufferSize = recordPacketSize;
}
recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize;
}
else
{
AudioConverterDispose(recordAudioConverterRef);
recordAudioConverterRef = NULL;
NSLog(@"STKAudioPlayer: Can't support this output format for recording");
}
}
else
{
recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize;
}
UInt32 propertySize = sizeof(UInt32);
UInt32 externallyFramed = 0;
OSStatus error = AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed, sizeof(recordAudioStreamBasicDescription), &recordAudioStreamBasicDescription, &propertySize, &externallyFramed);
if (externallyFramed)
{
recordPacketDescriptions = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * recordPacketsPerBuffer);
}
recordOutputBuffer = (UInt8 *)malloc(sizeof(UInt8) * recordOutputBufferSize);
error = AudioFileCreateWithURL(
(__bridge CFURLRef)(currentlyReadingEntry.dataSource.recordToFileUrl),
kAudioFileCAFType,
&recordAudioStreamBasicDescription,
kAudioFileFlags_EraseFile,
&recordAudioFileId);
recordFilePacketPosition = 0;
if (error)
{
NSLog(@"STKAudioPlayer failed to create a recording audio file at %@", currentlyReadingEntry.dataSource.recordToFileUrl);
[self closeRecordAudioFile];
}
}
} }
-(void) createOutputUnit -(void) createOutputUnit
@ -2245,6 +2459,9 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
} }
else if (!isRunning) else if (!isRunning)
{ {
stopReason = stopReasonIn;
self.internalState = STKAudioPlayerInternalStateStopped;
return; return;
} }
@ -2426,6 +2643,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL); status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL);
framesAdded = framesToDecode; framesAdded = framesToDecode;
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
{
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
}
if (status == 100) if (status == 100)
{ {
@ -2469,6 +2691,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
framesAdded += framesToDecode; framesAdded += framesToDecode;
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
{
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
}
if (status == 100) if (status == 100)
{ {
OSSpinLockLock(&pcmBufferSpinLock); OSSpinLockLock(&pcmBufferSpinLock);
@ -2513,6 +2740,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
framesAdded = framesToDecode; framesAdded = framesToDecode;
if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef)
{
[self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]];
}
if (status == 100) if (status == 100)
{ {
OSSpinLockLock(&pcmBufferSpinLock); OSSpinLockLock(&pcmBufferSpinLock);
@ -2547,6 +2779,66 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
} }
} }
- (void)handleRecordingOfAudioPackets:(UInt32)numberOfPackets audioBuffer:(AudioBuffer *)audioBuffer
{
if (recordAudioFileId && recordAudioConverterRef)
{
AudioConvertInfo recordConvertInfo;
recordConvertInfo.done = NO;
recordConvertInfo.numberOfPackets = numberOfPackets;
recordConvertInfo.packetDescriptions = NULL;
recordConvertInfo.audioBuffer = *audioBuffer;
AudioBufferList convertedData;
convertedData.mNumberBuffers = 1;
convertedData.mBuffers[0].mNumberChannels = recordAudioStreamBasicDescription.mChannelsPerFrame;
convertedData.mBuffers[0].mDataByteSize = recordOutputBufferSize;
convertedData.mBuffers[0].mData = recordOutputBuffer;
UInt32 ioOutputDataPackets;
OSStatus status;
while (1)
{
ioOutputDataPackets = recordPacketsPerBuffer;
status = AudioConverterFillComplexBuffer(recordAudioConverterRef, AudioConverterCallback, (void*)&recordConvertInfo, &ioOutputDataPackets, &convertedData, recordPacketDescriptions);
if (status == 100 || status == 0)
{
if (ioOutputDataPackets > 0)
{
OSStatus writeError = AudioFileWritePackets(recordAudioFileId,
NO,
convertedData.mBuffers[0].mDataByteSize,
recordPacketDescriptions,
recordFilePacketPosition,
&ioOutputDataPackets,
convertedData.mBuffers[0].mData);
if (writeError)
{
NSLog(@"STKAudioPlayer:handleRecordingOfAudioPackets failed on AudioFileWritePackets with error \"" OSSTATUS_PRINTF_PLACEHOLDER "\"", OSSTATUS_PRINTF_VALUE(writeError));
}
else
{
recordFilePacketPosition += ioOutputDataPackets;
}
}
}
else
{
NSLog(@"STKAudioPlayer: Unexpected error during recording audio file conversion");
}
if (status == 100)
{
break;
}
}
}
}
static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
{ {
STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon; STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon;
@ -2926,115 +3218,6 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
return averagePowerDb[channelNumber]; return averagePowerDb[channelNumber];
} }
-(BOOL) spectrumAnalyzerEnabled
{
return self->spectrumAnalyzerEnabled;
}
-(void) setSpectrumAnalyzerEnabled:(BOOL)value
{
if (self->spectrumAnalyzerEnabled == value)
{
return;
}
self->spectrumAnalyzerEnabled = value;
if (!value)
{
[self removeFrameFilterWithName:@"STKSpectrumAnalyzerFilter"];
}
else
{
if (!obtainedReal)
{
int maxSamples = 4096;
int log2n = log2f(maxSamples);
int n = 1 << log2n;
fftStride = 1;
int nOver2 = maxSamples / 2;
fftInput.realp = (float*)calloc(nOver2, sizeof(float));
fftInput.imagp =(float*)calloc(nOver2, sizeof(float));
obtainedReal = (float*)calloc(n, sizeof(float));
originalReal = (float*)calloc(n, sizeof(float));
window = (float*)calloc(maxSamples, sizeof(float));
vDSP_blkman_window(window, maxSamples, 0);
setupReal = vDSP_create_fftsetup(log2n, FFT_RADIX2);
}
[self appendFrameFilterWithName:@"STKSpectrumAnalyzerFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames)
{
int log2n = log2f(frameCount);
int n = 1 << log2n;
int nOver2 = frameCount / 2;
SInt16* samples16 = (SInt16*)frames;
SInt32* samples32 = (SInt32*)frames;
if (bytesPerFrame / channelsPerFrame == 2)
{
for (int i = 0, j = 0; i < frameCount * channelsPerFrame; i+= channelsPerFrame, j += 2)
{
originalReal[j] = samples16[i] / 32768.0;
}
}
else if (bytesPerFrame / channelsPerFrame == 4)
{
for (int i = 0, j = 0; i < frameCount * channelsPerFrame; i+= channelsPerFrame, j+= 2)
{
originalReal[j] = samples32[i] / 32768.0;
}
}
vDSP_hann_window(window, n, 0);
vDSP_vmul(originalReal, 2, window, 1, originalReal, 2, n);
vDSP_ctoz((COMPLEX*)originalReal, 2, &fftInput, 1, nOver2);
vDSP_fft_zrip(setupReal, &fftInput, fftStride, log2n, FFT_FORWARD);
float one = 1;
float scale = (float)1.0 / (2 * n);
vDSP_vsmul(fftInput.realp, 1, &scale, fftInput.realp, 1, nOver2);
vDSP_vsmul(fftInput.imagp, 1, &scale, fftInput.imagp, 1, nOver2);
pthread_mutex_lock(&self->playerMutex);
vDSP_zvmags(&fftInput, 1, obtainedReal, 1, nOver2);
vDSP_vdbcon(obtainedReal, 1, &one, obtainedReal, 1, nOver2, 0);
float vmin = -96;
float vmax = 0;
vDSP_vclip(obtainedReal, 1, &vmin, &vmax, obtainedReal, 1, nOver2);
pthread_mutex_unlock(&self->playerMutex);
}];
}
}
-(float) testPowerWithIndex:(int)index
{
pthread_mutex_lock(&self->playerMutex);
if (!obtainedReal)
{
pthread_mutex_unlock(&self->playerMutex);
return 0;
}
float retval = obtainedReal[index];
pthread_mutex_unlock(&self->playerMutex);
return retval;
}
-(BOOL) meteringEnabled -(BOOL) meteringEnabled
{ {
return self->meteringEnabled; return self->meteringEnabled;
@ -3064,69 +3247,74 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{ {
return; return;
} }
self->meteringEnabled = value;
if (!value) if (!value)
{ {
[self removeFrameFilterWithName:@"STKMeteringFilter"]; [self removeFrameFilterWithName:@"STKMeteringFilter"];
self->meteringEnabled = NO;
} }
else else
{ {
[self appendFrameFilterWithName:@"STKMeteringFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, void* frames) [self appendFrameFilterWithName:@"STKMeteringFilter" block:^(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 frameCount, float* frames)
{ {
SInt16* samples16 = (SInt16*)frames; STKFloatConverterToFloat(_floatConverter,&(pcmAudioBufferList),_floatBuffers,frameCount);
SInt32* samples32 = (SInt32*)frames;
UInt32 countLeft = 0; if ([self.delegate respondsToSelector:@selector(plotGraphWithBuffer:andLength:)]) {
UInt32 countRight = 0; [self.delegate plotGraphWithBuffer:*(_floatBuffers) andLength:frameCount];
Float32 decibelsLeft = STK_DBMIN; }
Float32 peakValueLeft = STK_DBMIN;
Float64 totalValueLeft = 0; // SInt16* samples16 = (SInt16*)frames;
Float32 previousFilteredValueOfSampleAmplitudeLeft = 0; // SInt32* samples32 = (SInt32*)frames;
Float32 decibelsRight = STK_DBMIN; // UInt32 countLeft = 0;
Float32 peakValueRight = STK_DBMIN; // UInt32 countRight = 0;
Float64 totalValueRight = 0; // Float32 decibelsLeft = STK_DBMIN;
Float32 previousFilteredValueOfSampleAmplitudeRight = 0; // Float32 peakValueLeft = STK_DBMIN;
// Float64 totalValueLeft = 0;
if (bytesPerFrame / channelsPerFrame == 2) // Float32 previousFilteredValueOfSampleAmplitudeLeft = 0;
{ // Float32 decibelsRight = STK_DBMIN;
for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame) // Float32 peakValueRight = STK_DBMIN;
{ // Float64 totalValueRight = 0;
Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples16[i]); // Float32 previousFilteredValueOfSampleAmplitudeRight = 0;
Float32 absoluteValueOfSampleAmplitudeRight = abs(samples16[i + 1]); //
// if (bytesPerFrame / channelsPerFrame == 2)
CALCULATE_METER(Left); // {
CALCULATE_METER(Right); // for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame)
} // {
} // Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples16[i]);
else if (bytesPerFrame / channelsPerFrame == 4) // Float32 absoluteValueOfSampleAmplitudeRight = abs(samples16[i + 1]);
{ //
for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame) // CALCULATE_METER(Left);
{ // CALCULATE_METER(Right);
Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples32[i]) / 32768.0; // }
Float32 absoluteValueOfSampleAmplitudeRight = abs(samples32[i + 1]) / 32768.0; // }
// else if (bytesPerFrame / channelsPerFrame == 4)
CALCULATE_METER(Left); // {
CALCULATE_METER(Right); // for (int i = 0; i < frameCount * channelsPerFrame; i += channelsPerFrame)
} // {
} // Float32 absoluteValueOfSampleAmplitudeLeft = abs(samples32[i]) / 32768.0;
else // Float32 absoluteValueOfSampleAmplitudeRight = abs(samples32[i + 1]) / 32768.0;
{ //
return; // CALCULATE_METER(Left);
} // CALCULATE_METER(Right);
// }
peakPowerDb[0] = MIN(MAX(decibelsLeft, -60), 0); // }
peakPowerDb[1] = MIN(MAX(decibelsRight, -60), 0); // else
// {
if (countLeft > 0) // return;
{ // }
averagePowerDb[0] = MIN(MAX(totalValueLeft / frameCount, -60), 0); //
} // peakPowerDb[0] = MIN(MAX(decibelsLeft, -60), 0);
// peakPowerDb[1] = MIN(MAX(decibelsRight, -60), 0);
if (countRight != 0) //
{ // if (countLeft > 0)
averagePowerDb[1] = MIN(MAX(totalValueRight / frameCount, -60), 0); // {
} // averagePowerDb[0] = MIN(MAX(totalValueLeft / frameCount, -60), 0);
// }
//
// if (countRight != 0)
// {
// averagePowerDb[1] = MIN(MAX(totalValueRight / frameCount, -60), 0);
// }
}]; }];
} }
} }

View File

@ -36,6 +36,8 @@
#import "STKHTTPDataSource.h" #import "STKHTTPDataSource.h"
#import "STKDataSourceWrapper.h" #import "STKDataSourceWrapper.h"
NS_ASSUME_NONNULL_BEGIN
typedef struct typedef struct
{ {
int watchdogPeriodSeconds; int watchdogPeriodSeconds;
@ -45,8 +47,10 @@ STKAutoRecoveringHTTPDataSourceOptions;
@interface STKAutoRecoveringHTTPDataSource : STKDataSourceWrapper @interface STKAutoRecoveringHTTPDataSource : STKDataSourceWrapper
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource; -(instancetype) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource;
@property (readonly) STKHTTPDataSource* innerDataSource; @property (readonly) STKHTTPDataSource* innerDataSource;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -101,22 +101,24 @@ static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* o
@implementation STKAutoRecoveringHTTPDataSource @implementation STKAutoRecoveringHTTPDataSource
@dynamic innerDataSource;
-(STKHTTPDataSource*) innerHTTPDataSource -(STKHTTPDataSource*) innerHTTPDataSource
{ {
return (STKHTTPDataSource*)self.innerDataSource; return (STKHTTPDataSource*)self.innerDataSource;
} }
-(id) initWithDataSource:(STKDataSource *)innerDataSource -(instancetype) initWithDataSource:(STKDataSource *)innerDataSource
{ {
return [self initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource]; return [self initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource];
} }
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn -(instancetype) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn
{ {
return [self initWithHTTPDataSource:innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions){}]; return [self initWithHTTPDataSource:innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions){}];
} }
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions)optionsIn -(instancetype) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions)optionsIn
{ {
if (self = [super initWithDataSource:innerDataSourceIn]) if (self = [super initWithDataSource:innerDataSourceIn])
{ {
@ -369,16 +371,15 @@ static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* o
[self.delegate dataSourceEof:self]; [self.delegate dataSourceEof:self];
} }
-(void) dataSourceErrorOccured:(STKDataSource*)dataSource - (void)dataSourceErrorOccured:(STKDataSource*)dataSource {
{
NSLog(@"dataSourceErrorOccured"); NSLog(@"dataSourceErrorOccured");
if (self.innerDataSource.httpStatusCode == 416 /* Range out of bounds */) /* Range out of bounds */
{ if (self.innerDataSource.httpStatusCode == 416) {
[super dataSourceEof:dataSource]; [super dataSourceEof:dataSource];
} } else if ([self hasGotNetworkConnection]) {
else [super dataSourceErrorOccured:dataSource];
{ } else {
[self processRetryOnError]; [self processRetryOnError];
} }
} }

View File

@ -34,6 +34,8 @@
#import "STKDataSource.h" #import "STKDataSource.h"
NS_ASSUME_NONNULL_BEGIN
@class STKCoreFoundationDataSource; @class STKCoreFoundationDataSource;
@interface CoreFoundationDataSourceClientInfo : NSObject @interface CoreFoundationDataSourceClientInfo : NSObject
@ -43,9 +45,10 @@
@interface STKCoreFoundationDataSource : STKDataSource @interface STKCoreFoundationDataSource : STKDataSource
{ {
@public
CFReadStreamRef stream;
@protected @protected
BOOL isInErrorState; BOOL isInErrorState;
CFReadStreamRef stream;
NSRunLoop* eventsRunLoop; NSRunLoop* eventsRunLoop;
} }
@ -61,3 +64,5 @@
-(CFStreamStatus) status; -(CFStreamStatus) status;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -41,8 +41,10 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
switch (eventType) switch (eventType)
{ {
case kCFStreamEventErrorOccurred: case kCFStreamEventErrorOccurred:
{
[datasource errorOccured]; [datasource errorOccured];
break; break;
}
case kCFStreamEventEndEncountered: case kCFStreamEventEndEncountered:
[datasource eof]; [datasource eof];
break; break;
@ -135,8 +137,6 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
{ {
CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL); CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL);
CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes); CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
eventsRunLoop = nil;
} }
} }

9
StreamingKit/StreamingKit/STKDataSource.h Normal file → Executable file
View File

@ -35,6 +35,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <AudioToolbox/AudioToolbox.h> #include <AudioToolbox/AudioToolbox.h>
NS_ASSUME_NONNULL_BEGIN
@class STKDataSource; @class STKDataSource;
@protocol STKDataSourceDelegate<NSObject> @protocol STKDataSourceDelegate<NSObject>
@ -45,10 +47,13 @@
@interface STKDataSource : NSObject @interface STKDataSource : NSObject
@property (readonly) BOOL supportsSeek;
@property (readonly) SInt64 position; @property (readonly) SInt64 position;
@property (readonly) SInt64 length; @property (readonly) SInt64 length;
@property (readonly) BOOL hasBytesAvailable; @property (readonly) BOOL hasBytesAvailable;
@property (readwrite, unsafe_unretained) id<STKDataSourceDelegate> delegate; @property (nonatomic, readwrite, assign) double durationHint;
@property (readwrite, unsafe_unretained, nullable) id<STKDataSourceDelegate> delegate;
@property (nonatomic, strong, nullable) NSURL *recordToFileUrl;
-(BOOL) registerForEvents:(NSRunLoop*)runLoop; -(BOOL) registerForEvents:(NSRunLoop*)runLoop;
-(void) unregisterForEvents; -(void) unregisterForEvents;
@ -59,3 +64,5 @@
-(AudioFileTypeID) audioFileTypeHint; -(AudioFileTypeID) audioFileTypeHint;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -79,4 +79,9 @@
return 0; return 0;
} }
-(BOOL) supportsSeek
{
return YES;
}
@end @end

View File

@ -34,10 +34,14 @@
#import "STKDataSource.h" #import "STKDataSource.h"
NS_ASSUME_NONNULL_BEGIN
@interface STKDataSourceWrapper : STKDataSource<STKDataSourceDelegate> @interface STKDataSourceWrapper : STKDataSource<STKDataSourceDelegate>
-(id) initWithDataSource:(STKDataSource*)innerDataSource; -(instancetype) initWithDataSource:(STKDataSource*)innerDataSource;
@property (readonly) STKDataSource* innerDataSource; @property (readonly) STKDataSource* innerDataSource;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -40,7 +40,7 @@
@implementation STKDataSourceWrapper @implementation STKDataSourceWrapper
-(id) initWithDataSource:(STKDataSource*)innerDataSourceIn -(instancetype) initWithDataSource:(STKDataSource*)innerDataSourceIn
{ {
if (self = [super init]) if (self = [super init])
{ {

View File

@ -0,0 +1,124 @@
//
// AEFloatConverter.h
// The Amazing Audio Engine
//
// Created by Michael Tyson on 25/10/2012.
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#ifdef __cplusplus
extern "C" {
#endif
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
/*!
* Universal converter to float format
*
* Use this class to easily convert arbitrary audio formats to floating point
* for use with utilities like the Accelerate framework.
*/
@interface STKFloatConverter : NSObject
/*!
* Initialize
*
* @param sourceFormat The audio format to use
*/
- (id)initWithSourceFormat:(AudioStreamBasicDescription)sourceFormat;
/*!
* Convert audio to floating-point
*
* This C function, safe to use in a Core Audio realtime thread context, will take
* an audio buffer list of audio in the format you provided at initialisation, and
* convert it into a noninterleaved float array.
*
* @param converter Pointer to the converter object.
* @param sourceBuffer An audio buffer list containing the source audio.
* @param targetBuffers An array of floating-point arrays to store the converted float audio into.
* Note that you must provide the correct number of arrays, to match the number of channels.
* @param frames The number of frames to convert.
* @return YES on success; NO on failure
*/
BOOL STKFloatConverterToFloat(STKFloatConverter* converter, AudioBufferList *sourceBuffer, float * const * targetBuffers, UInt32 frames);
/*!
* Convert audio to floating-point, in a buffer list
*
* This C function, safe to use in a Core Audio realtime thread context, will take
* an audio buffer list of audio in the format you provided at initialisation, and
* convert it into a noninterleaved float format.
*
* @param converter Pointer to the converter object.
* @param sourceBuffer An audio buffer list containing the source audio.
* @param targetBuffer An audio buffer list to store the converted floating-point audio.
* @param frames The number of frames to convert.
* @return YES on success; NO on failure
*/
BOOL STKFloatConverterToFloatBufferList(STKFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames);
/*!
* Convert audio from floating-point
*
* This C function, safe to use in a Core Audio realtime thread context, will take
* an audio buffer list of audio in the format you provided at initialisation, and
* convert it into a float array.
*
* @param converter Pointer to the converter object.
* @param sourceBuffers An array of floating-point arrays containing the floating-point audio to convert.
* Note that you must provide the correct number of arrays, to match the number of channels.
* @param targetBuffer An audio buffer list to store the converted audio into.
* @param frames The number of frames to convert.
* @return YES on success; NO on failure
*/
BOOL STKFloatConverterFromFloat(STKFloatConverter* converter, float * const * sourceBuffers, AudioBufferList *targetBuffer, UInt32 frames);
/*!
* Convert audio from floating-point, in a buffer list
*
* This C function, safe to use in a Core Audio realtime thread context, will take
* an audio buffer list of audio in the format you provided at initialisation, and
* convert it into a float array.
*
* @param converter Pointer to the converter object.
* @param sourceBuffer An audio buffer list containing the source audio.
* @param targetBuffer An audio buffer list to store the converted audio into.
* @param frames The number of frames to convert.
* @return YES on success; NO on failure
*/
BOOL STKFloatConverterFromFloatBufferList(STKFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames);
/*!
* The AudioStreamBasicDescription representing the converted floating-point format
*/
@property (nonatomic, readonly) AudioStreamBasicDescription floatingPointAudioDescription;
/*!
* The source audio format set at initialization
*/
@property (nonatomic, readonly) AudioStreamBasicDescription sourceFormat;
@end
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,211 @@
//
// AEFloatConverter.m
// The Amazing Audio Engine
//
// Created by Michael Tyson on 25/10/2012.
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import "STKFloatConverter.h"
#define checkResult(result,operation) (_checkResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__))
static inline BOOL _checkResult(OSStatus result, const char *operation, const char* file, int line) {
if ( result != noErr ) {
NSLog(@"%s:%d: %s result %d %08X %4.4s", file, line, operation, (int)result, (int)result, (char*)&result);
return NO;
}
return YES;
}
#define kNoMoreDataErr -2222
struct complexInputDataProc_t {
AudioBufferList *sourceBuffer;
};
@interface STKFloatConverter () {
AudioStreamBasicDescription _sourceAudioDescription;
AudioStreamBasicDescription _floatAudioDescription;
AudioConverterRef _toFloatConverter;
AudioConverterRef _fromFloatConverter;
AudioBufferList *_scratchFloatBufferList;
}
static OSStatus complexInputDataProc(AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData);
@end
@implementation STKFloatConverter
@synthesize sourceFormat = _sourceAudioDescription;
-(id)initWithSourceFormat:(AudioStreamBasicDescription)sourceFormat {
if ( !(self = [super init]) ) return nil;
_floatAudioDescription.mFormatID = kAudioFormatLinearPCM;
_floatAudioDescription.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
_floatAudioDescription.mChannelsPerFrame = sourceFormat.mChannelsPerFrame;
_floatAudioDescription.mBytesPerPacket = sizeof(float);
_floatAudioDescription.mFramesPerPacket = 1;
_floatAudioDescription.mBytesPerFrame = sizeof(float);
_floatAudioDescription.mBitsPerChannel = 8 * sizeof(float);
_floatAudioDescription.mSampleRate = sourceFormat.mSampleRate;
_sourceAudioDescription = sourceFormat;
if ( memcmp(&sourceFormat, &_floatAudioDescription, sizeof(AudioStreamBasicDescription)) != 0 ) {
checkResult(AudioConverterNew(&sourceFormat, &_floatAudioDescription, &_toFloatConverter), "AudioConverterNew");
checkResult(AudioConverterNew(&_floatAudioDescription, &sourceFormat, &_fromFloatConverter), "AudioConverterNew");
_scratchFloatBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList) + (_floatAudioDescription.mChannelsPerFrame-1)*sizeof(AudioBuffer));
_scratchFloatBufferList->mNumberBuffers = _floatAudioDescription.mChannelsPerFrame;
for ( int i=0; i<_scratchFloatBufferList->mNumberBuffers; i++ ) {
_scratchFloatBufferList->mBuffers[i].mNumberChannels = 1;
}
}
return self;
}
-(void)dealloc {
if ( _toFloatConverter ) AudioConverterDispose(_toFloatConverter);
if ( _fromFloatConverter ) AudioConverterDispose(_fromFloatConverter);
if ( _scratchFloatBufferList ) free(_scratchFloatBufferList);
// [super dealloc];
}
BOOL STKFloatConverterToFloat(STKFloatConverter* THIS, AudioBufferList *sourceBuffer, float * const * targetBuffers, UInt32 frames) {
if ( frames == 0 ) return YES;
if ( THIS->_toFloatConverter ) {
UInt32 priorDataByteSize = sourceBuffer->mBuffers[0].mDataByteSize;
for ( int i=0; i<sourceBuffer->mNumberBuffers; i++ ) {
sourceBuffer->mBuffers[i].mDataByteSize = frames * THIS->_sourceAudioDescription.mBytesPerFrame;
}
for ( int i=0; i<THIS->_scratchFloatBufferList->mNumberBuffers; i++ ) {
THIS->_scratchFloatBufferList->mBuffers[i].mData = targetBuffers[i];
THIS->_scratchFloatBufferList->mBuffers[i].mDataByteSize = frames * sizeof(float);
}
OSStatus result = AudioConverterFillComplexBuffer(THIS->_toFloatConverter,
complexInputDataProc,
&(struct complexInputDataProc_t) { .sourceBuffer = sourceBuffer },
&frames,
THIS->_scratchFloatBufferList,
NULL);
for ( int i=0; i<sourceBuffer->mNumberBuffers; i++ ) {
sourceBuffer->mBuffers[i].mDataByteSize = priorDataByteSize;
}
if ( !checkResult(result, "AudioConverterConvertComplexBuffer") ) {
return NO;
}
} else {
for ( int i=0; i<sourceBuffer->mNumberBuffers; i++ ) {
memcpy(targetBuffers[i], sourceBuffer->mBuffers[i].mData, frames * sizeof(float));
}
}
return YES;
}
BOOL STKFloatConverterToFloatBufferList(STKFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames) {
assert(targetBuffer->mNumberBuffers == converter->_floatAudioDescription.mChannelsPerFrame);
float *targetBuffers[targetBuffer->mNumberBuffers];
for ( int i=0; i<targetBuffer->mNumberBuffers; i++ ) {
targetBuffers[i] = (float*)targetBuffer->mBuffers[i].mData;
}
return STKFloatConverterToFloat(converter, sourceBuffer, targetBuffers, frames);
}
BOOL STKFloatConverterFromFloat(STKFloatConverter* THIS, float * const * sourceBuffers, AudioBufferList *targetBuffer, UInt32 frames) {
if ( frames == 0 ) return YES;
if ( THIS->_fromFloatConverter ) {
for ( int i=0; i<THIS->_scratchFloatBufferList->mNumberBuffers; i++ ) {
THIS->_scratchFloatBufferList->mBuffers[i].mData = sourceBuffers[i];
THIS->_scratchFloatBufferList->mBuffers[i].mDataByteSize = frames * sizeof(float);
}
UInt32 priorDataByteSize = targetBuffer->mBuffers[0].mDataByteSize;
for ( int i=0; i<targetBuffer->mNumberBuffers; i++ ) {
targetBuffer->mBuffers[i].mDataByteSize = frames * THIS->_sourceAudioDescription.mBytesPerFrame;
}
OSStatus result = AudioConverterFillComplexBuffer(THIS->_fromFloatConverter,
complexInputDataProc,
&(struct complexInputDataProc_t) { .sourceBuffer = THIS->_scratchFloatBufferList },
&frames,
targetBuffer,
NULL);
for ( int i=0; i<targetBuffer->mNumberBuffers; i++ ) {
targetBuffer->mBuffers[i].mDataByteSize = priorDataByteSize;
}
if ( !checkResult(result, "AudioConverterConvertComplexBuffer") ) {
return NO;
}
} else {
for ( int i=0; i<targetBuffer->mNumberBuffers; i++ ) {
memcpy(targetBuffer->mBuffers[i].mData, sourceBuffers[i], frames * sizeof(float));
}
}
return YES;
}
BOOL STKFloatConverterFromFloatBufferList(STKFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames) {
assert(sourceBuffer->mNumberBuffers == converter->_floatAudioDescription.mChannelsPerFrame);
float *sourceBuffers[sourceBuffer->mNumberBuffers];
for ( int i=0; i<sourceBuffer->mNumberBuffers; i++ ) {
sourceBuffers[i] = (float*)sourceBuffer->mBuffers[i].mData;
}
return STKFloatConverterFromFloat(converter, sourceBuffers, targetBuffer, frames);
}
static OSStatus complexInputDataProc(AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData) {
struct complexInputDataProc_t *arg = (struct complexInputDataProc_t*)inUserData;
if ( !arg->sourceBuffer ) {
return kNoMoreDataErr;
}
memcpy(ioData, arg->sourceBuffer, sizeof(AudioBufferList) + (arg->sourceBuffer->mNumberBuffers-1)*sizeof(AudioBuffer));
arg->sourceBuffer = NULL;
return noErr;
}
-(AudioStreamBasicDescription)floatingPointAudioDescription {
return _floatAudioDescription;
}
@end

View File

@ -34,10 +34,12 @@
#import "STKCoreFoundationDataSource.h" #import "STKCoreFoundationDataSource.h"
NS_ASSUME_NONNULL_BEGIN
@class STKHTTPDataSource; @class STKHTTPDataSource;
typedef void(^STKURLBlock)(NSURL* url); typedef void(^STKURLBlock)(NSURL* url);
typedef NSURL*(^STKURLProvider)(); typedef NSURL* _Nonnull (^STKURLProvider)();
typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek, STKURLBlock callback); typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek, STKURLBlock callback);
@interface STKHTTPDataSource : STKCoreFoundationDataSource @interface STKHTTPDataSource : STKCoreFoundationDataSource
@ -46,10 +48,13 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
@property (readonly) UInt32 httpStatusCode; @property (readonly) UInt32 httpStatusCode;
+(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension; +(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
-(id) initWithURL:(NSURL*)url; -(instancetype) initWithURL:(NSURL*)url;
-(id) initWithURLProvider:(STKURLProvider)urlProvider; -(instancetype) initWithURL:(NSURL*)url httpRequestHeaders:(NSDictionary*)httpRequestHeaders;
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider; -(instancetype) initWithURLProvider:(STKURLProvider)urlProvider;
-(NSRunLoop*) eventsRunLoop; -(instancetype) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
-(nullable NSRunLoop*) eventsRunLoop;
-(void) reconnect; -(void) reconnect;
@end @end
NS_ASSUME_NONNULL_END

359
StreamingKit/StreamingKit/STKHTTPDataSource.m Normal file → Executable file
View File

@ -38,17 +38,25 @@
@interface STKHTTPDataSource() @interface STKHTTPDataSource()
{ {
@private @private
BOOL supportsSeek;
UInt32 httpStatusCode; UInt32 httpStatusCode;
SInt64 seekStart; SInt64 seekStart;
SInt64 relativePosition; SInt64 relativePosition;
SInt64 fileLength; SInt64 fileLength;
int discontinuous; int discontinuous;
int requestSerialNumber; int requestSerialNumber;
int prefixBytesRead;
NSData* prefixBytes;
NSMutableData* iceHeaderData;
BOOL iceHeaderSearchComplete;
BOOL iceHeaderAvailable;
BOOL httpHeaderNotAvailable;
NSURL* currentUrl; NSURL* currentUrl;
STKAsyncURLProvider asyncUrlProvider; STKAsyncURLProvider asyncUrlProvider;
NSDictionary* httpHeaders; NSDictionary* httpHeaders;
AudioFileTypeID audioFileTypeHint; AudioFileTypeID audioFileTypeHint;
NSDictionary* requestHeaders;
} }
-(void) open; -(void) open;
@ -56,12 +64,19 @@
@implementation STKHTTPDataSource @implementation STKHTTPDataSource
-(id) initWithURL:(NSURL*)urlIn -(instancetype) initWithURL:(NSURL*)urlIn
{ {
return [self initWithURLProvider:^NSURL* { return urlIn; }]; return [self initWithURLProvider:^NSURL* { return urlIn; }];
} }
-(id) initWithURLProvider:(STKURLProvider)urlProviderIn -(instancetype) initWithURL:(NSURL *)urlIn httpRequestHeaders:(NSDictionary *)httpRequestHeaders
{
self = [self initWithURLProvider:^NSURL* { return urlIn; }];
self->requestHeaders = httpRequestHeaders;
return self;
}
-(instancetype) initWithURLProvider:(STKURLProvider)urlProviderIn
{ {
urlProviderIn = [urlProviderIn copy]; urlProviderIn = [urlProviderIn copy];
@ -71,7 +86,7 @@
}]; }];
} }
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProviderIn -(instancetype) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProviderIn
{ {
if (self = [super init]) if (self = [super init])
{ {
@ -110,6 +125,8 @@
@"audio/mpg": @(kAudioFileMP3Type), @"audio/mpg": @(kAudioFileMP3Type),
@"audio/mpeg": @(kAudioFileMP3Type), @"audio/mpeg": @(kAudioFileMP3Type),
@"audio/wav": @(kAudioFileWAVEType), @"audio/wav": @(kAudioFileWAVEType),
@"audio/x-wav": @(kAudioFileWAVEType),
@"audio/vnd.wav": @(kAudioFileWAVEType),
@"audio/aifc": @(kAudioFileAIFCType), @"audio/aifc": @(kAudioFileAIFCType),
@"audio/aiff": @(kAudioFileAIFFType), @"audio/aiff": @(kAudioFileAIFFType),
@"audio/x-m4a": @(kAudioFileM4AType), @"audio/x-m4a": @(kAudioFileM4AType),
@ -117,10 +134,18 @@
@"audio/aacp": @(kAudioFileAAC_ADTSType), @"audio/aacp": @(kAudioFileAAC_ADTSType),
@"audio/m4a": @(kAudioFileM4AType), @"audio/m4a": @(kAudioFileM4AType),
@"audio/mp4": @(kAudioFileMPEG4Type), @"audio/mp4": @(kAudioFileMPEG4Type),
@"video/mp4": @(kAudioFileMPEG4Type),
@"audio/caf": @(kAudioFileCAFType), @"audio/caf": @(kAudioFileCAFType),
@"audio/x-caf": @(kAudioFileCAFType),
@"audio/aac": @(kAudioFileAAC_ADTSType), @"audio/aac": @(kAudioFileAAC_ADTSType),
@"audio/aacp": @(kAudioFileAAC_ADTSType),
@"audio/ac3": @(kAudioFileAC3Type), @"audio/ac3": @(kAudioFileAC3Type),
@"audio/3gp": @(kAudioFile3GPType) @"audio/3gp": @(kAudioFile3GPType),
@"video/3gp": @(kAudioFile3GPType),
@"audio/3gpp": @(kAudioFile3GPType),
@"video/3gpp": @(kAudioFile3GPType),
@"audio/3gp2": @(kAudioFile3GP2Type),
@"video/3gp2": @(kAudioFile3GP2Type)
}; };
}); });
@ -139,70 +164,233 @@
return audioFileTypeHint; return audioFileTypeHint;
} }
-(void) dataAvailable -(NSDictionary*) parseIceHeader:(NSData*)headerData
{ {
if (stream == NULL) { NSMutableDictionary* retval = [[NSMutableDictionary alloc] init];
return; NSCharacterSet* characterSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
NSString* fullString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding];
NSArray* strings = [fullString componentsSeparatedByCharactersInSet:characterSet];
httpHeaders = [NSMutableDictionary dictionary];
for (NSString* s in strings)
{
if (s.length == 0)
{
continue;
}
if ([s hasPrefix:@"ICY "])
{
NSArray* parts = [s componentsSeparatedByString:@" "];
if (parts.count >= 2)
{
self->httpStatusCode = [parts[1] intValue];
}
continue;
}
NSRange range = [s rangeOfString:@":"];
if (range.location == NSNotFound)
{
continue;
}
NSString* key = [s substringWithRange: (NSRange){.location = 0, .length = range.location}];
NSString* value = [s substringFromIndex:range.location + 1];
[retval setValue:value forKey:key];
} }
if (self.httpStatusCode == 0) return retval;
{ }
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
-(BOOL) parseHttpHeader
{
if (!httpHeaderNotAvailable)
{
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
if (response) if (response)
{ {
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response); httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response); if (httpHeaders.count == 0)
{
httpHeaderNotAvailable = YES;
}
else
{
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
}
CFRelease(response); CFRelease(response);
} }
}
if (self.httpStatusCode == 200)
{ if (httpHeaderNotAvailable)
if (seekStart == 0) {
{ if (self->iceHeaderSearchComplete && !self->iceHeaderAvailable)
fileLength = (SInt64)[[httpHeaders objectForKey:@"Content-Length"] longLongValue]; {
} return YES;
}
NSString* contentType = [httpHeaders objectForKey:@"Content-Type"];
AudioFileTypeID typeIdFromMimeType = [STKHTTPDataSource audioFileTypeHintFromMimeType:contentType]; if (!self->iceHeaderSearchComplete)
{
if (typeIdFromMimeType != 0) UInt8 byte;
{ UInt8 terminal1[] = { '\n', '\n' };
audioFileTypeHint = typeIdFromMimeType; UInt8 terminal2[] = { '\r', '\n', '\r', '\n' };
}
} if (iceHeaderData == nil)
else if (self.httpStatusCode == 206) {
{ iceHeaderData = [NSMutableData dataWithCapacity:1024];
NSString* contentRange = [httpHeaders objectForKey:@"Content-Range"]; }
NSArray* components = [contentRange componentsSeparatedByString:@"/"];
while (true)
if (components.count == 2) {
{ if (![self hasBytesAvailable])
fileLength = [[components objectAtIndex:1] integerValue]; {
} break;
} }
else if (self.httpStatusCode == 416)
{ int read = [super readIntoBuffer:&byte withSize:1];
if (self.length >= 0)
{ if (read <= 0)
seekStart = self.length; {
} break;
}
[self eof];
[iceHeaderData appendBytes:&byte length:read];
return;
} if (iceHeaderData.length >= sizeof(terminal1))
else if (self.httpStatusCode >= 300) {
{ if (memcmp(&terminal1[0], [self->iceHeaderData bytes] + iceHeaderData.length - sizeof(terminal1), sizeof(terminal1)) == 0)
[self errorOccured]; {
self->iceHeaderAvailable = YES;
return; self->iceHeaderSearchComplete = YES;
}
break;
}
}
if (iceHeaderData.length >= sizeof(terminal2))
{
if (memcmp(&terminal2[0], [self->iceHeaderData bytes] + iceHeaderData.length - sizeof(terminal2), sizeof(terminal2)) == 0)
{
self->iceHeaderAvailable = YES;
self->iceHeaderSearchComplete = YES;
break;
}
}
if (iceHeaderData.length >= 4)
{
if (memcmp([self->iceHeaderData bytes], "ICY ", 4) != 0 && memcmp([self->iceHeaderData bytes], "HTTP", 4) != 0)
{
self->iceHeaderAvailable = NO;
self->iceHeaderSearchComplete = YES;
prefixBytes = iceHeaderData;
return YES;
}
}
}
if (!self->iceHeaderSearchComplete)
{
return NO;
}
}
httpHeaders = [self parseIceHeader:self->iceHeaderData];
self->iceHeaderData = nil;
}
if (([httpHeaders objectForKey:@"Accept-Ranges"] ?: [httpHeaders objectForKey:@"accept-ranges"]) != nil)
{
self->supportsSeek = YES;
}
if (self.httpStatusCode == 200)
{
if (seekStart == 0)
{
id value = [httpHeaders objectForKey:@"Content-Length"] ?: [httpHeaders objectForKey:@"content-length"];
fileLength = (SInt64)[value longLongValue];
}
NSString* contentType = [httpHeaders objectForKey:@"Content-Type"] ?: [httpHeaders objectForKey:@"content-type"] ;
AudioFileTypeID typeIdFromMimeType = [STKHTTPDataSource audioFileTypeHintFromMimeType:contentType];
if (typeIdFromMimeType != 0)
{
audioFileTypeHint = typeIdFromMimeType;
}
}
else if (self.httpStatusCode == 206)
{
NSString* contentRange = [httpHeaders objectForKey:@"Content-Range"] ?: [httpHeaders objectForKey:@"content-range"];
NSArray* components = [contentRange componentsSeparatedByString:@"/"];
if (components.count == 2)
{
fileLength = [[components objectAtIndex:1] integerValue];
}
}
else if (self.httpStatusCode == 416)
{
if (self.length >= 0)
{
seekStart = self.length;
}
[self eof];
return NO;
}
else if (self.httpStatusCode >= 300)
{
[self errorOccured];
return NO;
}
return YES;
}
-(void) dataAvailable
{
if (stream == NULL)
{
return;
}
if (self.httpStatusCode == 0)
{
if ([self parseHttpHeader])
{
if ([self hasBytesAvailable])
{
[super dataAvailable];
}
return;
}
else
{
return;
}
} }
else
[super dataAvailable]; {
[super dataAvailable];
}
} }
-(SInt64) position -(SInt64) position
@ -223,7 +411,7 @@
eventsRunLoop = savedEventsRunLoop; eventsRunLoop = savedEventsRunLoop;
[self seekToOffset:self.position]; [self seekToOffset:self->supportsSeek ? self.position : 0];
} }
-(void) seekToOffset:(SInt64)offset -(void) seekToOffset:(SInt64)offset
@ -242,17 +430,43 @@
self->isInErrorState = NO; self->isInErrorState = NO;
if (!self->supportsSeek && offset != self->relativePosition)
{
return;
}
[self openForSeek:YES]; [self openForSeek:YES];
} }
-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size -(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
{
return [self privateReadIntoBuffer:buffer withSize:size];
}
-(int) privateReadIntoBuffer:(UInt8*)buffer withSize:(int)size
{ {
if (size == 0) if (size == 0)
{ {
return 0; return 0;
} }
int read = (int)CFReadStreamRead(stream, buffer, size); if (prefixBytes != nil)
{
int count = MIN(size, (int)prefixBytes.length - prefixBytesRead);
[prefixBytes getBytes:buffer length:count];
prefixBytesRead += count;
if (prefixBytesRead >= prefixBytes.length)
{
prefixBytes = nil;
}
return count;
}
int read = [super readIntoBuffer:buffer withSize:size];
if (read < 0) if (read < 0)
{ {
@ -282,7 +496,7 @@
{ {
return; return;
} }
self->currentUrl = url; self->currentUrl = url;
if (url == nil) if (url == nil)
@ -292,13 +506,23 @@
CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (__bridge CFURLRef)self->currentUrl, kCFHTTPVersion1_1); CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (__bridge CFURLRef)self->currentUrl, kCFHTTPVersion1_1);
if (seekStart > 0) if (seekStart > 0 && supportsSeek)
{ {
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), (__bridge CFStringRef)[NSString stringWithFormat:@"bytes=%lld-", seekStart]); CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), (__bridge CFStringRef)[NSString stringWithFormat:@"bytes=%lld-", seekStart]);
discontinuous = YES; discontinuous = YES;
} }
for (NSString* key in self->requestHeaders)
{
NSString* value = [self->requestHeaders objectForKey:key];
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)value);
}
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*"));
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Ice-MetaData"), CFSTR("0"));
stream = CFReadStreamCreateForHTTPRequest(NULL, message); stream = CFReadStreamCreateForHTTPRequest(NULL, message);
if (stream == nil) if (stream == nil)
@ -309,6 +533,8 @@
return; return;
} }
CFReadStreamSetProperty(stream, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground, (__bridge CFStringRef)NSStreamNetworkServiceTypeBackground);
if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
{ {
@ -320,20 +546,15 @@
} }
// Proxy support // Proxy support
CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings(); CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings); CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings);
CFRelease(proxySettings); CFRelease(proxySettings);
// SSL support // SSL support
if ([self->currentUrl.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) if ([self->currentUrl.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)
{ {
NSDictionary* sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: NSDictionary* sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
(NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, (NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
[NSNull null], kCFStreamSSLPeerName, [NSNull null], kCFStreamSSLPeerName,
nil]; nil];
@ -346,7 +567,6 @@
self->httpStatusCode = 0; self->httpStatusCode = 0;
// Open // Open
if (!CFReadStreamOpen(stream)) if (!CFReadStreamOpen(stream))
{ {
CFRelease(stream); CFRelease(stream);
@ -380,4 +600,9 @@
return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position]; return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position];
} }
-(BOOL) supportsSeek
{
return self->supportsSeek;
}
@end @end

View File

@ -34,10 +34,14 @@
#import "STKCoreFoundationDataSource.h" #import "STKCoreFoundationDataSource.h"
NS_ASSUME_NONNULL_BEGIN
@interface STKLocalFileDataSource : STKCoreFoundationDataSource @interface STKLocalFileDataSource : STKCoreFoundationDataSource
+(AudioFileTypeID) audioFileTypeHintFromFileExtension:(NSString*)fileExtension; +(AudioFileTypeID) audioFileTypeHintFromFileExtension:(NSString*)fileExtension;
@property (readonly, copy) NSString* filePath; @property (readonly, copy) NSString* filePath;
-(id) initWithFilePath:(NSString*)filePath; -(instancetype) initWithFilePath:(NSString*)filePath;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -47,13 +47,13 @@
@implementation STKLocalFileDataSource @implementation STKLocalFileDataSource
@synthesize filePath; @synthesize filePath;
-(id) initWithFilePath:(NSString*)filePathIn -(instancetype) initWithFilePath:(NSString*)filePathIn
{ {
if (self = [super init]) if (self = [super init])
{ {
self.filePath = filePathIn; self.filePath = filePathIn;
audioFileTypeHint = [STKLocalFileDataSource audioFileTypeHintFromFileExtension:filePathIn.pathExtension]; audioFileTypeHint = [[self class] audioFileTypeHintFromFileExtension:filePathIn.pathExtension];
} }
return self; return self;

9
StreamingKit/StreamingKit/STKQueueEntry.h Normal file → Executable file
View File

@ -10,6 +10,8 @@
#import "libkern/OSAtomic.h" #import "libkern/OSAtomic.h"
#import "AudioToolbox/AudioToolbox.h" #import "AudioToolbox/AudioToolbox.h"
NS_ASSUME_NONNULL_BEGIN
@interface STKQueueEntry : NSObject @interface STKQueueEntry : NSObject
{ {
@public @public
@ -28,13 +30,14 @@
volatile int processedPacketsCount; volatile int processedPacketsCount;
volatile int processedPacketsSizeTotal; volatile int processedPacketsSizeTotal;
AudioStreamBasicDescription audioStreamBasicDescription; AudioStreamBasicDescription audioStreamBasicDescription;
double durationHint;
} }
@property (readonly) UInt64 audioDataLengthInBytes; @property (readonly) UInt64 audioDataLengthInBytes;
@property (readwrite, retain) NSObject* queueItemId; @property (readwrite, retain) NSObject* queueItemId;
@property (readwrite, retain) STKDataSource* dataSource; @property (readwrite, retain) STKDataSource* dataSource;
-(id) initWithDataSource:(STKDataSource*)dataSource andQueueItemId:(NSObject*)queueItemId; -(instancetype) initWithDataSource:(STKDataSource*)dataSource andQueueItemId:(NSObject*)queueItemId;
-(void) reset; -(void) reset;
-(double) duration; -(double) duration;
@ -42,4 +45,6 @@
-(double) calculatedBitRate; -(double) calculatedBitRate;
-(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription; -(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription;
@end @end
NS_ASSUME_NONNULL_END

11
StreamingKit/StreamingKit/STKQueueEntry.m Normal file → Executable file
View File

@ -14,7 +14,7 @@
@implementation STKQueueEntry @implementation STKQueueEntry
-(id) initWithDataSource:(STKDataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn -(instancetype) initWithDataSource:(STKDataSource*)dataSourceIn andQueueItemId:(NSObject*)queueItemIdIn
{ {
if (self = [super init]) if (self = [super init])
{ {
@ -23,6 +23,7 @@
self.dataSource = dataSourceIn; self.dataSource = dataSourceIn;
self.queueItemId = queueItemIdIn; self.queueItemId = queueItemIdIn;
self->lastFrameQueued = -1; self->lastFrameQueued = -1;
self->durationHint = dataSourceIn.durationHint;
} }
return self; return self;
@ -45,7 +46,7 @@
{ {
if (processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED || (audioStreamBasicDescription.mBytesPerFrame == 0 && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN)) if (processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED || (audioStreamBasicDescription.mBytesPerFrame == 0 && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN))
{ {
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; double averagePacketByteSize = (double)processedPacketsSizeTotal / (double)processedPacketsCount;
retval = averagePacketByteSize / packetDuration * 8; retval = averagePacketByteSize / packetDuration * 8;
@ -60,6 +61,8 @@
-(double) duration -(double) duration
{ {
if (durationHint > 0.0) return durationHint;
if (self->sampleRate <= 0) if (self->sampleRate <= 0)
{ {
return 0; return 0;
@ -107,7 +110,7 @@
-(Float64) progressInFrames -(Float64) progressInFrames
{ {
OSSpinLockLock(&self->spinLock); OSSpinLockLock(&self->spinLock);
Float64 retval = self->seekTime + self->framesPlayed; Float64 retval = (self->seekTime * self->audioStreamBasicDescription.mSampleRate) + self->framesPlayed;
OSSpinLockUnlock(&self->spinLock); OSSpinLockUnlock(&self->spinLock);
return retval; return retval;
@ -118,4 +121,4 @@
return [[self queueItemId] description]; return [[self queueItemId] description];
} }
@end @end

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.abstractpath.${PRODUCT_NAME:rfc1034identifier}</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>abstractpath.com.${PRODUCT_NAME:rfc1034identifier}</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>