AudioObjectGetPropertyData to get a list of input devices

后端 未结 4 1822
旧时难觅i
旧时难觅i 2020-11-30 04:15

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

相关标签:
4条回答
  • 2020-11-30 04:44

    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;
    }
    
    0 讨论(0)
  • 2020-11-30 04:46

    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
    }
    
    0 讨论(0)
  • 2020-11-30 05:05

    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.

    0 讨论(0)
  • 2020-11-30 05:06

    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;
    }
    
    0 讨论(0)
提交回复
热议问题