How do I utilize AudioObjectGetPropertyData
in OS X to retrieve a list of the system\'s input devices? I currently have the following dummy code for retrieving
To determine if a device is an input device you need to check and see if it has any input channels.
Here is code modified from the Objective-C class here:
static BOOL DeviceHasBuffersInScope(AudioObjectID deviceID, AudioObjectPropertyScope scope)
{
NSCParameterAssert(deviceID != kAudioObjectUnknown);
AudioObjectPropertyAddress propertyAddress = {
.mSelector = kAudioDevicePropertyStreamConfiguration,
.mScope = scope,
.mElement = kAudioObjectPropertyElementWildcard
};
UInt32 dataSize = 0;
OSStatus result = AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &dataSize);
if(result != kAudioHardwareNoError) {
return NO;
}
AudioBufferList *bufferList = malloc(dataSize);
if(!bufferList) {
return NO;
}
result = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, bufferList);
if(result != kAudioHardwareNoError) {
free(bufferList);
return NO;
}
BOOL supportsScope = bufferList->mNumberBuffers > 0;
free(bufferList);
return supportsScope;
}
static BOOL DeviceSupportsInput(AudioObjectID deviceID)
{
return DeviceHasBuffersInScope(deviceID, kAudioObjectPropertyScopeInput);
}
static BOOL DeviceSupportsOutput(AudioObjectID deviceID)
{
return DeviceHasBuffersInScope(deviceID, kAudioObjectPropertyScopeOutput);
}
NSArray<NSNumber *> * AllAudioDevices()
{
AudioObjectPropertyAddress propertyAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementWildcard
};
UInt32 dataSize = 0;
OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(result != kAudioHardwareNoError) {
return nil;
}
AudioObjectID *deviceIDs = (AudioObjectID *)malloc(dataSize);
if(!deviceIDs) {
return nil;
}
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, deviceIDs);
if(kAudioHardwareNoError != result) {
free(deviceIDs);
return nil;
}
NSMutableArray *allDevices = [NSMutableArray array];
for(NSInteger i = 0; i < (NSInteger)(dataSize / sizeof(AudioObjectID)); ++i) {
[allDevices addObject:[NSNumber numberWithUnsignedInt:deviceIDs[i]]];
}
free(deviceIDs);
return allDevices;
}
NSArray<NSNumber *> * AudioOutputDevices()
{
NSMutableArray *outputDevices = [NSMutableArray array];
NSArray *allDevices = AllAudioDevices();
for(NSNumber *device in allDevices) {
if(DeviceSupportsOutput(device.unsignedIntValue)) {
[outputDevices addObject:device];
}
}
return outputDevices;
}
NSArray<NSNumber *> * AudioInputDevices()
{
NSMutableArray *inputDevices = [NSMutableArray array];
NSArray *allDevices = AllAudioDevices();
for(NSNumber *device in allDevices) {
if(DeviceSupportsInput(device.unsignedIntValue)) {
[inputDevices addObject:device];
}
}
return inputDevices;
}
The original code snippet was:
Here is some code I converted that should work (untested though):
CFArrayRef CreateInputDeviceArray()
{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i\n", status);
return NULL;
}
UInt32 deviceCount = static_cast<UInt32>(dataSize / sizeof(AudioDeviceID));
AudioDeviceID *audioDevices = static_cast<AudioDeviceID *>(malloc(dataSize));
if(NULL == audioDevices) {
fputs("Unable to allocate memory", stderr);
return NULL;
}
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i\n", status);
free(audioDevices), audioDevices = NULL;
return NULL;
}
CFMutableArrayRef inputDeviceArray = CFArrayCreateMutable(kCFAllocatorDefault, deviceCount, &kCFTypeArrayCallBacks);
if(NULL == inputDeviceArray) {
fputs("CFArrayCreateMutable failed", stderr);
free(audioDevices), audioDevices = NULL;
return NULL;
}
// Iterate through all the devices and determine which are input-capable
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i\n", status);
continue;
}
// Query device name
CFStringRef deviceName = NULL;
dataSize = sizeof(deviceName);
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i\n", status);
continue;
}
// Query device manufacturer
CFStringRef deviceManufacturer = NULL;
dataSize = sizeof(deviceManufacturer);
propertyAddress.mSelector = kAudioDevicePropertyDeviceManufacturerCFString;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceManufacturer);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceManufacturerCFString) failed: %i\n", status);
continue;
}
// Determine if the device is an input device (it is an input device if it has input channels)
dataSize = 0;
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i\n", status);
continue;
}
AudioBufferList *bufferList = static_cast<AudioBufferList *>(malloc(dataSize));
if(NULL == bufferList) {
fputs("Unable to allocate memory", stderr);
break;
}
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
if(kAudioHardwareNoError != status)
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i\n", status);
free(bufferList), bufferList = NULL;
continue;
}
free(bufferList), bufferList = NULL;
// Add a dictionary for this device to the array of input devices
CFStringRef keys [] = { CFSTR("deviceUID"), CFSTR("deviceName"), CFSTR("deviceManufacturer") };
CFStringRef values [] = { deviceUID, deviceName, deviceManufacturer };
CFDictionaryRef deviceDictionary = CFDictionaryCreate(kCFAllocatorDefault,
reinterpret_cast<const void **>(keys),
reinterpret_cast<const void **>(values),
3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFArrayAppendValue(inputDeviceArray, deviceDictionary);
CFRelease(deviceDictionary), deviceDictionary = NULL;
}
free(audioDevices), audioDevices = NULL;
// Return a non-mutable copy of the array
CFArrayRef copy = CFArrayCreateCopy(kCFAllocatorDefault, inputDeviceArray);
CFRelease(inputDeviceArray), inputDeviceArray = NULL;
return copy;
}
Swift 3.0 Xcode 8 Beta 5
Struggled with this for a good while but this seems to work fine for now.
func handle(_ errorCode: OSStatus) throws {
if errorCode != kAudioHardwareNoError {
let error = NSError(domain: NSOSStatusErrorDomain, code: Int(errorCode), userInfo: [NSLocalizedDescriptionKey : "CAError: \(errorCode)" ])
NSApplication.shared().presentError(error)
throw error
}
}
func getInputDevices() throws -> [AudioDeviceID] {
var inputDevices: [AudioDeviceID] = []
// Construct the address of the property which holds all available devices
var devicesPropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
var propertySize = UInt32(0)
// Get the size of the property in the kAudioObjectSystemObject so we can make space to store it
try handle(AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &devicesPropertyAddress, 0, nil, &propertySize))
// Get the number of devices by dividing the property address by the size of AudioDeviceIDs
let numberOfDevices = Int(propertySize) / sizeof(AudioDeviceID.self)
// Create space to store the values
var deviceIDs: [AudioDeviceID] = []
for _ in 0 ..< numberOfDevices {
deviceIDs.append(AudioDeviceID())
}
// Get the available devices
try handle(AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &devicesPropertyAddress, 0, nil, &propertySize, &deviceIDs))
// Iterate
for id in deviceIDs {
// Get the device name for fun
var name: CFString = ""
var propertySize = UInt32(sizeof(CFString.self))
var deviceNamePropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyDeviceNameCFString, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
try handle(AudioObjectGetPropertyData(id, &deviceNamePropertyAddress, 0, nil, &propertySize, &name))
// Check the input scope of the device for any channels. That would mean it's an input device
// Get the stream configuration of the device. It's a list of audio buffers.
var streamConfigAddress = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyStreamConfiguration, mScope: kAudioDevicePropertyScopeInput, mElement: 0)
// Get the size so we can make room again
try handle(AudioObjectGetPropertyDataSize(id, &streamConfigAddress, 0, nil, &propertySize))
// Create a buffer list with the property size we just got and let core audio fill it
let audioBufferList = AudioBufferList.allocate(maximumBuffers: Int(propertySize))
try handle(AudioObjectGetPropertyData(id, &streamConfigAddress, 0, nil, &propertySize, audioBufferList.unsafeMutablePointer))
// Get the number of channels in all the audio buffers in the audio buffer list
var channelCount = 0
for i in 0 ..< Int(audioBufferList.unsafeMutablePointer.pointee.mNumberBuffers) {
channelCount = channelCount + Int(audioBufferList[i].mNumberChannels)
}
free(audioBufferList.unsafeMutablePointer)
// If there are channels, it's an input device
if channelCount > 0 {
Swift.print("Found input device '\(name)' with \(channelCount) channels")
inputDevices.append(id)
}
}
return inputDevices
}
Here's the best way I have found to sort inputs from outputs when iterating through CoreAudio device ids.
This is just the part inside the loop:
BOOL isMic = NO;
BOOL isSpeaker = NO;
AudioDeviceID device = audioDevices[i];
// Determine direction of the device by asking for the number of input or
// output streams.
propertyAddress.mSelector = kAudioDevicePropertyStreams;
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
UInt32 dataSize = 0;
OSStatus status = AudioObjectGetPropertyDataSize(device,
&propertyAddress,
0,
NULL,
&dataSize);
UInt32 streamCount = dataSize / sizeof(AudioStreamID);
if (streamCount > 0)
{
isMic = YES;
}
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
dataSize = 0;
status = AudioObjectGetPropertyDataSize(device,
&propertyAddress,
0,
NULL,
&dataSize);
streamCount = dataSize / sizeof(AudioStreamID);
if (streamCount > 0)
{
isSpeaker = YES;
}
I hope this helps someone else, I ended finding out that Apple provides the source for their C+++ HAL interface in xcode/Extras/CoreAudio/HAL/HPBase which was key in figuring this out.
I have slightly modified the code submitted by "sbooth" to print all the input devices along with the no. of buffers for each device and no. of channels for each buffer.
CFArrayRef CreateInputDeviceArray()
{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
OSStatus status = AudioHardwareServiceGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i\n", status);
return NULL;
}
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID *audioDevices = (AudioDeviceID *)(malloc(dataSize));
if(NULL == audioDevices) {
fputs("Unable to allocate memory", stderr);
return NULL;
}
status = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i\n", status);
free(audioDevices), audioDevices = NULL;
return NULL;
}
CFMutableArrayRef inputDeviceArray = CFArrayCreateMutable(kCFAllocatorDefault, deviceCount, &kCFTypeArrayCallBacks);
if(NULL == inputDeviceArray) {
fputs("CFArrayCreateMutable failed", stderr);
free(audioDevices), audioDevices = NULL;
return NULL;
}
// Iterate through all the devices and determine which are input-capable
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioHardwareServiceGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i\n", status);
continue;
}
// Query device name
CFStringRef deviceName = NULL;
dataSize = sizeof(deviceName);
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
status = AudioHardwareServiceGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i\n", status);
continue;
}
// Query device manufacturer
CFStringRef deviceManufacturer = NULL;
dataSize = sizeof(deviceManufacturer);
propertyAddress.mSelector = kAudioDevicePropertyDeviceManufacturerCFString;
status = AudioHardwareServiceGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceManufacturer);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyDeviceManufacturerCFString) failed: %i\n", status);
continue;
}
// Determine if the device is an input device (it is an input device if it has input channels)
dataSize = 0;
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
status = AudioHardwareServiceGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
fprintf(stderr, "AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i\n", status);
continue;
}
AudioBufferList *bufferList = (AudioBufferList *)(malloc(dataSize));
if(NULL == bufferList) {
fputs("Unable to allocate memory", stderr);
break;
}
status = AudioHardwareServiceGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
if(kAudioHardwareNoError != status)
fprintf(stderr, "AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i\n", status);
free(bufferList), bufferList = NULL;
continue;
}
UInt32 numBuffers = bufferList->mNumberBuffers;
printf("\n\ndeviceUID:%s \tdeviceName: %s\ndeviceManufacturer: %s\t#Buffers:%d", \
CFStringGetCStringPtr(deviceUID, kCFStringEncodingMacRoman),\
CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman), \
CFStringGetCStringPtr(deviceManufacturer, kCFStringEncodingMacRoman), \
numBuffers
);
for (UInt8 j = 0; j < numBuffers; j++) {
AudioBuffer ab = bufferList->mBuffers[j];
printf("\n#Channels: %d DataByteSize: %d", ab.mNumberChannels, ab.mDataByteSize);
}
free(bufferList), bufferList = NULL;
// Add a dictionary for this device to the array of input devices
CFStringRef keys [] = { CFSTR("deviceUID"), CFSTR("deviceName"), CFSTR("deviceManufacturer") };
CFStringRef values [] = { deviceUID, deviceName, deviceManufacturer };
CFDictionaryRef deviceDictionary = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)(keys),
(const void **)(values),
3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFArrayAppendValue(inputDeviceArray, deviceDictionary);
CFRelease(deviceDictionary), deviceDictionary = NULL;
}
free(audioDevices), audioDevices = NULL;
// Return a non-mutable copy of the array
CFArrayRef copy = CFArrayCreateCopy(kCFAllocatorDefault, inputDeviceArray);
CFRelease(inputDeviceArray), inputDeviceArray = NULL;
return copy;
}