From 13300a8ae9be6da34af7dfb1dec7679653318066 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Sat, 10 Dec 2011 14:00:28 +0800 Subject: [PATCH 01/11] added local file stream support; added prebuffering and a new thread to push buffer into audio queue(to pre-buffer audio file as soon as possible, to minimize influence of network various (specificially in iOS); removed a statement in - (void)stop, which can cause 'iuhw' error to audio queue between quickly skip songs --- Classes/AudioStreamer.h | 24 +- Classes/AudioStreamer.m | 535 +++++++++++++++++++++++++++------------- 2 files changed, 387 insertions(+), 172 deletions(-) diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index 42af6d9..93c49f0 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -11,7 +11,7 @@ // this copyright and permission notice. Attribution in compiled projects is // appreciated but not required. // -#define SHOUTCAST_METADATA +//#define SHOUTCAST_METADATA #if TARGET_OS_IPHONE #import @@ -20,12 +20,16 @@ #endif #else #import -#endif TARGET_OS_IPHONE +#endif// TARGET_OS_IPHONE #import #include #include +//#import "encryption.h" //custom encryption support +#import + + #define LOG_QUEUED_BUFFERS 0 #define kNumAQBufs 16 // Number of audio queue buffers we allocate. @@ -98,7 +102,8 @@ typedef enum AS_AUDIO_QUEUE_FLUSH_FAILED, AS_AUDIO_STREAMER_FAILED, AS_GET_AUDIO_TIME_FAILED, - AS_AUDIO_BUFFER_TOO_SMALL + AS_AUDIO_BUFFER_TOO_SMALL, + AS_AUDIO_MEMORY_ALLOC_FAILED, } AudioStreamerErrorCode; extern NSString * const ASStatusChangedNotification; @@ -179,6 +184,17 @@ extern NSString * const ASUpdateMetadataNotification; NSMutableString *metaDataString; // the metaDataString #endif BOOL vbr; // indicates VBR (or not) stream + +// EncryptionMethod _encryption;//加密方式 + uLong _crc32; + void * _encryptionSampledBuffer; //加密用缓冲 + NSUInteger _encryptionOffset; + + NSLock * _bufferLock; + NSMutableArray * _buffers; + NSThread * _bufferPushingThread; + + NSTimeInterval _lastTimeInterval; } @property AudioStreamerErrorCode errorCode; @@ -194,6 +210,7 @@ extern NSString * const ASUpdateMetadataNotification; @property (readonly) BOOL vbr; - (id)initWithURL:(NSURL *)aURL; +//- (id)initWithURL:(NSURL *)aURL encryption:(EncryptionMethod)method crc32:(uLong)crc32; - (void)start; - (void)stop; - (void)pause; @@ -209,6 +226,7 @@ extern NSString * const ASUpdateMetadataNotification; - (float)averagePowerForChannel:(NSUInteger)channelNumber; +- (void)setVolume:(float)vol; @end diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index da51239..c6c9459 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -21,6 +21,8 @@ #define kCFCoreFoundationVersionNumber_MIN 550.00 #endif +#define RELEASE_SAFELY(_x) if(_x){[(_x) release];_x=nil;} + #define BitRateEstimationMaxPackets 5000 #define BitRateEstimationMinPackets 50 @@ -57,9 +59,12 @@ NSString * const AS_AUDIO_STREAMER_FAILED_STRING = @"Audio playback failed"; NSString * const AS_NETWORK_CONNECTION_FAILED_STRING = @"Network connection failed"; NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQDefaultBufSize."; +NSString * const AS_AUDIO_MEMORY_ALLOC_FAILED_STRING = @"Alloc memory failed"; @interface AudioStreamer () @property (readwrite) AudioStreamerState state; +@property (readwrite) BOOL allBufferPushed; +@property (readwrite) BOOL finishedBuffer; - (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID @@ -82,25 +87,26 @@ - (void)enqueueBuffer; - (void)handleReadFromStream:(CFReadStreamRef)aStream eventType:(CFStreamEventType)eventType; +- (void)pushingBufferThread:(id)object; @end #pragma mark Audio Callback Function Prototypes -void MyAudioQueueOutputCallback(void* inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer); -void MyAudioQueueIsRunningCallback(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID); -void MyPropertyListenerProc( void * inClientData, +static void MyAudioQueueOutputCallback(void* inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer); +static void MyAudioQueueIsRunningCallback(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID); +static void MyPropertyListenerProc( void * inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 * ioFlags); -void MyPacketsProc( void * inClientData, +static void MyPacketsProc( void * inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void * inInputData, AudioStreamPacketDescription *inPacketDescriptions); -OSStatus MyEnqueueBuffer(AudioStreamer* myData); +static OSStatus MyEnqueueBuffer(AudioStreamer* myData); #if TARGET_OS_IPHONE -void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState); +static void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState); #endif #pragma mark Audio Callback Function Implementations @@ -231,6 +237,17 @@ @implementation AudioStreamer @synthesize httpHeaders; @synthesize numberOfChannels; @synthesize vbr; +@synthesize allBufferPushed = _allBufferPushed; +@synthesize finishedBuffer = _finishedBuffer; + +- (void)setVolume:(float)vol { + @synchronized(self) { + if (audioQueue) { + AudioQueueParameterValue v = vol; + AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, v); + } + } +} // // initWithURL @@ -246,10 +263,25 @@ - (id)initWithURL:(NSURL *)aURL #ifdef SHOUTCAST_METADATA metaDataString = [[NSMutableString alloc] initWithString:@""]; #endif + _buffers = [[NSMutableArray alloc] initWithCapacity:2048/kAQDefaultBufSize]; + _bufferLock = [[NSLock alloc] init]; + self.allBufferPushed = NO; + self.finishedBuffer = NO; + _lastTimeInterval = 0; } return self; } +//- (id)initWithURL:(NSURL *)aURL encryption:(EncryptionMethod)method crc32:(uLong)crc32 +//{ +// if ((self = [self initWithURL:aURL])) +// { +// _encryption = method; +// _crc32 = crc32; +// } +// return self; +//} + // // dealloc // @@ -262,6 +294,8 @@ - (void)dealloc #ifdef SHOUTCAST_METADATA [metaDataString release]; #endif + RELEASE_SAFELY(_buffers); + RELEASE_SAFELY(_bufferLock); [super dealloc]; } @@ -375,6 +409,8 @@ + (NSString *)stringForErrorCode:(AudioStreamerErrorCode)anErrorCode return AS_AUDIO_STREAMER_FAILED_STRING; case AS_AUDIO_BUFFER_TOO_SMALL: return AS_AUDIO_BUFFER_TOO_SMALL_STRING; + case AS_AUDIO_MEMORY_ALLOC_FAILED: + return AS_AUDIO_MEMORY_ALLOC_FAILED_STRING; default: return AS_AUDIO_STREAMER_FAILED_STRING; } @@ -499,6 +535,12 @@ - (void)setState:(AudioStreamerState)aStatus } } +- (AudioStreamerState)state { + @synchronized(self) { + return state; + } +} + // // isPlaying // @@ -673,61 +715,65 @@ - (BOOL)openReadStream NSAssert([[NSThread currentThread] isEqual:internalThread], @"File stream download must be started on the internalThread"); NSAssert(stream == nil, @"Download stream already initialized"); - - // - // Create the HTTP GET request - // - CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1); + if ([url isFileURL]) { + stream = CFReadStreamCreateWithFile(NULL, (CFURLRef)url); + } + else { + // + // Create the HTTP GET request + // + CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1); #ifdef SHOUTCAST_METADATA - CFHTTPMessageSetHeaderFieldValue(message, CFSTR("icy-metadata"), CFSTR("1")); + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("icy-metadata"), CFSTR("1")); #endif - // - // If we are creating this request to seek to a location, set the - // requested byte range in the headers. - // - if (fileLength > 0 && seekByteOffset > 0) - { - CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), - (CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", seekByteOffset, fileLength - 1]); - discontinuous = vbr; - } - - // - // Create the read stream that will receive data from the HTTP request - // - stream = CFReadStreamCreateForHTTPRequest(NULL, message); - CFRelease(message); - - // - // Enable stream redirection - // - if (CFReadStreamSetProperty( - stream, - kCFStreamPropertyHTTPShouldAutoredirect, - kCFBooleanTrue) == false) - { - [self presentAlertWithTitle:NSLocalizedStringFromTable(@"File Error", @"Errors", nil) - message:NSLocalizedStringFromTable(@"Unable to configure network read stream.", @"Errors", nil)]; - return NO; - } - - // - // Handle SSL connections - // - if( [[url absoluteString] rangeOfString:@"https"].location != NSNotFound ) - { - NSDictionary *sslSettings = + // + // If we are creating this request to seek to a location, set the + // requested byte range in the headers. + // + if (fileLength > 0 && seekByteOffset > 0) + { + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), + (CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", seekByteOffset, fileLength - 1]); + discontinuous = vbr; + } + + // + // Create the read stream that will receive data from the HTTP request + // + stream = CFReadStreamCreateForHTTPRequest(NULL, message); + CFRelease(message); + + // + // Enable stream redirection + // + if (CFReadStreamSetProperty( + stream, + kCFStreamPropertyHTTPShouldAutoredirect, + kCFBooleanTrue) == false) + { + [self presentAlertWithTitle:NSLocalizedStringFromTable(@"File Error", @"Errors", nil) + message:NSLocalizedStringFromTable(@"Unable to configure network read stream.", @"Errors", nil)]; + return NO; + } + + // + // Handle SSL connections + // + if( [[url absoluteString] rangeOfString:@"https"].location != NSNotFound ) + { + NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: - (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, - [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, - [NSNull null], kCFStreamSSLPeerName, - nil]; - - CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, sslSettings); - } + (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, + [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, + [NSNull null], kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, sslSettings); + } + } // // We're now ready to receive data @@ -787,8 +833,8 @@ - (BOOL)openReadStream // - (void)startInternal { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - @synchronized(self) { if (state != AS_STARTING_FILE_THREAD) @@ -796,7 +842,7 @@ - (void)startInternal if (state != AS_STOPPING && state != AS_STOPPED) { - NSLog(@"### Not starting audio thread. State code is: %ld", state); + NSLog(@"### Not starting audio thread. State code is: %d", state); } self.state = AS_INITIALIZED; [pool release]; @@ -840,10 +886,12 @@ - (void)startInternal BOOL isRunning = YES; do { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; - + @synchronized(self) { if (seekWasRequested) { [self internalSeekToTime:requestedSeekTime]; @@ -866,12 +914,18 @@ - (void)startInternal } self.state = AS_BUFFERING; } - } while (isRunning && ![self runLoopShouldExit]); + [pool release]; + [NSThread sleepForTimeInterval:0.01]; + } while ((self.allBufferPushed || isRunning || [self isFinishing]) && ![self runLoopShouldExit]); cleanup: @synchronized(self) { + if (_encryptionSampledBuffer) { + free(_encryptionSampledBuffer); + _encryptionSampledBuffer = NULL; + } // // Cleanup the read stream if it is still open // @@ -927,7 +981,6 @@ - (void)startInternal [internalThread release]; internalThread = nil; } - [pool release]; } @@ -1269,8 +1322,6 @@ - (void)stop { @synchronized(self) { - if (state == AS_WAITING_FOR_DATA || state == AS_STARTING_FILE_THREAD) - return; if (audioQueue && (state == AS_PLAYING || state == AS_PAUSED || state == AS_BUFFERING || state == AS_WAITING_FOR_QUEUE_TO_START)) @@ -1337,93 +1388,105 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } else if (eventType == kCFStreamEventEndEncountered) { - @synchronized(self) - { - if ([self isFinishing]) - { - return; - } - } - - // - // If there is a partially filled buffer, pass it to the AudioQueue for - // processing - // - if (bytesFilled) - { - if (self.state == AS_WAITING_FOR_DATA) - { - // - // Force audio data smaller than one whole buffer to play. - // - self.state = AS_FLUSHING_EOF; - } - [self enqueueBuffer]; - } - - @synchronized(self) - { - if (state == AS_WAITING_FOR_DATA) - { - [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; - } - - // - // We left the synchronized section to enqueue the buffer so we - // must check that we are !finished again before touching the - // audioQueue - // - else if (![self isFinishing]) - { - if (audioQueue) - { - // - // Set the progress at the end of the stream - // - err = AudioQueueFlush(audioQueue); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; - return; - } - - self.state = AS_STOPPING; - stopReason = AS_STOPPING_EOF; - err = AudioQueueStop(audioQueue, false); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; - return; - } - } - else - { - self.state = AS_STOPPED; - stopReason = AS_STOPPING_EOF; - } - } - } + self.finishedBuffer = YES; + if ([url isFileURL]) { + @synchronized(self) + { + if ([self isFinishing]) + { + return; + } + } + + // + // If there is a partially filled buffer, pass it to the AudioQueue for + // processing + // + if (bytesFilled) + { + if (self.state == AS_WAITING_FOR_DATA) + { + // + // Force audio data smaller than one whole buffer to play. + // + self.state = AS_FLUSHING_EOF; + } + [self enqueueBuffer]; + } + + @synchronized(self) + { + if (state == AS_WAITING_FOR_DATA) + { + [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; + } + + // + // We left the synchronized section to enqueue the buffer so we + // must check that we are !finished again before touching the + // audioQueue + // + else if (![self isFinishing]) + { + if (audioQueue) + { + // + // Set the progress at the end of the stream + // + err = AudioQueueFlush(audioQueue); + if (err) + { + [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; + return; + } + + self.state = AS_STOPPING; + stopReason = AS_STOPPING_EOF; + err = AudioQueueStop(audioQueue, false); + if (err) + { + [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; + return; + } + } + else + { + self.state = AS_STOPPED; + stopReason = AS_STOPPING_EOF; + } + } + } + } } else if (eventType == kCFStreamEventHasBytesAvailable) { - if (!httpHeaders) - { - CFTypeRef message = - CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); - httpHeaders = - (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message); - CFRelease(message); - //NSLog(@"headers %@", httpHeaders); - - // - // Only read the content length if we seeked to time zero, otherwise - // we only have a subset of the total bytes. - // - if (seekByteOffset == 0) - { - fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue]; - } - } + if ([url isFileURL]) { + NSFileManager * mgr = [[NSFileManager alloc] init]; + NSError * error = nil; + NSDictionary * attr = [mgr attributesOfItemAtPath:[url path] error:&error]; + fileLength = [attr fileSize]; + RELEASE_SAFELY(mgr) + } + else { + if (!httpHeaders) + { + CFTypeRef message = + CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); + httpHeaders = + (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message); + CFRelease(message); + //NSLog(@"headers %@", httpHeaders); + + // + // Only read the content length if we seeked to time zero, otherwise + // we only have a subset of the total bytes. + // + if (seekByteOffset == 0) + { + fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue]; + } + } + } if (!audioFileStream) { @@ -1464,7 +1527,13 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream // Read the bytes from the stream // length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize); - +/* NSTimeInterval t = [[NSDate date] timeIntervalSince1970]; + if (0 != _lastTimeInterval) { + float rate = length / (t - _lastTimeInterval); + TTDCONDITIONLOG(1, @"download rate:%.2f kB", rate / 1024.0f); + } + _lastTimeInterval = t; + */ if (length == -1) { [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; @@ -1475,8 +1544,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream { return; } + #ifdef SHOUTCAST_METADATA - // shoutcast parsing code from http://code.google.com/p/audiostreamer-meta/ + // shoutcast parsing code from http://code.google.com/p/AudioStreamer-meta/ // with modifications by John Fricker // get and handle the shoutcast metadata @@ -1758,28 +1828,154 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } // end discontinuous #else - if (discontinuous) - { - err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; - return; - } - } - else - { - err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; - return; - } - } + if (![url isFileURL]) { + NSData * data = [[NSData alloc] initWithBytes:bytes length:length]; + [_bufferLock lock]; + [_buffers addObject:data]; + [_bufferLock unlock]; + [data release]; + + if (nil == _bufferPushingThread) { + _bufferPushingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pushingBufferThread:) object:nil]; + [_bufferPushingThread setName:@"Push/Parse Buffer Thread"]; + [_bufferPushingThread start]; + } + } + else { + if (discontinuous) + { + err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); + if (err) + { + [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; + return; + } + } + else + { + err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); + if (err) + { + [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; + return; + } + } + } #endif } } +- (void)pushingBufferThread:(id)object +{ + @autoreleasepool { + NSData * data = nil; + + while (![self runLoopShouldExit]) { + @autoreleasepool { + data = nil; + [_bufferLock lock]; + if ([_buffers count]) { + data = [[[_buffers objectAtIndex:0] retain] autorelease]; + [_buffers removeObjectAtIndex:0]; + } + [_bufferLock unlock]; + if (data) { + if (discontinuous) + { + err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, kAudioFileStreamParseFlag_Discontinuity); + if (err) + { + [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; + return; + } + } + else + { + err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, 0); + if (err) + { + [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; + return; + } + } + } + else if(self.finishedBuffer){ + @synchronized(self) + { + if ([self isFinishing]) + { + return; + } + } + + // + // If there is a partially filled buffer, pass it to the AudioQueue for + // processing + // + if (bytesFilled) + { + if (self.state == AS_WAITING_FOR_DATA) + { + // + // Force audio data smaller than one whole buffer to play. + // + self.state = AS_FLUSHING_EOF; + } + [self enqueueBuffer]; + } + + @synchronized(self) + { + if (state == AS_WAITING_FOR_DATA) + { + [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; + } + + // + // We left the synchronized section to enqueue the buffer so we + // must check that we are !finished again before touching the + // audioQueue + // + else if (![self isFinishing]) + { + if (audioQueue) + { + // + // Set the progress at the end of the stream + // + err = AudioQueueFlush(audioQueue); + if (err) + { + [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; + return; + } + + self.state = AS_STOPPING; + stopReason = AS_STOPPING_EOF; + err = AudioQueueStop(audioQueue, false); + if (err) + { + [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; + return; + } + } + else + { + self.state = AS_STOPPED; + stopReason = AS_STOPPING_EOF; + } + } + } + } + else { + [NSThread sleepForTimeInterval:0.01]; + } + } + } + self.allBufferPushed = YES; + RELEASE_SAFELY(_bufferPushingThread); + } +} // // enqueueBuffer // @@ -1903,6 +2099,7 @@ - (void)createQueue // create the audio queue err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue); + if (err) { [self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED]; From 4b8fe64eb39c3f9a3707e7dc1421e7c2584d3e87 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Sat, 10 Dec 2011 14:46:19 +0800 Subject: [PATCH 02/11] added local file streaming to SHOUTCAST; fixed set streamer = NULL if openReadStream failed; added macro switch to turn on/off prebuffering feature --- Classes/AudioStreamer.h | 16 ++++------ Classes/AudioStreamer.m | 69 +++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index 93c49f0..d15d23e 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -11,7 +11,7 @@ // this copyright and permission notice. Attribution in compiled projects is // appreciated but not required. // -//#define SHOUTCAST_METADATA +#define SHOUTCAST_METADATA #if TARGET_OS_IPHONE #import @@ -26,8 +26,8 @@ #include #include -//#import "encryption.h" //custom encryption support -#import + +#define USE_PREBUFFER 1 #define LOG_QUEUED_BUFFERS 0 @@ -185,16 +185,12 @@ extern NSString * const ASUpdateMetadataNotification; #endif BOOL vbr; // indicates VBR (or not) stream -// EncryptionMethod _encryption;//加密方式 - uLong _crc32; - void * _encryptionSampledBuffer; //加密用缓冲 - NSUInteger _encryptionOffset; - + +#if defined (USE_PREBUFFER) && USE_PREBUFFER NSLock * _bufferLock; NSMutableArray * _buffers; NSThread * _bufferPushingThread; - - NSTimeInterval _lastTimeInterval; +#endif } @property AudioStreamerErrorCode errorCode; diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index c6c9459..1b4fab9 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -63,9 +63,11 @@ @interface AudioStreamer () @property (readwrite) AudioStreamerState state; +#if defined (USE_PREBUFFER) && USE_PREBUFFER @property (readwrite) BOOL allBufferPushed; @property (readwrite) BOOL finishedBuffer; - +- (void)pushingBufferThread:(id)object; +#endif - (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID ioFlags:(UInt32 *)ioFlags; @@ -86,8 +88,6 @@ - (void)internalSeekToTime:(double)newSeekTime; - (void)enqueueBuffer; - (void)handleReadFromStream:(CFReadStreamRef)aStream eventType:(CFStreamEventType)eventType; - -- (void)pushingBufferThread:(id)object; @end #pragma mark Audio Callback Function Prototypes @@ -237,9 +237,10 @@ @implementation AudioStreamer @synthesize httpHeaders; @synthesize numberOfChannels; @synthesize vbr; +#if defined (USE_PREBUFFER) && USE_PREBUFFER @synthesize allBufferPushed = _allBufferPushed; @synthesize finishedBuffer = _finishedBuffer; - +#endif - (void)setVolume:(float)vol { @synchronized(self) { if (audioQueue) { @@ -263,25 +264,16 @@ - (id)initWithURL:(NSURL *)aURL #ifdef SHOUTCAST_METADATA metaDataString = [[NSMutableString alloc] initWithString:@""]; #endif +#if defined (USE_PREBUFFER) && USE_PREBUFFER _buffers = [[NSMutableArray alloc] initWithCapacity:2048/kAQDefaultBufSize]; _bufferLock = [[NSLock alloc] init]; self.allBufferPushed = NO; self.finishedBuffer = NO; - _lastTimeInterval = 0; +#endif } return self; } -//- (id)initWithURL:(NSURL *)aURL encryption:(EncryptionMethod)method crc32:(uLong)crc32 -//{ -// if ((self = [self initWithURL:aURL])) -// { -// _encryption = method; -// _crc32 = crc32; -// } -// return self; -//} - // // dealloc // @@ -294,8 +286,10 @@ - (void)dealloc #ifdef SHOUTCAST_METADATA [metaDataString release]; #endif +#if defined (USE_PREBUFFER) && USE_PREBUFFER RELEASE_SAFELY(_buffers); RELEASE_SAFELY(_bufferLock); +#endif [super dealloc]; } @@ -786,6 +780,7 @@ - (BOOL)openReadStream if (!CFReadStreamOpen(stream)) { CFRelease(stream); + stream = NULL; [self presentAlertWithTitle:NSLocalizedStringFromTable(@"File Error", @"Errors", nil) message:NSLocalizedStringFromTable(@"Unable to configure network read stream.", @"Errors", nil)]; return NO; @@ -916,16 +911,16 @@ - (void)startInternal } [pool release]; [NSThread sleepForTimeInterval:0.01]; +#if defined (USE_PREBUFFER) && USE_PREBUFFER } while ((self.allBufferPushed || isRunning || [self isFinishing]) && ![self runLoopShouldExit]); +#else + } while (isRunning && ![self runLoopShouldExit]); +#endif cleanup: @synchronized(self) { - if (_encryptionSampledBuffer) { - free(_encryptionSampledBuffer); - _encryptionSampledBuffer = NULL; - } // // Cleanup the read stream if it is still open // @@ -1388,7 +1383,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } else if (eventType == kCFStreamEventEndEncountered) { +#if defined (USE_PREBUFFER) && USE_PREBUFFER self.finishedBuffer = YES; +#endif if ([url isFileURL]) { @synchronized(self) { @@ -1527,13 +1524,6 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream // Read the bytes from the stream // length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize); -/* NSTimeInterval t = [[NSDate date] timeIntervalSince1970]; - if (0 != _lastTimeInterval) { - float rate = length / (t - _lastTimeInterval); - TTDCONDITIONLOG(1, @"download rate:%.2f kB", rate / 1024.0f); - } - _lastTimeInterval = t; - */ if (length == -1) { [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; @@ -1551,7 +1541,7 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream // get and handle the shoutcast metadata int streamStart = 0; - if (metaDataInterval == 0) + if (![url isFileURL] && metaDataInterval == 0) { CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); UInt32 statusCode = CFHTTPMessageGetResponseStatusCode(myResponse); @@ -1774,6 +1764,22 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream #endif } #ifdef SHOUTCAST_METADATA +#if defined (USE_PREBUFFER) && USE_PREBUFFER + if (![url isFileURL]) { + NSData * data = [[NSData alloc] initWithBytes:bytes length:length]; + [_bufferLock lock]; + [_buffers addObject:data]; + [_bufferLock unlock]; + [data release]; + + if (nil == _bufferPushingThread) { + _bufferPushingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pushingBufferThread:) object:nil]; + [_bufferPushingThread setName:@"Push/Parse Buffer Thread"]; + [_bufferPushingThread start]; + } + } + else { +#endif if (discontinuous) { /* @@ -1826,8 +1832,12 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } } } // end discontinuous +#if defined (USE_PREBUFFER) && USE_PREBUFFER + } +#endif #else +#if defined (USE_PREBUFFER) && USE_PREBUFFER if (![url isFileURL]) { NSData * data = [[NSData alloc] initWithBytes:bytes length:length]; [_bufferLock lock]; @@ -1842,6 +1852,7 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } } else { +#endif if (discontinuous) { err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); @@ -1860,11 +1871,14 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream return; } } +#if defined (USE_PREBUFFER) && USE_PREBUFFER } +#endif #endif } } +#if defined (USE_PREBUFFER) && USE_PREBUFFER - (void)pushingBufferThread:(id)object { @autoreleasepool { @@ -1976,6 +1990,7 @@ - (void)pushingBufferThread:(id)object RELEASE_SAFELY(_bufferPushingThread); } } +#endif // // enqueueBuffer // From 83804469692003d51f41cf9ac36afec799040984 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Sun, 11 Dec 2011 16:04:03 +0800 Subject: [PATCH 03/11] added property ivar declaration to .h, which is required under MacOSX --- Classes/AudioStreamer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index d15d23e..0ff50c9 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -190,6 +190,8 @@ extern NSString * const ASUpdateMetadataNotification; NSLock * _bufferLock; NSMutableArray * _buffers; NSThread * _bufferPushingThread; + BOOL _allBufferPushed; + BOOL _finishedBuffer; #endif } From 1a022c104162bc855c1cc342a56788103de39df5 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Mon, 12 Dec 2011 14:46:42 +0800 Subject: [PATCH 04/11] remove use of @autoreleasepool, which cause crash on 10.6 --- Classes/AudioStreamer.h | 2 +- Classes/AudioStreamer.m | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index 0ff50c9..fd7bef9 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -11,7 +11,7 @@ // this copyright and permission notice. Attribution in compiled projects is // appreciated but not required. // -#define SHOUTCAST_METADATA +//#define SHOUTCAST_METADATA #if TARGET_OS_IPHONE #import diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 1b4fab9..5bf93c5 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -840,7 +840,7 @@ - (void)startInternal NSLog(@"### Not starting audio thread. State code is: %d", state); } self.state = AS_INITIALIZED; - [pool release]; + [pool drain]; return; } @@ -909,7 +909,7 @@ - (void)startInternal } self.state = AS_BUFFERING; } - [pool release]; + [pool drain]; [NSThread sleepForTimeInterval:0.01]; #if defined (USE_PREBUFFER) && USE_PREBUFFER } while ((self.allBufferPushed || isRunning || [self isFinishing]) && ![self runLoopShouldExit]); @@ -976,7 +976,7 @@ - (void)startInternal [internalThread release]; internalThread = nil; } - [pool release]; + [pool drain]; } // @@ -1881,11 +1881,15 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream #if defined (USE_PREBUFFER) && USE_PREBUFFER - (void)pushingBufferThread:(id)object { - @autoreleasepool { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + //@autoreleasepool + { NSData * data = nil; while (![self runLoopShouldExit]) { - @autoreleasepool { + NSAutoreleasePool * inpool = [[NSAutoreleasePool alloc] init]; +// @autoreleasepool + { data = nil; [_bufferLock lock]; if ([_buffers count]) { @@ -1985,10 +1989,13 @@ - (void)pushingBufferThread:(id)object [NSThread sleepForTimeInterval:0.01]; } } + [inpool drain]; } self.allBufferPushed = YES; RELEASE_SAFELY(_bufferPushingThread); } + + [pool drain]; } #endif // @@ -2606,7 +2613,7 @@ - (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ } } - [pool release]; + [pool drain]; } #if TARGET_OS_IPHONE From e36e592afbb59b5252ec39d6e08c6e6abefb08f3 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Mon, 12 Dec 2011 17:12:17 +0800 Subject: [PATCH 05/11] add synchronization to audio file stream --- Classes/AudioStreamer.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 5bf93c5..980f0fc 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -934,6 +934,7 @@ - (void)startInternal // // Close the audio file strea, // + @synchronized(_bufferLock) { if (audioFileStream) { err = AudioFileStreamClose(audioFileStream); @@ -943,6 +944,7 @@ - (void)startInternal [self failWithErrorCode:AS_FILE_STREAM_CLOSE_FAILED]; } } + } // // Dispose of the Audio Queue @@ -1791,7 +1793,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream if (lengthNoMetaData > 0) { //NSLog(@"Parsing no meta bytes (Discontinuous)."); + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, lengthNoMetaData, bytesNoMetaData, kAudioFileStreamParseFlag_Discontinuity); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1801,7 +1805,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream else if (metaDataInterval == 0) // make sure this isn't a stream with metadata { //NSLog(@"Parsing normal bytes (Discontinuous)."); + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1814,7 +1820,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream if (lengthNoMetaData > 0) { //NSLog(@"Parsing no meta bytes."); + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, lengthNoMetaData, bytesNoMetaData, 0); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1824,7 +1832,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream else if (metaDataInterval == 0) // make sure this isn't a stream with metadata { //NSLog(@"Parsing normal bytes."); + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1855,7 +1865,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream #endif if (discontinuous) { + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1864,7 +1876,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } else { + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1900,7 +1914,9 @@ - (void)pushingBufferThread:(id)object if (data) { if (discontinuous) { + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, kAudioFileStreamParseFlag_Discontinuity); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1909,7 +1925,9 @@ - (void)pushingBufferThread:(id)object } else { + @synchronized(_bufferLock) { err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, 0); + } if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; From 61492185e016a5e36a02fb26b4767adb33e1737c Mon Sep 17 00:00:00 2001 From: sprhawk Date: Mon, 12 Dec 2011 18:36:01 +0800 Subject: [PATCH 06/11] fixed a weird bug, see the comment --- Classes/AudioStreamer.h | 1 + Classes/AudioStreamer.m | 50 ++++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Classes/AudioStreamer.h b/Classes/AudioStreamer.h index fd7bef9..23bae44 100644 --- a/Classes/AudioStreamer.h +++ b/Classes/AudioStreamer.h @@ -188,6 +188,7 @@ extern NSString * const ASUpdateMetadataNotification; #if defined (USE_PREBUFFER) && USE_PREBUFFER NSLock * _bufferLock; + NSLock * _audioStreamLock; NSMutableArray * _buffers; NSThread * _bufferPushingThread; BOOL _allBufferPushed; diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 980f0fc..c1f4673 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -267,6 +267,7 @@ - (id)initWithURL:(NSURL *)aURL #if defined (USE_PREBUFFER) && USE_PREBUFFER _buffers = [[NSMutableArray alloc] initWithCapacity:2048/kAQDefaultBufSize]; _bufferLock = [[NSLock alloc] init]; + _audioStreamLock = [[NSLock alloc] init]; self.allBufferPushed = NO; self.finishedBuffer = NO; #endif @@ -289,6 +290,7 @@ - (void)dealloc #if defined (USE_PREBUFFER) && USE_PREBUFFER RELEASE_SAFELY(_buffers); RELEASE_SAFELY(_bufferLock); + RELEASE_SAFELY(_audioStreamLock); #endif [super dealloc]; } @@ -934,7 +936,7 @@ - (void)startInternal // // Close the audio file strea, // - @synchronized(_bufferLock) { + [_audioStreamLock lock]; if (audioFileStream) { err = AudioFileStreamClose(audioFileStream); @@ -944,7 +946,7 @@ - (void)startInternal [self failWithErrorCode:AS_FILE_STREAM_CLOSE_FAILED]; } } - } + [_audioStreamLock unlock]; // // Dispose of the Audio Queue @@ -1320,8 +1322,8 @@ - (void)stop @synchronized(self) { if (audioQueue && - (state == AS_PLAYING || state == AS_PAUSED || - state == AS_BUFFERING || state == AS_WAITING_FOR_QUEUE_TO_START)) + (self.state == AS_PLAYING || self.state == AS_PAUSED || + self.state == AS_BUFFERING || self.state == AS_WAITING_FOR_QUEUE_TO_START)) { self.state = AS_STOPPING; stopReason = AS_STOPPING_USER_ACTION; @@ -1332,15 +1334,17 @@ - (void)stop return; } } - else if (state != AS_INITIALIZED) + else if (self.state != AS_INITIALIZED) { self.state = AS_STOPPED; stopReason = AS_STOPPING_USER_ACTION; } seekWasRequested = NO; } - - while (state != AS_INITIALIZED) + //not use atomic property may accidentally encounter weird situation + //when state is AS_INITIALIZED but the while statement fall into a + //dead loop. + while (self.state != AS_INITIALIZED) { [NSThread sleepForTimeInterval:0.1]; } @@ -1793,9 +1797,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream if (lengthNoMetaData > 0) { //NSLog(@"Parsing no meta bytes (Discontinuous)."); - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, lengthNoMetaData, bytesNoMetaData, kAudioFileStreamParseFlag_Discontinuity); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1805,9 +1809,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream else if (metaDataInterval == 0) // make sure this isn't a stream with metadata { //NSLog(@"Parsing normal bytes (Discontinuous)."); - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1820,9 +1824,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream if (lengthNoMetaData > 0) { //NSLog(@"Parsing no meta bytes."); - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, lengthNoMetaData, bytesNoMetaData, 0); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1832,9 +1836,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream else if (metaDataInterval == 0) // make sure this isn't a stream with metadata { //NSLog(@"Parsing normal bytes."); - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1865,9 +1869,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream #endif if (discontinuous) { - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1876,9 +1880,9 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream } else { - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1914,9 +1918,9 @@ - (void)pushingBufferThread:(id)object if (data) { if (discontinuous) { - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, kAudioFileStreamParseFlag_Discontinuity); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; @@ -1925,9 +1929,9 @@ - (void)pushingBufferThread:(id)object } else { - @synchronized(_bufferLock) { + [_audioStreamLock lock]; err = AudioFileStreamParseBytes(audioFileStream, data.length, data.bytes, 0); - } + [_audioStreamLock unlock]; if (err) { [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; From afe604d079ba909bf26fe7a03cedc45f422141a2 Mon Sep 17 00:00:00 2001 From: sprhawk Date: Mon, 12 Dec 2011 23:05:02 +0800 Subject: [PATCH 07/11] fixed a dead-lock bug --- Classes/AudioStreamer.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index c1f4673..042fe9b 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -932,10 +932,14 @@ - (void)startInternal CFRelease(stream); stream = nil; } - + } // // Close the audio file strea, // + +//MUST divde @synchronized(self) {} into two blocks, +//or it will run in dead lock +//use a audioStreamLock is to prevent audio stream is closed when pushDataThread is pushing data [_audioStreamLock lock]; if (audioFileStream) { @@ -947,7 +951,8 @@ - (void)startInternal } } [_audioStreamLock unlock]; - + @synchronized(self) + { // // Dispose of the Audio Queue // From 41a3b012f04ddc3e4f1d49a76ab93ab7e2c0dbea Mon Sep 17 00:00:00 2001 From: sprhawk Date: Wed, 14 Dec 2011 02:22:52 +0800 Subject: [PATCH 08/11] added synchronization to _bufferPushingThread --- Classes/AudioStreamer.m | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 042fe9b..a85574e 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -937,7 +937,7 @@ - (void)startInternal // Close the audio file strea, // -//MUST divde @synchronized(self) {} into two blocks, +//MUST divde @synchronized(self) {} into two blocks , //or it will run in dead lock //use a audioStreamLock is to prevent audio stream is closed when pushDataThread is pushing data [_audioStreamLock lock]; @@ -1782,12 +1782,13 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream [_buffers addObject:data]; [_bufferLock unlock]; [data release]; - + @synchronized(self) { if (nil == _bufferPushingThread) { _bufferPushingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pushingBufferThread:) object:nil]; [_bufferPushingThread setName:@"Push/Parse Buffer Thread"]; [_bufferPushingThread start]; } + } } else { #endif @@ -1863,11 +1864,12 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream [_buffers addObject:data]; [_bufferLock unlock]; [data release]; - - if (nil == _bufferPushingThread) { - _bufferPushingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pushingBufferThread:) object:nil]; - [_bufferPushingThread setName:@"Push/Parse Buffer Thread"]; - [_bufferPushingThread start]; + @synchronized(self) { + if (nil == _bufferPushingThread) { + _bufferPushingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pushingBufferThread:) object:nil]; + [_bufferPushingThread setName:@"Push/Parse Buffer Thread"]; + [_bufferPushingThread start]; + } } } else { @@ -2019,7 +2021,9 @@ - (void)pushingBufferThread:(id)object [inpool drain]; } self.allBufferPushed = YES; - RELEASE_SAFELY(_bufferPushingThread); + @synchronized(self) { + RELEASE_SAFELY(_bufferPushingThread); + } } [pool drain]; From cdb51260da5111bc3362dc0940c653235857e23f Mon Sep 17 00:00:00 2001 From: sprhawk Date: Wed, 14 Dec 2011 22:19:15 +0800 Subject: [PATCH 09/11] added statement to pause when buffering --- Classes/AudioStreamer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index a85574e..384c2b6 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -1282,7 +1282,7 @@ - (void)pause { @synchronized(self) { - if (state == AS_PLAYING) + if (state == AS_PLAYING || state == AS_BUFFERING) //if is buffering, the user pauses it { err = AudioQueuePause(audioQueue); if (err) From 4037fca27d41ed963fe14f78e51184b1815549ac Mon Sep 17 00:00:00 2001 From: sprhawk Date: Wed, 14 Dec 2011 23:06:43 +0800 Subject: [PATCH 10/11] added a NSThread sleep for pre-buffering, see the comment --- Classes/AudioStreamer.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 384c2b6..35149ab 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -1871,6 +1871,8 @@ - (void)handleReadFromStream:(CFReadStreamRef)aStream [_bufferPushingThread start]; } } + //maybe the runloop runs too fast ,that _bufferPushingThread is frequently blocked before the streaing is pre-buffered. + [NSThread sleepForTimeInterval:0.01]; } else { #endif From 969fc6107b06c3fde4961d3ee0c9f07eaaacb93b Mon Sep 17 00:00:00 2001 From: sprhawk Date: Wed, 14 Dec 2011 23:28:00 +0800 Subject: [PATCH 11/11] move NSThread sleep call position --- Classes/AudioStreamer.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/AudioStreamer.m b/Classes/AudioStreamer.m index 35149ab..f5fcc59 100644 --- a/Classes/AudioStreamer.m +++ b/Classes/AudioStreamer.m @@ -2017,10 +2017,12 @@ - (void)pushingBufferThread:(id)object } } else { - [NSThread sleepForTimeInterval:0.01]; +// [NSThread sleepForTimeInterval:0.01]; } } [inpool drain]; + + [NSThread sleepForTimeInterval:0.01]; } self.allBufferPushed = YES; @synchronized(self) {