CoreAudio - how to determine the end of aac playable file

I play with CoreAudio on an iPhone and I cannot find how to find out when the song finished playing.

I put the property listener on kAudioQueueProperty_IsRunning , it works when the game starts, but not at the end of the file. It works when I stop AudioQueue ...

Do you have an idea?

Thank you very much.

PS: I use the sample code from the excellent iPhone Cool Projects book: http://apress.com/book/downloadfile/4453

Edit:

theres an error in the code that came out with the book. A minor modification is required to fix it - [AudioPlayer audioRequestDidFinish:] so that it calls [queue endOfStream].

+2
source share
2 answers

This class by Uli Kusterer clearly illustrates this functionality.

Here is the title:

 // // 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 , the UKSoundAQPropertyListenerCallback added to the audio UKSoundAQPropertyListenerCallback . It is configured to respond to the kAudioQueueProperty_IsRunning Audio Queue property. After calling the play method and ending the file, the UKSoundAQPropertyListenerCallback is UKSoundAQPropertyListenerCallback , which in turn calls the notifyDelegatePlaybackStateChanged method. I added an NSLog message to this method to illustrate when a file stops playing.

I would be happy to share an Xcode project that fully demonstrates this functionality.

+1
source

If you use the AudioQueue API, you fill the buffers manually, so you should know when you no longer have data. You can then use performSelectorOnMainThread to performSelectorOnMainThread your application you made.

If you need to know when your AudioConverter is at the end of a file, it usually signals this with zero-length buffers.

+1
source

Source: https://habr.com/ru/post/1335200/


All Articles