2
votes

I have the following code which opens an AudioQueue to playback 16 bit pcm @ 44,100hz. It has a very odd quirk where once the initial buffers are filled it plays back really quickly then gets "choppy" as it waits for more bytes to come over the network.

So either I am somehow messing up the code that copies a subrange of data into the buffer or I have told the AudioQueue to playback faster than the data comes over the network.

Anybody have any ideas? I've been stuck for a few days now.

//
// Created by Benjamin St Pierre on 15-01-02.
// Copyright (c) 2015 Lightning Strike Solutions. All rights reserved.
//

#import <MacTypes.h>
#import "MediaPlayer.h"


@implementation MediaPlayer


@synthesize sampleQueue;


void OutputBufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
    //Cast userData to MediaPlayer Objective-C class instance
    MediaPlayer *mediaPlayer = (__bridge MediaPlayer *) inUserData;
    // Fill buffer.
    [mediaPlayer fillAudioBuffer:inBuffer];
    // Re-enqueue buffer.
    OSStatus err = AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
    if (err != noErr)
        NSLog(@"AudioQueueEnqueueBuffer() error %d", (int) err);
}

- (void)fillAudioBuffer:(AudioQueueBufferRef)inBuffer {
    if (self.currentAudioPiece == nil || self.currentAudioPiece.duration >= self.currentAudioPieceIndex) {
        //grab latest sample from sample queue
        self.currentAudioPiece = sampleQueue.dequeue;
        self.currentAudioPieceIndex = 0;
    }

    //Check for empty sample queue
    if (self.currentAudioPiece == nil) {
        NSLog(@"Empty sample queue");
        memset(inBuffer->mAudioData, 0, kBufferByteSize);
        return;
    }

    UInt32 bytesToRead = inBuffer->mAudioDataBytesCapacity;

    while (bytesToRead > 0) {
        UInt32 maxBytesFromCurrentPiece = self.currentAudioPiece.audioData.length - self.currentAudioPieceIndex;
        //Take the min of what the current piece can provide OR what is needed to be read
        UInt32 bytesToReadNow = MIN(maxBytesFromCurrentPiece, bytesToRead);

        NSData *subRange = [self.currentAudioPiece.audioData subdataWithRange:NSMakeRange(self.currentAudioPieceIndex, bytesToReadNow)];
        //Copy what you can before continuing loop
        memcpy(inBuffer->mAudioData, subRange.bytes, subRange.length);
        bytesToRead -= bytesToReadNow;

        if (bytesToReadNow == maxBytesFromCurrentPiece) {
            @synchronized (sampleQueue) {
                self.currentAudioPiece = self.sampleQueue.dequeue;
                self.currentAudioPieceIndex = 0;
            }
        } else {
            self.currentAudioPieceIndex += bytesToReadNow;
        }
    }
    inBuffer->mAudioDataByteSize = kBufferByteSize;
}

- (void)startMediaPlayer {
    AudioStreamBasicDescription streamFormat;
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mSampleRate = 44100.0;
    streamFormat.mChannelsPerFrame = 2;
    streamFormat.mBytesPerFrame = 4;
    streamFormat.mFramesPerPacket = 1;
    streamFormat.mBytesPerPacket = 4;
    streamFormat.mBitsPerChannel = 16;
    streamFormat.mReserved = 0;
    streamFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;

    // New input queue
    OSStatus err = AudioQueueNewOutput(&streamFormat, OutputBufferCallback, (__bridge void *) self, nil, nil, 0, &outputQueue);
    if (err != noErr) {
        NSLog(@"AudioQueueNewOutput() error: %d", (int) err);
    }

    int i;
    // Enqueue buffers
    AudioQueueBufferRef buffer;
    for (i = 0; i < kNumberBuffers; i++) {
        err = AudioQueueAllocateBuffer(outputQueue, kBufferByteSize, &buffer);
        memset(buffer->mAudioData, 0, kBufferByteSize);
        buffer->mAudioDataByteSize = kBufferByteSize;
        if (err == noErr) {
            err = AudioQueueEnqueueBuffer(outputQueue, buffer, 0, nil);
            if (err != noErr) NSLog(@"AudioQueueEnqueueBuffer() error: %d", (int) err);
        } else {
            NSLog(@"AudioQueueAllocateBuffer() error: %d", (int) err);
            return;
        }
    }

    // Start queue
    err = AudioQueueStart(outputQueue, nil);
    if (err != noErr) NSLog(@"AudioQueueStart() error: %d", (int) err);
}

@end
1
You're calling Objective-C code in your render callback which is not something that is typically real time safe and is probably leading to the overloads. In -fillAudioBuffer: the use of a mutex is definitely not real time safe. - sbooth
With my first attempt at audio queue, I had sound artefacts for these two reasons: 1) I wasn't setting the buffer->mAudioDataByteSize correctly in the callback and 2) I was using too few buffers; increasing buffers from 3 to 5 solved my last remaining audio stuttering problem. - Johann Oskarsson

1 Answers

0
votes

I'm going to take a swag here and say that you're getting choppy playback because you aren't advancing the write pointer for your data. I don't know objective-C well enough to tell you if this syntax is correct, but here's what I think you need to add:

while (bytesToRead > 0) {
    ....
    memcpy(inBuffer->mAudioData, subRange.bytes, subRange.length);
    bytesToRead -= bytesToReadNow;
    inBuffer->mAudioData += bytesReadNow; // move the write pointer
    ...
}