// macOS CoreAudio device model for SDL's coreaudio backend (macOS build). // FX_LIB_COREAUDIO: one virtual output device (the iPhone route), answering the AudioObject // properties SDL 2.24 queries. Playback itself uses the real iOS AudioQueue. // FX_LIB_AUDIOTOOLBOX: AudioQueueSetProperty passes through, except macOS-only CurrentDevice. #include #include #include #include #include "fx.h" #define FOURCC(a, b, c, d) ((uint32_t)(a) << 24 | (uint32_t)(b) << 16 | (uint32_t)(c) << 8 | (uint32_t)(d)) #ifdef FX_LIB_COREAUDIO typedef uint32_t AudioObjectID; typedef struct { uint32_t mSelector, mScope, mElement; } FXPropertyAddress; typedef OSStatus (*FXPropertyListener)(AudioObjectID, uint32_t, const FXPropertyAddress *, void *); enum { kSystemObject = 1, kSpeaker = 0x100, kDevices = FOURCC('d', 'e', 'v', '#'), kDefaultOutput = FOURCC('d', 'O', 'u', 't'), kDefaultInput = FOURCC('d', 'I', 'n', ' '), kStreamConfiguration = FOURCC('s', 'l', 'a', 'y'), kName = FOURCC('l', 'n', 'a', 'm'), kNominalSampleRate = FOURCC('n', 's', 'r', 't'), kIsAlive = FOURCC('l', 'i', 'v', 'n'), kHogMode = FOURCC('o', 'i', 'n', 'k'), kUID = FOURCC('u', 'i', 'd', ' '), kScopeInput = FOURCC('i', 'n', 'p', 't'), kUnknownProperty = FOURCC('w', 'h', 'o', '?'), kBadObject = FOURCC('!', 'o', 'b', 'j'), }; // Writes the property into `out` (when non-NULL) and returns its size, or 0 if unsupported. static uint32_t fx_property(AudioObjectID object, const FXPropertyAddress *a, void *out, OSStatus *status) { *status = 0; if (object == kSystemObject) { switch (a->mSelector) { case kDevices: case kDefaultOutput: if (out) *(AudioObjectID *)out = kSpeaker; return 4; case kDefaultInput: if (out) *(AudioObjectID *)out = 0; return 4; // no capture device } } else if (object == kSpeaker) { int input = a->mScope == kScopeInput; switch (a->mSelector) { case kStreamConfiguration: if (out) { AudioBufferList *list = out; list->mNumberBuffers = input ? 0 : 1; list->mBuffers[0] = (AudioBuffer){2, 0, NULL}; } return sizeof(AudioBufferList); case kName: if (out) *(CFStringRef *)out = CFStringCreateCopy(NULL, CFSTR("iPhone")); return sizeof(CFStringRef); case kUID: if (out) *(CFStringRef *)out = CFStringCreateCopy(NULL, CFSTR("fx-ios-output")); return sizeof(CFStringRef); case kNominalSampleRate: if (out) *(Float64 *)out = 48000.0; return sizeof(Float64); case kIsAlive: if (out) *(uint32_t *)out = !input; return 4; case kHogMode: if (out) *(pid_t *)out = -1; return sizeof(pid_t); } } else { *status = kBadObject; return 0; } *status = kUnknownProperty; fx_log("[fx] CoreAudio: unsupported property '%c%c%c%c' on object %u", a->mSelector >> 24, a->mSelector >> 16, a->mSelector >> 8, a->mSelector, object); return 0; } FX_EXPORT Boolean AudioObjectHasProperty(AudioObjectID object, const FXPropertyAddress *a) { OSStatus status; return fx_property(object, a, NULL, &status) && !status; } FX_EXPORT OSStatus AudioObjectGetPropertyDataSize(AudioObjectID object, const FXPropertyAddress *a, uint32_t qsize, const void *q, uint32_t *size) { OSStatus status; uint32_t n = fx_property(object, a, NULL, &status); if (!status && size) *size = n; return status; } FX_EXPORT OSStatus AudioObjectGetPropertyData(AudioObjectID object, const FXPropertyAddress *a, uint32_t qsize, const void *q, uint32_t *size, void *data) { OSStatus status; uint32_t n = fx_property(object, a, NULL, &status); if (status) return status; if (!size || *size < n) return FOURCC('!', 's', 'i', 'z'); fx_property(object, a, data, &status); *size = n; return status; } FX_EXPORT OSStatus AudioObjectAddPropertyListener(AudioObjectID o, const FXPropertyAddress *a, FXPropertyListener l, void *c) { return 0; } FX_EXPORT OSStatus AudioObjectRemovePropertyListener(AudioObjectID o, const FXPropertyAddress *a, FXPropertyListener l, void *c) { return 0; } #endif #ifdef FX_LIB_AUDIOTOOLBOX FX_EXPORT OSStatus AudioQueueSetProperty(AudioQueueRef queue, AudioQueuePropertyID id, const void *data, UInt32 size) { static OSStatus (*real)(AudioQueueRef, AudioQueuePropertyID, const void *, UInt32); if (!real) { void *toolbox = dlopen("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox", RTLD_LAZY | RTLD_NOLOAD); real = toolbox ? dlsym(toolbox, "AudioQueueSetProperty") : NULL; } if (id == FOURCC('a', 'q', 'c', 'd')) return 0; // kAudioQueueProperty_CurrentDevice: iOS routes itself return real ? real(queue, id, data, size) : -1; } #endif