CoreAudio keys
Collection of functions to retrieve properties of an audio device.
This articles references methods to retrieve properties of an audio device. To find out more about a key, you can jump to its definition in Xcode and browse the header file. It’s not ideal, but it’s there. That’s where the description are taken from.
This article will evolve with time.
The function
checkError(_:)
is used throughout the article. It maps anOSStatus
to an error when the result code is not 0. You can find its implementation in the post resources.
Identification
CoreAudio uses two type of identifiers for audio devices. The first one, AudioDeviceID
is a type alias for UInt32
. It is constant during the life cycle of the app, but it’s not persisted across launches. It’s the identifier that is most often used in the AudioObjectGetPropertyData(_:_:_:_:_:_:)
function. The other identifier is the UID which is a CFString
and is persisted across launches. A function to retrieve one from another is provided below.
Name
Description: Contains a human readable name for the category of the given element in the given scope.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func name(for deviceID: AudioDeviceID) throws -> String {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var name: CFString = "" as CFString
try withUnsafeMutablePointer(to: &name) { namePointer in
var nameSize = UInt32.sizeOf(CFString.self)
try AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&nameSize,
namePointer
)
.checkError("Unable to get device name for device ID \(deviceID)")
}
return name as String
}
UID
Retrieve a device UID from its ID.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func uid(for deviceID: AudioDeviceID) throws -> String {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var uid: CFString = "" as CFString
try withUnsafeMutablePointer(to: &uid) { mutablePointer in
let rawPointer = UnsafeMutableRawPointer(mutablePointer)
var propertySize = UInt32.sizeOf(CFString.self)
try AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, nil, &propertySize, rawPointer)
.checkError("Unable to get UID of audio device with id \(deviceID) gor key 'kAudioDevicePropertyDeviceUID'")
}
return uid as String
}
UID → ID
Retrieve a device ID from its UID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func deviceID(for uid: IODevice.UID) throws -> IODevice.ID? {
var uid = uid as CFString
let uidSize = UInt32.sizeOf(uid)
var id: AudioDeviceID = kAudioDeviceUnknown
let idSize = UInt32.sizeOf(id)
try withUnsafeMutablePointer(to: &uid) { uidMutablePointer in
try withUnsafeMutablePointer(to: &id) { idMutablePointer in
var translation = AudioValueTranslation(
mInputData: uidMutablePointer,
mInputDataSize: uidSize,
mOutputData: idMutablePointer,
mOutputDataSize: idSize
)
var translationSize: UInt32 = .sizeOf(translation)
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDeviceForUID,
mScope: AudioScope.global.key,
mElement: kAudioObjectPropertyElementMain
)
try AudioObjectGetPropertyData(
AudioObjectID(kAudioObjectSystemObject),
&propertyAddress,
0,
nil,
&translationSize,
&translation
)
.checkError("Unable to get translation for key 'kAudioObjectSystemObject'")
}
}
guard id != kAudioDeviceUnknown else { return nil }
return id
}
Listing
List IDs of available audio devices.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var deviceIDs: [AudioDeviceID] {
get throws {
let objectID = AudioObjectID(kAudioObjectSystemObject)
var propertyAddress = AudioObjectPropertyAddress(
mSelector = kAudioHardwarePropertyDevices
mScope = kAudioObjectPropertyScopeGlobal
mElement = kAudioObjectPropertyElementMain
)
var count: UInt32 = 0
try AudioObjectGetPropertyDataSize(
objectID,
&propertyAddress,
0,
nil,
&count
)
.checkError("AudioObjectGetPropertyDataSize failed")
var ids: [AudioObjectID] = Array(repeating: 0, count: Int(count))
try AudioObjectGetPropertyData(
objectID,
&propertyAddress,
0,
nil,
&count,
&ids
)
.checkError("AudioObjectGetPropertyData failed")
return ids
}
}
Channels
Count
Get the number of channels of an audio device for the provided scope.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func channelsCount(
for deviceID: AudioDeviceID,
inScope scope: AudioObjectPropertyScope
) throws -> AVAudioChannelCount {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration
propertyAddress.mScope = scope
propertyAddress.mElement = kAudioObjectPropertyElementMain
var propertySize: UInt32 = 0
try AudioObjectGetPropertyDataSize(
deviceID,
&propertyAddress,
0,
nil,
&propertySize
)
.checkError("AudioObjectGetPropertyDataSize failed")
let bufferListPointer = UnsafeMutablePointer<AudioBufferList>.allocate(capacity: 2)
defer {
bufferListPointer.deallocate()
bufferListPointer.deinitialize(count: 2)
}
try AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&propertySize,
bufferListPointer
)
.checkError("AudioObjectGetPropertyData failed")
let bufferList = UnsafeMutableAudioBufferListPointer(bufferListPointer)
var outputChannels: UInt32 = 0
for buffer in bufferList {
outputChannels += buffer.mNumberChannels
}
return outputChannels
}
I believe the size of 2 for the buffer list of the input stream and output stream. I’ll check.
Preferred stereo output
Get the preferred output stereo channels.
Description: An array of two UInt32s, the first for the left channel, the second for the right channel, that indicate the channel numbers to use for stereo IO on the device. The value of this property can be different for input and output and there are no restrictions on the channel numbers that can be used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func outputPreferredStereoChannels(for deviceID: AudioDeviceID) throws -> [AVAudioChannelCount] {
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyPreferredChannelsForStereo,
mScope: kAudioObjectPropertyScopeOutput,
mElement: kAudioObjectPropertyElementMain
)
var channels: [AVAudioChannelCount] = [0, 0]
var size = UInt32.sizeOf(channels)
try AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&size,
&channels
)
.checkError("Unable got get value for key 'kAudioDevicePropertyPreferredChannelsForStereo' on device")
return channels
}
Set the output preferred stereo channels.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func setOutputPreferredStereoChannels(
of deviceID: AudioDeviceID,
left: AVAudioChannelCount,
right: AVAudioChannelCount
) throws {
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyPreferredChannelsForStereo,
mScope: kAudioObjectPropertyScopeOutput,
mElement: kAudioObjectPropertyElementMain
)
var channels = [left, right]
try AudioObjectSetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
.sizeOf(channels),
&channels
)
.checkError("Unable to set the key 'kAudioDevicePropertyPreferredChannelsForStereo' on device with value \(channels)")
}
Transport
Type
Description: Indicates how the AudioDevice is connected to the CPU.
Examples: HDMI, USB, PCI…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func transportType(for deviceID: AudioDeviceID) throws -> UInt32 {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioDevicePropertyTransportType
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var valueSize = UInt32(MemoryLayout<UInt32>.size)
var rawValue: UInt32 = 0
try? AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&valueSize,
&rawValue
)
.checkError("Unable to get '\(key)' for object with ID \(objectID)")
return rawValue
}
Compare the returned value to the transport type keys with the prefix
kAudioDeviceTransportType
to identify the port type.
Sample Rate
Actual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func actualSampleRate(forObject objectID: AudioObjectID) throws -> Double {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioDevicePropertyActualSampleRate
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var valueSize = UInt32(MemoryLayout<Double>.size)
var value: Double = 0
try? AudioObjectGetPropertyData(
objectID,
&propertyAddress,
0,
nil,
&valueSize,
&value
)
.checkError("Unable to get '\(key)' for object with ID \(objectID)")
return value
}
Available
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func availableSampleRates(forObject objectID) throws -> [AudioValueRange] {
var sampleRates: [AudioValueRange] = Array(
repeating: AudioValueRange(mMinimum: 0, mMaximum: 0),
count: 10
)
var size = UInt32.sizeOf(sampleRates)
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyAvailableNominalSampleRates,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)
try AudioObjectGetPropertyData(
id,
&propertyAddress,
0,
nil,
&size,
&sampleRates
)
.checkError("Unable to get 'kAudioDevicePropertyAvailableNominalSampleRates'")
return sampleRates.filter { $0.mMinimum != 0 || $0.mMaximum != 0 }
}
10
is an arbitrary value that is enough I believe to get all available sample rates. Only the sample rates with at least a minimum or a maximum value different from 0 are considered.
Aggregate
Those keys that are used on aggregate devices.
Active devices
Description: An array of AudioObjectIDs for all the active sub-devices in the aggregate device.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func activeDevices(in deviceID: AudioDeviceID) throws -> [AudioDeviceID] {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioAggregateDevicePropertyActiveSubDeviceList
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var count: UInt32 = 0
try AudioObjectGetPropertyDataSize(
deviceID,
&propertyAddress,
0,
nil,
&count
)
.checkError("AudioObjectGetPropertyDataSize failed")
var ids: [AudioObjectID] = Array(repeating: 0, count: Int(count))
try AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&count,
&ids
)
.checkError("AudioObjectGetPropertyData failed")
return ids
}
All devices
Description: The UIDs of all the devices, active or inactive, contained in the AudioAggregateDevice. The order of the items in the array is significant and is used to determine the order of the streams of the AudioAggregateDevice.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func allDevices(in deviceID: AudioDeviceID) throws -> [String] {
var propertyAddress = AudioObjectPropertyAddress()
propertyAddress.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal
propertyAddress.mElement = kAudioObjectPropertyElementMain
var count: UInt32 = 0
try AudioObjectGetPropertyDataSize(
deviceID,
&propertyAddress,
0,
nil,
&count
)
.checkError("AudioObjectGetPropertyDataSize failed")
var uids = Array(repeating: " " as CFString, count: Int(count)) as CFArray
try withUnsafeMutablePointer(to: &uids) { mutablePointer in
try AudioObjectGetPropertyData(
deviceID,
&propertyAddress,
0,
nil,
&count,
mutablePointer
)
.checkError("Unable to get get full sub devices list of aggregated device with key 'kAudioAggregateDevicePropertyFullSubDeviceList'")
}
return uids as! [String]
}