Using Audio Queue Services to playback
Using Audio Queue Services to playback
- Subject: Using Audio Queue Services to playback
- From: Якимов Лев <email@hidden>
- Date: Mon, 16 Jan 2012 14:16:31 +0400
Hi!
I making a VoIP application on iPhone and using AudioQueueServices for playback and record voice. Audio data streaming via UDP. The recording is ok, but playback plays only several buffers and after some time not playing anything but works ok. It means what AudioQueue still processing audio and calls AudioQueueOutputCallback function with no errors or fails. Only workaround that I found solves problem by reseting AudioQueue each time when detected lost packets, like in this ussue:
http://stackoverflow.com/questions/1971412/audio-queue-services-on-iphone-only-playing-the-first-enqueued-buffer
My playback code is modifed Apple's AudioFileStreamExample too (http://developer.apple.com/library/mac/#samplecode/AudioFileStreamExample/Introduction/Intro.html).
While testing I have no lost packets, but problem still appear. I tried to determing when AQ played all enqueued buffers and not enqueued next one yet to reset AQ. This solution solved problem with no sound but now I got some gaps waiting AQ to enqueue first buffers to start. Furthermore, there are missing chunks of voice during playback. Now I create two AQ and when first one played all enqueued buffers I switching to the next one. But still missing voice in playback. Algorithm described below:
0. Once create AQs and calculate time to play one audioQueue buffer.
1. RTP packet received from server. Extract and convert audio (a-law -> PCM).
2. Push audio to buffer. If buffer is filled, enqueue it. If more than one buffer enqueued, start current AQ.
3. When kAudioQueueProperty_IsRunning changes to "true", start AQPlayedBufferTimer.
4. When AQPlayedBufferTimer calls callback, check count of enqueued buffers not played. If no more enqueued buffers to play, stop current AQ with "inImmediate" parameter set to "false" to play remaining audio in buffer. Also switch to the next AQ and allocate its buffers.
5. When kAudioQueueProperty_IsRunning changes to "false", free currently stopped AQ buffers.
I observe that when playback missed some sound it still plays continuously like if it droped some audio packet. But all packets received so I cant figure out how in this case it losing some audio while playing.
Here is some main code I using:
// ----------------------------
// STEP 0. Once create AQs and calculate time to play one audioQueue buffer.
- (int) open
{
// allocate a struct for storing our state
myData = (MyData*)calloc(1, sizeof(MyData));
// initialize a mutex and condition so that we can block on buffers in use.
pthread_mutex_init(&myData->mutex, NULL);
pthread_cond_init(&myData->cond, NULL);
pthread_cond_init(&myData->done, NULL);
// format description
AudioStreamBasicDescription asbd;
//UInt32 asbdSize = sizeof(asbd);
setupAudioFormat(&asbd);
// determine play time for single buffer (just for PCM)
bufPlayTime = (kAQBufSize / 2/* / asbd.mBytesPerPacket*/) * asbd.mFramesPerPacket / asbd.mSampleRate;
// create audio queues
for(int j = 0; j < 2; j++)
{
// create the audio queue
OSStatus err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &myData->audioQueue[j]);
if (err) { PRINTERROR("AudioQueueNewOutput"); myData->failed = true; }
// listen for kAudioQueueProperty_IsRunning
err = AudioQueueAddPropertyListener(myData->audioQueue[j], kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
if (err) { PRINTERROR("AudioQueueAddPropertyListener"); myData->failed = true; }
}
// allocate first audio queue buffers
for (unsigned int i = 0; i < kNumAQBufs; ++i) {
OSStatus err = AudioQueueAllocateBuffer(myData->audioQueue[0], kAQBufSize, &myData->audioQueueBuffer[0][i]);
if (err) { PRINTERROR("AudioQueueAllocateBuffer"); myData->failed = true; }
}
}
// STEP 2. Push audio to buffer.
void HandleOutputBuffer(void * inClientData,
UInt32 inNumberBytes,
const void * inInputData)
{
// this is called by audio file stream when it finds packets of audio
MyData* myData = (MyData*)inClientData;
SInt64 packetSize = inNumberBytes;
// if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
size_t bufSpaceRemaining = kAQBufSize - myData->bytesFilled;
if (bufSpaceRemaining < packetSize) {
MyEnqueueBuffer(myData);
WaitForFreeBuffer(myData);
}
// copy data to the audio queue buffer
AudioQueueBufferRef fillBuf = myData->audioQueueBuffer[myData->aqIndex][myData->fillBufferIndex];
memcpy((char*)fillBuf->mAudioData + myData->bytesFilled, (const char*)inInputData, packetSize);
// keep track of bytes filled and packets filled
myData->bytesFilled += packetSize;
}
void MyAudioQueueIsRunningCallback(void* inClientData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
{
MyData* myData = (MyData*)inClientData;
UInt32 running;
UInt32 size;
OSStatus err = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &running, &size);
if (err) { PRINTERROR("get kAudioQueueProperty_IsRunning"); myData->error = ERR_AQ_GET_PROPERTY; return; }
if (!running)
{
// STEP 5. When kAudioQueueProperty_IsRunning changes to "false", free currently stopped AQ buffers.
pthread_mutex_lock(&myData->mutex);
pthread_cond_signal(&myData->done);
pthread_mutex_unlock(&myData->mutex);
printf("free buffers\n");
unsigned int aqIndex = (inAQ == myData->audioQueue[0]) ? 0 : 1; // detect index of stopped AudioQueue
for(int i = 0; i < kNumAQBufs; i++)
{
AudioQueueFreeBuffer(inAQ, myData->audioQueueBuffer[aqIndex][i]);
if (err) { PRINTERROR("AudioQueueFreeBuffer"); break; }
}
}
else
{
// STEP 3. When kAudioQueueProperty_IsRunning changes to "true", start AQPlayedBufferTimer.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // init pool
[(id)refToSelfP setBufferPlayedDelay];
[pool release]; // release pool
}
}
- (void) setBufferPlayedDelay
{
[self performSelector:@selector(bufferPlayed:) withObject:self afterDelay:bufPlayTime];
}
// STEP 4. When AQPlayedBufferTimer calls callback, check count of enqueued buffers not played. Etc.
- (void) bufferPlayed: (AudioPlayback *) ap
{
enqueredBufCount--;
if (!myData->started) return;
if (enqueredBufCount > 0)
{
// still can use this AQ
[self performSelector:@selector(bufferPlayed:) withObject:self afterDelay:bufPlayTime];
}
else
{
printf(" all buffers played. Stopping...\n");
// index of current audioQueue
unsigned int curIndex = myData->aqIndex;
// index of next audioQueue
unsigned int aqIndex = myData->aqIndex + 1;
if (aqIndex > 1) aqIndex = 0;
// allocate buffers of next audioQueue
for (unsigned int i = 0; i < kNumAQBufs; ++i) {
OSStatus err = AudioQueueAllocateBuffer(myData->audioQueue[aqIndex], kAQBufSize, &myData->audioQueueBuffer[aqIndex][i]);
if (err) { PRINTERROR("AudioQueueAllocateBuffer"); myData->failed = true; break; }
}
// copy unused audio buffer bytes (if exist)
//if (myData->bytesFilled > 0) {
// // copy data from current audio queue buffer to next
// AudioQueueBufferRef fillBuf = myData->audioQueueBuffer[curIndex][myData->fillBufferIndex];
// AudioQueueBufferRef fillBufNext = myData->audioQueueBuffer[aqIndex][myData->fillBufferIndex];
// memcpy((char*)fillBufNext->mAudioData, (char*)fillBuf->mAudioData, myData->bytesFilled);
//}
// enqueue last buffer if exist (use ONLY if need to play REST data!). Use BEFORE switching audioQueue!!!
BOOL playRest = NO;
if (myData->bytesFilled > 0) {
// first enqueue rest audio
MyEnqueueBuffer(myData);
//WaitForFreeBuffer(myData);
myData->bytesFilled = 0; // reset bytes filled
myData->packetsFilled = 0; // reset packets filled
// -1 for just enquered audio
filledBufCount--;
enqueredBufCount--;
playRest = YES;
}
// switch audioQueue
myData->aqIndex = aqIndex;
// play rest audio buffer bytes (if exist) and stop after that
if (playRest) {
if ( [self playNstopAudioQueue:curIndex] )
NSLog(@"Error to play rest AQ");
}
// if not exist, just stop audioQueue
else
{
if ( [self stopAudioQueue:curIndex] )
NSLog(@"Error to stop AQ");
}
}
}
- (int) playNstopAudioQueue: (unsigned int) aqIndex
{
if (!myData->started) return 0;
myData->started = false; // set this flag BEFORE call AudioQueueStop
printf("flushing\n");
OSStatus err = AudioQueueFlush(myData->audioQueue[aqIndex]);
if (err) { PRINTERROR("AudioQueueFlush"); myData->error = ERR_AQ_FLUSH; return 1; }
printf("stopping\n");
err = AudioQueueStop(myData->audioQueue[aqIndex], false);
if (err) { PRINTERROR("AudioQueueStop"); return 1; }
printf("done\n");
return 0;
}
- (int) stopAudioQueue: (unsigned int) aqIndex
{
if (!myData->started) return 0;
myData->started = false; // set this flag BEFORE call AudioQueueStop
printf("stopping\n");
OSStatus err = AudioQueueStop(myData->audioQueue[aqIndex], true);
if (err) { PRINTERROR("AudioQueueStop"); return 1; }
printf("done\n");
return 0;
}
// ----------------------------
Thanks for any help,
Lev
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden