hydra

Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
Log | Files | Refs | README | LICENSE

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 }