obs-studio/libobs/audio-monitoring/osx/coreaudio-enum-devices.c
Luke Yelavich 002696e795 libobs: Rework checking Mac audio device capabilities
As of commit 2eb5a22, CoreAudio devices that use one device handle for
both input and output can no longer be used for audio monitoring. This
commit fixes that.

Tested with the built-in output, a Magewell XI100DUSB-HDMI which is
input only, and a MOTU UltraLite audio interface, which shows as
output/input capable.
2018-07-21 09:14:12 +10:00

195 lines
4.3 KiB
C

#include <CoreFoundation/CFString.h>
#include <CoreAudio/CoreAudio.h>
#include "../../obs-internal.h"
#include "../../util/dstr.h"
#include "mac-helpers.h"
static inline bool cf_to_cstr(CFStringRef ref, char *buf, size_t size)
{
if (!ref) return false;
return (bool)CFStringGetCString(ref, buf, size, kCFStringEncodingUTF8);
}
static bool obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
void *data, AudioDeviceID id, bool allow_inputs)
{
UInt32 size = 0;
CFStringRef cf_name = NULL;
CFStringRef cf_uid = NULL;
char name[1024];
char uid[1024];
OSStatus stat;
bool cont = true;
AudioObjectPropertyAddress addr = {
kAudioDevicePropertyStreams,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
/* Check if the device is capable of audio output. */
AudioObjectGetPropertyDataSize(id, &addr, 0, NULL, &size);
if (!allow_inputs && !size)
return true;
size = sizeof(CFStringRef);
addr.mSelector = kAudioDevicePropertyDeviceUID;
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_uid);
if (!success(stat, "get audio device UID"))
goto fail;
addr.mSelector = kAudioDevicePropertyDeviceNameCFString;
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_name);
if (!success(stat, "get audio device name"))
goto fail;
if (!cf_to_cstr(cf_name, name, sizeof(name))) {
blog(LOG_WARNING, "%s: failed to convert name", __FUNCTION__);
goto fail;
}
if (!cf_to_cstr(cf_uid, uid, sizeof(uid))) {
blog(LOG_WARNING, "%s: failed to convert uid", __FUNCTION__);
goto fail;
}
cont = cb(data, name, uid);
fail:
if (cf_name)
CFRelease(cf_name);
if (cf_uid)
CFRelease(cf_uid);
return cont;
}
static void enum_audio_devices(obs_enum_audio_device_cb cb, void *data,
bool allow_inputs)
{
AudioObjectPropertyAddress addr = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 size = 0;
UInt32 count;
OSStatus stat;
AudioDeviceID *ids;
stat = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
0, NULL, &size);
if (!success(stat, "get data size"))
return;
ids = malloc(size);
count = size / sizeof(AudioDeviceID);
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
0, NULL, &size, ids);
if (success(stat, "get data")) {
for (UInt32 i = 0; i < count; i++) {
if (!obs_enum_audio_monitoring_device(cb, data, ids[i],
allow_inputs))
break;
}
}
free(ids);
}
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
{
enum_audio_devices(cb, data, false);
}
static bool alloc_default_id(void *data, const char *name, const char *id)
{
char **p_id = data;
UNUSED_PARAMETER(name);
*p_id = bstrdup(id);
return false;
}
static void get_default_id(char **p_id)
{
AudioObjectPropertyAddress addr = {
kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
if (*p_id)
return;
OSStatus stat;
AudioDeviceID id = 0;
UInt32 size = sizeof(id);
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0,
NULL, &size, &id);
if (success(stat, "AudioObjectGetPropertyData"))
obs_enum_audio_monitoring_device(alloc_default_id, p_id, id,
true);
if (!*p_id)
*p_id = bzalloc(1);
}
struct device_name_info {
const char *id;
char *name;
};
static bool enum_device_name(void *data, const char *name, const char *id)
{
struct device_name_info *info = data;
if (strcmp(info->id, id) == 0) {
info->name = bstrdup(name);
return false;
}
return true;
}
bool devices_match(const char *id1, const char *id2)
{
struct device_name_info info = {0};
char *default_id = NULL;
char *name1 = NULL;
char *name2 = NULL;
bool match;
if (!id1 || !id2)
return false;
if (strcmp(id1, "default") == 0) {
get_default_id(&default_id);
id1 = default_id;
}
if (strcmp(id2, "default") == 0) {
get_default_id(&default_id);
id2 = default_id;
}
info.id = id1;
enum_audio_devices(enum_device_name, &info, true);
name1 = info.name;
info.name = NULL;
info.id = id2;
enum_audio_devices(enum_device_name, &info, true);
name2 = info.name;
match = name1 && name2 && strcmp(name1, name2) == 0;
bfree(default_id);
bfree(name1);
bfree(name2);
return match;
}