devdiff.m (5151B)
1 // devdiff.m — dump the CoreAudio properties that matter for stereo capture, for every 2 // input-capable device. Built to A/B Hydra against Loopback's virtual mic: same channel 3 // count but different downstream behaviour in WebRTC ⇒ the difference must be in format / 4 // channel layout / labels, which this prints. 5 // 6 // clang -framework CoreAudio -framework CoreFoundation devdiff.m -o devdiff && ./devdiff 7 #import <CoreAudio/CoreAudio.h> 8 #import <CoreFoundation/CoreFoundation.h> 9 #import <stdio.h> 10 11 static CFStringRef cfprop(AudioObjectID dev, AudioObjectPropertySelector sel) { 12 CFStringRef s = NULL; UInt32 z = sizeof(s); 13 AudioObjectPropertyAddress a = { sel, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; 14 if (AudioObjectGetPropertyData(dev, &a, 0, NULL, &z, &s) == noErr) return s; 15 return NULL; 16 } 17 static void pcf(const char *label, CFStringRef s) { 18 if (!s) { printf("%s(nil)", label); return; } 19 char buf[256]; CFStringGetCString(s, buf, sizeof(buf), kCFStringEncodingUTF8); 20 printf("%s%s", label, buf); 21 } 22 23 static const char *chanLabel(AudioChannelLabel l) { 24 switch (l) { 25 case kAudioChannelLabel_Left: return "L"; 26 case kAudioChannelLabel_Right: return "R"; 27 case kAudioChannelLabel_Center: return "C"; 28 case kAudioChannelLabel_Mono: return "Mono"; 29 case kAudioChannelLabel_Unknown: return "Unknown(0xFFFFFFFF)"; 30 case kAudioChannelLabel_Unused: return "Unused"; 31 case kAudioChannelLabel_Discrete: return "Discrete"; 32 default: 33 if ((l & 0xFFFF0000) == (kAudioChannelLabel_Discrete_0 & 0xFFFF0000)) return "Discrete_N"; 34 return "other"; 35 } 36 } 37 38 static void dumpLayout(AudioObjectID dev) { 39 AudioObjectPropertyAddress a = { 40 kAudioDevicePropertyPreferredChannelLayout, 41 kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain 42 }; 43 UInt32 z = 0; 44 if (AudioObjectGetPropertyDataSize(dev, &a, 0, NULL, &z) != noErr || z == 0) { 45 printf(" input channel layout: (none)\n"); 46 return; 47 } 48 AudioChannelLayout *lay = (AudioChannelLayout *)malloc(z); 49 if (AudioObjectGetPropertyData(dev, &a, 0, NULL, &z, lay) == noErr) { 50 printf(" input channel layout: tag=0x%X", lay->mChannelLayoutTag); 51 if (lay->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { 52 printf(" descriptions=%u [", lay->mNumberChannelDescriptions); 53 for (UInt32 i = 0; i < lay->mNumberChannelDescriptions; i++) { 54 printf("%s%s", i ? "," : "", chanLabel(lay->mChannelDescriptions[i].mChannelLabel)); 55 } 56 printf("]"); 57 } else if (lay->mChannelLayoutTag == kAudioChannelLayoutTag_Stereo) { 58 printf(" (Stereo L/R)"); 59 } else if (lay->mChannelLayoutTag == kAudioChannelLayoutTag_Mono) { 60 printf(" (Mono)"); 61 } 62 printf("\n"); 63 } 64 free(lay); 65 } 66 67 static void dumpFormat(AudioObjectID dev) { 68 AudioStreamBasicDescription f; UInt32 z = sizeof(f); 69 AudioObjectPropertyAddress a = { 70 kAudioDevicePropertyStreamFormat, 71 kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain 72 }; 73 if (AudioObjectGetPropertyData(dev, &a, 0, NULL, &z, &f) == noErr) { 74 printf(" input ASBD: %.0f Hz, %u ch, %u bits, flags=0x%X\n", 75 f.mSampleRate, f.mChannelsPerFrame, f.mBitsPerChannel, f.mFormatFlags); 76 } else { 77 printf(" input ASBD: (no input stream)\n"); 78 } 79 } 80 81 static UInt32 inChannels(AudioObjectID dev) { 82 AudioObjectPropertyAddress a = { 83 kAudioDevicePropertyStreamConfiguration, 84 kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain 85 }; 86 UInt32 z = 0; if (AudioObjectGetPropertyDataSize(dev, &a, 0, NULL, &z) != noErr || z == 0) return 0; 87 AudioBufferList *bl = (AudioBufferList *)malloc(z); UInt32 ch = 0; 88 if (AudioObjectGetPropertyData(dev, &a, 0, NULL, &z, bl) == noErr) 89 for (UInt32 i = 0; i < bl->mNumberBuffers; i++) ch += bl->mBuffers[i].mNumberChannels; 90 free(bl); return ch; 91 } 92 93 int main(void) { 94 AudioObjectPropertyAddress a = { 95 kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain 96 }; 97 UInt32 z = 0; 98 AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &a, 0, NULL, &z); 99 UInt32 n = z / sizeof(AudioObjectID); 100 AudioObjectID *devs = (AudioObjectID *)malloc(z); 101 AudioObjectGetPropertyData(kAudioObjectSystemObject, &a, 0, NULL, &z, devs); 102 103 for (UInt32 i = 0; i < n; i++) { 104 UInt32 ich = inChannels(devs[i]); 105 if (ich == 0) continue; // input-capable only 106 CFStringRef name = cfprop(devs[i], kAudioObjectPropertyName); 107 char nm[256] = ""; if (name) { CFStringGetCString(name, nm, sizeof(nm), kCFStringEncodingUTF8); CFRelease(name); } 108 printf("● %s (input ch=%u)\n", nm, ich); 109 dumpFormat(devs[i]); 110 dumpLayout(devs[i]); 111 CFStringRef uid = cfprop(devs[i], kAudioDevicePropertyDeviceUID); 112 pcf(" uid: ", uid); printf("\n"); if (uid) CFRelease(uid); 113 } 114 free(devs); 115 return 0; 116 }