Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using AudioBufferList with Swift

I have a bridging function in Swift, one of whose arguments in C is AudioBufferList *. In Swift this generates an UnsafePointer<AudioBufferList>. I've manage to deference the pointer by calling audioData[0] (is there a better way?). But I'm struggling with the next 2 tiers down: the .mBuffers array of AudioBuffer's and their void * / UnsafePointer<()> .mData members.

In C it would simply be

Float32 *audioData = (Float 32*)abl->mBuffers[0]->mData;
output = audioData[sampleNum]...

In Swift the first odd thing is that it won't let me access the elements of mBuffers but is perfectly happy when I access it as a property. In other words, this works and even has correct data (for the first member of mBuffers I presume)...

println(abl[0].mBuffers.mNumberChannels)  // But .mBuffers should be an []!

Second, it let's me print out .mData subscripts but the value is always ()

println(abl[0].mBuffers.mData[10])  // Prints '()'

I've tried various casting ops and accessing with multiple indices but to no avail...any ideas?

Here are the C and Swift definitions for AudioBufferList and AudioBuffer for convenience...

// C
struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[1]; // this is a variable length array of mNumberBuffers elements
    // ...and a bit more for c++
}


struct AudioBuffer
{
    UInt32  mNumberChannels;
    UInt32  mDataByteSize;
    void*   mData;
};

...

// SWIFT

struct AudioBufferList {
    var mNumberBuffers: UInt32
    var mBuffers: (AudioBuffer)
}

struct AudioBuffer {
    var mNumberChannels: UInt32
    var mDataByteSize: UInt32
    var mData: UnsafePointer<()>
}
like image 647
Hari Honor Avatar asked Jul 19 '14 07:07

Hari Honor


2 Answers

I found this by accident. Oddly the type ahead was actually working with Swift when it suggested UnsafeMutableAudioBufferListPointer. Which you can initialize with an UnsafeMutablePointer argument. This type is a MutableCollectionType and provides subscript and generator access to the contained Audio Buffers.

For example you can set an ABL to silence with the following code

func renderCallback(ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus {

    let abl = UnsafeMutableAudioBufferListPointer(ioData)

    for buffer in abl {

        memset(buffer.mData, 0, Int(buffer.mDataByteSize))
    }

    return noErr
}
like image 122
Adam Ritenauer Avatar answered Sep 23 '22 07:09

Adam Ritenauer


Edit: Adam Ritenauer's answer is probably the best one now. To expand on it, you can look at the new utility functions/types in the iOS 8.3 Core Audio changes.

UnsafeMutableAudioBufferListPointer can be used to read/access some given data:

struct UnsafeMutableAudioBufferListPointer {
    init(_ p: UnsafeMutablePointer<AudioBufferList>)
    var count: Int
    subscript (index: Int) -> AudioBuffer { get nonmutating set }
}

And you can use the extensions on AudioBuffer & AudioBufferList to allocate your own:

extension AudioBufferList {
    static func sizeInBytes(maximumBuffers maximumBuffers: Int) -> Int
    static func allocate(maximumBuffers maximumBuffers: Int) -> UnsafeMutableAudioBufferListPointer
}

extension AudioBuffer {
    init<Element>(_ typedBuffer: UnsafeMutableBufferPointer<Element>, numberOfChannels: Int)
}

Old answer:

This is a bit tricky because AudioBufferList is actually a variable-size struct. This means it's declared as having a single AudioBuffer, but really it has as many as specified by the mNumberBuffers member. This notion doesn't translate very well to Swift, which is why you see var mBuffers: (AudioBuffer).

So the canonical way to access these buffers, and their data, would be using UnsafeArray. The code below provides some ideas, but UnsafePointer and UnsafeArray aren't well documented, so this could be wrong.

// ***WARNING: UNTESTED CODE AHEAD***

let foo: UnsafePointer<AudioBufferList> // from elsewhere...

// This looks intuitive, but accessing `foo.memory` may be doing a copy.
let bufs = UnsafeArray<AudioBuffer>(start: &foo.memory.mBuffers, length: Int(foo.memory.mNumberBuffers))

// This is another alternative that should work...
let bufsStart = UnsafePointer<AudioBuffer>(UnsafePointer<UInt32>(foo) + 1) // Offset to mBuffers member
let bufs = UnsafeArray<AudioBuffer>(start: bufsStart, length: Int(foo.memory.mNumberBuffers))

// Hopefully this isn't doing a copy, but it shouldn't be too much of a problem anyway.
let buf: AudioBuffer = bufs[0] // or you could use a for loop over bufs, etc.

typealias MySample = Float32
let numSamples = Int(buf.mDataByteSize / UInt32(sizeof(MySample)))
let samples = UnsafeArray<MySample>(start: UnsafePointer<MySample>(buf.mData), length: numSamples)

// Now use the samples array...

This seems to work in the playground but it's hard for me to test on real audio data. In particular, I'm not 100% sure that using start: &foo.memory.mBuffers will work as expected. (It returns a different pointer from the original, although the data seem to be there.) Give it a shot and report back!

Edit: to debug this, by the way, you can for example:

(lldb) p foo
(UnsafePointer<AudioBufferList>) $R1 = (value = Builtin.RawPointer = 0x0000000100700740)
(lldb) expr -lc -- ((int*)0x0000000100700740)[0]
(int) $2 = 42
(lldb) expr -lc -- ((int*)0x0000000100700740)[1]
(int) $3 = 43
...
like image 40
jtbandes Avatar answered Sep 22 '22 07:09

jtbandes