// MyFindQueueBuffer
//
// Returns the index of the specified buffer in the audioQueueBuffer array.
//
// This function is unchanged from Apple's example in AudioFileStreamExample.
//
int MyFindQueueBuffer(xxxxx* myData, AudioQueueBufferRef inBuffer)
{
for (unsigned int i = 0; i < kNumAQBufs; ++i) {
if (inBuffer == myData->buffers)
return i;
}
return -1;
}
//
// MyAudioQueueOutputCallback
//
// Called from the AudioQueue when playback of specific buffers completes. This
// function signals from the AudioQueue thread to the AudioStream thread that
// the buffer is idle and available for copying data.
//
// This function is unchanged from Apple's example in AudioFileStreamExample.
//
void MyAudioQueueOutputCallback(void* inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{
// this is called by the audio queue when it has finished decoding our data.
// The buffer is now free to be reused.
xxxxx* myData = (xxxxx*)inClientData;
unsigned int bufIndex = MyFindQueueBuffer(myData, inBuffer);
// signal waiting thread that the buffer is free.
pthread_mutex_lock(&myData->mutex);
myData->inuse[bufIndex] = false;
pthread_cond_signal(&myData->cond);
pthread_mutex_unlock(&myData->mutex);
}
//
// MyAudioQueueIsRunningCallback
//
// Called from the AudioQueue when playback is started or stopped. This
// information is used to toggle the observable "isPlaying" property and
// set the "finished" flag.
//
void MyAudioQueueIsRunningCallback(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID)
{
xxxxx *myData = (xxxxx *)inUserData;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (myData.isPlaying)
{
myData->trackEnded = true;
myData.isPlaying = false;
#ifdef TARGET_OS_IPHONE
AudioSessionSetActive(false);
#endif
}
else
{
myData.isPlaying = true;
if (myData->trackEnded)
{
myData.isPlaying = false;
}
//
// Note about this bug avoidance quirk:
//
// On cleanup of the AudioQueue thread, on rare occasions, there would
// be a crash in CFSetContainsValue as a CFRunLoopObserver was getting
// removed from the CFRunLoop.
//
// After lots of testing, it appeared that the audio thread was
// attempting to remove CFRunLoop observers from the CFRunLoop after the
// thread had already deallocated the run loop.
//
// 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!)
//
[NSRunLoop currentRunLoop];
}
[pool release];
}
#ifdef TARGET_OS_IPHONE
//
// MyAudioSessionInterruptionListener
//
// Invoked if the audio session is interrupted (like when the phone rings)
//
void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState)
{
}
#endif
#pragma mark -
#pragma mark CFReadStream Callback Function Implementations
//
// ReadStreamCallBack
//
// This is the callback for the CFReadStream from the network connection. This
// is where all network data is passed to the AudioFileStream.
//
// Invoked when an error occurs, the stream ends or we have data to read.
//
void ReadStreamCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void* dataIn)
{
xxxxx *myData = (xxxxx *)dataIn;
if (eventType == kCFStreamEventErrorOccurred)
{
myData->failed = YES;
}
else if (eventType == kCFStreamEventEndEncountered)
{
if (myData->failed || myData->trackEnded)
{
return;
}
//
// If there is a partially filled buffer, pass it to the AudioQueue for
// processing
//
if (myData->bytesFilled)
{
MyEnqueueBuffer(myData);
}
//
// If the AudioQueue started, then flush it (to make certain everything
// sent thus far will be processed) and subsequently stop the queue.
//
if (myData->started)
{
OSStatus err = AudioQueueFlush(myData->queue);
if (err) { NSLog(@"AudioQueueFlush"); return; }
err = AudioQueueStop(myData->queue, false);
if (err) { NSLog(@"AudioQueueStop"); return; }
CFReadStreamClose(stream);
CFRelease(stream);
myData->stream = nil;
}
else
{
//
// If we have reached the end of the file without starting, then we
// have failed to find any audio in the file. Abort.
//
myData->failed = YES;
}
}
else if (eventType == kCFStreamEventHasBytesAvailable)
{
if (myData->failed || myData->trackEnded)
{
return;
}
//
// Read the bytes from the stream
//
UInt8 bytes[kAQBufSize];
CFIndex length = CFReadStreamRead(stream, bytes, kAQBufSize);
if (length == -1)
{
myData->failed = YES;
return;
}
//
// Parse the bytes read by sending them through the AudioFileStream
//
if (length > 0)
{
if (myData->discontinuous)
{
OSStatus err = AudioFileStreamParseBytes(myData->audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity);
if (err) { NSLog(@"AudioFileStreamParseBytes"); myData->failed = true;}
}
else
{
OSStatus err = AudioFileStreamParseBytes(myData->audioFileStream, length, bytes, 0);
if (err) { NSLog(@"AudioFileStreamParseBytes"); myData->failed = true; }
}
}
}
}
@interface xxxxx (private)
static void propertyListenerCallback(void *inUserData, AudioQueueRef queueObject, AudioQueuePropertyID propertyID);
- (void) playBackIsRunningStateChanged;
static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void) callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32) readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;
@end
@implementation xxxxx
@synthesize isPlaying, trackClosed;
#pragma mark -
#pragma mark xxxxx
- (void)dealloc
{
[self close];
if (packetDescs != nil)
free(packetDescs);
[url release];
[super dealloc];
}
- (void)close
{
// it is preferrable to call close first, if there is a problem waiting for an autorelease
if (trackClosed)
return;
trackClosed = YES;
AudioQueueStop(queue, YES); // <-- YES means stop immediately
AudioQueueDispose(queue, YES);
AudioFileClose(audioFile);
kxxxTrackActive = NO;
}
- (id)initWithURL:(NSURL*)newUrl
{
self = [super init];
if (self != nil)
{
url = [newUrl retain];
}
return self;
}
- (id)initWithPath:(NSString*)path
{
UInt32 size, maxPacketSize;
char *cookie;
int i;
if (kxxxTrackActive)
{
NSLog(@"Other music is playing.");
return nil;
}
if (path == nil) return nil;
if(!(self = [super init])) return nil;
// try to open up the file using the specified path
if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, 0, &audioFile))
{
NSLog(@"File can not be opened!");
return nil;
}