Audio Queues

Nibbie
Posts: 1
Joined: 2008.08
Post: #31
A BIG thank you to AnotherJake - your code works like a charm.

A heads-up for those having problems playing mp3s - iPhone/iTouch seems to only like mp3 encoded at 44.1kHz and above.

Thanks again AnotherJake!


---------------

EDIT (25 August): Actually you can play less then 44.1kHz MP3s. The problem I was having was that I was trying unsuccessfully to play very short sound-effect MP3s. The solution was to decrease the size of the buffer:

static UInt32 gBufferSizeBytes = 0x10000;

---------------


I set it to 0x01000 and it worked fine even for 20kHz encoded MP3 files.
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #32
Oops! I can't say that it works on iPhone because of the NDA (so I don't know what you are talking about Sneaky), this is for Mac OS X. But I'm glad to hear from you ethan! Smile

I have received many kind emails about GBMusicTrack over the last several weeks. Again, for anyone who has comments or suggestions or improvements for it, you can contact me about it at anotherjake +AT_ mac.com

Whilst on that subject, Bence, from Hungary, added some small modifications to show an example of how to do seeking. I haven't had a chance to try it myself yet, but it looks pretty handy, and Bence said it'd be okay to share, so here it is for anyone who's interested:

BenceGBMusicTrack.h
Code:
#ifndef __GBMUSICTRACK_H
#define __GBMUSICTRACK_H

#import <Cocoa/Cocoa.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>
#include "Utility.h"


#define NUM_QUEUE_BUFFERS    3

typedef enum {
    EAudioStateClosed,
    EAudioStateStopped,
    EAudioStatePlaying,
    EAudioStatePaused,
    EAudioStateSeeking
} EAudioState;

@interface GBMusicTrack : NSObject
{
    AudioFileID                        audioFile;
    AudioStreamBasicDescription        dataFormat;
    AudioQueueRef                    queue;
    UInt64                            packetIndex;
    UInt32                            numPacketsToRead;
    AudioStreamPacketDescription*    packetDescs;
    BOOL                            repeat;
    BOOL                            trackClosed;
    AudioQueueBufferRef                buffers[NUM_QUEUE_BUFFERS];
    EAudioState                        audioState;
}

- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;
- (void)stop;
- (void)seek:(UInt64)packetOffset;
- (EAudioState)getState;


// 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;

extern NSString* GBMusicTrackFinishedPlayingNotification;

@end

#endif

BenceGBMusicTrack.m
Code:
#import "GBMusicTrack.h"
#include "SoundEngine.h"            // for callback function


static UInt32 gBufferSizeBytes = 0x10000; // 64k

NSString* GBMusicTrackFinishedPlayingNotification = @"GBMusicTrackFinishedPlayingNotification";

@interface GBMusicTrack (InternalMethods)

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;
- (void)prefetchBuffers;

@end

@implementation GBMusicTrack

#pragma mark -
#pragma mark GBMusicTrack

- (void)dealloc
{
    [self close];
    [super dealloc];
}

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

- (id)initWithPath:(NSString *)path
{
    UInt32        size, maxPacketSize;
    char        *cookie;
    
    audioState    = EAudioStateStopped;
    
    if (!(self = [super init])) return nil;
    if (path == nil) return nil;
    
    // try to open up the file using the specified path
    if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, kAudioFileCAFType, &audioFile))
    {
        LOGPAR("GBMusicTrack Error - initWithPath: could not open audio file. Path given was: %@", path);
        return nil;
    }
    
    // get the data format of the file
    size = sizeof(dataFormat);
    AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);
    
    // create a new playback queue using the specified data format and buffer callback
    AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);
    
    // 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;
            LOGPAR("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;
        
        // don't need packet descriptsions for CBR data
        packetDescs = nil;
    }
    
    // see if file uses a magic cookie (a magic cookie is meta data which some formats use)
    AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
    if (size > 0)
    {
        // copy the cookie data from the file into the audio queue
        cookie = malloc(sizeof(char) * size);
        AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
        AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
        free(cookie);
    }
    
    // allocate buffers
    for (int i = 0; i < NUM_QUEUE_BUFFERS; i++)
        AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
    
    packetIndex = 0;
    
    repeat        = NO;
    trackClosed    = NO;
    
    return self;
}

- (void)prefetchBuffers
{
    // prime buffers with some data
    for (int i = 0; i < NUM_QUEUE_BUFFERS; i++)
    {
        //AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
        if ([self readPacketsIntoBuffer:buffers[i]] == 0)
        {
            // this might happen if the file was so short that it needed less buffers than we planned on using
            break;
        }
    }
}

- (void)seek:(UInt64)packetOffset
{
    BOOL playbeforeseek = ([self getState] == EAudioStatePlaying);
    audioState    = EAudioStateSeeking;
    [self stop];
    packetIndex = packetOffset;
    if (playbeforeseek) [self play];
}

- (void)stop
{
    if (audioState != EAudioStateSeeking)
        audioState    = EAudioStateStopped;
    AudioQueueStop(queue, YES);
}

- (void)setGain:(Float32)gain
{
    AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}

- (void)setRepeat:(BOOL)yn
{
    repeat = yn;
}

- (void)play
{
    if (audioState == EAudioStateStopped || audioState == EAudioStateSeeking)
        [self prefetchBuffers];
    
    audioState    = EAudioStatePlaying;
    OSStatus err = AudioQueueStart(queue, nil);
    if (err) LOGPAR("GBMusicTrack AudioQueueStart ERROR %d", err);
}

- (void)pause
{
    audioState    = EAudioStatePaused;
    AudioQueuePause(queue);
}

#pragma mark -
#pragma mark Callback

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 (audioState == EAudioStateSeeking) return;
    
    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)
        {
            //[self pause];
            [self stop];
            
            // 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];
        }
    }
}

- (EAudioState)getState
{
    return audioState;
}

- (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];
    if (audioState != EAudioStatePlaying) {
        [self close];
        callbackPlayFinished();
    }
}

- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
    UInt32        numBytes, numPackets;
    
    // 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

And here's another little code snippet I put together from one of Bence's emails, which will get the artist string from some files. It doesn't seem to work with everything for some reason. More AudioToolbox bugs I suspect. Wink

Code:
if (noErr == AudioFileGetPropertyInfo (audioFile, kAudioFilePropertyInfoDictionary, &size, NULL))
{
    NSMutableDictionary        *myDictionary;
    if (noErr == AudioFileGetProperty(audioFile, kAudioFilePropertyInfoDictionary, &size, &myDictionary))
    {
        NSString    *artistString;
        if (CFDictionaryGetValueIfPresent((CFMutableDictionaryRef)myDictionary, CFSTR(kAFInfoDictionary_Artist), (const void **) &artistString))
            NSLog(@"Artist : %@", artistString);
        else
            NSLog(@"Unknown Artist");
        [myDictionary release];
    }
}
Quote this message in a reply
Nibbie
Posts: 3
Joined: 2008.09
Post: #33
Jake, thanks for all this! It works like a charm.

And I know you're using it to only play one sound file at a time. But I want to do some cross fading between sound files. So that requires 2 to play at the same time.

And I edited your code to allow this. And it works great in the sim. But on the device it plays the first song. Doesn't play the second and lags he whole app like crazy. Any ideas? Is it possible?
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #34
Thanks OdieGiblet!

Unfortunately, iPhone is under NDA so I cannot say that I know anything about GBMusicTrack working on iPhone.

+++++ GBMusicTrack was written exclusively for Mac OS X. +++++

That said, you are correct, it is only designed to play one track at a time. I cannot possibly stress that detail enough. If you would like to further discuss why I designed it this way, please contact me at anotherjake +AT+ mac.com Smile
Quote this message in a reply
Moderator
Posts: 613
Joined: 2004.09
Post: #35
OpenAL is a perfectly good alternative for playing multiple files at the same time, and works on a variety of platforms. Wink

Kyle Richter
DragonForged.com
Twitter: @kylerichter
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2008.09
Post: #36
AnotherJake Wrote:Heck, well for Mac, all you had to do was ask! Smile

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 [edit]found a memory leak, studied it, bug report filed[/edit] 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 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. 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.

*** NOTE *** Use this for background music only, and only one track at a time. 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 like this:

Code:
GBMusicTrack *song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"BackgroundMusic" ofType:@"mp3"]];
[song setRepeat:YES];
[song play];

GBMusicTrack.h
Code:
//
//  GBMusicTrack.h
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import <AppKit/AppKit.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>

#define NUM_QUEUE_BUFFERS    3

@interface GBMusicTrack : NSObject
{
    AudioFileID                        audioFile;
    AudioStreamBasicDescription        dataFormat;
    AudioQueueRef                    queue;
    UInt64                            packetIndex;
    UInt32                            numPacketsToRead;
    AudioStreamPacketDescription    *packetDescs;
    BOOL                            repeat;
    BOOL                            trackClosed;
    BOOL                            trackEnded;
    AudioQueueBufferRef                buffers[NUM_QUEUE_BUFFERS];
}

- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;

// 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;

extern NSString    *GBMusicTrackFinishedPlayingNotification;

@end

GBMusicTrack.m
Code:
//
//  GBMusicTrack.m
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

// last modified 8/7/08

#import "GBMusicTrack.h"

static UInt32    gBufferSizeBytes = 0x10000; // 64k

// *** 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;

NSString *GBMusicTrackFinishedPlayingNotification = @"GBMusicTrackFinishedPlayingNotification";

@interface GBMusicTrack (InternalMethods)

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 GBMusicTrack

#pragma mark -
#pragma mark GBMusicTrack

- (void)dealloc
{
    [self close];
    if (packetDescs != nil)
        free(packetDescs);
    [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);
    gThereIsAnActiveTrack = NO;
}

- (id)initWithPath:(NSString *)path
{
    UInt32        size, maxPacketSize;
    char        *cookie;
    int            i;
    
    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;
    }
    
    if(!(self = [super init])) return nil;
    if (path == nil) return nil;
    
    // try to open up the file using the specified path
    if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, 0, &audioFile))
    {
        NSLog(@"*** Error *** GBMusicTrack - initWithPath: could not open audio file. Path given was: %@", path);
        return nil;
    }
    
    // get the data format of the file
    size = sizeof(dataFormat);
    AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);
    
    // create a new playback queue using the specified data format and buffer callback
    AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);
    
    // 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;
        
        // don't need packet descriptions for CBR data
        packetDescs = nil;
    }
    
    // see if file uses a magic cookie (a magic cookie is meta data which some formats use)
    AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
    if (size > 0)
    {
        // copy the cookie data from the file into the audio queue
        cookie = malloc(sizeof(char) * size);
        AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
        AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
        free(cookie);
    }
    
    // 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);
    
    // allocate and prime buffers with some data
    packetIndex = 0;
    for (i = 0; i < NUM_QUEUE_BUFFERS; i++)
    {
        AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
        if ([self readPacketsIntoBuffer:buffers[i]] == 0)
        {
            // this might happen if the file was so short that it needed less buffers than we planned on using
            break;
        }
    }
    repeat = NO;
    trackClosed = NO;
    trackEnded = NO;
    gThereIsAnActiveTrack = YES;
    return self;
}

- (void)setGain:(Float32)gain
{
    if (trackClosed)
        return;
    AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}

- (void)setRepeat:(BOOL)yn
{
    repeat = yn;
}

- (void)play
{
    if (trackClosed)
        return;
    AudioQueueStart(queue, nil);
}

- (void)pause
{
    if (trackClosed)
        return;
    AudioQueuePause(queue);
}

#pragma mark -
#pragma mark Callback

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];
}

- (void)playBackIsRunningStateChanged
{
    if (trackEnded)
    {
        // go ahead and close the track now
        trackClosed = YES;
        AudioQueueDispose(queue, YES);
        AudioFileClose(audioFile);
        gThereIsAnActiveTrack = NO;
        
        // 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];
}

- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
    UInt32        numBytes, numPackets;
    
    // 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

Hi AnotherJake.

What a delicious code for Mac OSX!!!!!
I just tried it on my portable Mac OSX device... works like a charme...

Questions I may have:

Would it be difficult for you to manage a "song" buffer?
How would you manage a "resume" method? Let me explain.
I'd like to read web hosted mp3, and I could have connection prob. therefore, while checking the connection, if it is ok, I could resume the track from its last known position.

Many thanks for answering.

Pat
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2008.09
Post: #37
Thanks so much for this code.

Would it work to play an audio file (mp3) located on a web server?

If not, would it be possible for you to explain what modification would be necessary?

Thank you

PM
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #38
Thanks pm7_7! Smile

As far as streaming from a web server I have not even the slightest clue how that could be done. My guess is that you would have to probably use lower level stuff in Core Audio to intercept the decoded stream on the fly.

For the "resume" thing, I haven't tried it, but Bence's code (just a few posts above) might work for that. He added some handy seeking stuff.
Quote this message in a reply
Apprentice
Posts: 7
Joined: 2008.06
Post: #39
works great, thanks for the code for Mac OSX Smile I'll be looking forward more excellent code for Mac OSX from you =)
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #40
Thanks ishaq! Smile
Quote this message in a reply
Apprentice
Posts: 8
Joined: 2008.09
Post: #41
I finaly got it to work with one mp3 Smile works great. But anyone know how would i make this file work with 2 mp3s.. to switch between them? I tried [song close] but it didnt work ...before playing the second song

Please help.
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #42
Simply calling the close method is not enough, in addition to close you need to actually release it before playing the next one.

If that still doesn't work then try a different mp3 or maybe making the buffer size a little smaller. There have been reports of some tracks mysteriously causing problems.
Quote this message in a reply
Apprentice
Posts: 8
Joined: 2008.09
Post: #43
Hey thanks, i got it to work. .But i am having issue with looping mp3s. i tried m4a too and i still hear a GAP between the loops.. if i use aif or wav its perfect but because of the sizes i have .. i cant use the uncompressed format

anyone got any suggestions?
Quote this message in a reply
Moderator
Posts: 3,571
Joined: 2003.06
Post: #44
Yes, there is a gap between loops of compressed audio, and there doesn't appear to be any way around it (that I know of). What I and others have been doing is fading songs out at the ends. If someone comes up with a better workaround for this, I'd definitely like to hear from you!

The only two ideas I can suggest is to uncompress the sound into memory on load and use OpenAL instead, or perhaps uncompress the sound to disk once it's reached the customer and load it as a WAV.
Quote this message in a reply
Apprentice
Posts: 8
Joined: 2008.09
Post: #45
I did notice that when i encoded the mp3s they had a slight silence in the beginning and end of the file. but when i removed those it still didn't loop perfectly. the m4a format zoomed in didnt have those either.

there must be a way to fix it.. anyone tried the BenceGBMusicTrack to loop mp3s? i havent tried that version .. could that fix it?
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Core Audio mutlichannel audio tachyon 2 6,103 Mar 18, 2005 01:04 AM
Last Post: tachyon