2013-10-17 73 views
12

我想从文件功能做一个简单的回放,它似乎我的回调函数永远不会被调用。它没有任何意义,因为所有的OSStatus都返回0,其他数字也都显示正确(就像输出数据包从AudioFileReadPackets读取指针)。CoreAudio AudioQueue回调函数从未调用,没有错误报告

下面是设置:

OSStatus stat; 

stat = AudioFileOpenURL(
    (CFURLRef)urlpath, kAudioFileReadPermission, 0, &aStreamData->aFile 
); 

UInt32 dsze = 0; 
stat = AudioFileGetPropertyInfo(
    aStreamData->aFile, kAudioFilePropertyDataFormat, &dsze, 0 
); 

stat = AudioFileGetProperty(
    aStreamData->aFile, kAudioFilePropertyDataFormat, &dsze, &aStreamData->aDescription 
); 

stat = AudioQueueNewOutput(
    &aStreamData->aDescription, bufferCallback, aStreamData, NULL, NULL, 0, &aStreamData->aQueue 
); 

aStreamData->pOffset = 0; 

for(int i = 0; i < NUM_BUFFERS; i++) { 
    stat = AudioQueueAllocateBuffer(
     aStreamData->aQueue, aStreamData->aDescription.mBytesPerPacket, &aStreamData->aBuffer[i] 
    ); 

    bufferCallback(aStreamData, aStreamData->aQueue, aStreamData->aBuffer[i]); 
} 

stat = AudioQueuePrime(aStreamData->aQueue, 0, NULL); 
stat = AudioQueueStart(aStreamData->aQueue, NULL); 

(未显示在这里我检查的stat在功能之间的值,它只是回来正常。)

而且回调函数:

void bufferCallback(void *uData, AudioQueueRef queue, AudioQueueBufferRef buffer) { 
    UInt32 bread = 0; 
    UInt32 pread = buffer->mAudioDataBytesCapacity/player->aStreamData->aDescription.mBytesPerPacket; 

    OSStatus stat; 

    stat = AudioFileReadPackets(
     player->aStreamData->aFile, false, &bread, NULL, player->aStreamData->pOffset, &pread, buffer->mAudioData 
    ); 

    buffer->mAudioDataByteSize = bread; 

    stat = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); 

    player->aStreamData->pOffset += pread; 
} 

哪里aStreamData我的用户数据结构(通过typedef这样我就可以把它作为一个类属性)和玩家控制的Objective-C C的静态实例姑娘。如果需要其他代码,请告诉我。我有点在智慧的结尾。打印这里涉及的任何数字都会得到正确的结果,包括在allocate循环中自己调用它时包含bufferCallback中的函数。之后它永远不会被调用。启动方法返回并没有任何反应。另外,我正在使用外围设备(MBox Pro 3)来播放CoreAudio仅在输出时启动的声音。 IE浏览器如果我启动iTunes或其他东西,扬声器微弱地弹出,并且有一个LED从闪烁变为稳定。设备启动就像它一样,CA肯定是在做一些事情。 (当然,我也尝试了使用板载Macbook无声设备。)

我已经阅读过其他解决方案来解决类似问题,但它们不起作用。像我现在正在使用多个缓冲区的东西,似乎没有任何区别。

我基本上认为我在某种程度上做了某些明显错误的事情,但不知道它可能是什么。我已经阅读了相关文档,查看了可用的代码示例,并在网上搜索了一些答案,看起来这是我所需要做的,而且应该只是去做。

至少,还有什么我可以做的调查?

+0

我不知道为什么它不起作用。但是,为什么你不使用AudioUnit? –

+0

@AliaksandrAndrashuk AudioUnit不仅仅是为了效果吗?将它们用于从文件流式传输是否合理? – Radiodef

+0

是的。您可以设置AUFilePlayer - > RemoteIO(在AUGraph中)播放文件。 –

回答

2

我的第一个答案不够好,所以我编了一个最小的例子,播放一个2通道的16位波形文件。

与你的代码的主要区别是,我做了一个属性侦听器侦听播放开始和停止事件。

至于你的代码,它乍一看似乎是合法的。有两件事我会指出,虽然: 1.似乎你正在分配缓冲区与太小缓冲区大小。我注意到,如果缓冲区太小,AudioQueues将不会播放,这似乎适合您的问题。 2.您是否验证过返回的属性?

回到我的代码示例:

一切都是硬编码的,所以它是不完全良好的编码实践,但它表明你如何能做到这一点。

AudioStreamTest.h

#import <Foundation/Foundation.h> 
#import <AudioToolbox/AudioToolbox.h> 

uint32_t bufferSizeInSamples; 
AudioFileID file; 
UInt32 currentPacket; 
AudioQueueRef audioQueue; 
AudioQueueBufferRef buffer[3]; 
AudioStreamBasicDescription audioStreamBasicDescription; 

@interface AudioStreamTest : NSObject 

- (void)start; 
- (void)stop; 

@end 

AudioStreamTest。m

#import "AudioStreamTest.h" 

@implementation AudioStreamTest 

- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     bufferSizeInSamples = 441; 

     file = NULL; 
     currentPacket = 0; 

     audioStreamBasicDescription.mBitsPerChannel = 16; 
     audioStreamBasicDescription.mBytesPerFrame = 4; 
     audioStreamBasicDescription.mBytesPerPacket = 4; 
     audioStreamBasicDescription.mChannelsPerFrame = 2; 
     audioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
     audioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM; 
     audioStreamBasicDescription.mFramesPerPacket = 1; 
     audioStreamBasicDescription.mReserved = 0; 
     audioStreamBasicDescription.mSampleRate = 44100; 
    } 

    return self; 
} 

- (void)start { 
    AudioQueueNewOutput(&audioStreamBasicDescription, AudioEngineOutputBufferCallback, (__bridge void *)(self), NULL, NULL, 0, &audioQueue); 

    AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioEnginePropertyListenerProc, NULL); 

    AudioQueueStart(audioQueue, NULL); 
} 

- (void)stop { 
    AudioQueueStop(audioQueue, YES); 
    AudioQueueRemovePropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioEnginePropertyListenerProc, NULL); 
} 

void AudioEngineOutputBufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { 
    if (file == NULL) return; 

    UInt32 bytesRead = bufferSizeInSamples * 4; 
    UInt32 packetsRead = bufferSizeInSamples; 
    AudioFileReadPacketData(file, false, &bytesRead, NULL, currentPacket, &packetsRead, inBuffer->mAudioData); 
    inBuffer->mAudioDataByteSize = bytesRead; 
    currentPacket += packetsRead; 

    if (bytesRead == 0) { 
     AudioQueueStop(inAQ, false); 
    } 
    else { 
     AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); 
    } 
} 

void AudioEnginePropertyListenerProc (void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID) { 
    //We are only interested in the property kAudioQueueProperty_IsRunning 
    if (inID != kAudioQueueProperty_IsRunning) return; 

    //Get the status of the property 
    UInt32 isRunning = false; 
    UInt32 size = sizeof(isRunning); 
    AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &isRunning, &size); 

    if (isRunning) { 
     currentPacket = 0; 

     NSString *fileName = @"/Users/roy/Documents/XCodeProjectsData/FUZZ/03.wav"; 
     NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: fileName]; 
     AudioFileOpenURL((__bridge CFURLRef) fileURL, kAudioFileReadPermission, 0, &file); 

     for (int i = 0; i < 3; i++){ 
      AudioQueueAllocateBuffer(audioQueue, bufferSizeInSamples * 4, &buffer[i]); 

      UInt32 bytesRead = bufferSizeInSamples * 4; 
      UInt32 packetsRead = bufferSizeInSamples; 
      AudioFileReadPacketData(file, false, &bytesRead, NULL, currentPacket, &packetsRead, buffer[i]->mAudioData); 
      buffer[i]->mAudioDataByteSize = bytesRead; 
      currentPacket += packetsRead; 

      AudioQueueEnqueueBuffer(audioQueue, buffer[i], 0, NULL); 
     } 
    } 

    else { 
     if (file != NULL) { 
      AudioFileClose(file); 
      file = NULL; 

      for (int i = 0; i < 3; i++) { 
       AudioQueueFreeBuffer(audioQueue, buffer[i]); 
       buffer[i] = NULL; 
      } 
     } 
    } 
} 

-(void)dealloc { 
    [super dealloc]; 
    AudioQueueDispose(audioQueue, true); 
    audioQueue = NULL; 
} 

@end 

最后,我想包括我今天所做的一些研究来测试AudioQueues的健壮性。

我注意到,如果你制作的AudioQueue缓冲区太小,它根本不会播放。这让我玩了一下,看看它为什么不玩。

如果我尝试只能容纳150个样本的缓冲区大小,我根本听不到声音。

如果我尝试可容纳175个样本的缓冲区大小,它会播放整首歌曲,但会带来很多失真。 175相当于少于4毫秒的音频。

只要您持续提供缓冲区,AudioQueue就会一直询问新的缓冲区。这与AudioQueue实际播放缓冲区无关。

如果提供大小为0的缓冲区,缓冲区将丢失,并且该队列入队请求返回错误kAudioQueueErr_BufferEmpty。您永远不会看到AudioQueue要求您再次填充该缓冲区。如果这发生在您发布的最后一个队列中,AudioQueue将停止要求您填充更多缓冲区。在这种情况下,您将听不到该会话的更多音频。

为了明白为什么AudioQueues没有播放任何小缓冲区大小的东西,我做了一个测试,看看我的回调是否被调用,即使没有声音。答案是只要AudioQueues正在播放并需要数据,缓冲区就会一直被调用。

所以,如果你不断缓冲队列,缓冲区永远不会丢失。它不会发生。当然,除非有错误。

那么为什么没有声音播放?

我测试了看'AudioQueueEnqueueBuffer()'是否返回了任何错误。它没。我的播放例程中也没有其他错误。从文件读取返回的数据也很好。

一切都很正常,缓冲区很好,数据重新排队很好,只是没有声音。

所以我最后的测试是缓慢增加缓冲区大小,直到我能听到任何东西。我终于听到了微弱而零星的扭曲。

然后向我走来......

看来,问题在于,该系统试图保持流中同步与时间,所以如果你排队的音频,并时刻为您想的声音游戏已经过去了,它只会跳过缓冲区的那一部分。如果缓冲区大小变得太小,则越来越多的数据被丢弃或跳过,直到音频系统再次同步。如果缓冲区大小太小,那永远不会。 (如果您选择的缓冲区大小足以支持连续播放,您可以听到这种失真。)

如果您仔细考虑它,它是音频队列工作的唯一途径,但它是一种很好的当你像我一样无知,并“发现”它是如何工作的时候就会意识到这一点。

+0

*“似乎你正在分配缓冲区与太小的缓冲区大小”*我让AudioFileGetProperty填充它。我见过这样的例子,即苹果自己的SpeakHere我不明白为什么它应该是一个问题。基本上你认为我应该改变它? *“您是否验证过返回的属性?”* ASBD返回关于该文件的正确信息。这让我对这个乱码音频感到困惑。 – Radiodef

+0

我不能说这确实是你的问题,但我测试了不同的缓冲区大小,并且能够产生一切从没有声音到各种程度的乱码声音,带有非常小的缓冲区,以充分播放带有足够大缓冲区的音频。一切似乎都很正常。这当然取决于你的硬件。我的硬件比较老,可能比新的Mac更快。 – RoyGal

2

我决定再次看看这个,并且能够通过使缓冲区变大来解决这个问题。我已经接受@RoyGal的答案,因为这是他们的建议,但我想提供实际的代码,因为我猜别人有同样的问题(问题有几个最喜欢的,目前不是我)。

有一两件事我想是使包大小:

aData->aDescription.mFramesPerPacket = 512; // or some other number 
aData->aDescription.mBytesPerPacket = (
    aData->aDescription.mFramesPerPacket * aData->aDescription.mBytesPerFrame 
); 

这不起作用:它会导致AudioQueuePrime失败与AudioConverterNew returned -50消息。我想它想要mFramesPerPacket是PCM的1。

(我也尝试设置kAudioQueueProperty_DecodeBufferSizeFrames财产似乎并没有做任何事情,不知道那是什么了。)

的解决方案似乎是只分配缓冲区(S)指定的大小:

AudioQueueAllocateBuffer(
    aData->aQueue, 
    aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS/N_BUFFERS, 
    &aData->aBuffer[i] 
); 

而且尺寸必须足够大。我发现一个神奇的数字是:

mBytesPerPacket * 1024/N_BUFFERS 

(其中N_BUFFERS是缓冲区的数目,应当> 1或无法顺畅播放。)

这里是一个MCVE演示该问题及解决方法:

#import <Foundation/Foundation.h> 

#import <AudioToolbox/AudioToolbox.h> 
#import <AudioToolbox/AudioQueue.h> 
#import <AudioToolbox/AudioFile.h> 

#define N_BUFFERS 2 
#define N_BUFFER_PACKETS 1024 

typedef struct AStreamData { 
    AudioFileID aFile; 
    AudioQueueRef aQueue; 
    AudioQueueBufferRef aBuffer[N_BUFFERS]; 
    AudioStreamBasicDescription aDescription; 
    SInt64 pOffset; 
    volatile BOOL isRunning; 
} AStreamData; 

void printASBD(AudioStreamBasicDescription* desc) { 
    printf("mSampleRate = %d\n", (int)desc->mSampleRate); 
    printf("mBytesPerPacket = %d\n", desc->mBytesPerPacket); 
    printf("mFramesPerPacket = %d\n", desc->mFramesPerPacket); 
    printf("mBytesPerFrame = %d\n", desc->mBytesPerFrame); 
    printf("mChannelsPerFrame = %d\n", desc->mChannelsPerFrame); 
    printf("mBitsPerChannel = %d\n", desc->mBitsPerChannel); 
} 

void bufferCallback(
    void *vData, AudioQueueRef aQueue, AudioQueueBufferRef aBuffer 
) { 
    AStreamData* aData = (AStreamData*)vData; 

    UInt32 bRead = 0; 
    UInt32 pRead = (
     aBuffer->mAudioDataBytesCapacity/aData->aDescription.mBytesPerPacket 
    ); 

    OSStatus stat; 

    stat = AudioFileReadPackets(
     aData->aFile, false, &bRead, NULL, aData->pOffset, &pRead, aBuffer->mAudioData 
    ); 
    if(stat != 0) { 
     printf("AudioFileReadPackets returned %d\n", stat); 
    } 

    if(pRead == 0) { 
     aData->isRunning = NO; 
     return; 
    } 

    aBuffer->mAudioDataByteSize = bRead; 

    stat = AudioQueueEnqueueBuffer(aQueue, aBuffer, 0, NULL); 
    if(stat != 0) { 
     printf("AudioQueueEnqueueBuffer returned %d\n", stat); 
    } 

    aData->pOffset += pRead; 
} 

AStreamData* beginPlayback(NSURL* path) { 
    static AStreamData* aData; 
    aData = malloc(sizeof(AStreamData)); 

    OSStatus stat; 

    stat = AudioFileOpenURL(
     (CFURLRef)path, kAudioFileReadPermission, 0, &aData->aFile 
    ); 
    printf("AudioFileOpenURL returned %d\n", stat); 

    UInt32 dSize = 0; 

    stat = AudioFileGetPropertyInfo(
     aData->aFile, kAudioFilePropertyDataFormat, &dSize, 0 
    ); 
    printf("AudioFileGetPropertyInfo returned %d\n", stat); 

    stat = AudioFileGetProperty(
     aData->aFile, kAudioFilePropertyDataFormat, &dSize, &aData->aDescription 
    ); 
    printf("AudioFileGetProperty returned %d\n", stat); 

    printASBD(&aData->aDescription); 

    stat = AudioQueueNewOutput(
     &aData->aDescription, bufferCallback, aData, NULL, NULL, 0, &aData->aQueue 
    ); 
    printf("AudioQueueNewOutput returned %d\n", stat); 

    aData->pOffset = 0; 

    for(int i = 0; i < N_BUFFERS; i++) { 
     // change YES to NO for stale playback 
     if(YES) { 
      stat = AudioQueueAllocateBuffer(
       aData->aQueue, 
       aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS/N_BUFFERS, 
       &aData->aBuffer[i] 
      ); 
     } else { 
      stat = AudioQueueAllocateBuffer(
       aData->aQueue, 
       aData->aDescription.mBytesPerPacket, 
       &aData->aBuffer[i] 
      ); 
     } 

     printf(
      "AudioQueueAllocateBuffer returned %d for aBuffer[%d] with capacity %d\n", 
      stat, i, aData->aBuffer[i]->mAudioDataBytesCapacity 
     ); 

     bufferCallback(aData, aData->aQueue, aData->aBuffer[i]); 
    } 

    UInt32 numFramesPrepared = 0; 
    stat = AudioQueuePrime(aData->aQueue, 0, &numFramesPrepared); 
    printf("AudioQueuePrime returned %d with %d frames prepared\n", stat, numFramesPrepared); 

    stat = AudioQueueStart(aData->aQueue, NULL); 
    printf("AudioQueueStart returned %d\n", stat); 

    UInt32 pSize = sizeof(UInt32); 
    UInt32 isRunning; 
    stat = AudioQueueGetProperty(
     aData->aQueue, kAudioQueueProperty_IsRunning, &isRunning, &pSize 
    ); 
    printf("AudioQueueGetProperty returned %d\n", stat); 

    aData->isRunning = !!isRunning; 
    return aData; 
} 

void endPlayback(AStreamData* aData) { 
    OSStatus stat = AudioQueueStop(aData->aQueue, NO); 
    printf("AudioQueueStop returned %d\n", stat); 
} 

NSString* getPath() { 
    // change NO to YES and enter path to hard code 
    if(NO) { 
     return @""; 
    } 

    char input[512]; 
    printf("Enter file path: "); 
    scanf("%[^\n]", input); 

    return [[NSString alloc] initWithCString:input encoding:NSASCIIStringEncoding]; 
} 

int main(int argc, const char* argv[]) { 
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 

    NSURL* path = [NSURL fileURLWithPath:getPath()]; 
    AStreamData* aData = beginPlayback(path); 

    if(aData->isRunning) { 
     do { 
      printf("Queue is running...\n"); 
      [NSThread sleepForTimeInterval:1.0]; 
     } while(aData->isRunning); 
     endPlayback(aData); 
    } else { 
     printf("Playback did not start\n"); 
    } 

    [pool drain]; 
    return 0; 
}