Looks like no one’s replied in a while. To start the conversation again, simply ask a new question.

AudioFileStream, CFReadStream and AudioQueueEnqueueBuffer

Hi guys,

Here is what I have got so far with regards to streaming and playing an MP3 using AudioFileStream, CFReadStream and AudioQueue..

I hope it helps some people start, and I hope some people can help me finish!

First, I define a custom data structure to hold stuff throughout..


typedef struct {
AudioFileStreamID audioFileStream;
AudioStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
CFReadStream readStream;
UInt32 mNumPackets;
AudioStreamPacketDescription *mPacketDescs;
} CustomData;
CustomData customData;



I also set up an AudioQueue with a callback..


AudioQueueNewOutput(&customData.mDataFormat,
AudioOutputCallBack,
&customData,
NULL,
kCFRunLoopCommonModes,
0,
&customData.mQueue);


I set up an AudioFileStream..


AudioFileStreamOpen(0,
audioFileStream_Properties,
audioFileStream_Packets,
kAudioFileMP3Type,
&customData.audioFileStream);


..this has call back functions attached when the AudioFileStream receives either stream properties or stream packets..

I then open a data stream to a URL of an MP3 stream..


NSURL *url = [NSURL URLWithString:@"http://mp3stream.com"];
CFHTTPMessageRef message = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
CFSTR("GET"),
(CFURLRef)url,
kCFHTTPVersion1_1);
customData.readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, message);
CFOptionFlags events = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccured | kCFStreamEventEndEncountered;
CFStreamClientContext dataStreamContext = {0, self, NULL, NULL, NULL};
if (CFReadStreamSetClient(customData.readStream, events, readStreamEventCallBack, &dataStreamContext)) {
CFReadStreamScheduleWithRunLoop(customData.readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
CFReadStreamOpen(customData.readStream);


..this opens a CFReadStream with a call back function that is called on bytes available events, error events and end events..


void readStreamEventCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientcallBackInfo) {
switch(eventType) {
case kCFStreamEventHasBytesAvailable:
UInt8 buf[4096];
CFIndex bytesRead = CFReadStreamRead(stream, buf, 4096);
if (bytesRead > 0) {
AudioFileStreamParseBytes(customData.audioFileStream, bytesRead, buf, 0);
}
case kCFStreamEventErrorOccurred:
//do stuff
break;
case kCFStreamEventEndOccurred:
//do stuff
break;
}
}



So when the CFReadStream reads some bytes it passes them to the AudioFileStream which will in turn call its callback functions when it receives stream properties or stream packets..

In audioFileStream_Properties I attempt to get the dataFormat of the stream..


void audioFileStream_Properties (void *inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 *ioFlags) {
if (inPropertyID == kAudioFileStreamProperty_DataFormat) {

UInt32 size;
AudioFileStreamGetPropertyInfo(inAudioFileStream, inPropertyID, &size, nil)
AudioFileStreamGetProperty(inAudioFileStream, inPropertyID, &size, &customData.mDataFormat);
}
}


Most of these function calls return an OSStatus which should be checked for error (!= 0) but I have left them out for now..

Then the AudioFileStream packets..


void audioFileStream_Packets (void *inClientData,
UInt32 inNumberBytes,
UInt32 inNumberPackets,
const void *inInputData,
AudioStreamPacketDescription *inPacketDescs) {
customData.mNumPackets = inNumberPackets;
customData.mPacketDescs = inPacketDescs;
AudioQueueBufferRef outBuffer;
AudioQueueAllocateBuffer(customData.mQueue, inNumberBytes, &outBuffer);
AudioOutputCallBack(&customData, customData.mQueue, outBuffer);
}


..here I try and allocate the stream packets into the an AudioQueueBuffer and force that buffer into the AudioQueue callback..


static void AudioOutputCallBack(void *inCustomData,
AudioQueueRef outAQ,
AudioQueueBufferRef) {
CustomData *customData = (CustomData*)inCustomData;
AudioQueueEnqueueBuffer(customData->mQueue, outBuffer, (customData->mPacketDescs ? customData->mNumPackets : 0), playState->mPacketDescs);
}


..Now, I would hope this would be queuing buffers up ready to play! However,
AudioQueueEnqueueBuffer returns error 1718449215 - I am not sure what this means, I am led to believe it might be a dataFormat error 😟

Hope everyone call follow!

Adam

null

Message was edited by: adamlah

macbook, Mac OS X (10.5.4), its good

Posted on Jul 24, 2008 9:11 AM

Reply
Question marked as Best reply

Posted on Jul 28, 2008 6:31 PM

I just spent the last hour working through the same issue.

I think your problem is that you're not putting anything in the buffer that you're enqueueing.

For VBR data received off an audio file stream, get your buffer using AudioQueueAllocateBufferWithPacketDescriptions(). Your callback for the audio queue will get called when it's done with the buffer; it's there that you should call AudioQueueFreeBuffer().

Here's what my code looks like:

- (void)handleStreamPacket:(const void *)inInputData
descriptions:(AudioStreamPacketDescription *)inPacketDescriptions
numBytes:(UInt32)inNumberBytes
numPackets:(UInt32)inNumberPackets {
AudioQueueBufferRef outBuffer;
OSStatus result = AudioQueueAllocateBufferWithPacketDescriptions(queue, inNumberBytes, inNumberPackets, &outBuffer);
memcpy(outBuffer->mAudioData, inInputData, inNumberBytes);
memcpy(outBuffer->mPacketDescriptions, inPacketDescriptions, sizeof(AudioStreamPacketDescription) * inNumberPackets);
outBuffer->mAudioDataByteSize = inNumberBytes;
outBuffer->mPacketDescriptionCount = inNumberPackets;
result = AudioQueueEnqueueBuffer(queue, outBuffer, 0, NULL);
currentPacket += inNumberPackets;
}
8 replies
Question marked as Best reply

Jul 28, 2008 6:31 PM in response to adamlah

I just spent the last hour working through the same issue.

I think your problem is that you're not putting anything in the buffer that you're enqueueing.

For VBR data received off an audio file stream, get your buffer using AudioQueueAllocateBufferWithPacketDescriptions(). Your callback for the audio queue will get called when it's done with the buffer; it's there that you should call AudioQueueFreeBuffer().

Here's what my code looks like:

- (void)handleStreamPacket:(const void *)inInputData
descriptions:(AudioStreamPacketDescription *)inPacketDescriptions
numBytes:(UInt32)inNumberBytes
numPackets:(UInt32)inNumberPackets {
AudioQueueBufferRef outBuffer;
OSStatus result = AudioQueueAllocateBufferWithPacketDescriptions(queue, inNumberBytes, inNumberPackets, &outBuffer);
memcpy(outBuffer->mAudioData, inInputData, inNumberBytes);
memcpy(outBuffer->mPacketDescriptions, inPacketDescriptions, sizeof(AudioStreamPacketDescription) * inNumberPackets);
outBuffer->mAudioDataByteSize = inNumberBytes;
outBuffer->mPacketDescriptionCount = inNumberPackets;
result = AudioQueueEnqueueBuffer(queue, outBuffer, 0, NULL);
currentPacket += inNumberPackets;
}

Jul 29, 2008 1:37 PM in response to adamlah

Hey. I just read your post and felt I should try to help out since this made me pull my hair out for days. One of the problems I had was that streaming audio seems to be a bit different. Start simple. First off, don't do the AudioQueueAllocateBuffer the way you are doing it. You are basically allocating a buffer every time you get a callback about data being available in the stream. Try this: Make that allocation a one-time thing and set the buffer size to maybe 16KB. Every time you get a callback about bytes being available, you buffer them locally in your code. Here's the thing though...you should start buffering only after you've correctly set up the stream. How do you do that? Well wait until you receive the callback for a property value that was found in the stream and make sure it's the kAudioFileStreamProperty_ReadyToProducePackets property. Once that is called you know that you can set up the data format that you'll pass to AudioQueueNewOutput. Just read the kAudioFileStreamProperty_DataFormat property and there is your AudioStreamBasicDescription*. Once you exit this, you know that you can set a flag that you are "ready to produce packets" Your callback that says "audio data found in audio stream" can skip buffering locally until that flag is TRUE. Once it's true, just buffer locally. Of course you have to start the audio queue running by priming it so in this callback you will also have a a 1-time branch that executes only if you haven't started things up (called your audio queue buffer available function). Once you do that all this callback should do is just accumulate the packets into a local buffer. Your callback "audio queue buffer available" will ready from the local buffer and copy that data over to the audio queue buffer.

This killed me for days, but this is the only way I could get this working...and yes it actually works 🙂. Anyway...if you have trouble understanding what I just said feel free to send me a message.

Hope it helps.

<adrian />

Jul 30, 2008 7:01 AM in response to oGLOWo

Thankyou guys, very helpful comments.

I've changed some areas of my code to try and fit things together.. but I am still having a few headaches and am unsure of how to correctly use AudioQueuePrime..

These are the change I have made. Do you think you could help me out with some example source code?

..I changed my AudioFileStream property call back to look like..


void audioFileStream_Properties (void *inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 *ioFlags) {

if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) {
AudioQueueNewOutput(playState.mDataFormat,
AudioOutputCallBack,
&playState,
NULL,
kCFRunLoopCommonModes,
0,
&playState.mQueue);
playState.mReady = true;

} else if (inPropertyID == kAudioFileStreamProperty_DataFormat) {

UInt32 size;
AudioFileStreamGetPropertyInfo(inAudioFileStream, inPropertyID, &size, nil)
AudioFileStreamGetProperty(inAudioFileStream, inPropertyID, &size, &playState.mDataFormat);
}
}


and changed the AudioFileStream data callback..


void audioFileStream_Packets (void *inClientData,
UInt32 inNumberBytes,
UInt32 inNumberPackets,
const void *inInputData,
AudioStreamPacketDescription *inPacketDescs) {

if (!playState.mReady) return;
AudioQueueBufferRef outBuffer;
AudioQueueAllocateBufferWithPacketDescriptions(playState.mQueue,
inNumberBytes,
inNumberPackets,
&outBuffer);
outBuffer->mAudioDataByteSize = inNumberBytes;
outBuffer->mPacketDescriptionCount = inNumberPackets;
AudioQueueEnqueueBuffer(playState.mQueue, outBuffer, 0, NULL);
playState.currentPackets += inNumberPackets;
}


..finally I also changed my AudioOutputCallBack..

{code}
static void AudioOutputCallBack(void *inCustomData,
AudioQueueRef outAQ,
AudioQueueBufferRef) {

PlayState *playState = (PlayState*)playState;

AudioQueueFreeBuffer(playState->mQueue, playState->mBuffer);

}
{/code}

..However, now I get an error when trying to initially allocate the buffer 😟

I also still think I have a mistake when getting or setting the mDataFormat property from the stream.

I expect I need to utilize my AudioQueueBufferRef (playState->mBuffer) more. I am a little confused from here.

Please, if you would be willing to share sample code with me but do not wish to post, I would be very greatful if you could email me.. adamlah gmail.com

Thanks for your help so far! 🙂

Jul 31, 2008 12:56 AM in response to oGLOWo

Hi Adrian,

Please ignore my last post I was slightly confused. After re-reading your post I have found it very helpful. Below are my new audioFileStream callbacks..

So now all I have to do prime and start my queue? I am not quite sure where this bit goes, I do not understand your reference to "audio queue buffer available" function.

I expect I also need to do something in my AudioQueueOutputCallback function defined in AudioQueueNewOutput, I have not worked this bit out yet.

Thankyou so much for your help!


void audioFileStream_PropertyListenerProc (void *inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 *ioFlags) {
OSStatus status;
if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) {
printf("Opening Audio Queue.. ");
AudioQueueNewOutput(&playState.mDataFormat,
AudioOutputCallBack,
&playState,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&playState.mQueue);
playState.playing = true;
for (int i=0; i<NUM_BUFFERS && playState.playing; i++) {
status = AudioQueueAllocateBuffer(playState.mQueue, BUFSIZE,
&playState.mBuffers);
//AudioOutputCallBack(&playState, playState.queue, playState.buffers
);
if (status !=0) printf("Error Allocating Buffer %d ", i);
}
playState.mReady = true;
} else if (inPropertyID == kAudioFileStreamProperty_DataFormat) {
//setupAudioFormat(&playState.mDataFormat);
//UInt32 size = sizeof(playState.mDataFormat);
//UInt32 maxPacketSize;
//size = sizeof(maxPacketSize);
UInt32 size;
status = AudioFileStreamGetPropertyInfo(inAudioFileStream,
inPropertyID, &size, nil);
if (status != 0) printf("Error getting DataFormat Size Info ");
status = AudioFileStreamGetProperty(inAudioFileStream, inPropertyID,
&size, &playState.mDataFormat);
if (status != 0) printf("Error getting DataFormat ");
}
}


and.......


void audioFileStream_PacketsProc (void *inClientData,
UInt32 inNumberBytes,
UInt32 inNumberPackets,
const void *inInputData,
AudioStreamPacketDescription *inPacketDescs) {
printf("---Packets Callback ");
if (!playState.mReady) return;
AudioQueueBufferRef outBuffer;
OSStatus result =
AudioQueueAllocateBufferWithPacketDescriptions(playState.mQueue,
inNumberBytes,
inNumberPackets,
&outBuffer);
if (result != 0) printf("Error allocating buffer..");
memcpy(outBuffer->mAudioData, inInputData, inNumberBytes);
memcpy(outBuffer->mPacketDescriptions, inPacketDescs,
sizeof(AudioStreamPacketDescription) *inNumberPackets);
outBuffer->mAudioDataByteSize = inNumberBytes;
outBuffer->mPacketDescriptionCount = inNumberPackets;
playState.mNumPackets = inNumberPackets;
playState.mPacketDescs = inPacketDescs;
result = AudioQueueEnqueueBuffer(playState.mQueue, outBuffer, 0, NULL);
if (result != 0) printf("Error enqueuing buffer..");
playState.currentPacket += inNumberPackets;


Message was edited by: adamlah

Jul 31, 2008 11:25 AM in response to adamlah

Hey. I guess I confused you because "audio queue buffer available" is the name of my AudioQueueOutputCallback that you pass to AudioQueueNewOutput. So instead of giving you a mess of code here, I'll just put up some pseudo code and we'll talk in private in more detail if you want. Maybe this post will help some people out there trying to stream stuff 🙂.

I'm going to list my 3 callbacks:
1. AudioQueueOutputCallback
2. PropertyListenerProc
3. PacketsProc

I've omitted all the malloc/free alloc/init/release so make sure you take care of it.

void PropertyListenerProc(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags) {
// IF AND ONLY IF the property is "ready to produce packets" do the following:
// 1.set flag playerState->readyToProducePackets = TRUE;
// 2.use AudioFileStreamGetPropertyInfo and AudioFileStreamGetProperty
// to get the AudioStreamBasicDescription
// 3. I store the details of the description in my player state
// playerState->dataFormat->bitsPerChannel = streamDescription->mBitsPerChannel;
// playerState->dataFormat->formatFlags = streamDescription->mFormatFlags.
// ...you get the idea.. just copy all the data 🙂
//
// 4. call AudioQueueNewOutput to create the queue
// 5. call AudioQueueSetParameter and set kAudioQueueParam_Volume to 1.0.
// 6. set flag playerState->isRunning = TRUE;
// 7. use your loop for (int i = 0; i < num buffers to allocate; ++i)
// and call AudioQueueAllocateBuffer.
// HINT: Start with 1 buffer just to make sure you have the audio playing THEN worry about more buffers.
}

void PacketProc(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions) {
// 1.I call my accumulatePackets function to just accumulate the packets that came in temporarily
// It's just a byte buffer... can use NSData.
// IF AND ONLY IF playerState->readyToProducePackets == TRUE AND playerState->buffersPrimed == FALSE do the following:
// 2. set flags playerState->buffersPrimed = TRUE, playerState->isRunning = TRUE;
// 3. "prime the buffers" I just call my own AudioQueueOutputCallback instead of using the AudioQueuePrime function
}

void AudioQueueOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
// 1. if playerState->isRunning == FALSE just return;

// 2. set flag playerState->isRunning to TRUE
// IF AND ONLY IF you have packets in your local byte buffer do the following:
// 3. take the audio packets you put in your local byte buffer and copy the data into the
// the audio queue's data buffer
// 4. I constructed my own array of AudioPacketDescription*
// and set playerState->packetDescriptions = the stuff I just contructed
// 5. now I call AudioQueueEnqueueBuffer

// IF YOU DON'T have packets in your local byte buffer
// set flags playerState->isRunning = FALSE
// playerState->packetDescriptions = NULL
}

Sep 6, 2008 10:55 AM in response to almerica

There's a decent example of using the AudioFileStream and AudioQueue services (along with the buffer handling and such) in the example code that comes with XCode for Mac OS. Assuming XCode is installed in /Developer on your Mac, take a look at /Developer/Examples/CoreAudio/Services/AudioFileStreamExample/afsclient.c. This includes code for a very simple server sending an audio file over a TCP socket and the code for a client that connects to said server, receives the file as a stream, uses the Audio File Stream services and builds the audio queue buffers and such and plays the audio.

Granted, the above example doesn't do the HTTP streaming piece (it uses a lower level BSD socket interface), but implementing the HTTP streaming using CFReadStream is fairly trivial. The +CFNetwork Programming Guide+ includes a pretty good example of using a CFReadStream for handling streaming data from an HTTP service in either a polled or RunLoop driven manner. (Just be aware there are a few minor typos in the CFNetwork example code if you are cutting and pasting.)

AudioFileStream, CFReadStream and AudioQueueEnqueueBuffer

Welcome to Apple Support Community
A forum where Apple customers help each other with their products. Get started with your Apple ID.