2017-04-19 19:54:15 +00:00
|
|
|
#include <CoreFoundation/CFString.h>
|
|
|
|
#include <CoreAudio/CoreAudio.h>
|
|
|
|
|
|
|
|
#include "../../obs-internal.h"
|
|
|
|
#include "../../util/dstr.h"
|
2019-07-27 12:47:10 +00:00
|
|
|
#include "../../util/apple/cfstring-utils.h"
|
2017-04-19 19:54:15 +00:00
|
|
|
|
|
|
|
#include "mac-helpers.h"
|
|
|
|
|
2017-06-29 19:01:10 +00:00
|
|
|
static bool obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
2019-09-22 21:19:10 +00:00
|
|
|
void *data, AudioDeviceID id,
|
|
|
|
bool allow_inputs)
|
2017-04-19 19:54:15 +00:00
|
|
|
{
|
2019-09-22 21:19:10 +00:00
|
|
|
UInt32 size = 0;
|
2017-04-19 19:54:15 +00:00
|
|
|
CFStringRef cf_name = NULL;
|
2019-09-22 21:19:10 +00:00
|
|
|
CFStringRef cf_uid = NULL;
|
|
|
|
char *name = NULL;
|
|
|
|
char *uid = NULL;
|
|
|
|
OSStatus stat;
|
|
|
|
bool cont = true;
|
2017-04-19 19:54:15 +00:00
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
AudioObjectPropertyAddress addr = {kAudioDevicePropertyStreams,
|
|
|
|
kAudioDevicePropertyScopeOutput,
|
|
|
|
kAudioObjectPropertyElementMaster};
|
2017-04-19 19:54:15 +00:00
|
|
|
|
2018-12-16 16:14:58 +00:00
|
|
|
/* Check if the device is capable of audio output. */
|
|
|
|
AudioObjectGetPropertyDataSize(id, &addr, 0, NULL, &size);
|
|
|
|
if (!allow_inputs && !size)
|
|
|
|
return true;
|
2017-04-19 19:54:15 +00:00
|
|
|
|
|
|
|
size = sizeof(CFStringRef);
|
|
|
|
|
|
|
|
addr.mSelector = kAudioDevicePropertyDeviceUID;
|
|
|
|
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_uid);
|
|
|
|
if (!success(stat, "get audio device UID"))
|
2018-12-16 16:14:58 +00:00
|
|
|
goto fail;
|
2017-04-19 19:54:15 +00:00
|
|
|
|
|
|
|
addr.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
|
|
|
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_name);
|
|
|
|
if (!success(stat, "get audio device name"))
|
|
|
|
goto fail;
|
|
|
|
|
2019-07-27 12:47:10 +00:00
|
|
|
name = cfstr_copy_cstr(cf_name, kCFStringEncodingUTF8);
|
|
|
|
if (!name) {
|
2017-04-19 19:54:15 +00:00
|
|
|
blog(LOG_WARNING, "%s: failed to convert name", __FUNCTION__);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2019-07-27 12:47:10 +00:00
|
|
|
uid = cfstr_copy_cstr(cf_uid, kCFStringEncodingUTF8);
|
|
|
|
if (!uid) {
|
2017-04-19 19:54:15 +00:00
|
|
|
blog(LOG_WARNING, "%s: failed to convert uid", __FUNCTION__);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2017-06-29 19:01:10 +00:00
|
|
|
cont = cb(data, name, uid);
|
2017-04-19 19:54:15 +00:00
|
|
|
|
|
|
|
fail:
|
2019-07-27 12:47:10 +00:00
|
|
|
bfree(name);
|
|
|
|
bfree(uid);
|
2017-04-19 19:54:15 +00:00
|
|
|
if (cf_name)
|
|
|
|
CFRelease(cf_name);
|
|
|
|
if (cf_uid)
|
|
|
|
CFRelease(cf_uid);
|
2017-06-29 19:01:10 +00:00
|
|
|
return cont;
|
2017-04-19 19:54:15 +00:00
|
|
|
}
|
|
|
|
|
2017-06-29 19:01:10 +00:00
|
|
|
static void enum_audio_devices(obs_enum_audio_device_cb cb, void *data,
|
2019-09-22 21:19:10 +00:00
|
|
|
bool allow_inputs)
|
2017-04-19 19:54:15 +00:00
|
|
|
{
|
2019-09-22 21:19:10 +00:00
|
|
|
AudioObjectPropertyAddress addr = {kAudioHardwarePropertyDevices,
|
|
|
|
kAudioObjectPropertyScopeGlobal,
|
|
|
|
kAudioObjectPropertyElementMaster};
|
2017-04-19 19:54:15 +00:00
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
UInt32 size = 0;
|
|
|
|
UInt32 count;
|
|
|
|
OSStatus stat;
|
2017-04-19 19:54:15 +00:00
|
|
|
AudioDeviceID *ids;
|
|
|
|
|
|
|
|
stat = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
|
2019-09-22 21:19:10 +00:00
|
|
|
0, NULL, &size);
|
2017-04-19 19:54:15 +00:00
|
|
|
if (!success(stat, "get data size"))
|
|
|
|
return;
|
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
ids = malloc(size);
|
2017-04-19 19:54:15 +00:00
|
|
|
count = size / sizeof(AudioDeviceID);
|
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0,
|
|
|
|
NULL, &size, ids);
|
2017-04-19 19:54:15 +00:00
|
|
|
if (success(stat, "get data")) {
|
2017-06-29 19:01:10 +00:00
|
|
|
for (UInt32 i = 0; i < count; i++) {
|
|
|
|
if (!obs_enum_audio_monitoring_device(cb, data, ids[i],
|
2019-09-22 21:19:10 +00:00
|
|
|
allow_inputs))
|
2017-06-29 19:01:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-04-19 19:54:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(ids);
|
|
|
|
}
|
2017-06-29 19:01:10 +00:00
|
|
|
|
|
|
|
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,
|
2019-09-22 21:19:10 +00:00
|
|
|
kAudioObjectPropertyElementMaster};
|
2017-06-29 19:01:10 +00:00
|
|
|
|
|
|
|
if (*p_id)
|
|
|
|
return;
|
|
|
|
|
2019-09-22 21:19:10 +00:00
|
|
|
OSStatus stat;
|
2017-06-29 19:01:10 +00:00
|
|
|
AudioDeviceID id = 0;
|
2019-09-22 21:19:10 +00:00
|
|
|
UInt32 size = sizeof(id);
|
2017-06-29 19:01:10 +00:00
|
|
|
|
|
|
|
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0,
|
2019-09-22 21:19:10 +00:00
|
|
|
NULL, &size, &id);
|
2017-06-29 19:01:10 +00:00
|
|
|
if (success(stat, "AudioObjectGetPropertyData"))
|
|
|
|
obs_enum_audio_monitoring_device(alloc_default_id, p_id, id,
|
2019-09-22 21:19:10 +00:00
|
|
|
true);
|
2017-06-29 19:01:10 +00:00
|
|
|
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;
|
|
|
|
}
|