Этот класс от Ули Кустерера ясно иллюстрирует эту функциональность.
Вот заголовок:
//
// UKSound.h
// MobileMoose
//
// Created by Uli Kusterer on 14.07.08.
// Copyright 2008 The Void Software. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#define kNumberBuffers 2
@class UKSound;
@protocol UKSoundDelegate
@optional
-(void) sound: (UKSound*)sender didFinishPlaying: (BOOL)state;
@end
@interface UKSound : NSObject
{
AudioFileID mAudioFile;
AudioStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
UInt64 mPacketIndex;
UInt32 mNumPacketsToRead;
AudioStreamPacketDescription * mPacketDescs;
BOOL mDone;
id<UKSoundDelegate> delegate;
int maxBufferSizeBytes;
}
@property (assign) id<UKSoundDelegate> delegate;
-(id) initWithContentsOfURL: (NSURL*)theURL;
-(void) notifyDelegatePlaybackStateChanged: (id)sender;
-(void) play;
// private:
-(void) audioQueue: (AudioQueueRef)inAQ processBuffer: (AudioQueueBufferRef)inCompleteAQBuffer;
@end
Вот реализация:
//
// UKSound.m
// MobileMoose
//
// Created by Uli Kusterer on 14.07.08.
// Copyright 2008 The Void Software. All rights reserved.
//
#import "UKSound.h"
static void UKSoundAQBufferCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer)
{
UKSound* myself = (UKSound*)inUserData;
[myself audioQueue: inAQ processBuffer: inCompleteAQBuffer];
}
static void UKSoundAQPropertyListenerCallback( void * inUserData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
{
[(UKSound*)inUserData performSelectorOnMainThread: @selector(notifyDelegatePlaybackStateChanged:) withObject: nil waitUntilDone: NO];
}
@implementation UKSound
@synthesize delegate;
-(id) initWithContentsOfURL: (NSURL*)theURL
{
self = [super init];
if( self )
{
maxBufferSizeBytes = 0x10000;
OSStatus err = AudioFileOpenURL( (CFURLRef)theURL, kAudioFileReadPermission, 0, &mAudioFile );
if( err != noErr )
NSLog(@"Couldn't open AudioFile.");
UInt32 size = sizeof(mDataFormat);
err = AudioFileGetProperty( mAudioFile, kAudioFilePropertyDataFormat, &size, &mDataFormat );
if( err != noErr )
NSLog(@"Couldn't determine audio file format.");
err = AudioQueueNewOutput( &mDataFormat, UKSoundAQBufferCallback, self, NULL, NULL, 0, &mQueue );
if( err != noErr )
NSLog(@"Couldn't create new output for queue.");
// We have a couple of things to take care of now
// (1) Setting up the conditions around VBR or a CBR format - affects how we will read from the file
// if format is VBR we need to use a packet table.
if( mDataFormat.mBytesPerPacket == 0 || mDataFormat.mFramesPerPacket == 0 )
{
// first check to see what the max size of a packet is - if it is bigger
// than our allocation default size, that needs to become larger
UInt32 maxPacketSize;
size = sizeof(maxPacketSize);
err = AudioFileGetProperty( mAudioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
if( err != noErr )
NSLog(@"Couldn't get max packet size of audio file.");
if( maxPacketSize > maxBufferSizeBytes )
maxBufferSizeBytes = maxPacketSize;
// we also need packet descpriptions for the file reading
mNumPacketsToRead = maxBufferSizeBytes / maxPacketSize;
mPacketDescs = malloc( sizeof(AudioStreamPacketDescription) * mNumPacketsToRead );
}
else
{
mNumPacketsToRead = maxBufferSizeBytes / mDataFormat.mBytesPerPacket;
mPacketDescs = NULL;
}
// (2) If the file has a cookie, we should get it and set it on the AQ
size = sizeof(UInt32);
err = AudioFileGetPropertyInfo( mAudioFile, kAudioFilePropertyMagicCookieData, &size, NULL );
if( !err && size )
{
char* cookie = malloc( size );
err = AudioFileGetProperty( mAudioFile, kAudioFilePropertyMagicCookieData, &size, cookie );
if( err != noErr )
NSLog(@"Couldn't get magic cookie of audio file.");
err = AudioQueueSetProperty( mQueue, kAudioQueueProperty_MagicCookie, cookie, size );
if( err != noErr )
NSLog(@"Couldn't transfer magic cookie of audio file to qudio queue.");
free( cookie );
}
err = AudioQueueAddPropertyListener( mQueue, kAudioQueueProperty_IsRunning,
UKSoundAQPropertyListenerCallback,
self );
if( err != noErr )
NSLog(@"Couldn't register for playback state changes.");
// prime the queue with some data before starting
mDone = false;
mPacketIndex = 0;
for( int i = 0; i < kNumberBuffers; ++i )
{
err = AudioQueueAllocateBuffer( mQueue, maxBufferSizeBytes, &mBuffers[i] );
if( err != noErr )
NSLog(@"Couldn't allocate buffer %d.", i);
UKSoundAQBufferCallback( self, mQueue, mBuffers[i] );
if( mDone ) break;
}
}
return self;
}
-(void) dealloc
{
OSStatus err = AudioQueueDispose( mQueue, true );
err = AudioFileClose( mAudioFile );
if( mPacketDescs )
free( mPacketDescs );
[super dealloc];
}
-(void) play
{
OSStatus err = AudioQueueStart( mQueue, NULL );
if( err != noErr )
NSLog(@"Couldn't start audio queue.");
else
[self retain];
}
-(BOOL) isPlaying
{
UInt32 state = NO,
size = sizeof(UInt32);
OSStatus err = AudioQueueGetProperty( mQueue, kAudioQueueProperty_IsRunning, &state, &size );
if( err != noErr )
NSLog(@"Couldn't get play state of queue.");
return state;
}
-(void) notifyDelegatePlaybackStateChanged: (id)sender;
{
if( ![self isPlaying] )
{
NSLog(@"Insert your functionality here.");
[delegate sound: self didFinishPlaying: YES];
AudioQueueStop( mQueue, false );
[self release];
}
}
-(void) audioQueue: (AudioQueueRef)inAQ processBuffer: (AudioQueueBufferRef)inCompleteAQBuffer
{
if( mDone )
return;
UInt32 numBytes;
UInt32 nPackets = mNumPacketsToRead;
// Read nPackets worth of data into buffer
OSStatus err = AudioFileReadPackets( mAudioFile, false, &numBytes, mPacketDescs, mPacketIndex, &nPackets,
inCompleteAQBuffer->mAudioData);
if( err != noErr )
NSLog(@"Couldn't read into buffer.");
if (nPackets > 0)
{
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
// Queues the buffer for audio input/output.
err = AudioQueueEnqueueBuffer( inAQ, inCompleteAQBuffer, (mPacketDescs ? nPackets : 0), mPacketDescs );
if( err != noErr )
NSLog(@"Couldn't enqueue buffer.");
mPacketIndex += nPackets;
}
else
{
UInt32 state = NO,
size = sizeof(UInt32);
OSStatus err = AudioQueueGetProperty( mQueue, kAudioQueueProperty_IsRunning, &state, &size );
// I should be calling the following, but it always makes the app hang.
if( state )
{
err = AudioQueueStop( mQueue, false );
if( err != noErr )
NSLog(@"Couldn't stop queue.");
// reading nPackets == 0 is our EOF condition
}
mDone = true;
}
}
@end
Когда вы вызываете метод initWithContentsOfURL: (NSURL*)theURL
, UKSoundAQPropertyListenerCallback
добавляется в аудио-очередь. Он настроен для ответа на свойство kAudioQueueProperty_IsRunning
Audio Queue. После вызова метода play
и завершения воспроизведения файла вызывается UKSoundAQPropertyListenerCallback
, который, в свою очередь, вызывает метод notifyDelegatePlaybackStateChanged
. Я добавил в этот метод сообщение NSLog, чтобы проиллюстрировать, когда файл перестает воспроизводиться.
Я был бы рад поделиться проектом XCode, который полностью демонстрирует эту функциональность.