【问题标题】:AudioObjectGetPropertyData to get a list of input devicesAudioObjectGetPropertyData 获取输入设备列表
【发布时间】:2011-06-02 07:16:29
【问题描述】:

如何在 OS X 中使用AudioObjectGetPropertyData 来检索系统输入设备的列表?我目前有以下用于检索全局设备列表的虚拟代码:

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

AudioObjectPropertyAddress thePropertyAddress = { kAudioHardwarePropertyDevices, 
                                                  kAudioObjectPropertyScopeGlobal, 
                                                  kAudioObjectPropertyElementMaster };

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

【问题讨论】:

    标签: macos core-audio


    【解决方案1】:

    要确定一个设备是否是输入设备,您需要检查它是否有任何输入通道。

    这里是从Objective-C类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;
    }
    
    

    原来的sn-p代码是:

    这是我转换的一些应该可以工作的代码(虽然未经测试):

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

    【讨论】:

    • 我只是想说谢谢你的回答和代码 sn-p - 它非常有用!
    • 获取设备UID的原因是什么?是不是因为 UID 在拔出和插入的设备之间仍然存在,但 ID 可能会改变?
    • @andrewrk 是的,就是这样。
    • 啊,找到了这个文档:...但是堆栈溢出不会让我将它粘贴到评论中。 paste.ubuntu.com/11996131
    • 代码仅在以下情况下列出输入设备:沙盒被禁用或在权利中启用音频输入。
    【解决方案2】:

    这是我发现在遍历 CoreAudio 设备 ID 时从输出中排序输入的最佳方法。

    这只是循环内的部分:

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

    我希望这对其他人有所帮助,我最终发现 Apple 在 xcode/Extras/CoreAudio/HAL/HPBase 中提供了他们的 C++ HAL 接口的源代码,这是解决这个问题的关键。

    【讨论】:

      【解决方案3】:

      我稍微修改了“sbooth”提交的代码以打印所有输入设备以及编号。每个设备的缓冲区和没有。每个缓冲区的通道数。

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

      【讨论】:

      • 你如何在 Swift 中做到这一点?
      【解决方案4】:

      Swift 3.0 Xcode 8 Beta 5

      为此苦苦挣扎了一段时间,但现在似乎还可以。

      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
      }
      

      【讨论】:

        猜你喜欢
        • 2015-12-26
        • 2013-10-22
        • 1970-01-01
        • 2012-06-14
        • 2015-12-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多