Browse Source

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
Shaolin 8 years ago
parent
commit
e006d961a4

+ 110 - 10
libobs/audio-monitoring/osx/coreaudio-enum-devices.c

@@ -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;
+}

+ 34 - 15
libobs/audio-monitoring/osx/coreaudio-output.c

@@ -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)

+ 63 - 0
libobs/audio-monitoring/win32/wasapi-enum-devices.c

@@ -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;
+}

+ 34 - 12
libobs/audio-monitoring/win32/wasapi-output.c

@@ -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);
 	}

+ 0 - 2
libobs/obs-source.c

@@ -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;
 

+ 4 - 2
libobs/obs-source.h

@@ -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)
 
 /** @} */
 

+ 1 - 1
plugins/linux-pulseaudio/pulse-input.c

@@ -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,

+ 1 - 1
plugins/mac-capture/mac-audio.c

@@ -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,

+ 1 - 1
plugins/win-wasapi/win-wasapi.cpp

@@ -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;