Problem
Using Chris Adamson’s book Learning Core Audio I’m getting familiar with Core Audio for Mac/iOS. The code examples are 100% Objective-C. I’m trying to follow along using Swift 3.
This small piece of software is used to find usable AudioStreamBasicDescription
s for a given file type and format.
import Foundation
import AudioToolbox
// Create ID for specific fileType and fileFormat
var fileTypeAndFormat = AudioFileTypeAndFormatID()
fileTypeAndFormat.mFileType = kAudioFileCAFType
fileTypeAndFormat.mFormatID = kAudioFormatLinearPCM
// Target: Gather possible AudioStreamBasicDescriptions for given fileType and fileFormat
// Get size of array containing all available AudioStreamBasicDescriptions
var possibleError = noErr
var infoSize: UInt32 = 0
possibleError = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,
UInt32(MemoryLayout<AudioFileTypeAndFormatID>.stride),
&fileTypeAndFormat,
&infoSize)
assert(possibleError == noErr)
// Get number of AudioStreamBasicDescription stored in array
let asbdCount = Int(infoSize) / MemoryLayout<AudioStreamBasicDescription>.stride
// Allocate memory for array to store AudioStreamBasicDescriptions in
var asbds = [AudioStreamBasicDescription](repeating: AudioStreamBasicDescription(), count: asbdCount)
// Fill array with all available AudioStreamBasicDescriptions
possibleError = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,
UInt32(MemoryLayout<AudioFileTypeAndFormatID>.stride),
&fileTypeAndFormat,
&infoSize,
&asbds)
assert(possibleError == noErr)
// Print each AudioStreamBasicDescription of array
for n in 0..<asbdCount {
var reformattedID = asbds[n].mFormatID.bigEndian
// TODO: This seems too ugly to be true
withUnsafePointer(to: &reformattedID) { stringIDPtr in
print(String(format: "%d: mFormatID: %s, mFormatFlags: %d, mBitsPerChannel: %d", n, stringIDPtr,
asbds[n].mFormatFlags, asbds[n].mBitsPerChannel))
}
}
In an production environment the assert
s should be replaced with better error handling, but correct error handling was not the purpose of this script.
Specific parts I’d like to get a review on:
- The memory allocation to store available
StreamDescriptions
for format in. Is the allocation via array Swift-style-conform? - Getting the
mFormatID
of anAudioStreamBasicDescription
viawithUnsafePointer(to: &reformattedID)
seems utterly unpleasant. Can this be realized in a more elegant way?
Solution
The “file and format ID” can be created in one step as
var fileTypeAndFormat = AudioFileTypeAndFormatID(mFileType: kAudioFileCAFType,
mFormatID: kAudioFormatLinearPCM)
Using a Swift Array
as storage is fine, and has the advantage that
the memory is managed automatically (contrary to memory from malloc
or UnsafeMutablePointer.alloc()
). The array creation can be written
a tiny be shorter as
var audioDescriptions = Array(repeating: AudioStreamBasicDescription(), count: asbdCount)
because the element type is inferred from the first argument. I have
also chosen a different name, asbds
is very short and cryptic.
For-loops are often better replaced by an enumeration:
for audioDesc in audioDescriptions {
// ...
}
or, if you need the element index as well:
for (idx, audioDesc) in audioDescriptions.enumerated() {
// ...
}
This saves array lookups inside the loop.
Treating the “four-character code” in mFormatID
as a string is
dangerous and causes undefined behaviour, because the %s
format
expects a zero-terminated sequence of char
s.
You can get additional unwanted output, or the program might crash.
You could use the %4.4s
format instead, which limits the output
to four characters:
for (idx, audioDesc) in audioDescriptions.enumerated() {
var reformattedID = audioDesc.mFormatID.bigEndian
withUnsafePointer(to: &reformattedID) { stringIDPtr in
print(String(format: "%d: mFormatID: %4.4s, mFormatFlags: %d, mBitsPerChannel: %d",
idx, stringIDPtr,
audioDesc.mFormatFlags, audioDesc.mBitsPerChannel))
}
}
A better solution is to convert the integer to a four-character
string, e.g. using one of the methods in
How to convert memory of Int32 as four characters, for example
func fourCCToString(_ value: FourCharCode) -> String {
let utf16 = [
UInt16((value >> 24) & 0xFF),
UInt16((value >> 16) & 0xFF),
UInt16((value >> 8) & 0xFF),
UInt16((value & 0xFF)) ]
return String(utf16CodeUnits: utf16, count: 4)
}
which is then used as
for (idx, audioDesc) in audioDescriptions.enumerated() {
let reformattedID = fourCCToString(audioDesc.mFormatID)
print(String(format: "%d: mFormatID: %@, mFormatFlags: %d, mBitsPerChannel: %d",
idx, reformattedID,
audioDesc.mFormatFlags, audioDesc.mBitsPerChannel))
}
Note that a Swift string is printed with the %@
format.
Or simply use string interpolation:
for (idx, audioDesc) in audioDescriptions.enumerated() {
let reformattedID = fourCCToString(audioDesc.mFormatID)
print("(idx): mFormatID: (reformattedID), mFormatFlags: (audioDesc.mFormatFlags), mBitsPerChannel: (audioDesc.mBitsPerChannel)")
}