I am trying to do a simple playback from a file functionality and it appears that my callback function is never called. It doesn't really make sense because all of the OSStatuses come back 0 and other numbers all appear correct as well (like the output packets read pointer from AudioFileReadPackets).
我尝试从一个文件功能做一个简单的回放,看起来我的回调函数从来没有被调用过。这并没有什么意义,因为所有的osstat都返回了0,而其他的数字也都是正确的(就像输出数据包从audiofileread数据包中读取指针一样)。
Here is the setup:
这是设置:
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);
(Not shown is where I'm checking the value of stat
in between the functions, it just comes back normal.)
(这里没有显示的是我在函数之间检查stat的值,它只是恢复正常。)
And the callback function:
回调函数:
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;
}
Where aStreamData is my user data struct (typedefed so I can use it as a class property) and player is a static instance of the controlling Objective-C class. If any other code is wanted please let me know. I am a bit at my wit's end. Printing any of the numbers involved here yields the correct result, including functions in bufferCallback when I call it myself in the allocate loop. It just never gets called thereafter. The start up method returns and nothing happens.
aStreamData是我的用户数据结构(typedefed,所以我可以将它用作一个类属性),而player是控制Objective-C类的静态实例。如果需要其他代码,请告诉我。我有点不知所措了。在这里打印任何涉及到的数字都会得到正确的结果,包括当我在分配循环中调用它时,在bufferCallback中的函数。它永远不会被调用。启动方法返回,什么也没有发生。
Also anecdotally, I am using a peripheral device (an MBox Pro 3) to play the sound which CoreAudio only boots up when it is about to output. IE if I start iTunes or something, the speakers pop faintly and there is an LED that goes from blinking to solid. The device boots up like it does so CA is definitely doing something. (Also I've of course tried it with the onboard Macbook sound sans the device.)
同样有趣的是,我正在使用一个外围设备(一个MBox Pro 3)来播放CoreAudio在即将输出时才启动的声音。如果我启动iTunes之类的东西,扬声器就会发出微弱的声音,然后就会有一个从闪烁到固体的LED灯。设备启动时就像这样,CA肯定在做一些事情。(当然,我也试过用车载Macbook的声音来测试它。)
I've read other solutions to problems that sound similiar and they don't work. Stuff like using multiple buffers which I am doing now and doesn't appear to make any difference.
我已经读过其他解决问题的方法,这些问题听起来很相似,但却不奏效。比如我现在正在使用的多个缓冲区,看起来并没有什么不同。
I basically assume I am doing something obviously wrong somehow but not sure what it could be. I've read the relevant documentation, looked at the available code examples and scoured the net a bit for answers and it appears that this is all I need to do and it should just go.
我基本上假设我在做一些明显错误的事情,但不确定它会是什么。我已经阅读了相关的文档,查看了可用的代码示例,并在网上搜索了一些答案,看起来这就是我所需要做的,它应该就这么做了。
At the very least, is there anything else I can do to investigate?
至少,还有什么我可以做的调查吗?
2 个解决方案
#1
2
My first answer was not good enough, so I compiled a minimal example that will play a 2 channel, 16 bit wave file.
我的第一个答案不够好,所以我编译了一个最小的示例,它将播放一个2通道,16位wave文件。
The main difference from your code is that I made a property listener listening for play start and stop events.
与您的代码的主要区别是,我让一个属性侦听器侦听play启动和停止事件。
As for your code, it seems legit at first glance. Two things I will point out, though: 1. Is seems you are allocating buffers with TOO SMALL a buffer size. I have noticed that AudioQueues won't play if the buffers are too small, which seems to fit your problem. 2. Have you verified the properties returned?
至于你的代码,乍一看似乎是合法的。不过我要指出两点:1。似乎您正在分配缓冲区大小过小的缓冲区。我注意到,如果缓冲区太小,那么audioqueue就不会发挥作用,这似乎符合您的问题。2。你验证过返回的属性了吗?
Back to my code example:
回到我的代码示例:
Everything is hard coded, so it is not exactly good coding practice, but it shows how you can do it.
所有的东西都是硬编码的,所以这不是一个好的编码实践,但是它显示了你是如何做到的。
AudioStreamTest.h
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
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
Lastly, I want to include some research I have done today to test the robustness of AudioQueues.
最后,我想要包括一些我今天做的研究来测试audioqueue的健壮性。
I have noticed that if you make too small AudioQueue buffers, it won't play at all. That made me play around a bit to see why it is not playing.
我注意到,如果您的AudioQueue缓冲区太小,那么它根本就不会播放。这让我有点想知道为什么它不玩。
If I try buffer size that can hold only 150 samples, I get no sound at all.
如果我尝试的缓冲区大小只能容纳150个样本,那么我完全没有声音。
If I try buffer size that can hold 175 samples, it plays the whole song through, but with A lot of distortion. 175 amounts to a tad less than 4 ms of audio.
如果我尝试缓冲大小可以容纳175个样本,它会播放整个歌曲,但是会有很多失真。175相当于一个tad小于4 ms的音频。
AudioQueue keeps asking for new buffers as long as you keep supplying buffers. That is regardless of AudioQueue actually playing your buffers or not.
只要您继续提供缓冲区,AudioQueue一直要求新的缓冲区。那就是不管AudioQueue是否在播放缓冲区。
If you supply a buffer with size 0, the buffer will be lost and an error kAudioQueueErr_BufferEmpty is returned for that queue enqueue request. You will never see AudioQueue ask you to fill that buffer again. If this happened for the last queue you have posted, AudioQueue will stop asking you to fill any more buffers. In that case you will not hear any more audio for that session.
如果您提供了一个大小为0的缓冲区,那么缓冲区将丢失,并且为该队列队列请求返回一个错误kAudioQueueErr_BufferEmpty。您将永远不会看到AudioQueue请求您再次填充该缓冲区。如果这发生在您发布的最后一个队列中,AudioQueue将停止请求您填充任何更多的缓冲区。在这种情况下,您将不再听到该会话的任何音频。
To see why AudioQueues is not playing anything with smaller buffer sizes, I made a test to see if my callback is called at all even when there is no sound. The answer is that the buffers gets called all the time as long as AudioQueues is playing and needs data.
为了查看为什么audioqueue没有使用较小的缓冲区大小,我做了一个测试,看看是否在没有声音的情况下调用回调函数。答案是,只要audioqueue在播放并需要数据,缓冲区就会一直被调用。
So if you keep feeding buffers to the queue, no buffer is ever lost. It doesn't happen. Unless there is an error, of course.
因此,如果您继续向队列提供缓冲区,就不会丢失缓冲区。它不会发生。当然,除非有错误。
So why is no sound playing?
那么为什么没有声音播放呢?
I tested to see if 'AudioQueueEnqueueBuffer()' returned any errors. It did not. No other errors within my play routine either. The data returned from reading from file is also good.
我测试了是否“AudioQueueEnqueueBuffer()”返回了任何错误。事实并非如此。在我的游戏程序中也没有其他错误。从文件读取返回的数据也很好。
Everything is normal, buffers are good, data re-enqueued is good, there is just no sound.
一切正常,缓冲区很好,数据重新排队很好,没有声音。
So my last test was to slowly increase buffer size till I could hear anything. I finally heard faint and sporadic distortion.
所以我的最后一个测试是慢慢增加缓冲大小直到我能听到任何声音。我终于听到了微弱的、断断续续的扭曲。
Then it came to me...
然后它就来找我……
It seems that the problem lies with that the system tries to keep the stream in sync with time so if you enqueue audio, and the time for the audio you wanted to play has passed, it will just skip that part of the buffer. If the buffer size becomes too small, more and more data is dropped or skipped until the audio system is in sync again. Which is never if the buffer size is too small. (You can hear this as distortion if you chose a buffer size that is barely large enough to support continuous play.)
似乎问题在于系统试图保持流与时间同步,所以如果你加入了音频,而你想播放的音频的时间已经过去了,它将跳过缓冲区的那一部分。如果缓冲区的大小变得太小,将会有越来越多的数据被删除或跳过,直到音频系统再次同步。如果缓冲区太小,那就永远不可能。(如果你选择的缓冲区大小刚好足够支持连续播放,你可以听到这种失真。)
If you think about it, it is the only way the audio queue can work, but it is a good realisation when you are clueless like me and "discover" how it really works.
如果你仔细想想,这是音频队列唯一的工作方式,但当你像我一样,并“发现”它是如何工作的时候,这是一个很好的认识。
#2
2
I decided to take a look at this again and was able to solve it by making the buffers larger. I've accepted the answer by @RoyGal since it was their suggestion but I wanted to provide the actual code that works since I guess others are having the same problem (question has a few favorites that aren't me at the moment).
我决定再看一遍,通过使缓冲区更大来解决它。我已经接受了@RoyGal的答案,因为这是他们的建议,但是我想提供实际的代码,因为我猜其他人也有同样的问题(问题有一些我现在不喜欢的东西)。
One thing I tried was making the packet size larger:
我尝试过的一件事是让数据包的尺寸变大:
aData->aDescription.mFramesPerPacket = 512; // or some other number
aData->aDescription.mBytesPerPacket = (
aData->aDescription.mFramesPerPacket * aData->aDescription.mBytesPerFrame
);
This does NOT work: it causes AudioQueuePrime
to fail with an AudioConverterNew returned -50 message. I guess it wants mFramesPerPacket
to be 1 for PCM.
这不起作用:它会导致AudioQueuePrime以一个AudioConverterNew返回-50消息失败。我猜它想要mFramesPerPacket为PCM的1。
(I also tried setting the kAudioQueueProperty_DecodeBufferSizeFrames
property which didn't seem to do anything. Not sure what it's for.)
(我还尝试设置了kaudioqueueproperty_decodebuffersizeframe属性,它似乎什么也没做。不知道这是干什么用的。
The solution seems to be to only allocate the buffer(s) with the specified size:
解决方案似乎是只分配具有指定大小的缓冲区:
AudioQueueAllocateBuffer(
aData->aQueue,
aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS / N_BUFFERS,
&aData->aBuffer[i]
);
And the size has to be sufficiently large. I found the magic number is:
尺寸必须足够大。我发现这个神奇的数字是:
mBytesPerPacket * 1024 / N_BUFFERS
(Where N_BUFFERS
is the number of buffers and should be > 1
or playback is choppy.)
(n_buffer是缓冲区的数目,应该是> 1或回放是choppy。)
Here is an MCVE demonstrating the issue and solution:
这里有一个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;
}
#1
2
My first answer was not good enough, so I compiled a minimal example that will play a 2 channel, 16 bit wave file.
我的第一个答案不够好,所以我编译了一个最小的示例,它将播放一个2通道,16位wave文件。
The main difference from your code is that I made a property listener listening for play start and stop events.
与您的代码的主要区别是,我让一个属性侦听器侦听play启动和停止事件。
As for your code, it seems legit at first glance. Two things I will point out, though: 1. Is seems you are allocating buffers with TOO SMALL a buffer size. I have noticed that AudioQueues won't play if the buffers are too small, which seems to fit your problem. 2. Have you verified the properties returned?
至于你的代码,乍一看似乎是合法的。不过我要指出两点:1。似乎您正在分配缓冲区大小过小的缓冲区。我注意到,如果缓冲区太小,那么audioqueue就不会发挥作用,这似乎符合您的问题。2。你验证过返回的属性了吗?
Back to my code example:
回到我的代码示例:
Everything is hard coded, so it is not exactly good coding practice, but it shows how you can do it.
所有的东西都是硬编码的,所以这不是一个好的编码实践,但是它显示了你是如何做到的。
AudioStreamTest.h
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
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
Lastly, I want to include some research I have done today to test the robustness of AudioQueues.
最后,我想要包括一些我今天做的研究来测试audioqueue的健壮性。
I have noticed that if you make too small AudioQueue buffers, it won't play at all. That made me play around a bit to see why it is not playing.
我注意到,如果您的AudioQueue缓冲区太小,那么它根本就不会播放。这让我有点想知道为什么它不玩。
If I try buffer size that can hold only 150 samples, I get no sound at all.
如果我尝试的缓冲区大小只能容纳150个样本,那么我完全没有声音。
If I try buffer size that can hold 175 samples, it plays the whole song through, but with A lot of distortion. 175 amounts to a tad less than 4 ms of audio.
如果我尝试缓冲大小可以容纳175个样本,它会播放整个歌曲,但是会有很多失真。175相当于一个tad小于4 ms的音频。
AudioQueue keeps asking for new buffers as long as you keep supplying buffers. That is regardless of AudioQueue actually playing your buffers or not.
只要您继续提供缓冲区,AudioQueue一直要求新的缓冲区。那就是不管AudioQueue是否在播放缓冲区。
If you supply a buffer with size 0, the buffer will be lost and an error kAudioQueueErr_BufferEmpty is returned for that queue enqueue request. You will never see AudioQueue ask you to fill that buffer again. If this happened for the last queue you have posted, AudioQueue will stop asking you to fill any more buffers. In that case you will not hear any more audio for that session.
如果您提供了一个大小为0的缓冲区,那么缓冲区将丢失,并且为该队列队列请求返回一个错误kAudioQueueErr_BufferEmpty。您将永远不会看到AudioQueue请求您再次填充该缓冲区。如果这发生在您发布的最后一个队列中,AudioQueue将停止请求您填充任何更多的缓冲区。在这种情况下,您将不再听到该会话的任何音频。
To see why AudioQueues is not playing anything with smaller buffer sizes, I made a test to see if my callback is called at all even when there is no sound. The answer is that the buffers gets called all the time as long as AudioQueues is playing and needs data.
为了查看为什么audioqueue没有使用较小的缓冲区大小,我做了一个测试,看看是否在没有声音的情况下调用回调函数。答案是,只要audioqueue在播放并需要数据,缓冲区就会一直被调用。
So if you keep feeding buffers to the queue, no buffer is ever lost. It doesn't happen. Unless there is an error, of course.
因此,如果您继续向队列提供缓冲区,就不会丢失缓冲区。它不会发生。当然,除非有错误。
So why is no sound playing?
那么为什么没有声音播放呢?
I tested to see if 'AudioQueueEnqueueBuffer()' returned any errors. It did not. No other errors within my play routine either. The data returned from reading from file is also good.
我测试了是否“AudioQueueEnqueueBuffer()”返回了任何错误。事实并非如此。在我的游戏程序中也没有其他错误。从文件读取返回的数据也很好。
Everything is normal, buffers are good, data re-enqueued is good, there is just no sound.
一切正常,缓冲区很好,数据重新排队很好,没有声音。
So my last test was to slowly increase buffer size till I could hear anything. I finally heard faint and sporadic distortion.
所以我的最后一个测试是慢慢增加缓冲大小直到我能听到任何声音。我终于听到了微弱的、断断续续的扭曲。
Then it came to me...
然后它就来找我……
It seems that the problem lies with that the system tries to keep the stream in sync with time so if you enqueue audio, and the time for the audio you wanted to play has passed, it will just skip that part of the buffer. If the buffer size becomes too small, more and more data is dropped or skipped until the audio system is in sync again. Which is never if the buffer size is too small. (You can hear this as distortion if you chose a buffer size that is barely large enough to support continuous play.)
似乎问题在于系统试图保持流与时间同步,所以如果你加入了音频,而你想播放的音频的时间已经过去了,它将跳过缓冲区的那一部分。如果缓冲区的大小变得太小,将会有越来越多的数据被删除或跳过,直到音频系统再次同步。如果缓冲区太小,那就永远不可能。(如果你选择的缓冲区大小刚好足够支持连续播放,你可以听到这种失真。)
If you think about it, it is the only way the audio queue can work, but it is a good realisation when you are clueless like me and "discover" how it really works.
如果你仔细想想,这是音频队列唯一的工作方式,但当你像我一样,并“发现”它是如何工作的时候,这是一个很好的认识。
#2
2
I decided to take a look at this again and was able to solve it by making the buffers larger. I've accepted the answer by @RoyGal since it was their suggestion but I wanted to provide the actual code that works since I guess others are having the same problem (question has a few favorites that aren't me at the moment).
我决定再看一遍,通过使缓冲区更大来解决它。我已经接受了@RoyGal的答案,因为这是他们的建议,但是我想提供实际的代码,因为我猜其他人也有同样的问题(问题有一些我现在不喜欢的东西)。
One thing I tried was making the packet size larger:
我尝试过的一件事是让数据包的尺寸变大:
aData->aDescription.mFramesPerPacket = 512; // or some other number
aData->aDescription.mBytesPerPacket = (
aData->aDescription.mFramesPerPacket * aData->aDescription.mBytesPerFrame
);
This does NOT work: it causes AudioQueuePrime
to fail with an AudioConverterNew returned -50 message. I guess it wants mFramesPerPacket
to be 1 for PCM.
这不起作用:它会导致AudioQueuePrime以一个AudioConverterNew返回-50消息失败。我猜它想要mFramesPerPacket为PCM的1。
(I also tried setting the kAudioQueueProperty_DecodeBufferSizeFrames
property which didn't seem to do anything. Not sure what it's for.)
(我还尝试设置了kaudioqueueproperty_decodebuffersizeframe属性,它似乎什么也没做。不知道这是干什么用的。
The solution seems to be to only allocate the buffer(s) with the specified size:
解决方案似乎是只分配具有指定大小的缓冲区:
AudioQueueAllocateBuffer(
aData->aQueue,
aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS / N_BUFFERS,
&aData->aBuffer[i]
);
And the size has to be sufficiently large. I found the magic number is:
尺寸必须足够大。我发现这个神奇的数字是:
mBytesPerPacket * 1024 / N_BUFFERS
(Where N_BUFFERS
is the number of buffers and should be > 1
or playback is choppy.)
(n_buffer是缓冲区的数目,应该是> 1或回放是choppy。)
Here is an MCVE demonstrating the issue and solution:
这里有一个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;
}