Compare commits

...

41 Commits

Author SHA1 Message Date
Thong Nguyen a9dfb2eddf Fixed thread related crashes in OutputRenderCallback 2014-04-07 15:28:34 +01:00
Thong Nguyen 3fcf054a23 Fix STKAudioPlayer.progress property thread-related crash 2014-04-07 15:15:36 +01:00
Thong Nguyen a615419404 Fixed deployment target min should be 4.3 not 6.0 2014-04-05 13:15:43 +01:00
Thong Nguyen e43a4613f8 startInternal now cleans up audio resources when the playback thread is finished. Added fix for undefned DBL_MAX 2014-04-05 13:08:58 +01:00
Thong Nguyen ca928dfe1e Removed buffering work (now in different branch) 2014-03-24 16:32:38 +00:00
Thong Nguyen d7d583c3ba Some chunking/paging buffering data source work 2014-02-24 17:05:54 +00:00
Thong Nguyen 7540045361 Some buffering data source tests/work 2014-02-20 17:10:51 +00:00
Thong Nguyen 972ae0e15b Some more buffering data source work 2014-02-18 19:02:47 +00:00
Thong Nguyen e3ed6c6dee Removed AudioDataSource (for now). Changed all int64_t and long long types to SInt64. Started adding Buffered data source 2014-02-18 13:38:03 +00:00
Thong Nguyen 3398e8c64e Explicitly wakeup playback thread if a delayed seek was queued 2014-02-16 17:07:39 +00:00
Thong Nguyen f99201f54d Explicitly wakeup playback thread if a delayed seek was queued 2014-02-16 17:07:10 +00:00
Thong Nguyen 44b9e7d2d1 Removed errant NSLog 2014-02-16 16:50:54 +00:00
Thong Nguyen 7aae2bcb6b Allow queueing of a seek request for a file before it has loaded 2014-02-16 16:50:12 +00:00
Thong Nguyen 5a8068b859 STKAutoRecoveringHTTPDataSource shouldn't retry if inner data source no longer is registered 2014-02-16 11:57:23 +00:00
Thong Nguyen 728fc5bb21 More controllable grace period for waiting for data after seek 2014-02-16 11:43:01 +00:00
Thong Nguyen c4053c964e Smaller setting for STK_CYCLES_REQUIRED_BEFORE_SEEK_BECOMES_PLAYING 2014-02-16 01:49:23 +00:00
Thong Nguyen c31df15a43 STKAudioPlayerInternalStateWaitingForDataAfterSeek no longer considered a buffering state externally. STKAudioPlayerInternalStateWaitingForDataAfterSeek turns into a playing state after a certain period. Prevents flapping between states when seeking tracks 2014-02-16 01:24:08 +00:00
Thong Nguyen 923baf5b89 STKAudioPlayerInternalStatePendingNext and STKAudioPlayerInternalStateWaitingForDataAfterSeeks are both now considered STKAudioPlayerStateBuffering states externally 2014-02-15 22:02:56 +00:00
Thong Nguyen 9199785202 Better backgroundTask start/stopping using blocks to safely avoid referencing STKAudioPlayer 2014-02-15 17:04:52 +00:00
Thong Nguyen 0618027252 Fixed iOS background task holding onto a reference to STKAudioPlayer and preventing it from being dealloc immediately 2014-02-15 16:55:16 +00:00
Thong Nguyen 188f880f5a Tidied up some warnings 2014-02-14 22:19:41 +00:00
Thong Nguyen ae9cee68f0 Slightly better bitrate calculation for VBR files 2014-02-14 22:06:46 +00:00
Thong Nguyen eeece64417 Changed duration calculation to work better with short files 2014-02-14 18:34:55 +00:00
Thong Nguyen 243dc1f8a2 Fixed STKAudioPlayer:stop sometimes blocking too long 2014-02-14 14:49:08 +00:00
Thong Nguyen 8c608440ae Merge pull request #81 from AndrewKosovich/master
Ported error handling from the previous Audjustable player.
2014-02-14 12:16:10 +00:00
Andrew Kosovich 9aed1b082a Ported error handling from the previous Audjustable player.
STKDataSource is designed to return values < 0 in case of error, but new STKAudioPlayer doesn't handle that, which causes crash few lines later when calling AudioFileStreamParseBytes with read<0.
2014-02-14 13:37:43 +02:00
Thong Nguyen 6eb149f83a Progress now returns 0 if player is stopped 2014-02-13 22:24:26 +00:00
Thong Nguyen 1243dbf0e1 setDataSource now immediately stops whatever is currently playing. STKAutoRecoveringHTTPDataSource watchdog timer improvements 2014-02-13 22:02:48 +00:00
Thong Nguyen a15c2c27ff Fixed stream not set to nil when error occurs when opening 2014-02-13 18:06:24 +00:00
Thong Nguyen aa441045aa Fixed pendingQueue and mostRecentlyQueuedStillPendingItem returning STKQueueEntry instead of queueItemId 2014-02-13 17:54:24 +00:00
Thong Nguyen 569764d869 Added watchdog to STKAutoRecoveringHTTPDataSource to catch TCP timeouts which otherwise would not raise an error since we aren't do any writes to the socket 2014-02-13 17:33:45 +00:00
Thong Nguyen 511b756694 Fixed pendingQueue and mostRecentlyQueuedStillPendingItem returning STKQueueEntry instead of original queueItemId 2014-02-13 11:58:19 +00:00
Thong Nguyen ab0c4d1315 STKAutoRecoveringHTTPDataSource now stops retrying to connect if DataSource no longer has an event loop 2014-02-13 11:34:05 +00:00
Thong Nguyen 6216abb0ab Changed STKCoreFoundationDataSource to not open in registerForEvents (delay open and registration) 2014-02-12 23:09:53 +00:00
Thong Nguyen 8c5b4fb298 Fixed openForSeek on STKHTTPDataSource potentially handling old URLs if URL provider is async 2014-02-12 19:53:21 +00:00
Thong Nguyen 03e9b8b208 Updated README 2014-02-11 23:13:34 +00:00
Thong Nguyen 60d48a0682 Added the ability to dynamically remove or add EQ unit to save battery when EQ unit is not needed 2014-02-11 19:24:41 +00:00
Thong Nguyen dee6322751 Set default kAudioUnitProperty_MaximumFramesPerSlice on all audio units to 4096 to fix playback problems on lockscreen 2014-02-10 23:14:32 +00:00
Thong Nguyen 5e3048a7bf Merge branch 'nbandeq' 2014-02-10 14:15:37 +00:00
Thong Nguyen 13fc64baa2 Merge pull request #77 from AndrewKosovich/master
Fixed crash in the STKHTTPDataSource.
2014-02-06 17:20:17 +00:00
Andrew Kosovich 5e4b500785 Fixed crash in the STKHTTPDataSource. 2014-02-06 18:33:03 +02:00
22 changed files with 634 additions and 3286 deletions
@@ -230,7 +230,7 @@
A1115929188D686000641365 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0500;
LastUpgradeCheck = 0510;
ORGANIZATIONNAME = "Thong Nguyen";
TargetAttributes = {
A111594B188D686000641365 = {
@@ -332,7 +332,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -366,7 +365,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../StreamingKit/StreamingKit",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -377,7 +376,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -405,7 +403,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../StreamingKit/StreamingKit",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
@@ -415,9 +413,9 @@
A111595E188D686000641365 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "ExampleApp/ExampleApp-Prefix.pch";
INFOPLIST_FILE = "ExampleApp/ExampleApp-Info.plist";
@@ -432,7 +430,6 @@
A111595F188D686000641365 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -449,7 +446,6 @@
A1115961188D686000641365 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
@@ -472,7 +468,6 @@
A1115962188D686000641365 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
+7 -4
View File
@@ -26,19 +26,22 @@
NSError* error;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error];
Float32 bufferLength = 0.1;
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength);
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 0.1;
audioPlayer.volume = 1;
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds];
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
audioPlayerView.delegate = self;
audioPlayerView.audioPlayer = audioPlayer;
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
+3
View File
@@ -51,6 +51,7 @@
UILabel* label;
UILabel* statusLabel;
UISlider* slider;
UISwitch* enableEqSwitch;
UISwitch* repeatSwitch;
UIButton* muteButton;
UIButton* playButton;
@@ -65,4 +66,6 @@
@property (readwrite, retain) STKAudioPlayer* audioPlayer;
@property (readwrite, unsafe_unretained) id<AudioPlayerViewDelegate> delegate;
- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayer;
@end
+15 -2
View File
@@ -47,12 +47,14 @@
@implementation AudioPlayerView
@synthesize audioPlayer, delegate;
- (id)initWithFrame:(CGRect)frame
- (id)initWithFrame:(CGRect)frame andAudioPlayer:(STKAudioPlayer*)audioPlayerIn
{
self = [super initWithFrame:frame];
if (self)
{
self.audioPlayer = audioPlayerIn;
CGSize size = CGSizeMake(220, 50);
playFromHTTPButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
@@ -97,7 +99,12 @@
size = CGSizeMake(80, 50);
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake((320 - size.width) / 2, frame.size.height * 0.15 + 180, size.width, size.height)];
repeatSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(30, frame.size.height * 0.15 + 180, size.width, size.height)];
enableEqSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(320 - size.width - 30, frame.size.height * 0.15 + 180, size.width, size.height)];
enableEqSwitch.on = audioPlayer.equalizerEnabled;
[enableEqSwitch addTarget:self action:@selector(onEnableEqSwitch) forControlEvents:UIControlEventAllTouchEvents];
label = [[UILabel alloc] initWithFrame:CGRectMake(0, slider.frame.origin.y + slider.frame.size.height + 10, frame.size.width, 25)];
@@ -123,6 +130,7 @@
[self addSubview:stopButton];
[self addSubview:meter];
[self addSubview:muteButton];
[self addSubview:enableEqSwitch];
[self setupTimer];
[self updateControls];
@@ -131,6 +139,11 @@
return self;
}
-(void) onEnableEqSwitch
{
audioPlayer.equalizerEnabled = self->enableEqSwitch.on;
}
-(void) sliderChanged
{
if (!audioPlayer)
+9 -1
View File
@@ -40,14 +40,22 @@
[[self.window contentView] addSubview:playFromHTTPButton];
[[self.window contentView] addSubview:meter];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = YES, .equalizerBandFrequencies = {0, 50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} } ];
audioPlayer.delegate = self;
audioPlayer.meteringEnabled = YES;
audioPlayer.volume = 0.1;
[self performSelector:@selector(test) withObject:nil afterDelay:4];
[self performSelector:@selector(test) withObject:nil afterDelay:8];
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
}
-(void) test
{
audioPlayer.equalizerEnabled = !audioPlayer.equalizerEnabled;
}
-(void) playFromHTTP
{
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
+2
View File
@@ -16,6 +16,8 @@ The primary motivation of this project was to decouple the input data sources fr
* Optimised for low CPU/battery usage (0% - 1% CPU usage when streaming).
* Optimised for linear data sources. Random access sources are required only for seeking.
* StreamingKit 0.2.0 uses the AudioUnit API rather than the slower AudioQueues API which allows real-time interception of the raw PCM data for features such as level metering, EQ, etc.
* Power metering
* Inbuilt equalizer/EQ (iOS 5.0 and above, OSX 10.9 Mavericks and above) with support for dynamically changing/enabling/disabling EQ while playing.
* Example apps for iOS and Mac OSX provided.
## Installation
@@ -401,7 +401,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = STK;
LastUpgradeCheck = 0500;
LastUpgradeCheck = 0510;
ORGANIZATIONNAME = "Thong Nguyen";
};
buildConfigurationList = A1E7C4C3188D57F50010896F /* Build configuration list for PBXProject "StreamingKit" */;
@@ -635,7 +635,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -663,7 +662,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
@@ -673,7 +672,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -695,7 +693,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
@@ -704,7 +702,6 @@
A1E7C4EC188D57F60010896F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
DSTROOT = /tmp/StreamingKit.dst;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -721,7 +718,6 @@
A1E7C4ED188D57F60010896F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
DSTROOT = /tmp/StreamingKit.dst;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -738,7 +734,6 @@
A1E7C4EF188D57F60010896F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
@@ -759,7 +754,6 @@
A1E7C4F0188D57F60010896F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -38,7 +38,7 @@
#import <Foundation/Foundation.h>
#import <pthread.h>
#import "STKDataSource.h"
#include <AudioToolbox/AudioToolbox.h>
#import <AudioToolbox/AudioToolbox.h>
#if TARGET_OS_IPHONE
#include "UIKit/UIApplication.h"
@@ -62,6 +62,8 @@ typedef enum
STKAudioPlayerStopReasonNone = 0,
STKAudioPlayerStopReasonEof,
STKAudioPlayerStopReasonUserAction,
STKAudioPlayerStopReasonPendingNext,
STKAudioPlayerStopReasonDisposed,
STKAudioPlayerStopReasonError = 0xffff
}
STKAudioPlayerStopReason;
@@ -92,6 +94,8 @@ typedef struct
UInt32 bufferSizeInSeconds;
/// Number of seconds of decompressed audio is required before playback first starts for each item (Default is 0.5 seconds. Must be larger than bufferSizeInSeconds)
Float32 secondsRequiredToStartPlaying;
/// Seconds after a seek is performed before data needs to come in (after which the state will change to playing/buffering)
Float32 gracePeriodAfterSeekInSeconds;
/// Number of seconds of decompressed audio required before playback resumes after a buffer underrun (Default is 5 seconds. Must be larger than bufferSizeinSeconds)
Float32 secondsRequiredToStartPlayingAfterBufferUnderun;
}
@@ -140,6 +144,8 @@ typedef void(^STKFrameFilter)(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UIn
@property (readonly) double progress;
/// Enables or disables peak and average decibel meteting
@property (readwrite) BOOL meteringEnabled;
/// Enables or disables the EQ
@property (readwrite) BOOL equalizerEnabled;
/// Returns an array of STKFrameFilterEntry objects representing the filters currently in use
@property (readonly) NSArray* frameFilters;
/// Returns the items pending to be played (includes buffering and upcoming items but does not include the current item)
+324 -126
View File
@@ -42,6 +42,10 @@
#import "libkern/OSAtomic.h"
#import <float.h>
#ifndef DBL_MAX
#define DBL_MAX 1.7976931348623157e+308
#endif
#pragma mark Defines
#define kOutputBus 0
@@ -54,9 +58,10 @@
#define STK_DEFAULT_PCM_BUFFER_SIZE_IN_SECONDS (10)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING (1)
#define STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN (7.5)
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (2048)
#define STK_MAX_COMPRESSED_PACKETS_FOR_BITRATE_CALCULATION (4096)
#define STK_DEFAULT_READ_BUFFER_SIZE (64 * 1024)
#define STK_DEFAULT_PACKET_BUFFER_SIZE (2048)
#define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5)
#define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]];
@@ -81,8 +86,19 @@ static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options)
{
options->secondsRequiredToStartPlayingAfterBufferUnderun = MIN(STK_DEFAULT_SECONDS_REQUIRED_TO_START_PLAYING_AFTER_BUFFER_UNDERRUN, options->bufferSizeInSeconds);
}
if (options->gracePeriodAfterSeekInSeconds == 0)
{
options->gracePeriodAfterSeekInSeconds = MIN(STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS, options->bufferSizeInSeconds);
}
}
#define CHECK_STATUS_AND_REPORT(call) \
if ((status = (call))) \
{ \
[self unexpectedError:STKAudioPlayerErrorAudioSystemError]; \
}
#define CHECK_STATUS_AND_RETURN(call) \
if ((status = (call))) \
{ \
@@ -150,6 +166,8 @@ STKAudioPlayerInternalState;
#pragma mark STKAudioPlayer
static UInt32 maxFramesPerSlice = 4096;
static AudioComponentDescription mixerDescription;
static AudioComponentDescription nbandUnitDescription;
static AudioComponentDescription outputUnitDescription;
@@ -169,8 +187,12 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
Float32 averagePowerDb[2];
BOOL meteringEnabled;
BOOL equalizerOn;
BOOL equalizerEnabled;
STKAudioPlayerOptions options;
NSMutableArray* converterNodes;
AUGraph audioGraph;
AUNode eqNode;
AUNode mixerNode;
@@ -186,9 +208,11 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioComponentInstance outputUnit;
UInt32 eqBandCount;
int32_t waitingForDataAfterSeekFrameCount;
UInt32 framesRequiredToStartPlaying;
UInt32 framesRequiredToPlayAfterRebuffering;
UInt32 framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying;
STKQueueEntry* volatile currentlyPlayingEntry;
STKQueueEntry* volatile currentlyReadingEntry;
@@ -209,6 +233,7 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioStreamBasicDescription audioConverterAudioStreamBasicDescription;
BOOL deallocating;
BOOL discontinuous;
NSArray* frameFilters;
NSThread* playbackThread;
@@ -216,10 +241,8 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription;
AudioFileStreamID audioFileStream;
NSConditionLock* threadStartedLock;
NSConditionLock* threadFinishedCondLock;
#if TARGET_OS_IPHONE
UIBackgroundTaskIdentifier backgroundTaskId;
#endif
void(^stopBackBackgroundTaskBlock)();
int32_t seekVersion;
OSSpinLock seekLock;
@@ -340,13 +363,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
stopReason = STKAudioPlayerStopReasonNone;
break;
case STKAudioPlayerInternalStateRunning:
case STKAudioPlayerInternalStatePendingNext:
case STKAudioPlayerInternalStateStartingThread:
case STKAudioPlayerInternalStatePlaying:
case STKAudioPlayerInternalStateWaitingForDataAfterSeek:
case STKAudioPlayerInternalStateWaitingForDataAfterSeek:
newState = STKAudioPlayerStatePlaying;
stopReason = STKAudioPlayerStopReasonNone;
break;
case STKAudioPlayerInternalStatePendingNext:
case STKAudioPlayerInternalStateRebuffering:
case STKAudioPlayerInternalStateWaitingForData:
newState = STKAudioPlayerStateBuffering;
@@ -368,9 +391,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
stopReason = STKAudioPlayerStopReasonError;
break;
}
OSSpinLockLock(&internalStateLock);
waitingForDataAfterSeekFrameCount = 0;
if (value == internalState)
{
OSSpinLockUnlock(&internalStateLock);
@@ -392,7 +417,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
STKAudioPlayerState previousState = self.state;
if (newState != previousState)
if (newState != previousState && !deallocating)
{
self.state = newState;
@@ -444,11 +469,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
options = optionsIn;
self->volume = 1.0;
self->equalizerEnabled = optionsIn.equalizerBandFrequencies[0] != 0;
PopulateOptionsWithDefault(&options);
framesRequiredToStartPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlaying;
framesRequiredToPlayAfterRebuffering = canonicalAudioStreamBasicDescription.mSampleRate * options.secondsRequiredToStartPlayingAfterBufferUnderun;
framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying = canonicalAudioStreamBasicDescription.mSampleRate * options.gracePeriodAfterSeekInSeconds;
pcmAudioBuffer = &pcmAudioBufferList.mBuffers[0];
@@ -489,39 +516,62 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
return self;
}
-(void) dealloc
-(void) destroyAudioResources
{
if (currentlyReadingEntry)
if (currentlyReadingEntry)
{
currentlyReadingEntry.dataSource.delegate = nil;
[currentlyReadingEntry.dataSource unregisterForEvents];
currentlyReadingEntry = nil;
}
if (currentlyPlayingEntry)
{
currentlyPlayingEntry.dataSource.delegate = nil;
[currentlyReadingEntry.dataSource unregisterForEvents];
OSSpinLockLock(&currentEntryReferencesLock);
currentlyPlayingEntry = nil;
OSSpinLockUnlock(&currentEntryReferencesLock);
}
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonEof];
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed];
[self clearQueue];
if (audioFileStream)
{
AudioFileStreamClose(audioFileStream);
audioFileStream = nil;
}
if (audioConverterRef)
{
AudioConverterDispose(audioConverterRef);
audioConverterRef = nil;
}
if (outputUnit)
if (audioGraph)
{
AudioComponentInstanceDispose(outputUnit);
AUGraphUninitialize(audioGraph);
AUGraphClose(audioGraph);
DisposeAUGraph(audioGraph);
audioGraph = nil;
}
AUGraphClose(audioGraph);
}
-(void) dealloc
{
NSLog(@"STKAudioPlayer dealloc");
deallocating = YES;
[self destroyAudioResources];
pthread_mutex_destroy(&playerMutex);
pthread_mutex_destroy(&mainThreadSyncCallMutex);
pthread_cond_destroy(&playerThreadReadyCondition);
@@ -533,37 +583,33 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
-(void) startSystemBackgroundTask
{
#if TARGET_OS_IPHONE
pthread_mutex_lock(&playerMutex);
__block UIBackgroundTaskIdentifier backgroundTaskId = UIBackgroundTaskInvalid;
backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}];
stopBackBackgroundTaskBlock = [^
{
if (backgroundTaskId != UIBackgroundTaskInvalid)
{
pthread_mutex_unlock(&playerMutex);
return;
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self stopSystemBackgroundTask];
}];
}
pthread_mutex_unlock(&playerMutex);
} copy];
#endif
}
-(void) stopSystemBackgroundTask
{
#if TARGET_OS_IPHONE
pthread_mutex_lock(&playerMutex);
if (stopBackBackgroundTaskBlock != NULL)
{
if (backgroundTaskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
stopBackBackgroundTaskBlock();
stopBackBackgroundTaskBlock = NULL;
}
pthread_mutex_unlock(&playerMutex);
#endif
}
@@ -662,7 +708,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
{
LOGINFO(([NSString stringWithFormat:@"Playing: %@", [queueItemId description]]));
[self startSystemBackgroundTask];
if (![self audioGraphIsRunning])
{
[self startSystemBackgroundTask];
}
[self clearQueue];
@@ -699,7 +748,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
{
pthread_mutex_lock(&playerMutex);
{
[self startSystemBackgroundTask];
if (![self audioGraphIsRunning])
{
[self startSystemBackgroundTask];
}
[upcomingQueue enqueue:[[STKQueueEntry alloc] initWithDataSource:dataSourceIn andQueueItemId:queueItemId]];
}
@@ -729,8 +781,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
currentlyReadingEntry->parsedHeader = YES;
currentlyReadingEntry->audioDataOffset = offset;
[currentlyReadingEntry updateAudioDataSource];
break;
}
case kAudioFileStreamProperty_FileFormat:
@@ -782,8 +832,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
entryToUpdate->packetBufferSize = packetBufferSize;
}
[entryToUpdate updateAudioDataSource];
[self createAudioConverter:&currentlyReadingEntry->audioStreamBasicDescription];
pthread_mutex_unlock(&playerMutex);
@@ -800,8 +848,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
currentlyReadingEntry->audioDataByteCount = audioDataByteCount;
[currentlyReadingEntry updateAudioDataSource];
break;
}
case kAudioFileStreamProperty_ReadyToProducePackets:
@@ -887,20 +933,15 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
OSSpinLockLock(&currentEntryReferencesLock);
STKQueueEntry* entry = currentlyPlayingEntry;
OSSpinLockUnlock(&currentEntryReferencesLock);
if (entry == nil)
{
OSSpinLockUnlock(&currentEntryReferencesLock);
return 0;
return 0;
}
double retval = [entry duration];
OSSpinLockUnlock(&currentEntryReferencesLock);
double progress = [self progress];
if (retval < progress && retval > 0)
@@ -923,7 +964,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
return 0;
}
OSSpinLockLock(&currentEntryReferencesLock);
STKQueueEntry* entry = currentlyPlayingEntry;
OSSpinLockUnlock(&currentEntryReferencesLock);
if (entry == nil)
{
@@ -933,7 +976,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
OSSpinLockLock(&entry->spinLock);
double retval = entry->seekTime + (entry->framesPlayed / canonicalAudioStreamBasicDescription.mSampleRate);
OSSpinLockUnlock(&entry->spinLock);
return retval;
}
@@ -1161,6 +1204,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
pthread_mutex_unlock(&mainThreadSyncCallMutex);
});
pthread_mutex_lock(&mainThreadSyncCallMutex);
while (true)
{
if (disposeWasRequested)
@@ -1173,10 +1218,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
break;
}
pthread_mutex_lock(&mainThreadSyncCallMutex);
pthread_cond_wait(&mainThreadSyncCallReadyCondition, &mainThreadSyncCallMutex);
pthread_mutex_unlock(&mainThreadSyncCallMutex);
}
pthread_mutex_unlock(&mainThreadSyncCallMutex);
}
-(void) playbackThreadQueueMainThreadSyncBlock:(void(^)())block
@@ -1215,7 +1260,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
{
pthread_mutex_lock(&playerMutex);
{
if (self.internalState == STKAudioPlayerInternalStatePaused)
if (disposeWasRequested)
{
pthread_mutex_unlock(&playerMutex);
return NO;
}
else if (self.internalState == STKAudioPlayerInternalStatePaused)
{
pthread_mutex_unlock(&playerMutex);
@@ -1274,14 +1325,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
}
if (disposeWasRequested)
{
pthread_mutex_unlock(&playerMutex);
return NO;
}
if (currentlyPlayingEntry && currentlyPlayingEntry->parsedHeader)
if (currentlyPlayingEntry && currentlyPlayingEntry->parsedHeader && [currentlyPlayingEntry calculatedBitRate] > 0.0)
{
int32_t originalSeekVersion;
BOOL originalSeekToTimeRequested;
@@ -1309,7 +1353,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
}
pthread_mutex_unlock(&playerMutex);
return YES;
}
@@ -1320,8 +1363,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
{
playbackThreadRunLoop = [NSRunLoop currentRunLoop];
NSThread.currentThread.threadPriority = 1;
[threadStartedLock lockWhenCondition:0];
[threadStartedLock lockWhenCondition:0];
[threadStartedLock unlockWithCondition:1];
[playbackThreadRunLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
@@ -1343,6 +1386,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
disposeWasRequested = NO;
seekToTimeWasRequested = NO;
[currentlyReadingEntry.dataSource unregisterForEvents];
[currentlyPlayingEntry.dataSource unregisterForEvents];
currentlyReadingEntry.dataSource.delegate = nil;
currentlyPlayingEntry.dataSource.delegate = nil;
@@ -1355,6 +1401,10 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
self.internalState = STKAudioPlayerInternalStateDisposed;
playbackThreadRunLoop = nil;
[self destroyAudioResources];
[threadFinishedCondLock lock];
[threadFinishedCondLock unlockWithCondition:1];
}
@@ -1372,7 +1422,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
return;
}
long long seekByteOffset = currentEntry->audioDataOffset + (requestedSeekTime / self.duration) * (currentlyReadingEntry.audioDataLengthInBytes);
SInt64 seekByteOffset = currentEntry->audioDataOffset + (requestedSeekTime / self.duration) * (currentlyReadingEntry.audioDataLengthInBytes);
if (seekByteOffset > currentEntry.dataSource.length - (2 * currentEntry->packetBufferSize))
{
@@ -1410,10 +1460,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
AudioConverterReset(audioConverterRef);
}
[currentEntry updateAudioDataSource];
[currentEntry reset];
[currentEntry.dataSource seekToOffset:seekByteOffset];
self->waitingForDataAfterSeekFrameCount = 0;
self.internalState = STKAudioPlayerInternalStateWaitingForDataAfterSeek;
if (audioGraph)
@@ -1455,6 +1506,18 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
}
}
if (read < 0)
{
// iOS will shutdown network connections if the app is backgrounded (i.e. device is locked when player is paused)
// We try to reopen -- should probably add a back-off protocol in the future
SInt64 position = currentlyReadingEntry.dataSource.position;
[currentlyReadingEntry.dataSource seekToOffset:position];
return;
}
int flags = 0;
if (discontinuous)
@@ -1686,17 +1749,19 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[self invokeOnPlaybackThread:^
{
disposeWasRequested = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
pthread_mutex_lock(&playerMutex);
disposeWasRequested = YES;
pthread_cond_signal(&playerThreadReadyCondition);
pthread_mutex_unlock(&playerMutex);
pthread_mutex_lock(&mainThreadSyncCallMutex);
disposeWasRequested = YES;
pthread_cond_signal(&mainThreadSyncCallReadyCondition);
pthread_mutex_unlock(&mainThreadSyncCallMutex);
CFRunLoopStop([runLoop getCFRunLoop]);
}
if (wait)
@@ -1704,6 +1769,12 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
[threadFinishedCondLock lockWhenCondition:1];
[threadFinishedCondLock unlockWithCondition:0];
}
[self destroyAudioResources];
runLoop = nil;
playbackThread = nil;
playbackThreadRunLoop = nil;
}
-(BOOL) muted
@@ -1863,6 +1934,10 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
UInt32 flag = 1;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)));
flag = 0;
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag)));
#endif
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &canonicalAudioStreamBasicDescription, sizeof(canonicalAudioStreamBasicDescription)));
@@ -1879,6 +1954,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &mixerDescription, &mixerNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, mixerNode, &mixerDescription, &mixerUnit));
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
UInt32 busCount = 1;
@@ -1901,9 +1977,10 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
{
return;
}
CHECK_STATUS_AND_RETURN(AUGraphAddNode(audioGraph, &nbandUnitDescription, &eqNode));
CHECK_STATUS_AND_RETURN(AUGraphNodeInfo(audioGraph, eqNode, NULL, &eqUnit));
CHECK_STATUS_AND_RETURN(AudioUnitSetProperty(eqUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)));
while (self->options.equalizerBandFrequencies[eqBandCount] != 0)
{
@@ -1943,34 +2020,46 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
AudioComponentInstance convertUnit;
CHECK_STATUS_AND_RETURN_VALUE(AUGraphAddNode(audioGraph, &convertUnitDescription, &convertNode), 0);
CHECK_STATUS_AND_RETURN_VALUE(status = AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
CHECK_STATUS_AND_RETURN_VALUE(AUGraphNodeInfo(audioGraph, convertNode, &mixerDescription, &convertUnit), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat)), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &desFormat, sizeof(desFormat)), 0);
CHECK_STATUS_AND_RETURN_VALUE(AudioUnitSetProperty(convertUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)), 0);
[converterNodes addObject:@(convertNode)];
return convertNode;
}
-(void) connectNodes:(AUNode)srcNode desNode:(AUNode)desNode srcUnit:(AudioComponentInstance)srcUnit desUnit:(AudioComponentInstance)desUnit
{
OSStatus status;
BOOL addConverter = NO;
AudioStreamBasicDescription srcFormat, desFormat;
UInt32 size = sizeof(AudioStreamBasicDescription);
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(srcUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &srcFormat, &size));
CHECK_STATUS_AND_RETURN(AudioUnitGetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desFormat, &size));
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
addConverter = memcmp(&srcFormat, &desFormat, sizeof(srcFormat)) != 0;
if (status)
if (addConverter)
{
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, mixerNode, 0));
status = AudioUnitSetProperty(desUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &srcFormat, sizeof(srcFormat));
addConverter = status != 0;
}
else
{
if (addConverter)
{
AUNode convertNode = [self createConverterNode:srcFormat desFormat:desFormat];
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, convertNode, 0));
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, convertNode, 0, desNode, 0));
}
else
{
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, srcNode, 0, desNode, 0));
}
}
}
-(void) setOutputCallbackForFirstNode:(AUNode)firstNode firstUnit:(AudioComponentInstance)firstUnit
@@ -1994,7 +2083,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
CHECK_STATUS_AND_RETURN(AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct));
status = AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0);
CHECK_STATUS_AND_RETURN(AUGraphConnectNodeInput(audioGraph, converterNode, 0, firstNode, 0));
}
else
{
@@ -2005,8 +2094,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
-(void) createAudioGraph
{
OSStatus status;
NSMutableArray* nodes = [[NSMutableArray alloc] init];
NSMutableArray* units = [[NSMutableArray alloc] init];
converterNodes = [[NSMutableArray alloc] init];
CHECK_STATUS_AND_RETURN(NewAUGraph(&audioGraph));
CHECK_STATUS_AND_RETURN(AUGraphOpen(audioGraph));
@@ -2015,10 +2103,45 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
[self createMixerUnit];
[self createOutputUnit];
[self connectGraph];
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
self.volume = self->volume;
}
-(void) connectGraph
{
OSStatus status;
NSMutableArray* nodes = [[NSMutableArray alloc] init];
NSMutableArray* units = [[NSMutableArray alloc] init];
AUGraphClearConnections(audioGraph);
for (NSNumber* converterNode in converterNodes)
{
CHECK_STATUS_AND_REPORT(AUGraphRemoveNode(audioGraph, (AUNode)converterNode.intValue));
}
[converterNodes removeAllObjects];
if (eqNode)
{
[nodes addObject:@(eqNode)];
[units addObject:[NSValue valueWithPointer:eqUnit]];
if (self->equalizerEnabled)
{
[nodes addObject:@(eqNode)];
[units addObject:[NSValue valueWithPointer:eqUnit]];
self->equalizerOn = YES;
}
else
{
self->equalizerOn = NO;
}
}
else
{
self->equalizerOn = NO;
}
if (mixerNode)
@@ -2026,13 +2149,13 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
[nodes addObject:@(mixerNode)];
[units addObject:[NSValue valueWithPointer:mixerUnit]];
}
if (outputNode)
{
[nodes addObject:@(outputNode)];
[units addObject:[NSValue valueWithPointer:outputUnit]];
}
[self setOutputCallbackForFirstNode:(AUNode)[[nodes objectAtIndex:0] intValue] firstUnit:(AudioComponentInstance)[[units objectAtIndex:0] pointerValue]];
for (int i = 0; i < nodes.count - 1; i++)
@@ -2044,10 +2167,21 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
[self connectNodes:srcNode desNode:desNode srcUnit:srcUnit desUnit:desUnit];
}
}
-(BOOL) audioGraphIsRunning
{
OSStatus status;
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
return NO;
}
CHECK_STATUS_AND_RETURN(AUGraphInitialize(audioGraph));
self.volume = self->volume;
return isRunning;
}
-(BOOL) startAudioGraph
@@ -2056,18 +2190,7 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
[self resetPcmBuffers];
Boolean isRunning;
status = AUGraphIsRunning(audioGraph, &isRunning);
if (status)
{
[self unexpectedError:STKAudioPlayerErrorAudioSystemError];
return NO;
}
if (isRunning)
if ([self audioGraphIsRunning])
{
return NO;
}
@@ -2081,6 +2204,8 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl
return NO;
}
[self stopSystemBackgroundTask];
return YES;
}
@@ -2167,17 +2292,24 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
{
return;
}
if (seekToTimeWasRequested || disposeWasRequested)
{
return;
}
if (disposeWasRequested)
{
return;
}
if (audioConverterRef == nil)
{
return;
}
if ((seekToTimeWasRequested && [currentlyPlayingEntry calculatedBitRate] > 0.0))
{
[self wakeupPlaybackThread];
return;
}
discontinuous = NO;
OSStatus status;
@@ -2234,7 +2366,6 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
}
if (disposeWasRequested
|| seekToTimeWasRequested
|| self.internalState == STKAudioPlayerInternalStateStopped
|| self.internalState == STKAudioPlayerInternalStateDisposed
|| self.internalState == STKAudioPlayerInternalStatePendingNext)
@@ -2243,6 +2374,15 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu
return;
}
if (seekToTimeWasRequested && [currentlyPlayingEntry calculatedBitRate] > 0.0)
{
pthread_mutex_unlock(&playerMutex);
[self wakeupPlaybackThread];
return;
}
waiting = YES;
@@ -2397,11 +2537,15 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{
STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon;
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
STKQueueEntry* entry = audioPlayer->currentlyPlayingEntry;
STKQueueEntry* currentlyReadingEntry = audioPlayer->currentlyReadingEntry;
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
OSSpinLockLock(&audioPlayer->pcmBufferSpinLock);
BOOL waitForBuffer = NO;
BOOL muted = audioPlayer->muted;
STKQueueEntry* entry = audioPlayer->currentlyPlayingEntry;
AudioBuffer* audioBuffer = audioPlayer->pcmAudioBuffer;
UInt32 frameSizeInBytes = audioPlayer->pcmBufferFrameSizeInBytes;
UInt32 used = audioPlayer->pcmBufferUsedFrameCount;
@@ -2415,14 +2559,14 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{
if (state == STKAudioPlayerInternalStateWaitingForData)
{
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
SInt64 framesRequiredToStartPlaying = audioPlayer->framesRequiredToStartPlaying;
if (entry->lastFrameQueued >= 0)
{
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, audioPlayer->currentlyPlayingEntry->lastFrameQueued);
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued);
}
if (entry && audioPlayer->currentlyReadingEntry == entry
if (entry && currentlyReadingEntry == entry
&& entry->framesQueued < framesRequiredToStartPlaying)
{
waitForBuffer = YES;
@@ -2430,7 +2574,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
}
else if (state == STKAudioPlayerInternalStateRebuffering)
{
int64_t framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
SInt64 framesRequiredToStartPlaying = audioPlayer->framesRequiredToPlayAfterRebuffering;
if (entry->lastFrameQueued >= 0)
{
@@ -2444,7 +2588,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
}
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
{
int64_t framesRequiredToStartPlaying = inNumberFrames;
SInt64 framesRequiredToStartPlaying = 1024;
if (entry->lastFrameQueued >= 0)
{
@@ -2561,6 +2705,25 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
else if (state == STKAudioPlayerInternalStateWaitingForDataAfterSeek)
{
if (totalFramesCopied == 0)
{
OSAtomicAdd32(inNumberFrames - totalFramesCopied, &audioPlayer->waitingForDataAfterSeekFrameCount);
if (audioPlayer->waitingForDataAfterSeekFrameCount > audioPlayer->framesRequiredBeforeWaitingForDataAfterSeekBecomesPlaying)
{
[audioPlayer setInternalState:STKAudioPlayerInternalStatePlaying ifInState:^BOOL(STKAudioPlayerInternalState state)
{
return (state & STKAudioPlayerInternalStateRunning) && state != STKAudioPlayerInternalStatePaused;
}];
}
}
else
{
audioPlayer->waitingForDataAfterSeekFrameCount = 0;
}
}
}
if (frameFilters)
@@ -2575,6 +2738,17 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
entry->filter(asbd.mChannelsPerFrame, asbd.mBytesPerFrame, inNumberFrames, ioData->mBuffers[0].mData);
}
}
if (audioPlayer->equalizerEnabled != audioPlayer->equalizerOn)
{
Boolean isUpdated;
[audioPlayer connectGraph];
AUGraphUpdate(audioPlayer->audioGraph, &isUpdated);
isUpdated = isUpdated;
}
if (entry == nil)
{
@@ -2583,8 +2757,8 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
OSSpinLockLock(&entry->spinLock);
int64_t extraFramesPlayedNotAssigned = 0;
int64_t framesPlayedForCurrent = totalFramesCopied;
SInt64 extraFramesPlayedNotAssigned = 0;
SInt64 framesPlayedForCurrent = totalFramesCopied;
if (entry->lastFrameQueued >= 0)
{
@@ -2602,17 +2776,23 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
{
pthread_mutex_lock(&audioPlayer->playerMutex);
if (lastFramePlayed && entry == audioPlayer->currentlyPlayingEntry)
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
STKQueueEntry* currentlyPlayingEntry = audioPlayer->currentlyPlayingEntry;
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
if (lastFramePlayed && entry == currentlyPlayingEntry)
{
[audioPlayer audioQueueFinishedPlaying:entry];
while (extraFramesPlayedNotAssigned > 0)
{
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
STKQueueEntry* newEntry = audioPlayer->currentlyPlayingEntry;
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
if (newEntry != nil)
{
int64_t framesPlayedForCurrent = extraFramesPlayedNotAssigned;
SInt64 framesPlayedForCurrent = extraFramesPlayedNotAssigned;
OSSpinLockLock(&newEntry->spinLock);
@@ -2657,9 +2837,16 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
NSArray* retval;
NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:upcomingQueue.count + bufferingQueue.count];
[mutableArray skipQueueWithQueue:upcomingQueue];
[mutableArray skipQueueWithQueue:bufferingQueue];
for (STKQueueEntry* entry in upcomingQueue)
{
[mutableArray addObject:[entry queueItemId]];
}
for (STKQueueEntry* entry in bufferingQueue)
{
[mutableArray addObject:[entry queueItemId]];
}
retval = [NSArray arrayWithArray:mutableArray];
pthread_mutex_unlock(&playerMutex);
@@ -2684,7 +2871,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
if (upcomingQueue.count > 0)
{
NSObject* retval = [upcomingQueue objectAtIndex:0];
NSObject* retval = [[upcomingQueue objectAtIndex:0] queueItemId];
pthread_mutex_unlock(&playerMutex);
@@ -2693,7 +2880,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
if (bufferingQueue.count > 0)
{
NSObject* retval = [bufferingQueue objectAtIndex:0];
NSObject* retval = [[bufferingQueue objectAtIndex:0] queueItemId];
pthread_mutex_unlock(&playerMutex);
@@ -2956,4 +3143,15 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
return self->volume;
}
-(BOOL) equalizerEnabled
{
return self->equalizerEnabled;
}
-(void) setEqualizerEnabled:(BOOL)value
{
self->equalizerEnabled = value;
}
@end
@@ -36,6 +36,13 @@
#import "STKHTTPDataSource.h"
#import "STKDataSourceWrapper.h"
typedef struct
{
int watchdogPeriodSeconds;
int inactivePeriodBeforeReconnectSeconds;
}
STKAutoRecoveringHTTPDataSourceOptions;
@interface STKAutoRecoveringHTTPDataSource : STKDataSourceWrapper
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource;
@@ -38,18 +38,38 @@
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import "mach/mach_time.h"
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import "STKAutoRecoveringHTTPDataSource.h"
#define MAX_IMMEDIATE_RECONNECT_ATTEMPTS (2)
#define DEFAULT_WATCHDOG_PERIOD_SECONDS (8)
#define DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS (15)
static uint64_t GetTickCount(void)
{
static mach_timebase_info_data_t sTimebaseInfo;
uint64_t machTime = mach_absolute_time();
if (sTimebaseInfo.denom == 0 )
{
(void) mach_timebase_info(&sTimebaseInfo);
}
uint64_t millis = ((machTime / 1000000) * sTimebaseInfo.numer) / sTimebaseInfo.denom;
return millis;
}
@interface STKAutoRecoveringHTTPDataSource()
{
int serial;
int waitSeconds;
NSTimer* timeoutTimer;
BOOL waitingForNetwork;
uint64_t ticksWhenLastDataReceived;
SCNetworkReachabilityRef reachabilityRef;
STKAutoRecoveringHTTPDataSourceOptions options;
}
-(void) reachabilityChanged;
@@ -66,6 +86,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
}
static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* options)
{
if (options->watchdogPeriodSeconds == 0)
{
options->watchdogPeriodSeconds = DEFAULT_WATCHDOG_PERIOD_SECONDS;
}
if (options->inactivePeriodBeforeReconnectSeconds == 0)
{
options->inactivePeriodBeforeReconnectSeconds = DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS;
}
}
@implementation STKAutoRecoveringHTTPDataSource
-(STKHTTPDataSource*) innerHTTPDataSource
@@ -79,6 +112,11 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn
{
return [self initWithHTTPDataSource:innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions){}];
}
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions)optionsIn
{
if (self = [super initWithDataSource:innerDataSourceIn])
{
@@ -90,6 +128,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
PopulateOptionsWithDefault(&optionsIn);
self->options = optionsIn;
reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
}
@@ -117,14 +159,70 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
[super registerForEvents:runLoop];
[self startNotifierOnRunLoop:runLoop];
if (timeoutTimer)
{
[timeoutTimer invalidate];
timeoutTimer = nil;
}
ticksWhenLastDataReceived = GetTickCount();
[self createTimeoutTimer];
return YES;
}
-(void) unregisterForEvents
{
[super unregisterForEvents];
[self stopNotifier];
[self destroyTimeoutTimer];
}
-(void) timeoutTimerTick:(NSTimer*)timer
{
if (![self hasBytesAvailable])
{
if ([self hasGotNetworkConnection])
{
uint64_t currentTicks = GetTickCount();
if (((currentTicks - ticksWhenLastDataReceived) / 1000) >= options.inactivePeriodBeforeReconnectSeconds)
{
serial++;
NSLog(@"timeoutTimerTick %lld/%lld", self.position, self.length);
[self attemptReconnectWithSerial:@(serial)];
}
}
}
}
-(void) createTimeoutTimer
{
[self destroyTimeoutTimer];
NSRunLoop* runLoop = self.innerDataSource.eventsRunLoop;
if (runLoop == nil)
{
return;
}
timeoutTimer = [NSTimer timerWithTimeInterval:options.watchdogPeriodSeconds target:self selector:@selector(timeoutTimerTick:) userInfo:@(serial) repeats:YES];
[runLoop addTimer:timeoutTimer forMode:NSRunLoopCommonModes];
}
-(void) destroyTimeoutTimer
{
if (timeoutTimer)
{
[timeoutTimer invalidate];
timeoutTimer = nil;
}
}
-(void) stopNotifier
@@ -148,6 +246,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
return NO;
}
-(void) seekToOffset:(int64_t)offset
{
ticksWhenLastDataReceived = GetTickCount();
[super seekToOffset:offset];
}
-(void) close
{
[self destroyTimeoutTimer];
[super close];
}
-(void) dealloc
{
NSLog(@"STKAutoRecoveringHTTPDataSource dealloc");
@@ -155,6 +266,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
self.innerDataSource.delegate = nil;
[self stopNotifier];
[self destroyTimeoutTimer];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
@@ -170,15 +282,25 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
{
waitingForNetwork = NO;
NSLog(@"reachabilityChanged %lld/%lld", self.position, self.length);
serial++;
[self attemptReconnectWithSerial:@(serial)];
}
}
-(void) dataSourceDataAvailable:(STKDataSource*)dataSource
{
if (![self.innerDataSource hasBytesAvailable])
{
return;
}
serial++;
waitSeconds = 1;
ticksWhenLastDataReceived = GetTickCount();
[super dataSourceDataAvailable:dataSource];
}
@@ -191,7 +313,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
NSLog(@"attemptReconnect %lld/%lld", self.position, self.length);
[self seekToOffset:self.position];
if (self.innerDataSource.eventsRunLoop)
{
[self.innerDataSource reconnect];
}
}
-(void) attemptReconnectWithTimer:(NSTimer*)timer
@@ -214,10 +339,14 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
if (runLoop == nil)
{
[self performSelector:@selector(attemptReconnectWithSerial:) withObject:@(serial) afterDelay:waitSeconds];
// DataSource no longer used
return;
}
else
{
serial++;
NSTimer* timer = [NSTimer timerWithTimeInterval:waitSeconds target:self selector:@selector(attemptReconnectWithTimer:) userInfo:@(serial) repeats:NO];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
@@ -54,6 +54,7 @@
-(BOOL) reregisterForEvents;
-(void) open;
-(void) openCompleted;
-(void) dataAvailable;
-(void) eof;
-(void) errorOccured;
@@ -49,6 +49,9 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
case kCFStreamEventHasBytesAvailable:
[datasource dataAvailable];
break;
case kCFStreamEventOpenCompleted:
[datasource openCompleted];
break;
default:
break;
}
@@ -101,6 +104,11 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
{
if (stream)
{
if (eventsRunLoop)
{
[self unregisterForEvents];
}
CFReadStreamClose(stream);
CFRelease(stream);
@@ -112,7 +120,7 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
{
}
-(void) seekToOffset:(long long)offset
-(void) seekToOffset:(SInt64)offset
{
}
@@ -127,6 +135,8 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
{
CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL);
CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
eventsRunLoop = nil;
}
}
@@ -148,10 +158,10 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
{
eventsRunLoop = runLoop;
if (!stream)
if (!stream)
{
[self open];
// Will register when they open or seek
return YES;
}
@@ -184,4 +194,8 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
return 0;
}
-(void) openCompleted
{
}
@end
+3 -8
View File
@@ -43,15 +43,10 @@
-(void) dataSourceEof:(STKDataSource*)dataSource;
@end
@protocol AudioDataSource<NSObject>
@property (readwrite) double averageBitRate;
@property (readwrite) long long audioDataOffset;
@end
@interface STKDataSource : NSObject
@property (readonly) long long position;
@property (readonly) long long length;
@property (readonly) SInt64 position;
@property (readonly) SInt64 length;
@property (readonly) BOOL hasBytesAvailable;
@property (readwrite, unsafe_unretained) id<STKDataSourceDelegate> delegate;
@@ -59,7 +54,7 @@
-(void) unregisterForEvents;
-(void) close;
-(void) seekToOffset:(long long)offset;
-(void) seekToOffset:(SInt64)offset;
-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size;
-(AudioFileTypeID) audioFileTypeHint;
+4 -4
View File
@@ -37,12 +37,12 @@
@implementation STKDataSource
@synthesize delegate;
-(long long) length
-(SInt64) length
{
return 0;
}
-(void) seekToOffset:(long long)offset
-(void) seekToOffset:(SInt64)offset
{
}
@@ -51,7 +51,7 @@
return -1;
}
-(long long) position
-(SInt64) position
{
return 0;
}
@@ -66,7 +66,7 @@
}
-(void) close
{
{
}
-(BOOL) hasBytesAvailable
@@ -62,12 +62,12 @@
self.innerDataSource.delegate = nil;
}
-(long long) length
-(SInt64) length
{
return self.innerDataSource.length;
}
-(void) seekToOffset:(long long)offset
-(void) seekToOffset:(SInt64)offset
{
return [self.innerDataSource seekToOffset:offset];
}
@@ -77,7 +77,7 @@
return [self.innerDataSource readIntoBuffer:buffer withSize:size];
}
-(long long) position
-(SInt64) position
{
return self.innerDataSource.position;
}
@@ -43,12 +43,13 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
@interface STKHTTPDataSource : STKCoreFoundationDataSource
@property (readonly, retain) NSURL* url;
@property (readwrite) UInt32 httpStatusCode;
@property (readonly) UInt32 httpStatusCode;
+(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
-(id) initWithURL:(NSURL*)url;
-(id) initWithURLProvider:(STKURLProvider)urlProvider;
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
-(NSRunLoop*) eventsRunLoop;
-(void) reconnect;
@end
+55 -22
View File
@@ -38,10 +38,13 @@
@interface STKHTTPDataSource()
{
@private
long long seekStart;
long long relativePosition;
long long fileLength;
UInt32 httpStatusCode;
SInt64 seekStart;
SInt64 relativePosition;
SInt64 fileLength;
int discontinuous;
int requestSerialNumber;
NSURL* currentUrl;
STKAsyncURLProvider asyncUrlProvider;
NSDictionary* httpHeaders;
@@ -138,21 +141,28 @@
-(void) dataAvailable
{
if (stream == NULL) {
return;
}
if (self.httpStatusCode == 0)
{
CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
CFRelease(response);
if (response)
{
httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
self->httpStatusCode = (UInt32)CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
CFRelease(response);
}
if (self.httpStatusCode == 200)
{
if (seekStart == 0)
{
fileLength = (long long)[[httpHeaders objectForKey:@"Content-Length"] integerValue];
fileLength = (SInt64)[[httpHeaders objectForKey:@"Content-Length"] longLongValue];
}
NSString* contentType = [httpHeaders objectForKey:@"Content-Type"];
@@ -195,28 +205,34 @@
[super dataAvailable];
}
-(long long) position
-(SInt64) position
{
return seekStart + relativePosition;
}
-(long long) length
-(SInt64) length
{
return fileLength >= 0 ? fileLength : 0;
}
-(void) seekToOffset:(long long)offset
-(void) reconnect
{
if (eventsRunLoop)
{
[self unregisterForEvents];
}
NSRunLoop* savedEventsRunLoop = eventsRunLoop;
if (stream)
{
CFReadStreamClose(stream);
CFRelease(stream);
}
[self close];
eventsRunLoop = savedEventsRunLoop;
[self seekToOffset:self.position];
}
-(void) seekToOffset:(SInt64)offset
{
NSRunLoop* savedEventsRunLoop = eventsRunLoop;
[self close];
eventsRunLoop = savedEventsRunLoop;
NSAssert([NSRunLoop currentRunLoop] == eventsRunLoop, @"Seek called on wrong thread");
@@ -255,8 +271,18 @@
-(void) openForSeek:(BOOL)forSeek
{
int localRequestSerialNumber;
requestSerialNumber++;
localRequestSerialNumber = requestSerialNumber;
asyncUrlProvider(self, forSeek, ^(NSURL* url)
{
if (localRequestSerialNumber != self->requestSerialNumber)
{
return;
}
self->currentUrl = url;
if (url == nil)
@@ -317,7 +343,7 @@
[self reregisterForEvents];
self.httpStatusCode = 0;
self->httpStatusCode = 0;
// Open
@@ -325,6 +351,8 @@
{
CFRelease(stream);
CFRelease(message);
stream = 0;
[self errorOccured];
@@ -337,6 +365,11 @@
});
}
-(UInt32) httpStatusCode
{
return self->httpStatusCode;
}
-(NSRunLoop*) eventsRunLoop
{
return self->eventsRunLoop;
@@ -36,8 +36,8 @@
@interface STKLocalFileDataSource()
{
long long position;
long long length;
SInt64 position;
SInt64 length;
AudioFileTypeID audioFileTypeHint;
}
@property (readwrite, copy) NSString* filePath;
@@ -153,12 +153,12 @@
CFReadStreamOpen(stream);
}
-(long long) position
-(SInt64) position
{
return position;
}
-(long long) length
-(SInt64) length
{
return length;
}
@@ -181,7 +181,7 @@
return retval;
}
-(void) seekToOffset:(long long)offset
-(void) seekToOffset:(SInt64)offset
{
CFStreamStatus status = kCFStreamStatusClosed;
@@ -200,6 +200,18 @@
[self open];
}
if (stream == 0)
{
CFRunLoopPerformBlock(eventsRunLoop.getCFRunLoop, NSRunLoopCommonModes, ^
{
[self errorOccured];
});
CFRunLoopWakeUp(eventsRunLoop.getCFRunLoop);
return;
}
if (CFReadStreamSetProperty(stream, kCFStreamPropertyFileCurrentOffset, (__bridge CFTypeRef)[NSNumber numberWithLongLong:offset]) != TRUE)
{
position = 0;
+3 -4
View File
@@ -22,9 +22,9 @@
UInt64 audioDataByteCount;
UInt32 packetBufferSize;
volatile Float64 seekTime;
volatile int64_t framesQueued;
volatile int64_t framesPlayed;
volatile int64_t lastFrameQueued;
volatile SInt64 framesQueued;
volatile SInt64 framesPlayed;
volatile SInt64 lastFrameQueued;
volatile int processedPacketsCount;
volatile int processedPacketsSizeTotal;
AudioStreamBasicDescription audioStreamBasicDescription;
@@ -40,7 +40,6 @@
-(double) duration;
-(Float64) progressInFrames;
-(double) calculatedBitRate;
-(void) updateAudioDataSource;
-(BOOL) isDefinitelyCompatible:(AudioStreamBasicDescription*)basicDescription;
@end
+13 -20
View File
@@ -9,7 +9,8 @@
#import "STKQueueEntry.h"
#import "STKDataSource.h"
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS (64)
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN (2)
#define STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED (64)
@implementation STKQueueEntry
@@ -17,6 +18,8 @@
{
if (self = [super init])
{
self->spinLock = OS_SPINLOCK_INIT;
self.dataSource = dataSourceIn;
self.queueItemId = queueItemIdIn;
self->lastFrameQueued = -1;
@@ -38,13 +41,16 @@
{
double retval;
if (packetDuration && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS)
if (packetDuration > 0)
{
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
retval = averagePacketByteSize / packetDuration * 8;
return retval;
if (processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_PREFERRED || (audioStreamBasicDescription.mBytesPerFrame == 0 && processedPacketsCount > STK_BIT_RATE_ESTIMATION_MIN_PACKETS_MIN))
{
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
retval = averagePacketByteSize / packetDuration * 8;
return retval;
}
}
retval = (audioStreamBasicDescription.mBytesPerFrame * audioStreamBasicDescription.mSampleRate) * 8;
@@ -52,19 +58,6 @@
return retval;
}
-(void) updateAudioDataSource
{
if ([self.dataSource conformsToProtocol:@protocol(AudioDataSource)])
{
double calculatedBitrate = [self calculatedBitRate];
id<AudioDataSource> audioDataSource = (id<AudioDataSource>)self.dataSource;
audioDataSource.averageBitRate = calculatedBitrate;
audioDataSource.audioDataOffset = audioDataOffset;
}
}
-(double) duration
{
if (self->sampleRate <= 0)