AnotherJake Wrote:Can't help you on iPhone audio because of NDA, sorry. Maybe tomorrow. Post a thread on it when we get out of NDA.

In the meantime, on the Mac, I would suggest using OpenAL for short sounds like explosions and laser/gunfire, etc. For background music you can use Audio Queues on Mac OS X 10.5 or greater.

If you are talking about trying to understand using Audio Queues on Mac OS X 10.5 (I am NOT talking about iPhone, because it cannot be discussed as to whether or not it even has Audio Queues) then yes, it is convoluted and difficult to approach -- *especially* the example code.

Alright...I'll bite...

How would you do it on MAC using Audio Queues?

Mac users swear by their computers, PC users swear at their computers. ~Unknown

*** NOTE *** For iPhone, use this for background music only, and only one track at a time. The iPhone hardware doesn't work with multiple compressed tracks pumping through its dedicated audio decompression hardware.

If you are looping it, it is better to set the track to repeat rather than releasing it and reallocating it again.

You'd use it by alloc-initing a new track every time you want to play a new one, and you release it as soon as it's done playing, and subsequently alloc-init a new one to play (if you are going to play a different track, otherwise set it to repeat). Even if you plan on playing the same track again later, you need to release it first if you want to play a different one in the meantime. It is designed this way to conserve resources. So for now, maybe you can figure out how to get stuff rolling with Audio Queues using this.

// close is called automatically in GBMusicTrack's dealloc method, but it is recommended
// to call close first, so that the associated Audio Queue is released immediately, instead
// of having to wait for a possible autorelease, which may cause some conflict
- (void)close;

// *** NOTE *** GBMusicTrack is only designed to play one track at a time, as background music, so gThereIsAnActiveTrack
// is used to prevent multiple tracks from playing. Use something else like OpenAL to play sounds like lasers and explosions
static BOOL gThereIsAnActiveTrack = NO;

if (gThereIsAnActiveTrack)
{
NSLog(@"*** WARNING *** GBMusicTrack only plays one track at a time! You must close the previously running track"
" before you can play another. Requested track was: %@", [path lastPathComponent]);
return nil;
}

// calculate number of packets to read and allocate space for packet descriptions if needed
if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
{
// since we didn't get sizes to work with, then this must be VBR data (Variable BitRate), so
// we'll have to ask Core Audio to give us a conservative estimate of the largest packet we are
// likely to read with kAudioFilePropertyPacketSizeUpperBound
size = sizeof(maxPacketSize);
AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
if (maxPacketSize > gBufferSizeBytes)
{
// hmm... well, we don't want to go over our buffer size, so we'll have to limit it I guess
maxPacketSize = gBufferSizeBytes;
NSLog(@"*** Warning *** GBMusicTrack - initWithPath: had to limit packet size requested for file: %@", [path lastPathComponent]);
}
numPacketsToRead = gBufferSizeBytes / maxPacketSize;

// will need a packet description for each packet since this is VBR data, so allocate space accordingly
packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
}
else
{
// for CBR data (Constant BitRate), we can simply fill each buffer with as many packets as will fit
numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;

// we want to know when the playing state changes so we can properly dispose of the audio queue when it's done
AudioQueueAddPropertyListener(queue, kAudioQueueProperty_IsRunning, propertyListenerCallback, self);

static void propertyListenerCallback(void *inUserData, AudioQueueRef queueObject, AudioQueuePropertyID propertyID)
{
// redirect back to the class to handle it there instead, so we have direct access to the instance variables
if (propertyID == kAudioQueueProperty_IsRunning)
[(GBMusicTrack *)inUserData playBackIsRunningStateChanged];
}

// we're not in the main thread during this callback, so enqueue a message on the main thread to post notification
// that we're done, or else the notification will have to be handled in this thread, making things more difficult
[self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
}
}

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
// redirect back to the class to handle it there instead, so we have direct access to the instance variables
[(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}

- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
// I guess it's possible for the callback to continue to be called since this is in another thread, so to be safe,
// don't do anything else if the track is closed, and also don't bother reading anymore packets if the track ended
if (trackClosed || trackEnded)
return;

if ([self readPacketsIntoBuffer:buffer] == 0)
{
if (repeat)
{
// End Of File reached, so rewind and refill the buffer using the beginning of the file instead
packetIndex = 0;
[self readPacketsIntoBuffer:buffer];
}
else
{
// set it to stop, but let it play to the end, where the property listener will pick up that it actually finished
AudioQueueStop(queue, NO);
trackEnded = YES;
}
}
}

- (void)postTrackFinishedPlayingNotification:(id)object
{
// if we're here then we're in the main thread as specified by the callback, so now we can post notification that
// the track is done without the notification observer(s) having to worry about thread safety and autorelease pools
[[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}

// read packets into buffer from file
numPackets = numPacketsToRead;
AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
if (numPackets > 0)
{
// - End Of File has not been reached yet since we read some packets, so enqueue the buffer we just read into
// the audio queue, to be played next
// - (packetDescs ? numPackets : 0) means that if there are packet descriptions (which are used only for Variable
// BitRate data (VBR)) we'll have to send one for each packet, otherwise zero
buffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);

// move ahead to be ready for next time we need to read from the file
packetIndex += numPackets;
}
return numPackets;
}

AnotherJake Wrote:This should be split into a different thread by a moderator when they get a chance.

Heck, well for Mac, all you had to do was ask!

Here is what I've been doing so far. This isn't well tested and I can't guarantee any accuracy or that there isn't a memory leak or some other dreadful bug, but I commented the bejeezus out of it -- against my desire not to comment anything. I am tentatively planning on adding it to the GameBase framework project, but I haven't tested it enough to be satisfied with it. Plus, I am in the middle of writing a musicPlayer class to automatically handle these tracks in playlists, like a behind-the-scenes iTunes for game developers. This is just the raw class to play a tune. You'd use it by alloc-initing a new track every time you want to play one, and you release it as soon as it's done playing, and subsequently alloc-init a new one to play. Even if you plan on playing the same track again later, you need to release it first if you want to play a different one in the meantime. It is designed this way to conserve resources. The musicPlayer class I was talking about will manage this automatically. I'd post that too, but it's not *quite* finished (as in, it's feature complete, but it's being tested and bugs are being found and squashed). So for now, maybe you can figure out how to get stuff rolling with Audio Queues using this.

// close is called automatically in GBMusicTrack's dealloc method, but it is recommended
// to call close first, so that the associated Audio Queue is released immediately, instead
// of having to wait for a possible autorelease, which may cause some conflict
- (void)close;

- (void)close
{
// it is preferrable to call close first, before dealloc if there is a problem waiting for
// an autorelease
if (trackClosed)
return;
trackClosed = YES;
AudioQueueStop(queue, YES);
AudioQueueDispose(queue, YES);
AudioFileClose(audioFile);
}

// calculate number of packets to read and allocate space for packet descriptions if needed
if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
{
// since we didn't get sizes to work with, then this must be VBR data (Variable BitRate), so
// we'll have to ask Core Audio to give us a conservative estimate of the largest packet we are
// likely to read with kAudioFilePropertyPacketSizeUpperBound
size = sizeof(maxPacketSize);
AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
if (maxPacketSize > gBufferSizeBytes)
{
// hmm... well, we don't want to go over our buffer size, so we'll have to limit it I guess
maxPacketSize = gBufferSizeBytes;
NSLog(@"GBMusicTrack Warning - initWithPath: had to limit packet size requested for file path: %@", path);
}
numPacketsToRead = gBufferSizeBytes / maxPacketSize;

// will need a packet description for each packet since this is VBR data, so allocate space accordingly
packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
}
else
{
// for CBR data (Constant BitRate), we can simply fill each buffer with as many packets as will fit
numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
// redirect back to the class to handle it there instead, so we have direct access to the instance variables
[(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}

- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
if ([self readPacketsIntoBuffer:buffer] == 0)
{
// End Of File reached, so rewind and refill the buffer using the beginning of the file instead
packetIndex = 0;
[self readPacketsIntoBuffer:buffer];

// if not repeating then we'll pause it so it's ready to play again immediately if needed
if (!repeat)
{
AudioQueuePause(queue);

// we're not in the main thread during this callback, so enqueue a message on the main thread to post notification
// that we're done, or else the notification will have to be handled in this thread, making things more difficult
[self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
}
}
}

- (void)postTrackFinishedPlayingNotification:(id)object
{
// if we're here then we're in the main thread as specified by the callback, so now we can post notification that
// the track is done without the notification observer(s) having to worry about thread safety and autorelease pools
[[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}

// read packets into buffer from file
numPackets = numPacketsToRead;
AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
if (numPackets > 0)
{
// - End Of File has not been reached yet since we read some packets, so enqueue the buffer we just read into
// the audio queue, to be played next
// - (packetDescs ? numPackets : 0) means that if there are packet descriptions (which are used only for Variable
// BitRate data (VBR)) we'll have to send one for each packet, otherwise zero
buffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);

// move ahead to be ready for next time we need to read from the file
packetIndex += numPackets;
}
return numPackets;
}

@end

Well, that looks almost identical to the other example code I have been looking through, and I have everything in there. But when I run my code it breaks at AudioQueueEnqueueBuffer. Could this be because it cannot find the audio file I want or perhaps for some other reason?

Mac users swear by their computers, PC users swear at their computers. ~Unknown

AnotherJake Wrote:Did you actually try GBMusicTrack, or are you assuming it won't work either just by looking at it?

Oh no, don't get me wrong, I'm not assuming it won't work. But seeing as I don't wish to plagiarize your material, or the material from which I was deriving my examples, I wrote my own stuff that was VERY similar to your implementation. I keep getting an EXC_BAD_ACCESS error at AudioQueueEnQueueBuffer and I was wondering if you knew what might cause this.

Mac users swear by their computers, PC users swear at their computers. ~Unknown

Can't tell you why you're getting an EXC_BAD_ACCESS without seeing your code and then perhaps spending time tracking it down for you, which is why it's easier to just post known working code for you to use and work off of (which doesn't violate NDA).

Don't worry about "plagiarizing" my material. If it's posted here, it's free to do with as you please or else I wouldn't have posted it. I'm all about sharing knowledge freely (and making money freely too!). Don't worry about calling it your own. I would prefer you use it, find problems, and share your fixes back. It's only under copyright so some malevolent entity can't come back and screw us for sharing free code in GameBase. But if it makes you feel better, since I hold copyright for this file, I hereby give you permission to "plagiarize" it as you see fit.

AnotherJake Wrote:Can't tell you why you're getting an EXC_BAD_ACCESS without seeing your code and then perhaps spending time tracking it down for you, which is why it's easier to just post known working code for you to use and work off of (which doesn't violate NDA).

Don't worry about "plagiarizing" my material. If it's posted here, it's free to do with as you please or else I wouldn't have posted it. I'm all about sharing knowledge freely (and making money freely too!). Don't worry about calling it your own. I would prefer you use it, find problems, and share your fixes back. It's only under copyright so some malevolent entity can't come back and screw us for sharing free code in GameBase. But if it makes you feel better, since I hold copyright for this file, I hereby give you permission to "plagiarize" it as you see fit.

Alrighty then. Thanks! A couple questions: If I do decide to use your implementation, can I specify a file name / file URL for your player to read without having to jump through the hoops that all of the examples I've been referencing have been telling me to? Also, it is suited for a particular file format? I plan on using mostly aiff encoded files. Thanks for the help!

[EDIT]
Man! I didn't even look at the first few lines of code you posted. Very nice! Now why didn't Apple do that to begin with??!!

Mac users swear by their computers, PC users swear at their computers. ~Unknown

Well, this is going to seem a little strange, and I apologize, but it something that must be said:

HOLY CRAP, ANOTHER JAKE...I LOVE YOU!!!! I tried out your sound engine and it worked like a CHARM!!!! I have spent the last 6 days, tearing my hair out over sound and in one fell swoop you fixed it all!! Thank you, my friend! I will be sure to include you in the credits somewhere, thank you very much!

Mac users swear by their computers, PC users swear at their computers. ~Unknown

Talyn Wrote:Well, this is going to seem a little strange, and I apologize, but it something that must be said:

HOLY CRAP, ANOTHER JAKE...I LOVE YOU!!!! I tried out your sound engine and it worked like a CHARM!!!! I have spent the last 6 days, tearing my hair out over sound and in one fell swoop you fixed it all!! Thank you, my friend! I will be sure to include you in the credits somewhere, thank you very much!

Aww shucks... It was nuthin'... Glad you like it!

It's even easier to use with the GBMusicPlayer class I'm writing for it, to manage it like a jukebox. Hopefully I'll finish that soon, because I'm getting sick of working on it.

Quote:Man! I didn't even look at the first few lines of code you posted. Very nice! Now why didn't Apple do that to begin with??!!

I ... don't ... know .... This is the part where I can't help but chuckle about when I read the Audio Queue documentation and saw a note that went something to the effect of: "Note that this is a C interface and doesn't need C++ to use, but we use C++ in the examples to simplify it."

"simplify" it? Riiight...

I think it's more like this is a perfect example of an API designer who is so addicted to C++ that they can't see how convoluted they make things by even barely *touching* C++. I don't know if they were in a rush or what, and just grabbing stuff off the shelf, but C++ was entirely unnecessary.

Now, I'm not a big C++ basher, but I'm going to take this special opportunity to rant a bit: I've said many times in the past that I've seen way too many examples of C++ misused (which is one of the reasons why I avoid using it myself) and this is one of those times. I had to go through a lot of extra hassle trying to comprehend the Audio Queue examples by re-writing them in a more sane language. The documentation leaves me with the impression that the API designer was so encumbered by their own example code that they spent all their time trying to untangle it for the reader, rather than explaining the core concepts in a fluid fashion. Because of this, the documentation left a few core questions that I had along the way, unanswered.

In the end, I like the Audio Queue API and think it is long overdue, but I think Apple should re-work the example code and documentation. It's not the best stuff I've seen from them, but at least it is a far sight better than the HID manager! Audio Queue is appreciated, and I don't want to sound too harsh, but I'll give it a D+ for now, because I think they could do the documentation and example code better.