Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ build
.DS_Store
*.xcodeproj/*.mode*
*.xcodeproj/*.pbxuser
*.xcodeproj/*.xcworkspace
*.xcodeproj/xcuserdata
*.xcodeproj/*.pbxproj
10 changes: 10 additions & 0 deletions AudioStreamer.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 9 additions & 11 deletions Classes/AudioStreamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,16 @@
//
#define SHOUTCAST_METADATA

#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif
#else
#import <Cocoa/Cocoa.h>
#endif TARGET_OS_IPHONE

#import <Foundation/Foundation.h>
#include <pthread.h>
#include <AudioToolbox/AudioToolbox.h>

#define LOG_QUEUED_BUFFERS 0

#define kNumAQBufs 16 // Number of audio queue buffers we allocate.
#define kNumStartsAQBufs 16

#define kNumAQBufs 1500 // Number of audio queue buffers we allocate.
// Needs to be big enough to keep audio pipeline
// busy (non-zero number of queued buffers) but
// not so big that audio takes too long to begin
Expand Down Expand Up @@ -177,17 +172,21 @@ extern NSString * const ASUpdateMetadataNotification;
unsigned int dataBytesRead; // how many bytes of data have been read
NSMutableString *metaDataString; // the metaDataString
#endif
BOOL vbr; // indicates VBR (or not) stream

}

@property AudioStreamerErrorCode errorCode;
@property (readonly) AudioStreamerState state;
@property (readonly) AudioStreamerStopReason stopReason;
@property (readonly) double progress;
@property (readonly) double bufferFillPercentage;
@property (readonly) double duration;
@property (readwrite) UInt32 bitRate;
@property (readonly) NSDictionary *httpHeaders;
@property (readonly) UInt32 numberOfChannels;
@property (assign, getter=isMeteringEnabled) BOOL meteringEnabled;

@property (readonly) BOOL vbr;

- (id)initWithURL:(NSURL *)aURL;
- (void)start;
Expand All @@ -211,4 +210,3 @@ extern NSString * const ASUpdateMetadataNotification;




101 changes: 66 additions & 35 deletions Classes/AudioStreamer.m
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ @implementation AudioStreamer

@synthesize errorCode;
@synthesize state;
@synthesize stopReason;
@synthesize bitRate;
@synthesize httpHeaders;
@synthesize numberOfChannels;

@synthesize vbr;

//
// initWithURL
Expand Down Expand Up @@ -264,6 +265,17 @@ - (void)dealloc
[super dealloc];
}

//
// bufferFillPercentage
//
// returns a value between 0 and 1 that represents how full the buffer is
//
-(double)bufferFillPercentage
{
return (double)buffersUsed/(double)(kNumAQBufs - 1);
}


//
// isFinishing
//
Expand Down Expand Up @@ -676,8 +688,8 @@ - (BOOL)openReadStream
if (fileLength > 0 && seekByteOffset > 0)
{
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"),
(CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", seekByteOffset, fileLength]);
discontinuous = YES;
(CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", seekByteOffset, fileLength - 1]);
discontinuous = vbr;
}

//
Expand Down Expand Up @@ -944,6 +956,7 @@ - (void)start
initWithTarget:self
selector:@selector(startInternal)
object:nil];
[internalThread setName:@"InternalThread"];
[internalThread start];
}
}
Expand Down Expand Up @@ -1101,17 +1114,24 @@ - (double)progress
//
- (double)calculatedBitRate
{
if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets)
if (vbr)
{
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
return 8.0 * averagePacketByteSize / packetDuration;
}
if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets)
{
double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
return 8.0 * averagePacketByteSize / packetDuration;
}

if (bitRate)
if (bitRate)
{
return (double)bitRate;
}
}
else
{
return (double)bitRate;
bitRate = 8.0 * asbd.mSampleRate * asbd.mBytesPerPacket * asbd.mFramesPerPacket;
return bitRate;
}

return 0;
}

Expand Down Expand Up @@ -1218,8 +1238,12 @@ - (void)pause
else if (state == AS_PAUSED)
{
err = AudioQueueStart(audioQueue, NULL);
#if TARGET_OS_IPHONE
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
#if TARGET_OS_IPHONE
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
if (bgTaskId != UIBackgroundTaskInvalid) {
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
}
}
#endif
if (err)
{
Expand Down Expand Up @@ -1809,13 +1833,15 @@ - (void)enqueueBuffer
// AudioFileStream stays a small amount ahead of the AudioQueue to
// avoid an audio glitch playing streaming files on iPhone SDKs < 3.0
//
if (state == AS_FLUSHING_EOF || buffersUsed == kNumAQBufs - 1)
if (state == AS_FLUSHING_EOF || buffersUsed == kNumStartsAQBufs - 1)
{
if (self.state == AS_BUFFERING)
{
err = AudioQueueStart(audioQueue, NULL);
#if TARGET_OS_IPHONE
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
}
#endif
if (err)
{
Expand All @@ -1829,8 +1855,10 @@ - (void)enqueueBuffer
self.state = AS_WAITING_FOR_QUEUE_TO_START;

err = AudioQueueStart(audioQueue, NULL);
#if TARGET_OS_IPHONE
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
#if TARGET_OS_IPHONE
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
}
#endif
if (err)
{
Expand Down Expand Up @@ -1891,18 +1919,25 @@ - (void)createQueue
}

// get the packet size if it is available
UInt32 sizeOfUInt32 = sizeof(UInt32);
err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize);
if (err || packetBufferSize == 0)
if (vbr)
{
err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize);
UInt32 sizeOfUInt32 = sizeof(UInt32);
err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize);
if (err || packetBufferSize == 0)
{
// No packet size available, just use the default
packetBufferSize = kAQDefaultBufSize;
err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize);
if (err || packetBufferSize == 0)
{
// No packet size available, just use the default
packetBufferSize = kAQDefaultBufSize;
}
}
}

else
{
packetBufferSize = kAQDefaultBufSize;
}

// allocate audio queue buffers
for (unsigned int i = 0; i < kNumAQBufs; ++i)
{
Expand Down Expand Up @@ -2114,6 +2149,7 @@ - (void)handleAudioPackets:(const void *)inInputData

if (!audioQueue)
{
vbr = (inPacketDescriptions != nil);
[self createQueue];
}
}
Expand Down Expand Up @@ -2310,9 +2346,6 @@ - (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ
propertyID:(AudioQueuePropertyID)inID
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#if TARGET_OS_IPHONE
UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
#endif
@synchronized(self)
{
if (inID == kAudioQueueProperty_IsRunning)
Expand All @@ -2337,21 +2370,20 @@ - (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ
// By creating an NSRunLoop for the AudioQueue thread, it changes the
// thread destruction order and seems to avoid this crash bug -- or
// at least I haven't had it since (nasty hard to reproduce error!)
//
#if TARGET_OS_IPHONE
newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
#endif
//

[NSRunLoop currentRunLoop];

self.state = AS_PLAYING;

#if TARGET_OS_IPHONE
if (bgTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask: bgTaskId];
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
if (bgTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask: bgTaskId];
}

bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
}

bgTaskId = newTaskId;
#endif
}
else
Expand Down Expand Up @@ -2390,4 +2422,3 @@ - (void)handleInterruptionChangeToState:(AudioQueuePropertyID)inInterruptionStat

@end


2 changes: 1 addition & 1 deletion Classes/MacStreamingPlayerController.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ @implementation MacStreamingPlayerController

- (void)awakeFromNib
{
[downloadSourceField setStringValue:@"http://192.168.1.2/~matt/inside.m4a"];
[downloadSourceField setStringValue:@"http://shoutmedia.abc.net.au:10326"];
}

//
Expand Down
18 changes: 16 additions & 2 deletions Classes/iPhoneStreamingPlayerAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

#import <dispatch/dispatch.h>

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif

#import "iPhoneStreamingPlayerAppDelegate.h"
#import "iPhoneStreamingPlayerViewController.h"
#import "AudioStreamer.h"
Expand All @@ -39,6 +43,7 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application {
selector:@selector(presentAlertWithTitle:)
name:ASPresentAlertWithTitleNotification
object:nil];
[[NSThread currentThread] setName:@"Main Thread"];
}


Expand All @@ -50,12 +55,15 @@ - (void)dealloc {

- (void)presentAlertWithTitle:(NSNotification *)notification
{
NSString *title = [[notification userInfo] objectForKey:@"title"];
NSString *message = [[notification userInfo] objectForKey:@"message"];

//NSLog(@"Current Thread = %@", [NSThread currentThread]);
dispatch_queue_t main_queue = dispatch_get_main_queue();

dispatch_async(main_queue, ^{

NSString *title = [[notification userInfo] objectForKey:@"title"];
NSString *message = [[notification userInfo] objectForKey:@"message"];
//NSLog(@"Current Thread (in main queue) = %@", [NSThread currentThread]);
if (!uiIsVisible) {
#ifdef TARGET_OS_IPHONE
if(kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) {
Expand All @@ -77,11 +85,14 @@ - (void)presentAlertWithTitle:(NSNotification *)notification
cancelButtonTitle:NSLocalizedString(@"OK", @"")
otherButtonTitles: nil]
autorelease];
/*
[alert
performSelector:@selector(show)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
*/
[alert show];
#else
NSAlert *alert =
[NSAlert
Expand All @@ -90,11 +101,14 @@ - (void)presentAlertWithTitle:(NSNotification *)notification
alternateButton:nil
otherButton:nil
informativeTextWithFormat:message];
/*
[alert
performSelector:@selector(runModal)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
*/
[alert runModal];
#endif
}
});
Expand Down
1 change: 1 addition & 0 deletions Classes/iPhoneStreamingPlayerViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
IBOutlet UISlider *progressSlider;
IBOutlet UITextField *metadataArtist;
IBOutlet UITextField *metadataTitle;
IBOutlet UITextField *metadataAlbum;
AudioStreamer *streamer;
NSTimer *progressUpdateTimer;
NSTimer *levelMeterUpdateTimer;
Expand Down
Loading