How do I turn an array of Float into an AVAudioPCMBuffer?

I have an array of Float (representing audio samples) and I want to turn it into an AVAudioPCMBuffer so I can pass it to AVAudioFile's write(from:). There's an obvious way (actually not obvious at all, I cribbed it from this gist):

var floats: [Float] = ... // this comes from somewhere else
let audioBuffer = AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(floats.count * MemoryLayout<Float>.size), mData: &floats)
var bufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
let outputAudioBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, bufferListNoCopy: &bufferList)!
try self.renderedAudioFile?.write(from: outputAudioBuffer)

This works (I get the audio output I expect) but in Xcode 13.4.1 this gives me a warning on the &floats: Cannot use inout expression here; argument 'mData' must be a pointer that outlives the call to 'init(mNumberChannels:mDataByteSize:mData:)'

Ok, scope the pointer then:

var floats: [Float] = ... // this comes from somewhere else
try withUnsafeMutablePointer(to: &floats) { bytes in
    let audioBuffer = AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(bytes.pointee.count * MemoryLayout<Float>.size), mData: bytes)
    var bufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
    let outputAudioBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, bufferListNoCopy: &bufferList)!
    try self.renderedAudioFile?.write(from: outputAudioBuffer)
}

The warning goes away, but now the output is garbage. I really don't understand this as floats.count and bytes.pointee.count are the same number. What am I doing wrong?

Accepted Reply

Thanks to 'sbooth' on StackOverflow for the answer, I was missing .baseAddress on the mData: argument:

var floats: [Float] = ... // this comes from somewhere else
try floats.withUnsafeMutableBufferPointer { bytes in
    let audioBuffer = AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(bytes.count * MemoryLayout<Float>.size), mData: bytes.baseAddress)
    var bufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
    let outputAudioBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, bufferListNoCopy: &bufferList)!
    try self.renderedAudioFile?.write(from: outputAudioBuffer)
}

Replies

The global withUnsafeMutablePointer(to:) function isn't the right one to use here. Use Array's withUnsafeBufferPointer(_:) function instead:

floats.withUnsafeBufferPointer {
    …
}

Unfortunately that doesn't work either: Cannot convert value of type 'UnsafeBufferPointer<Float>' to expected argument type 'UnsafeMutableRawPointer?' (where bytes is passed in to the AudioBuffer constructor.)

  • .withUnsafeMutableBufferPointer doesn't work either, for reference.

Add a Comment

Thanks to 'sbooth' on StackOverflow for the answer, I was missing .baseAddress on the mData: argument:

var floats: [Float] = ... // this comes from somewhere else
try floats.withUnsafeMutableBufferPointer { bytes in
    let audioBuffer = AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(bytes.count * MemoryLayout<Float>.size), mData: bytes.baseAddress)
    var bufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
    let outputAudioBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, bufferListNoCopy: &bufferList)!
    try self.renderedAudioFile?.write(from: outputAudioBuffer)
}