AudioObjectGetPropertyData to get a list of input devices

Anonymous picture Anonymous · Jan 1, 2011 · Viewed 14k times · Source

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 a global list of devices:

AudioDeviceID devices[12];
UInt32 arraySize = sizeof(devices);

AudioObjectPropertyAddress thePropertyAddress = { kAudioHardwarePropertyDevices, 
                                                  kAudioObjectPropertyScopeGlobal, 
                                                  kAudioObjectPropertyElementMaster };

AudioObjectGetPropertyData(kAudioObjectSystemObject, 
                           &thePropertyAddress, 
                           0, 
                           NULL, 
                           &arraySize, 
                           &devices);

Answer

sbooth picture sbooth · Jan 2, 2011

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;
}