audio-monitoring: Add ability to monitor Outputs
(Note: This commits also modifies the linux-pulseaudio, mac-capture, and win-wasapi plugins) Do not prevent the targeted output device from being monitored if the selected monitor output device is a different one. Closes jp9000/obs-studio#872
This commit is contained in:
parent
162450c882
commit
e006d961a4
@ -12,8 +12,8 @@ static inline bool cf_to_cstr(CFStringRef ref, char *buf, size_t size)
|
||||
return (bool)CFStringGetCString(ref, buf, size, kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
static void obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
||||
void *data, AudioDeviceID id)
|
||||
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;
|
||||
@ -21,6 +21,7 @@ static void obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
||||
char name[1024];
|
||||
char uid[1024];
|
||||
OSStatus stat;
|
||||
bool cont = true;
|
||||
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioDevicePropertyStreams,
|
||||
@ -29,16 +30,18 @@ static void obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
||||
};
|
||||
|
||||
/* check to see if it's a mac input device */
|
||||
AudioObjectGetPropertyDataSize(id, &addr, 0, NULL, &size);
|
||||
if (!size)
|
||||
return;
|
||||
if (!allow_inputs) {
|
||||
AudioObjectGetPropertyDataSize(id, &addr, 0, NULL, &size);
|
||||
if (!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"))
|
||||
return;
|
||||
return true;
|
||||
|
||||
addr.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_name);
|
||||
@ -55,16 +58,18 @@ static void obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cb(data, name, uid);
|
||||
cont = cb(data, name, uid);
|
||||
|
||||
fail:
|
||||
if (cf_name)
|
||||
CFRelease(cf_name);
|
||||
if (cf_uid)
|
||||
CFRelease(cf_uid);
|
||||
return cont;
|
||||
}
|
||||
|
||||
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
|
||||
static void enum_audio_devices(obs_enum_audio_device_cb cb, void *data,
|
||||
bool allow_inputs)
|
||||
{
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
@ -88,9 +93,104 @@ void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
|
||||
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
|
||||
0, NULL, &size, ids);
|
||||
if (success(stat, "get data")) {
|
||||
for (UInt32 i = 0; i < count; i++)
|
||||
obs_enum_audio_monitoring_device(cb, data, ids[i]);
|
||||
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;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ struct audio_monitor {
|
||||
|
||||
volatile bool active;
|
||||
bool paused;
|
||||
bool ignore;
|
||||
};
|
||||
|
||||
static inline bool fill_buffer(struct audio_monitor *monitor)
|
||||
@ -137,7 +138,10 @@ static void buffer_audio(void *data, AudioQueueRef aq, AudioQueueBufferRef buf)
|
||||
UNUSED_PARAMETER(aq);
|
||||
}
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
extern bool devices_match(const char *id1, const char *id2);
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
{
|
||||
const struct audio_output_info *info = audio_output_get_info(
|
||||
obs->audio.audio);
|
||||
@ -156,6 +160,8 @@ static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
.mBitsPerChannel = sizeof(float) * 8
|
||||
};
|
||||
|
||||
monitor->source = source;
|
||||
|
||||
monitor->channels = channels;
|
||||
monitor->buffer_size =
|
||||
channels * sizeof(float) * info->samples_per_sec / 100 * 3;
|
||||
@ -163,14 +169,26 @@ static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
|
||||
pthread_mutex_init_value(&monitor->mutex);
|
||||
|
||||
stat = AudioQueueNewOutput(&desc, buffer_audio, monitor, NULL, NULL, 0,
|
||||
&monitor->queue);
|
||||
if (!success(stat, "AudioStreamBasicDescription")) {
|
||||
const char *uid = obs->audio.monitoring_device_id;
|
||||
if (!uid || !*uid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *uid = obs->audio.monitoring_device_id;
|
||||
if (!uid || !*uid) {
|
||||
if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) {
|
||||
obs_data_t *s = obs_source_get_settings(source);
|
||||
const char *s_dev_id = obs_data_get_string(s, "device_id");
|
||||
bool match = devices_match(s_dev_id, uid);
|
||||
obs_data_release(s);
|
||||
|
||||
if (match) {
|
||||
monitor->ignore = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
stat = AudioQueueNewOutput(&desc, buffer_audio, monitor, NULL, NULL, 0,
|
||||
&monitor->queue);
|
||||
if (!success(stat, "AudioStreamBasicDescription")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -266,19 +284,20 @@ static void audio_monitor_free(struct audio_monitor *monitor)
|
||||
pthread_mutex_destroy(&monitor->mutex);
|
||||
}
|
||||
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor)
|
||||
{
|
||||
monitor->source = source;
|
||||
obs_source_add_audio_capture_callback(source, on_audio_playback,
|
||||
monitor);
|
||||
if (monitor->ignore)
|
||||
return;
|
||||
|
||||
obs_source_add_audio_capture_callback(monitor->source,
|
||||
on_audio_playback, monitor);
|
||||
}
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
{
|
||||
struct audio_monitor *monitor = bzalloc(sizeof(*monitor));
|
||||
|
||||
if (!audio_monitor_init(monitor)) {
|
||||
if (!audio_monitor_init(monitor, source)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -286,7 +305,7 @@ struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
da_push_back(obs->audio.monitors, &monitor);
|
||||
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
|
||||
|
||||
audio_monitor_init_final(monitor, source);
|
||||
audio_monitor_init_final(monitor);
|
||||
return monitor;
|
||||
|
||||
fail:
|
||||
@ -303,9 +322,9 @@ void audio_monitor_reset(struct audio_monitor *monitor)
|
||||
audio_monitor_free(monitor);
|
||||
memset(monitor, 0, sizeof(*monitor));
|
||||
|
||||
success = audio_monitor_init(monitor);
|
||||
success = audio_monitor_init(monitor, source);
|
||||
if (success)
|
||||
audio_monitor_init_final(monitor, source);
|
||||
audio_monitor_init_final(monitor);
|
||||
}
|
||||
|
||||
void audio_monitor_destroy(struct audio_monitor *monitor)
|
||||
|
@ -103,3 +103,66 @@ fail:
|
||||
safe_release(enumerator);
|
||||
safe_release(collection);
|
||||
}
|
||||
|
||||
static void get_default_id(char **p_id)
|
||||
{
|
||||
IMMDeviceEnumerator *immde = NULL;
|
||||
IMMDevice *device = NULL;
|
||||
WCHAR *w_id = NULL;
|
||||
HRESULT hr;
|
||||
|
||||
if (*p_id)
|
||||
return;
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, &immde);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = immde->lpVtbl->GetDefaultAudioEndpoint(immde,
|
||||
eRender, eConsole, &device);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = device->lpVtbl->GetId(device, &w_id);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
os_wcs_to_utf8_ptr(w_id, 0, p_id);
|
||||
|
||||
fail:
|
||||
if (!*p_id)
|
||||
*p_id = bzalloc(1);
|
||||
if (immde)
|
||||
immde->lpVtbl->Release(immde);
|
||||
if (device)
|
||||
device->lpVtbl->Release(device);
|
||||
if (w_id)
|
||||
CoTaskMemFree(w_id);
|
||||
}
|
||||
|
||||
bool devices_match(const char *id1, const char *id2)
|
||||
{
|
||||
char *default_id = 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;
|
||||
}
|
||||
|
||||
match = strcmp(id1, id2) == 0;
|
||||
bfree(default_id);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ struct audio_monitor {
|
||||
uint32_t sample_rate;
|
||||
uint32_t channels;
|
||||
bool source_has_video : 1;
|
||||
bool ignore : 1;
|
||||
|
||||
int64_t lowest_audio_offset;
|
||||
struct circlebuf delay_buffer;
|
||||
@ -203,6 +204,9 @@ unlock:
|
||||
|
||||
static inline void audio_monitor_free(struct audio_monitor *monitor)
|
||||
{
|
||||
if (monitor->ignore)
|
||||
return;
|
||||
|
||||
if (monitor->source) {
|
||||
obs_source_remove_audio_capture_callback(
|
||||
monitor->source, on_audio_playback, monitor);
|
||||
@ -235,7 +239,10 @@ static enum speaker_layout convert_speaker_layout(DWORD layout, WORD channels)
|
||||
return (enum speaker_layout)channels;
|
||||
}
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
extern bool devices_match(const char *id1, const char *id2);
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
{
|
||||
IMMDeviceEnumerator *immde = NULL;
|
||||
WAVEFORMATEX *wfex = NULL;
|
||||
@ -243,12 +250,26 @@ static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
UINT32 frames;
|
||||
HRESULT hr;
|
||||
|
||||
pthread_mutex_init_value(&monitor->playback_mutex);
|
||||
|
||||
monitor->source = source;
|
||||
|
||||
const char *id = obs->audio.monitoring_device_id;
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_init_value(&monitor->playback_mutex);
|
||||
if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) {
|
||||
obs_data_t *s = obs_source_get_settings(source);
|
||||
const char *s_dev_id = obs_data_get_string(s, "device_id");
|
||||
bool match = devices_match(s_dev_id, id);
|
||||
obs_data_release(s);
|
||||
|
||||
if (match) {
|
||||
monitor->ignore = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------ *
|
||||
* Init device */
|
||||
@ -352,14 +373,15 @@ fail:
|
||||
return success;
|
||||
}
|
||||
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor)
|
||||
{
|
||||
monitor->source = source;
|
||||
if (monitor->ignore)
|
||||
return;
|
||||
|
||||
monitor->source_has_video =
|
||||
(source->info.output_flags & OBS_SOURCE_VIDEO) != 0;
|
||||
obs_source_add_audio_capture_callback(source, on_audio_playback,
|
||||
monitor);
|
||||
(monitor->source->info.output_flags & OBS_SOURCE_VIDEO) != 0;
|
||||
obs_source_add_audio_capture_callback(monitor->source,
|
||||
on_audio_playback, monitor);
|
||||
}
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
@ -367,7 +389,7 @@ struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
struct audio_monitor monitor = {0};
|
||||
struct audio_monitor *out;
|
||||
|
||||
if (!audio_monitor_init(&monitor)) {
|
||||
if (!audio_monitor_init(&monitor, source)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -377,7 +399,7 @@ struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
da_push_back(obs->audio.monitors, &out);
|
||||
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
|
||||
|
||||
audio_monitor_init_final(out, source);
|
||||
audio_monitor_init_final(out);
|
||||
return out;
|
||||
|
||||
fail:
|
||||
@ -391,14 +413,14 @@ void audio_monitor_reset(struct audio_monitor *monitor)
|
||||
bool success;
|
||||
|
||||
pthread_mutex_lock(&monitor->playback_mutex);
|
||||
success = audio_monitor_init(&new_monitor);
|
||||
success = audio_monitor_init(&new_monitor, monitor->source);
|
||||
pthread_mutex_unlock(&monitor->playback_mutex);
|
||||
|
||||
if (success) {
|
||||
obs_source_t *source = monitor->source;
|
||||
audio_monitor_free(monitor);
|
||||
*monitor = new_monitor;
|
||||
audio_monitor_init_final(monitor, source);
|
||||
audio_monitor_init_final(monitor);
|
||||
} else {
|
||||
audio_monitor_free(&new_monitor);
|
||||
}
|
||||
|
@ -3997,8 +3997,6 @@ void obs_source_set_monitoring_type(obs_source_t *source,
|
||||
|
||||
if (!obs_source_valid(source, "obs_source_set_monitoring_type"))
|
||||
return;
|
||||
if (source->info.output_flags & OBS_SOURCE_DO_NOT_MONITOR)
|
||||
return;
|
||||
if (source->monitoring_type == type)
|
||||
return;
|
||||
|
||||
|
@ -123,10 +123,12 @@ enum obs_source_type {
|
||||
/**
|
||||
* Source cannot have its audio monitored
|
||||
*
|
||||
* Specifies that this source may cause a feedback loop if audio is monitored.
|
||||
* Specifies that this source may cause a feedback loop if audio is monitored
|
||||
* with a device selected as desktop audio.
|
||||
*
|
||||
* This is used primarily with desktop audio capture sources.
|
||||
*/
|
||||
#define OBS_SOURCE_DO_NOT_MONITOR (1<<9)
|
||||
#define OBS_SOURCE_DO_NOT_SELF_MONITOR (1<<9)
|
||||
|
||||
/** @} */
|
||||
|
||||
|
@ -538,7 +538,7 @@ struct obs_source_info pulse_output_capture = {
|
||||
.type = OBS_SOURCE_TYPE_INPUT,
|
||||
.output_flags = OBS_SOURCE_AUDIO |
|
||||
OBS_SOURCE_DO_NOT_DUPLICATE |
|
||||
OBS_SOURCE_DO_NOT_MONITOR,
|
||||
OBS_SOURCE_DO_NOT_SELF_MONITOR,
|
||||
.get_name = pulse_output_getname,
|
||||
.create = pulse_create,
|
||||
.destroy = pulse_destroy,
|
||||
|
@ -798,7 +798,7 @@ struct obs_source_info coreaudio_output_capture_info = {
|
||||
.type = OBS_SOURCE_TYPE_INPUT,
|
||||
.output_flags = OBS_SOURCE_AUDIO |
|
||||
OBS_SOURCE_DO_NOT_DUPLICATE |
|
||||
OBS_SOURCE_DO_NOT_MONITOR,
|
||||
OBS_SOURCE_DO_NOT_SELF_MONITOR,
|
||||
.get_name = coreaudio_output_getname,
|
||||
.create = coreaudio_create_output_capture,
|
||||
.destroy = coreaudio_destroy,
|
||||
|
@ -590,7 +590,7 @@ void RegisterWASAPIOutput()
|
||||
info.type = OBS_SOURCE_TYPE_INPUT;
|
||||
info.output_flags = OBS_SOURCE_AUDIO |
|
||||
OBS_SOURCE_DO_NOT_DUPLICATE |
|
||||
OBS_SOURCE_DO_NOT_MONITOR;
|
||||
OBS_SOURCE_DO_NOT_SELF_MONITOR;
|
||||
info.get_name = GetWASAPIOutputName;
|
||||
info.create = CreateWASAPIOutput;
|
||||
info.destroy = DestroyWASAPISource;
|
||||
|
Loading…
x
Reference in New Issue
Block a user