CoreAudio AudioQueue 回调函数从未调用过,没有报错

Posted

技术标签:

【中文标题】CoreAudio AudioQueue 回调函数从未调用过,没有报错【英文标题】:CoreAudio AudioQueue callback function never called, no errors reported 【发布时间】:2013-10-26 09:31:54 【问题描述】:

我正在尝试从文件功能中进行简单的播放,但似乎从未调用过我的回调函数。这实际上没有任何意义,因为所有 OSStatuse 都返回 0 并且其他数字也都显示正确(例如来自 AudioFileReadPackets 的输出数据包读取指针)。

设置如下:

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 是我的用户数据结构(类型定义,因此我可以将其用作类属性),而 player 是控制 Objective-C 类的静态实例。如果需要任何其他代码,请告诉我。我有点不知所措。打印此处涉及的任何数字都会产生正确的结果,包括我在分配循环中自己调用 bufferCallback 中的函数。此后它永远不会被调用。启动方法返回,没有任何反应。

另外,我正在使用外围设备(MBox Pro 3)播放 CoreAudio 仅在即将输出时才会启动的声音。 IE 如果我启动 iTunes 或其他东西,扬声器会发出微弱的爆裂声,并且有一个 LED 从闪烁变为常亮。设备像它一样启动,所以 CA 肯定在做某事。 (当然,我也尝试过在没有设备的情况下使用板载 Macbook 声音。)

我已经阅读了其他听起来相似但不起作用的问题的解决方案。诸如使用多个缓冲区之类的东西,我现在正在这样做,但似乎没有任何区别。

我基本上假设我在某种程度上做了一些明显错误的事情,但不确定它可能是什么。我已经阅读了相关文档,查看了可用的代码示例并在网上搜索了一些答案,看来这就是我需要做的所有事情,应该就可以了。

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

【问题讨论】:

我不知道为什么它不起作用。但是,你为什么不使用 AudioUnit? @AliaksandrAndrashuk AudioUnits 不只是用于效果吗?使用它们从文件流式传输是否合理? 是的。您可以设置 AUFilePlayer -> RemoteIO (in AUGraph) 来播放文件。 尝试手动设置(大)缓冲区大小 【参考方案1】:

我的第一个答案不够好,所以我编译了一个最小的示例,它将播放一个 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

最后,我想介绍一下我今天所做的一些研究,以测试 AudioQueue 的稳健性。

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

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

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

只要您继续提供缓冲区,AudioQueue 就会不断请求新的缓冲区。这与 AudioQueue 是否实际播放缓冲区无关。

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

为了了解为什么 AudioQueues 不播放任何缓冲区大小较小的内容,我做了一个测试,看看我的回调是否在没有声音的情况下被调用。答案是,只要 AudioQueues 正在播放并且需要数据,缓冲区就会一直被调用。

因此,如果您继续向队列提供缓冲区,则不会丢失任何缓冲区。它不会发生。当然,除非有错误。

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

我测试了“AudioQueueEnqueueBuffer()”是否返回任何错误。它没。我的游戏程序中也没有其他错误。从文件读取返回的数据也不错。

一切正常,缓冲区很好,重新入队的数据很好,就是没有声音。

所以我的最后一个测试是慢慢增加缓冲区大小,直到我能听到任何声音。我终于听到了微弱和零星的失真。

然后它来找我......

问题似乎在于系统试图保持流与时间同步,所以如果您将音频排入队列,并且您想要播放的音频的时间已经过去,它只会跳过缓冲区的那部分.如果缓冲区大小变得太小,则会丢弃或跳过越来越多的数据,直到音频系统再次同步。如果缓冲区大小太小,则永远不会。 (如果您选择的缓冲区大小几乎不足以支持连续播放,您可能会听到这种失真。)

如果您考虑一下,这是音频队列可以工作的唯一方式,但是当您像我一样一无所知并“发现”它的真正工作原理时,这是一个很好的认识。

【讨论】:

“似乎您分配的缓冲区的缓冲区大小太小了” 我让 AudioFileGetProperty 填充它。我已经看到了显示这一点的示例,即 Apple 自己的 SpeakHere所以我不明白为什么它应该是一个问题。基本上你认为我应该改变它? “您是否验证了返回的属性?” ASBD 会返回有关文件的正确信息。这就是让我对乱码音频感到困惑的地方。 我不能说这正是你的问题,但我正在测试不同的缓冲区大小,并且能够用非常小的缓冲区产生从根本没有声音到不同程度的乱码的所有内容,到用足够大的缓冲区完美播放音频。其他一切似乎都很正常。这当然取决于您的硬件。我的硬件很旧,可能比新的 Mac 更快。【参考方案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 属性,它似乎没有做任何事情。不确定它的用途。)

解决方案似乎是只分配具有指定大小的缓冲区:

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

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

mBytesPerPacket * 1024 / N_BUFFERS

(其中N_BUFFERS 是缓冲区的数量,应该是&gt; 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;

【讨论】:

以上是关于CoreAudio AudioQueue 回调函数从未调用过,没有报错的主要内容,如果未能解决你的问题,请参考以下文章

CoreAudio AudioQueue PCM 速度快且不稳定

如何在不冻结 GUI 的情况下让 AudioQueue 播放?

AudioQueue PropertyListener IsRunning 只回调一次

Core Audio的渲染回调不会改变输出音频[重复]

CoreAudio Input Render 回调从外部音频接口 Mac OS 10.14 Mojave 渲染所有 0

AudioQueue 吃了我的缓冲区(前 15 毫秒)