I am playing with CoreAudio on the iPhone, and I am unable to find how to know when the song has finished to play.
I put a property listener on kAudioQueueProperty_IsRunning
, it is working when starting playing but not at the end of the file. It's work when I stop AudioQueue...
Do you have an idea ?
Thanks a lot.
PS : I am using sample code from the excellent book iPhone Cool Projects : http://apress.com/book/downloadfile/4453
Edit :
there’s a bug in the code that went out with the book. The fix requires a small modificatio开发者_如何学Pythonn to -[AudioPlayer audioRequestDidFinish:] so that it calls [queue endOfStream].
This class from Uli Kusterer illustrates this functionality clearly.
Here's the header:
//
// 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
Here's the implementation:
//
// 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
When you call the initWithContentsOfURL: (NSURL*)theURL
method, the UKSoundAQPropertyListenerCallback
is added to the audio queue. It is set to respond to the kAudioQueueProperty_IsRunning
Audio Queue Property. After the play
method is called and the file finishes playing, UKSoundAQPropertyListenerCallback
is called which in turn calls the notifyDelegatePlaybackStateChanged
method. I have added an NSLog message to this method to illustrate when the file stops playing.
I'd be happy to share an Xcode project that fully demonstrates this functionality.
If you're using the AudioQueue API, you're filling the buffers manually, so you should know when you have no more data. You can then use performSelectorOnMainThread
to message your app that you're done playing.
If you need to know when your AudioConverter is at the end of the file, it usually signals it with zero-length buffers.
精彩评论