• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Using Audio Queue Services to playback
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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

  • Prev by Date: Re: Remove someone from the mailing lists?
  • Next by Date: How to launch auval in 64 bit from Xcode
  • Previous by thread: Re: Remove someone from the mailing lists?
  • Next by thread: How to launch auval in 64 bit from Xcode
  • Index(es):
    • Date
    • Thread