CoreAudio - how to determine the end of the playing aac file

僤鯓⒐⒋嵵緔 提交于 2019-12-08 02:01:06

问题


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 modification to -[AudioPlayer audioRequestDidFinish:] so that it calls [queue endOfStream].


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/4189292/coreaudio-how-to-determine-the-end-of-the-playing-aac-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!