3

I've been trying to use Apple's CoreAudio from swift. I found many examples on how to enumerate streams and channels on a device. However, all of them seem to use incorrect size when calling UnsafeMutablePointer<AudioBufferList>.allocate().

They first request property data size, which returns the number of bytes. Then they use this number of bytes to allocate an (unsafe) AudioBufferList of that size (using the number of bytes as the size of the list!).

Please see my comments inline below:

var address = AudioObjectPropertyAddress(
    mSelector:AudioObjectPropertySelector(kAudioDevicePropertyStreamConfiguration),
    mScope:AudioObjectPropertyScope(kAudioDevicePropertyScopeInput),
    mElement:0)

var propsize = UInt32(0);
var result:OSStatus = AudioObjectGetPropertyDataSize(self.id, &address, 0, nil, &propsize);
if (result != 0) {
    return false;
}

// ABOVE: propsize is set to number of bytes that property data contains, typical number are 8 (no streams), 24 (1 stream, 2 interleaved channels)
// BELOW: propsize is used for AudioBufferList capacity (in number of buffers!)

let bufferList = UnsafeMutablePointer<AudioBufferList>.allocate(capacity:Int(propsize))
result = AudioObjectGetPropertyData(self.id, &address, 0, nil, &propsize, bufferList);
if (result != 0) {
    return false
}

let buffers = UnsafeMutableAudioBufferListPointer(bufferList)
for bufferNum in 0..<buffers.count {
    if buffers[bufferNum].mNumberChannels > 0 {
        return true
    }
}

This works all of the time, because it allocates much more memory than needed for UnsafeMutablePointer<AudioBufferList>, but this is obviously wrong.

I've been searching for a way to correctly allocate UnsafeMutablePointer<AudioBufferList> from the number of bytes that is returned by AudioObjectGetPropertyDataSize(), but I cannot find anything for the whole day. Please help ;)

Alexander
  • 59,041
  • 12
  • 98
  • 151
akuz
  • 607
  • 7
  • 14
  • Does this answer help? https://stackoverflow.com/a/38982906/3141234 You want to allocate a `AudioBufferList` large enough to fit the property you're trying to access, and then pass it by reference – Alexander Mar 22 '20 at 14:58
  • @Alexander-ReinstateMonica that answer is using the prop size incorrectly, the answer by OOPer below is correct – akuz Mar 22 '20 at 16:22

2 Answers2

2

to correctly allocate UnsafeMutablePointer<AudioBufferList> from the number of bytes that is returned by AudioObjectGetPropertyDataSize()

You should not allocate UnsafeMutablePointer<AudioBufferList>, but allocate raw bytes of the exact size and cast it to UnsafeMutablePointer<AudioBufferList>.

Some thing like this:

        let propData = UnsafeMutableRawPointer.allocate(byteCount: Int(propsize), alignment: MemoryLayout<AudioBufferList>.alignment)
        result = AudioObjectGetPropertyData(self.id, &address, 0, nil, &propsize, propData);
        if (result != 0) {
            return false
        }
        let bufferList = propData.assumingMemoryBound(to: AudioBufferList.self)
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • This works marvellously, thank you! Could you please give me some additional info why I should use `assumingMemoryBound()` as opposed to `bindMemory()`? Apple has some warnings about using memory that is bound to the wrong type... – akuz Mar 22 '20 at 09:46
  • 1
    @akuz, I think the memory bound to a specific type is sort of an imaginary concept and has no practical difference as for now. (Sorry, but my info may be too old.) And `bindMemory` counts `capacity` by its pointee type's size which is not the actually allocated size, as you know. – OOPer Mar 22 '20 at 10:45
  • OOPer - that's what I thought, but wanted to ask anyway... Thanks! It's amazing that all the examples on stackoverflow use `propSize` in bytes to allocate memory of that many `AudioBufferList`s! Your answer was very helpful, and it works - thanks again! – akuz Mar 22 '20 at 11:47
  • OOPer -- would you happen to know the answer to this as well? https://stackoverflow.com/questions/60801491/how-to-access-multiple-buffers-in-unsafepointeraudiobufferlist-non-mutable – akuz Mar 23 '20 at 11:11
  • 1
    Of the examples I've seen, I like this the best. The only addition I would suggest is that the call to `allocate()` should generally be paired with a call to `deallocate()` – user1325158 Aug 09 '20 at 18:30
1

I fully agree with the accepted answer of using UnsafeMutableRawPointer.allocate(byteCount:alignment:) (though it should also be paired with a call to deallocate() for getting the device stream configuration, but just wanted to share another option for completeness (this shouldn't be upvoted for this question)

If you truly need to calculate the number of buffers from the number of bytes (I'm not sure there is actually any such need), it can be done.

When first converting code to Swift, I used something like :

        let numBuffers = (Int(propsize) - MemoryLayout<AudioBufferList>.offset(of: \AudioBufferList.mBuffers)!) / MemoryLayout<AudioBuffer>.size
        if numBuffers == 0 { // Avoid trying to allocate zero buffers
            return false
        }
        let bufferList = AudioBufferList.allocate(maximumBuffers: numBuffers)
        defer { bufferList.unsafeMutablePointer.deallocate() }
        err = AudioObjectGetPropertyData(id, &address, 0, nil, &propsize, bufferList.unsafeMutablePointer)

Again, I do NOT actually recommend this approach to get the stream configuration - it's unnecessarily complex IMO, and I've since adopted something like the accepted answer. So this may not have value other than as an academic exercise.

user1325158
  • 194
  • 3
  • 9