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

Rad*_*def 13 macos objective-c callback core-audio audioqueue

我试图从文件功能做一个简单的回放,似乎我的回调函数永远不会被调用.它并没有真正意义,因为所有OSStatus都返回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);
Run Code Online (Sandbox Code Playgroud)

(未显示的是我正在检查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;
}
Run Code Online (Sandbox Code Playgroud)

其中aStreamData是我的用户数据结构(typedefed所以我可以将它用作类属性)而player是控制Objective-C类的静态实例.如果需要任何其他代码,请告诉我.我有点在我的智慧结束.打印此处涉及的任何数字都会产生正确的结果,包括我在allocate循环中自己调用bufferCallback时的函数.它之后永远不会被调用.启动方法返回,没有任何反应.

另外有趣的是,我正在使用外围设备(MBox Pro 3)来播放CoreAudio在即将输出时才启动的声音.IE如果我启动iTunes或其他东西,扬声器会微弱地弹出,并且有一个LED从闪烁变为稳定.设备启动就像它一样,所以CA肯定在做某事.(当然,我当然尝试使用板载的Macbook声音,而不是设备.)

我已经阅读了其他听起来类似的问题的解决方案,但是它们不起作用.就像使用我正在做的多个缓冲区一样,似乎没有任何区别.

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

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

Roy*_*Gal 3

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

与您的代码的主要区别在于,我创建了一个属性侦听器来侦听播放开始和停止事件。

至于你的代码,乍一看似乎是合法的。不过,我要指出两件事: 1. 似乎您分配的缓冲区大小太小。我注意到如果缓冲区太小,AudioQueues 将无法播放,这似乎适合您的问题。2. 退回的物品您是否核实过?

回到我的代码示例:

一切都是硬编码的,因此这并不是很好的编码实践,但它展示了如何做到这一点。

音频流测试.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
Run Code Online (Sandbox Code Playgroud)

音频流测试.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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

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

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

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

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

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

然后我想到了......

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

如果你仔细想想,这是音频队列工作的唯一方式,但当你像我一样一无所知并“发现”它真正如何工作时,这是一个很好的实现。