我在iPhone上玩CoreAudio,我无法知道如何知道这首歌的播放时间。
我在kAudioQueueProperty_IsRunning
上放置了一个属性监听器,它在开始播放时工作,但不在文件末尾。当我停止AudioQueue时它会工作......
你有什么想法吗?
非常感谢。
PS:我正在使用优秀书籍iPhone Cool Projects中的示例代码:http://apress.com/book/downloadfile/4453
编辑:
代码中出现了一个与本书有关的错误。修复需要对 - [AudioPlayer audioRequestDidFinish:]进行一些小修改,以便它调用[queue endOfStream]。
答案 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何时位于文件的末尾,它通常会使用零长度缓冲区发出信号。