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

发布于 2024-10-03 00:38:00 字数 454 浏览 3 评论 0原文

我正在 iPhone 上使用 CoreAudio,但我无法找到如何知道歌曲何时播放完毕。

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

你有想法吗?

多谢。

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

编辑:

本书附带的代码中有一个错误。该修复需要对 -[AudioPlayer audioRequestDidFinish:] 进行小修改,以便它调用 [queue endOfStream]。

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].

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清醇 2024-10-10 00:38:00

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 项目。

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.

笑咖 2024-10-10 00:38:00

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文