Anonymous
Anonymous

Reputation: 1820

AudioObjectGetPropertyData to get a list of input devices

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

Upvotes: 20

Views: 18630

Answers (4)

sbooth
sbooth

Reputation: 16976

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

Upvotes: 33

user4181645
user4181645

Reputation:

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
}

Upvotes: 12

Raunak
Raunak

Reputation: 3414

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

Upvotes: 4

voidref
voidref

Reputation: 1123

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.

Upvotes: 8

Related Questions