.h文件
#ifdef TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif TARGET_OS_IPHONE
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>
#include <pthread.h>
#include <AudioToolbox/AudioToolbox.h>
#define NUM_QUEUE_BUFFERS 3
#define kNumAQBufs 6 // number of audio queue buffers we allocate
#define kAQBufSize 32 * 1024 // number of bytes in each audio queue buffer
#define kAQMaxPacketDescs 512 // number of packet descriptions in our array
@interface xxxxx : NSObject {
/*-----------------USED FOR HTTP STREAM------------------*/
NSURL *url;
BOOL isPlaying;
@public
AudioFileStreamID audioFileStream; // the audio file stream parser
AudioStreamPacketDescription packetDescsQueue[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio
CFReadStreamRef stream;
unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled
size_t bytesFilled; // how many bytes have been filled
size_t packetsFilled; // how many packets have been filled
bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use
bool started; // flag to indicate that the queue has been started
bool failed; // flag to indicate an error occurred
bool discontinuous; // flag to trigger bug-avoidance
pthread_mutex_t mutex; // a mutex to protect the inuse flags
pthread_cond_t cond; // a condition varable for handling the inuse flags
pthread_mutex_t mutex2; // a mutex to protect the AudioQueue buffer
/*-------------------USED FOR LOCAL FILE--------------------*/
AudioFileID audioFile;
AudioStreamBasicDescription dataFormat;
AudioStreamPacketDescription *packetDescs;
UInt64 packetIndex;
UInt32 numPacketsToRead;
BOOL repeat;
BOOL trackClosed;
/*--------------------USED FOR PUBLIC------------------------*/
BOOL trackEnded;
AudioQueueRef queue;
AudioQueueBufferRef buffers[NUM_QUEUE_BUFFERS];
}
@property BOOL isPlaying;
@property BOOL trackClosed;
- (id) initWithURL:(NSURL*) newURL;
- (id) initWithPath:(NSString*) path;
- (void) setGain:(Float32)gain;
- (void) setRepeat:(BOOL)yn;
- (void) setPlayingWhenAutoLock;
- (void) play;
- (void) playURL;
- (void) pause;
- (void) stopURL;
- (void) close;
extern NSString *xxxTrackFinishedPlayingNotification;
@end
.m文件
#import "xxxxx.h"
#import <CFNetwork/CFNetwork.h>
static UInt32 kxxxBufferSizeBytes = 0x10000; // 64k
static BOOL kxxxTrackActive = NO;
NSString *xxxTrackFinishedPlayingNotification = @"xxxTrackFinishedPlayingNotification";
#pragma mark -
#pragma mark CFReadStream Callback Function Prototypes
void ReadStreamCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void* dataIn);
#pragma mark -
#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, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags);
void MyPacketsProc(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);
OSStatus MyEnqueueBuffer(xxxxx* myData);
#ifdef TARGET_OS_IPHONE
void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState);
#endif
#pragma mark -
#pragma mark Audio Callback Function Implementations
//
// MyPropertyListenerProc
//
// Receives notification when the AudioFileStream has audio packets to be
// played. In response, this function creates the AudioQueue, getting it
// ready to begin playback (playback won't begin until audio packets are
// sent to the queue in MyEnqueueBuffer).
//
// This function is adapted from Apple's example in AudioFileStreamExample with
// kAudioQueueProperty_IsRunning listening added.
//
void MyPropertyListenerProc(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags)
{
// this is called by audio file stream when it finds property values
xxxxx* myData = (xxxxx*)inClientData;
OSStatus err = noErr;
switch (inPropertyID) {
case kAudioFileStreamProperty_ReadyToProducePackets :
{
myData->discontinuous = true;
// the file stream parser is now ready to produce audio packets.
// get the stream format.
AudioStreamBasicDescription asbd;
UInt32 asbdSize = sizeof(asbd);
err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
if (err) { NSLog(@"get kAudioFileStreamProperty_DataFormat"); myData->failed = true; break; }
// create the audio queue
err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &myData->queue);
if (err) { NSLog(@"AudioQueueNewOutput"); myData->failed = true; break; }
// listen to the "isRunning" property
err = AudioQueueAddPropertyListener(myData->queue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
if (err) { NSLog(@"AudioQueueAddPropertyListener"); myData->failed = true; break; }
// allocate audio queue buffers
for (unsigned int i = 0; i < kNumAQBufs; ++i) {
err = AudioQueueAllocateBuffer(myData->queue, kAQBufSize, &myData->buffers);
if (err) { NSLog(@"AudioQueueAllocateBuffer"); myData->failed = true; break; }
}
// get the cookie size
UInt32 cookieSize;
Boolean writable;
err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);
if (err) { NSLog(@"info kAudioFileStreamProperty_MagicCookieData"); break; }
// get the cookie data
void* cookieData = calloc(1, cookieSize);
err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);
if (err) { NSLog(@"get kAudioFileStreamProperty_MagicCookieData"); free(cookieData); break; }
// set the cookie on the queue.
err = AudioQueueSetProperty(myData->queue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize);
free(cookieData);
if (err) { NSLog(@"set kAudioQueueProperty_MagicCookie"); break; }
break;
}
}
}
//
// MyPacketsProc
//
// When the AudioStream has packets to be played, this function gets an
// idle audio buffer and copies the audio packets into it. The calls to
// MyEnqueueBuffer won't return until there are buffers available (or the
// playback has been stopped).
//
// This function is adapted from Apple's example in AudioFileStreamExample with
// CBR functionality added.
//
void MyPacketsProc(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions)
{
// this is called by audio file stream when it finds packets of audio
xxxxx* myData = (xxxxx*)inClientData;
// we have successfully read the first packests from the audio stream, so
// clear the "discontinuous" flag
myData->discontinuous = false;
// the following code assumes we're streaming VBR data. for CBR data, the second branch is used.
if (inPacketDescriptions)
{
for (int i = 0; i < inNumberPackets; ++i) {
SInt64 packetOffset = inPacketDescriptions.mStartOffset;
SInt64 packetSize = inPacketDescriptions.mDataByteSize;
// If the audio was terminated before this point, then
// exit.
if (myData->trackEnded)
{
return;
}
// if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
size_t bufSpaceRemaining = kAQBufSize - myData->bytesFilled;
if (bufSpaceRemaining < packetSize) {
MyEnqueueBuffer(myData);
}
pthread_mutex_lock(&myData->mutex2);
// If the audio was terminated while waiting for a buffer, then
// exit.
if (myData->trackEnded)
{
pthread_mutex_unlock(&myData->mutex2);
return;
}
// copy data to the audio queue buffer
AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];
memcpy((char*)fillBuf->mAudioData + myData->bytesFilled, (const char*)inInputData + packetOffset, packetSize);
pthread_mutex_unlock(&myData->mutex2);
// fill out packet description
myData->packetDescsQueue[myData->packetsFilled] = inPacketDescriptions;
myData->packetDescsQueue[myData->packetsFilled].mStartOffset = myData->bytesFilled;
// keep track of bytes filled and packets filled
myData->bytesFilled += packetSize;
myData->packetsFilled += 1;
// if that was the last free packet description, then enqueue the buffer.
size_t packetsDescsRemaining = kAQMaxPacketDescs - myData->packetsFilled;
if (packetsDescsRemaining == 0) {
MyEnqueueBuffer(myData);
}
}
}
else
{
size_t offset = 0;
while (inNumberBytes)
{
// if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
size_t bufSpaceRemaining = kAQBufSize - myData->bytesFilled;
if (bufSpaceRemaining < inNumberBytes) {
MyEnqueueBuffer(myData);
}
pthread_mutex_lock(&myData->mutex2);
// If the audio was terminated while waiting for a buffer, then
// exit.
if (myData->trackEnded)
{
pthread_mutex_unlock(&myData->mutex2);
return;
}
// copy data to the audio queue buffer
AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];
bufSpaceRemaining = kAQBufSize - myData->bytesFilled;
size_t copySize;
if (bufSpaceRemaining < inNumberBytes)
{
copySize = bufSpaceRemaining;
}
else
{
copySize = inNumberBytes;
}
memcpy((char*)fillBuf->mAudioData + myData->bytesFilled, (const char*)(inInputData + offset), copySize);
pthread_mutex_unlock(&myData->mutex2);
// keep track of bytes filled and packets filled
myData->bytesFilled += copySize;
myData->packetsFilled = 0;
inNumberBytes -= copySize;
offset += copySize;
}
}
}
//
// MyEnqueueBuffer
//
// Called from MyPacketsProc and connectionDidFinishLoading to pass filled audio
// bufffers (filled by MyPacketsProc) to the AudioQueue for playback. This
// function does not return until a buffer is idle for further filling or
// the AudioQueue is stopped.
//
// This function is adapted from Apple's example in AudioFileStreamExample with
// CBR functionality added.
//
OSStatus MyEnqueueBuffer(xxxxx* myData)
{
OSStatus err = noErr;
myData->inuse[myData->fillBufferIndex] = true; // set in use flag
// enqueue buffer
AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];
fillBuf->mAudioDataByteSize = myData->bytesFilled;
if (myData->packetsFilled)
{
err = AudioQueueEnqueueBuffer(myData->queue, fillBuf, myData->packetsFilled, myData->packetDescsQueue);
}
else
{
err = AudioQueueEnqueueBuffer(myData->queue, fillBuf, 0, NULL);
}
if (err) { NSLog(@"AudioQueueEnqueueBuffer"); myData->failed = true; return err; }
if (!myData->started) { // start the queue if it has not been started already
err = AudioQueueStart(myData->queue, NULL);
if (err) { NSLog(@"AudioQueueStart"); myData->failed = true; return err; }
myData->started = true;
}
// go to next buffer
if (++myData->fillBufferIndex >= kNumAQBufs) myData->fillBufferIndex = 0;
myData->bytesFilled = 0; // reset bytes filled
myData->packetsFilled = 0; // reset packets filled
// wait until next buffer is not in use
pthread_mutex_lock(&myData->mutex);
while (myData->inuse[myData->fillBufferIndex] && !myData->trackEnded)
{
pthread_cond_wait(&myData->cond, &myData->mutex);
}
pthread_mutex_unlock(&myData->mutex);
return err;
}
//
// 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