Problems with AudioQueue in Swift: re-using buffers doesn't seem to work

1k Views Asked by At

I'm trying to play a simple beep using a sine wave of given frequency. I'm using Swift 4.2 and AudioQueues for the purpose and building for iPhone on iOS 12. However I'm having a lot of difficulty getting this set up.

The problem I'm currently seeing is that, when I enqueue the initial buffers, I get a little 'click' as these are played. When the buffers are re-used however, nothing happens. I have proved this by using an unreasonably high number of buffers (50,000 odd) which produces a one-second bleep at about the level I expect. This is correct for the given sample rate. Once all the initial buffers have been played once however, the sample stops.

I have tried using run loops other than the internal one. I've checked my callback is called, is receiving the right objects and appears to be setting the correct values to the buffer's content, but it just stops working. Nor do any of the AudioQueue functions return any abnormal OSStatus codes. I've tried various options in the basic description to account for data format and endianness. I just can't think what else could be wrong, since what I have is similar to the majority of examples I've found.

(N.B. I have omitted the commands that start and stop the audio queue for brevity. Suffice to say that createAudioQueue() is called.)

Here is my code:

func synthesizerCallback(userData: UnsafeMutableRawPointer?, audioQueue: AudioQueueRef, buffer: AudioQueueBufferRef) {
    guard let rawSynth = userData else { return }
    let synth = Unmanaged<Synthesizer>.fromOpaque(rawSynth).takeUnretainedValue()
    synth.write(withAudioQueue: audioQueue, toBuffer: buffer)
}

class Synthesizer {

    enum Errors: Error {
        case coreAudioError(OSStatus)
        case couldNotInitialiseAudioQueue
    }

    private var format: AudioStreamBasicDescription
    private var outputQueue: AudioQueueRef?
    private var outputBuffers: [AudioQueueBufferRef?]
    private var offset: Double = 0

    let sampleRate: Double
    let channels: Int

    init(sampleRate: Double = 44100, channels: Int = 2, bufferCount: Int = 3) {
        assert(bufferCount > 2, "Need at least three buffers!")

        self.sampleRate = sampleRate
        self.channels = channels
        self.outputBuffers = Array<AudioQueueBufferRef?>(repeating: nil, count: bufferCount)

        let uChannels = UInt32(channels)
        let channelBytes = UInt32(MemoryLayout<Int16>.size)
        let bytesPerFrame = uChannels * channelBytes

        self.format = AudioStreamBasicDescription(
            mSampleRate: Float64(sampleRate),
            mFormatID: kAudioFormatLinearPCM,
            mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
            mBytesPerPacket: bytesPerFrame,
            mFramesPerPacket: 1,
            mBytesPerFrame: bytesPerFrame,
            mChannelsPerFrame: uChannels,
            mBitsPerChannel: channelBytes * 8,
            mReserved: 0
        )
    }

    private func createAudioQueue() throws {
        let selfPointer = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
        let createOutputResult = AudioQueueNewOutput(&format, synthesizerCallback, selfPointer, nil, nil, 0, &outputQueue)

        if createOutputResult != 0 { throw Errors.coreAudioError(createOutputResult) }
        guard let outputQueue = outputQueue else { throw Errors.couldNotInitialiseAudioQueue }

        for i in 0 ..< outputBuffers.count {
            let createBufferResult = AudioQueueAllocateBuffer(outputQueue, format.mBytesPerFrame, &outputBuffers[i])
            if createBufferResult != 0 { throw Errors.coreAudioError(createBufferResult) }
            guard let outputBuffer = outputBuffers[i] else { throw Errors.couldNotInitialiseAudioQueue }
            synthesizerCallback(userData: selfPointer, audioQueue: outputQueue, buffer: outputBuffer)
        }

        let primeQueueResult = AudioQueuePrime(outputQueue, 0, nil)
        if primeQueueResult != 0 { throw Errors.coreAudioError(primeQueueResult) }

        let startQueueResult = AudioQueueStart(outputQueue, nil)
        if startQueueResult != 0 { throw Errors.coreAudioError(startQueueResult) }
    }

    func write(withAudioQueue audioQueue: AudioQueueRef, toBuffer buffer: AudioQueueBufferRef) {
        let dataSize = MemoryLayout<Int16>.stride * channels
        let phase = offset / sampleRate * 10000 * Double.pi * 2
        let value = Int16(sin(phase) * Double(Int16.max))
        var allChannels = Array<Int16>(repeatElement(value, count: channels))

        buffer.pointee.mAudioData.copyMemory(from: &allChannels, byteCount: dataSize)
        buffer.pointee.mAudioDataByteSize = UInt32(dataSize)

        let enqueueResult = AudioQueueEnqueueBuffer(audioQueue, buffer, 0, nil)
        if enqueueResult != 0 { print(enqueueResult) }

        offset += 1
    }

}

UPDATE Oddly, the sound seems to be continuous if I supply around 550 - 12000 buffers. Much higher or lower and it either cuts out, glitches badly or both. I guess there's some circumstances under which a buffer might not be ready to be refilled, and I need to do something to circumvent this problem, but I don't know what that might be.

0

There are 0 best solutions below