CoreAudio - 如何确定播放aac文件的结束

时间:2010-11-15 22:04:46

标签: iphone-sdk-3.0 ios4 core-audio

我在iPhone上玩CoreAudio,我无法知道如何知道这首歌的播放时间。

我在kAudioQueueProperty_IsRunning上放置了一个属性监听器,它在开始播放时工作,但不在文件末尾。当我停止AudioQueue时它会工作......

你有什么想法吗?

非常感谢。

PS:我正在使用优秀书籍iPhone Cool Projects中的示例代码:http://apress.com/book/downloadfile/4453

编辑:

代码中出现了一个与本书有关的错误。修复需要对 - [AudioPlayer audioRequestDidFinish:]进行一些小修改,以便它调用[queue endOfStream]。

2 个答案:

答案 0 :(得分:1)

Uli Kusterer的这个课程清楚地说明了这个功能。

这是标题:

//
//  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音频队列属性。调用play方法并且文件播放完毕后,将调用UKSoundAQPropertyListenerCallback,然后调用notifyDelegatePlaybackStateChanged方法。我在此方法中添加了一条NSLog消息,以说明文件何时停止播放。

我很乐意分享一个完全展示此功能的Xcode项目。

答案 1 :(得分:1)

如果您正在使用AudioQueue API,则需要手动填充缓冲区,因此您应该知道何时没有更多数据。然后,您可以使用performSelectorOnMainThread向您的应用发送已完成播放的消息。

如果您需要知道AudioConverter何时位于文件的末尾,它通常会使用零长度缓冲区发出信号。