A. 在專案的 "Frameworks" 新增以下的 framework: CoreAudio.framework 與 AudioToolbox.framework .
B. 在專案的 "Groups & Files" 新增一個 Group: "Audio", 並新增以下的檔案: GBMusicTrack.h 與 GBMusicTrack.m
//
// GBMusicTrack.h
// GameBase
//
// Created by Jake Peterson (AnotherJake) on 7/6/08.
// Copyright 2008 Jake Peterson. All rights reserved.
//
//#import <Cocoa/Cocoa.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;
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
// GameBase
//
// Created by Jake Peterson (AnotherJake) on 7/6/08.
// Copyright 2008 Jake Peterson. All rights reserved.
//
#import "GBMusicTrack.h"
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;
@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
if (trackClosed)
return;
trackClosed = YES;
AudioQueueStop(queue, YES);
AudioQueueDispose(queue, YES);
AudioFileClose(audioFile);
}
- (id)initWithPath:(NSString *)path
{
UInt32 size, maxPacketSize;
char *cookie;
int i;
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))
{
NSLog(@"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;
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;
// 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 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;
return self;
}
- (void)setGain:(Float32)gain
{
AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}
- (void)setRepeat:(BOOL)yn
{
repeat = yn;
}
- (void)play
{
AudioQueueStart(queue, nil);
}
- (void)pause
{
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 ([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];
}
- (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
------------------------------------------------------------------------------------------------------
C. 在 xxxViewController.m 檔:
#import "GBMusicTrack.h"
D. 在 xxxViewController.m 檔, 修改 viewDidLoad():
- (void)viewDidLoad {
[super viewDidLoad];
....
//background music
GBMusicTrack *song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"streetFighter" ofType:@"mp3"]];
[song setRepeat:YES];
[song play];
}
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。