jpenguin icon

obs-pule-monitor

jpenguin | PRO | 07/17/17 06:30:25 PM UTC | 0 ⭐ | 1055 👁️ | Never ⏰ | []
text |

36.08 KB

|

None

|

0 👍

/

0 👎

diff --git a/UI/adv-audio-control.cpp b/UI/adv-audio-control.cppindex b7bfd4d08..531b00173 100644--- a/UI/adv-audio-control.cpp+++ b/UI/adv-audio-control.cpp@@ -32,7 +32,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) 	volume                         = new QSpinBox(); 	forceMono                      = new QCheckBox(); 	panning                        = new QSlider(Qt::Horizontal);-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	monitoringType                 = new QComboBox(); #endif 	syncOffset                     = new QSpinBox();@@ -93,7 +93,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) 	syncOffset->setValue(int(cur_sync / NSEC_PER_MSEC));  	int idx;-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.None"), 			(int)OBS_MONITORING_TYPE_NONE); 	monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.MonitorOnly"),@@ -138,7 +138,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) 			this, SLOT(panningChanged(int))); 	QWidget::connect(syncOffset, SIGNAL(valueChanged(int)), 			this, SLOT(syncOffsetChanged(int)));-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	QWidget::connect(monitoringType, SIGNAL(currentIndexChanged(int)), 			this, SLOT(monitoringTypeChanged(int))); #endif@@ -163,7 +163,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) 	layout->addWidget(forceMonoContainer, lastRow, idx++); 	layout->addWidget(panningContainer, lastRow, idx++); 	layout->addWidget(syncOffset, lastRow, idx++);-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	layout->addWidget(monitoringType, lastRow, idx++); #endif 	layout->addWidget(mixerContainer, lastRow, idx++);@@ -178,7 +178,7 @@ OBSAdvAudioCtrl::~OBSAdvAudioCtrl() 	forceMonoContainer->deleteLater(); 	panningContainer->deleteLater(); 	syncOffset->deleteLater();-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	monitoringType->deleteLater(); #endif 	mixerContainer->deleteLater();diff --git a/UI/window-basic-adv-audio.cpp b/UI/window-basic-adv-audio.cppindex 33fe0cb5a..2894f6bf2 100644--- a/UI/window-basic-adv-audio.cpp+++ b/UI/window-basic-adv-audio.cpp@@ -42,7 +42,7 @@ OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent) 	label = new QLabel(QTStr("Basic.AdvAudio.SyncOffset")); 	label->setAlignment(Qt::AlignHCenter); 	mainLayout->addWidget(label, 0, idx++);-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	label = new QLabel(QTStr("Basic.AdvAudio.Monitoring")); 	label->setAlignment(Qt::AlignHCenter); 	mainLayout->addWidget(label, 0, idx++);diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cppindex 52f10b9b8..5785b1543 100644--- a/UI/window-basic-settings.cpp+++ b/UI/window-basic-settings.cpp@@ -410,7 +410,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) 	HookWidget(ui->colorRange,           COMBO_CHANGED,  ADV_CHANGED); 	HookWidget(ui->disableOSXVSync,      CHECK_CHANGED,  ADV_CHANGED); 	HookWidget(ui->resetOSXVSync,        CHECK_CHANGED,  ADV_CHANGED);-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	HookWidget(ui->monitoringDevice,     COMBO_CHANGED,  ADV_CHANGED); #endif #ifdef _WIN32@@ -431,15 +431,11 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) 	HookWidget(ui->enableNewSocketLoop,  CHECK_CHANGED,  ADV_CHANGED); 	HookWidget(ui->enableLowLatencyMode, CHECK_CHANGED,  ADV_CHANGED); -#if !defined(_WIN32) && !defined(__APPLE__)-	delete ui->monitoringDevice;-	delete ui->monitoringDeviceLabel;-	delete ui->advAudioGroupBox;+#if !defined(_WIN32) && !defined(__APPLE__) && !HAVE_PULSEAUDIO 	delete ui->enableAutoUpdates;-	ui->monitoringDevice = nullptr;-	ui->monitoringDeviceLabel = nullptr;-	ui->advAudioGroupBox = nullptr;+	delete ui->advAudioGroupBox; 	ui->enableAutoUpdates = nullptr;+	ui->advAudioGroupBox = nullptr; #endif  #ifdef _WIN32@@ -484,7 +480,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) 	delete ui->advancedGeneralGroupBox; 	delete ui->enableNewSocketLoop; 	delete ui->enableLowLatencyMode;-#ifdef __APPLE__+#if defined(__APPLE__) || HAVE_PULSEAUDIO 	delete ui->disableAudioDucking; #endif 	ui->rendererLabel = nullptr;@@ -496,7 +492,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) 	ui->advancedGeneralGroupBox = nullptr; 	ui->enableNewSocketLoop = nullptr; 	ui->enableLowLatencyMode = nullptr;-#ifdef __APPLE__+#if defined(__APPLE__) || HAVE_PULSEAUDIO 	ui->disableAudioDucking = nullptr; #endif #endif@@ -583,7 +579,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)  	FillSimpleRecordingValues(); 	FillSimpleStreamingValues();-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	FillAudioMonitoringDevices(); #endif @@ -2025,7 +2021,7 @@ void OBSBasicSettings::LoadAdvancedSettings() 			"Video", "ColorSpace"); 	const char *videoColorRange = config_get_string(main->Config(), 			"Video", "ColorRange");-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	const char *monDevName = config_get_string(main->Config(), "Audio", 			"MonitoringDeviceName"); 	const char *monDevId = config_get_string(main->Config(), "Audio",@@ -2059,7 +2055,7 @@ void OBSBasicSettings::LoadAdvancedSettings()  	LoadRendererList(); -#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	if (!SetComboByValue(ui->monitoringDevice, monDevId)) 		SetInvalidValue(ui->monitoringDevice, monDevName, monDevId); #endif@@ -2622,7 +2618,7 @@ void OBSBasicSettings::SaveAdvancedSettings() 	SaveCombo(ui->colorFormat, "Video", "ColorFormat"); 	SaveCombo(ui->colorSpace, "Video", "ColorSpace"); 	SaveComboData(ui->colorRange, "Video", "ColorRange");-#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	SaveCombo(ui->monitoringDevice, "Audio", "MonitoringDeviceName"); 	SaveComboData(ui->monitoringDevice, "Audio", "MonitoringDeviceId"); #endif@@ -2648,7 +2644,7 @@ void OBSBasicSettings::SaveAdvancedSettings() 	SaveSpinBox(ui->reconnectMaxRetries, "Output", "MaxRetries"); 	SaveComboData(ui->bindToIP, "Output", "BindIP"); -#if defined(_WIN32) || defined(__APPLE__)+#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO 	QString newDevice = ui->monitoringDevice->currentData().toString();  	if (lastMonitoringDevice != newDevice) {diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txtindex cd2b80e1e..e86bb8f2b 100644--- a/libobs/CMakeLists.txt+++ b/libobs/CMakeLists.txt@@ -12,12 +12,20 @@ if (NOT "${FFMPEG_AVCODEC_LIBRARIES}" STREQUAL "") endif()  if(UNIX)+	find_package(PulseAudio)+	if (NOT "${PULSEAUDIO_LIBRARY}" STREQUAL "")+		message(STATUS "Found PulseAudio - Audio Monitor enabled")+		set(HAVE_PULSEAUDIO "1")+	else()+		set(HAVE_PULSEAUDIO "0")+	endif() 	find_package(DBus QUIET) 	if (NOT APPLE) 		find_package(X11_XCB REQUIRED) 	endif() else() 	set(HAVE_DBUS "0")+	set(HAVE_PULSEAUDIO "0") endif()  find_package(ImageMagick QUIET COMPONENTS MagickCore)@@ -145,12 +153,22 @@ elseif(UNIX) 		util/threading-posix.c 		util/pipe-posix.c 		util/platform-nix.c)+ 	set(libobs_PLATFORM_HEADERS 		util/threading-posix.h)-	set(libobs_audio_monitoring_SOURCES-		audio-monitoring/null/null-audio-monitoring.c-		) +	if(HAVE_PULSEAUDIO)+		set(libobs_audio_monitoring_HEADERS+			audio-monitoring/pulse/pulseaudio-wrapper.h)++		set(libobs_audio_monitoring_SOURCES+			audio-monitoring/pulse/pulseaudio-wrapper.c+			audio-monitoring/pulse/pulseaudio-enum-devices.c+			audio-monitoring/pulse/pulseaudio-output.c)+	else()+		set(libobs_audio_monitoring_SOURCES+			audio-monitoring/null/null-audio-monitoring.c)+	endif() 	if(DBUS_FOUND) 		set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES} 			util/platform-nix-dbus.c)@@ -168,6 +186,12 @@ elseif(UNIX) 		${libobs_PLATFORM_DEPS} 		${X11_XCB_LIBRARIES}) +	if(HAVE_PULSEAUDIO)+		set(libobs_PLATFORM_DEPS+			${libobs_PLATFORM_DEPS}+			${PULSEAUDIO_LIBRARY})+	endif()+ 	if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 		# use the sysinfo compatibility library on bsd 		find_package(Libsysinfo REQUIRED)@@ -358,7 +382,9 @@ set(libobs_SOURCES 	${libobs_graphics_SOURCES} 	${libobs_mediaio_SOURCES} 	${libobs_util_SOURCES}-	${libobs_libobs_SOURCES})+	${libobs_libobs_SOURCES}+	${libobs_audio_monitoring_SOURCES}+	)  set(libobs_HEADERS 	${libobs_config_HEADERS}@@ -367,7 +393,6 @@ set(libobs_HEADERS 	${libobs_mediaio_HEADERS} 	${libobs_util_HEADERS} 	${libobs_libobs_HEADERS}-	${libobs_audio_monitoring_SOURCES} 	${libobs_audio_monitoring_HEADERS} 	) diff --git a/libobs/audio-monitoring/null/null-audio-monitoring.c b/libobs/audio-monitoring/null/null-audio-monitoring.cindex 9d7b2a4ed..7926e84f6 100644--- a/libobs/audio-monitoring/null/null-audio-monitoring.c+++ b/libobs/audio-monitoring/null/null-audio-monitoring.c@@ -1,4 +1,4 @@-#include "../../obs-internal.h"+#include <obs-internal.h>  void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data) {diff --git a/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.cnew file mode 100644index 000000000..f59882997--- /dev/null+++ b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c@@ -0,0 +1,33 @@+#include <obs-internal.h>+#include "pulseaudio-wrapper.h"++static void pulseaudio_output_info(pa_context *c, const pa_source_info *i,+		int eol, void *userdata)+{+	UNUSED_PARAMETER(c);+	if (eol != 0 || i->monitor_of_sink == PA_INVALID_INDEX)+		goto skip;++	struct enum_cb *ecb = (struct enum_cb *) userdata;+	if (ecb->cont)+		ecb->cont = ecb->cb(ecb->data, i->description, i->name);++skip:+	pulseaudio_signal(0);+}++void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,+		void *data)+{+	struct enum_cb *ecb = bzalloc(sizeof(struct enum_cb));+	ecb->cb = cb;+	ecb->data = data;+	ecb->cont = 1;++	pulseaudio_init();+	pa_source_info_cb_t pa_cb = pulseaudio_output_info;+	pulseaudio_get_source_info_list(pa_cb, (void *) ecb);+	pulseaudio_unref();++	bfree(ecb);+}diff --git a/libobs/audio-monitoring/pulse/pulseaudio-output.c b/libobs/audio-monitoring/pulse/pulseaudio-output.cnew file mode 100644index 000000000..b182eb088--- /dev/null+++ b/libobs/audio-monitoring/pulse/pulseaudio-output.c@@ -0,0 +1,469 @@+#include "obs-internal.h"+#include "pulseaudio-wrapper.h"++#define PULSE_DATA(voidptr) struct audio_monitor *data = voidptr;+#define blog(level, msg, ...) blog(level, "pulse-am: " msg, ##__VA_ARGS__)++struct audio_monitor {+	obs_source_t *source;+	pa_stream *stream;+	char *device;++	enum speaker_layout speakers;+	pa_sample_format_t format;+	uint_fast32_t samples_per_sec;+	uint_fast32_t bytes_per_frame;+	uint_fast8_t channels;++	uint_fast32_t packets;+	uint_fast64_t frames;++	struct circlebuf new_data;+	audio_resampler_t *resampler;+	size_t buffer_size;+	size_t bytesRemaining;++	size_t bytes_per_channel;+	bool ignore : 1;+	pthread_mutex_t playback_mutex;+};++static enum speaker_layout pulseaudio_channels_to_obs_speakers(+		uint_fast32_t channels)+{+	if ((channels >= 1 && channels <= 6) || channels == 8)+		return (enum speaker_layout) channels;+	return SPEAKERS_UNKNOWN;+}++static enum audio_format pulseaudio_to_obs_audio_format(+		pa_sample_format_t format)+{+	switch (format) {+	case PA_SAMPLE_U8:+		return AUDIO_FORMAT_U8BIT;+	case PA_SAMPLE_S16LE:+		return AUDIO_FORMAT_16BIT;+	case PA_SAMPLE_S32LE:+		return AUDIO_FORMAT_32BIT;+	case PA_SAMPLE_FLOAT32LE:+		return AUDIO_FORMAT_FLOAT;+	default:+		return AUDIO_FORMAT_UNKNOWN;+	}+}++static void process_byte(void *p, size_t frames, size_t channels, float vol)+{+	register char *cur = (char *) p;+	register char *end = cur + frames * channels;++	while (cur < end)+		*(cur++) *= vol;+}++static void process_short(void *p, size_t frames, size_t channels, float vol)+{+	register short *cur = (short *) p;+	register short *end = cur + frames * channels;++	while (cur < end)+		*(cur++) *= vol;+}++static void process_float(void *p, size_t frames, size_t channels, float vol)+{+	register float *cur = (float *) p;+	register float *end = cur + frames * channels;++	while (cur < end)+		*(cur++) *= vol;+}++void process_volume(const struct audio_monitor *monitor, float vol,+		uint8_t *const *resample_data, uint32_t resample_frames)+{+	switch (monitor->bytes_per_channel) {+	case 1:+		process_byte(resample_data[0], resample_frames,+				monitor->channels, vol);+		break;+	case 2:+		process_short(resample_data[0], resample_frames,+				monitor->channels, vol);+		break;+	default:+		process_float(resample_data[0], resample_frames,+				monitor->channels, vol);+		break;+	}+}++static void do_stream_write(void *param)+{+	PULSE_DATA(param);+	uint8_t *buffer = NULL;++	while (data->new_data.size >= data->buffer_size &&+			data->bytesRemaining > 0) {+		size_t bytesToFill = data->buffer_size;++		if (bytesToFill > data->bytesRemaining)+			bytesToFill = data->bytesRemaining;++		pa_stream_begin_write(data->stream, (void **) &buffer,+				&bytesToFill);++		circlebuf_pop_front(&data->new_data, buffer, bytesToFill);++		pulseaudio_lock();+		pa_stream_write(data->stream, buffer, bytesToFill, NULL,+				0LL, PA_SEEK_RELATIVE);+		pulseaudio_unlock();++		data->bytesRemaining -= bytesToFill;+	}+}++static void on_audio_playback(void *param, obs_source_t *source,+		const struct audio_data *audio_data, bool muted)+{+	struct audio_monitor *monitor = param;+	float vol = source->user_volume;+	size_t bytes;++	uint8_t *resample_data[MAX_AV_PLANES];+	uint32_t resample_frames;+	uint64_t ts_offset;+	bool success;++	if (pthread_mutex_trylock(&monitor->playback_mutex) != 0)+		return;++	if (os_atomic_load_long(&source->activate_refs) == 0)+		goto unlock;++	success = audio_resampler_resample(monitor->resampler, resample_data,+			&resample_frames, &ts_offset,+			(const uint8_t *const *) audio_data->data,+			(uint32_t) audio_data->frames);++	if (!success)+		goto unlock;++	bytes = monitor->bytes_per_frame * resample_frames;++	if (muted) {+		memset(resample_data[0], 0, bytes);+	} else {+		if (!close_float(vol, 1.0f, EPSILON)) {+			process_volume(monitor, vol, resample_data,+					resample_frames);+		}+	}++	circlebuf_push_back(&monitor->new_data, resample_data[0], bytes);+	monitor->packets++;+	monitor->frames += resample_frames;++unlock:+	pthread_mutex_unlock(&monitor->playback_mutex);+	do_stream_write(param);+}++static void pulseaudio_stream_write(pa_stream *p, size_t nbytes, void *userdata)+{+	UNUSED_PARAMETER(p);+	PULSE_DATA(userdata);++	pthread_mutex_lock(&data->playback_mutex);+	data->bytesRemaining += nbytes;+	pthread_mutex_unlock(&data->playback_mutex);++	pulseaudio_signal(0);+}++static void pulseaudio_server_info(pa_context *c, const pa_server_info *i,+		void *userdata)+{+	UNUSED_PARAMETER(c);+	UNUSED_PARAMETER(userdata);++	blog(LOG_INFO, "Server name: '%s %s'", i->server_name,+			i->server_version);++	pulseaudio_signal(0);+}++static void pulseaudio_source_info(pa_context *c, const pa_source_info *i,+		int eol, void *userdata)+{+	UNUSED_PARAMETER(c);+	PULSE_DATA(userdata);+	// An error occured+	if (eol < 0) {+		data->format = PA_SAMPLE_INVALID;+		goto skip;+	}+	// Terminating call for multi instance callbacks+	if (eol > 0)+		goto skip;++	blog(LOG_INFO, "Audio format: %s, %"PRIu32" Hz, %"PRIu8" channels",+			pa_sample_format_to_string(i->sample_spec.format),+			i->sample_spec.rate, i->sample_spec.channels);++	pa_sample_format_t format = i->sample_spec.format;+	if (pulseaudio_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) {+		format = PA_SAMPLE_S16LE;++		blog(LOG_INFO, "Sample format %s not supported by OBS,"+				"using %s instead for recording",+				pa_sample_format_to_string(+						i->sample_spec.format),+				pa_sample_format_to_string(format));+	}++	uint8_t channels = i->sample_spec.channels;+	if (pulseaudio_channels_to_obs_speakers(channels) == SPEAKERS_UNKNOWN) {+		channels = 2;++		blog(LOG_INFO, "%c channels not supported by OBS,"+				"using %c instead for recording",+				i->sample_spec.channels,+				channels);+	}++	data->format = format;+	data->samples_per_sec = i->sample_spec.rate;+	data->channels = channels;+skip:+	pulseaudio_signal(0);+}++static void pulseaudio_stop_playback(struct audio_monitor *monitor)+{+	if (monitor->stream) {+		pa_stream_disconnect(monitor->stream);+		pa_stream_unref(monitor->stream);+		monitor->stream = NULL;+	}++	blog(LOG_INFO, "Stopped Monitoring in '%s'", monitor->device);+	blog(LOG_INFO, "Got %"PRIuFAST32" packets with %"PRIuFAST64" frames",+			monitor->packets, monitor->frames);++	monitor->packets = 0;+	monitor->frames = 0;+}++static bool audio_monitor_init(struct audio_monitor *monitor,+		obs_source_t *source)+{+	pthread_mutex_init_value(&monitor->playback_mutex);++	monitor->source = source;++	const char *id = obs->audio.monitoring_device_id;+	if (!id)+		return false;++	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;+			blog(LOG_INFO, "Prevented feedback-loop in '%s'",+					s_dev_id);+			return true;+		}+	}++	pulseaudio_init();++	if (strcmp(id, "default") == 0)+		get_default_id(&monitor->device);+	else+		monitor->device = bstrdup(id);++	if (!monitor->device)+		return false;++	if (pulseaudio_get_server_info(pulseaudio_server_info,+			(void *) monitor) < 0) {+		blog(LOG_ERROR, "Unable to get server info !");+		return false;+	}++	if (pulseaudio_get_source_info(pulseaudio_source_info, monitor->device,+			(void *) monitor) < 0) {+		blog(LOG_ERROR, "Unable to get source info !");+		return false;+	}+	if (monitor->format == PA_SAMPLE_INVALID) {+		blog(LOG_ERROR,+				"An error occurred while getting the source info!");+		return false;+	}++	pa_sample_spec spec;+	spec.format = monitor->format;+	spec.rate = (uint32_t) monitor->samples_per_sec;+	spec.channels = monitor->channels;++	if (!pa_sample_spec_valid(&spec)) {+		blog(LOG_ERROR, "Sample spec is not valid");+		return false;+	}++	const struct audio_output_info *info = audio_output_get_info(+			obs->audio.audio);++	struct resample_info from = {+			.samples_per_sec = info->samples_per_sec,+			.speakers = info->speakers,+			.format = AUDIO_FORMAT_FLOAT_PLANAR+	};+	struct resample_info to = {+			.samples_per_sec = (uint32_t) monitor->samples_per_sec,+			.speakers = pulseaudio_channels_to_obs_speakers(+					monitor->channels),+			.format = pulseaudio_to_obs_audio_format(+					monitor->format)+	};++	monitor->resampler = audio_resampler_create(&to, &from);+	if (!monitor->resampler) {+		blog(LOG_WARNING, "%s: %s", __FUNCTION__,+				"Failed to create resampler");+		return false;+	}++	monitor->bytes_per_channel = get_audio_bytes_per_channel(+			pulseaudio_to_obs_audio_format(monitor->format));+	monitor->speakers = pulseaudio_channels_to_obs_speakers(spec.channels);+	monitor->bytes_per_frame = pa_frame_size(&spec);++	monitor->stream = pulseaudio_stream_new(+			obs_source_get_name(monitor->source), &spec, NULL);+	if (!monitor->stream) {+		blog(LOG_ERROR, "Unable to create stream");+		return false;+	}++	pa_buffer_attr attr;+	attr.fragsize = (uint32_t) -1;+	attr.maxlength = (uint32_t) -1;+	attr.minreq = (uint32_t) -1;+	attr.prebuf = (uint32_t) -1;+	attr.tlength = pa_usec_to_bytes(25000, &spec);++	monitor->buffer_size =+			monitor->bytes_per_frame * pa_usec_to_bytes(100, &spec);++	pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY;++	if (pthread_mutex_init(&monitor->playback_mutex, NULL) != 0) {+		blog(LOG_WARNING, "%s: %s", __FUNCTION__,+				"Failed to init mutex");+		return false;+	}++	int_fast32_t ret = pulseaudio_connect_playback(monitor->stream,+			monitor->device, &attr, flags);+	if (ret < 0) {+		pulseaudio_stop_playback(monitor);+		blog(LOG_ERROR, "Unable to connect to stream");+		return false;+	}++	blog(LOG_INFO, "Started Monitoring in '%s'", monitor->device);+	return true;+}++static void audio_monitor_init_final(struct audio_monitor *monitor)+{+	if (monitor->ignore)+		return;++	obs_source_add_audio_capture_callback(monitor->source,+			on_audio_playback, monitor);++	pulseaudio_write_callback(monitor->stream, pulseaudio_stream_write,+			(void *) monitor);+}++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);++	audio_resampler_destroy(monitor->resampler);+	circlebuf_free(&monitor->new_data);++	if (monitor->stream)+		pulseaudio_stop_playback(monitor);+	pulseaudio_unref();++	bfree(monitor->device);+}++struct audio_monitor *audio_monitor_create(obs_source_t *source)+{+	struct audio_monitor monitor = {0};+	struct audio_monitor *out;++	if (!audio_monitor_init(&monitor, source))+		goto fail;++	out = bmemdup(&monitor, sizeof(monitor));++	pthread_mutex_lock(&obs->audio.monitoring_mutex);+	da_push_back(obs->audio.monitors, &out);+	pthread_mutex_unlock(&obs->audio.monitoring_mutex);++	audio_monitor_init_final(out);+	return out;++fail:+	audio_monitor_free(&monitor);+	return NULL;+}++void audio_monitor_reset(struct audio_monitor *monitor)+{+	struct audio_monitor new_monitor = {0};+	bool success;+	audio_monitor_free(monitor);++	pthread_mutex_lock(&monitor->playback_mutex);+	success = audio_monitor_init(&new_monitor, monitor->source);+	pthread_mutex_unlock(&monitor->playback_mutex);++	if (success) {+		*monitor = new_monitor;+		audio_monitor_init_final(monitor);+	} else {+		audio_monitor_free(&new_monitor);+	}+}++void audio_monitor_destroy(struct audio_monitor *monitor)+{+	if (monitor) {+		audio_monitor_free(monitor);++		pthread_mutex_lock(&obs->audio.monitoring_mutex);+		da_erase_item(obs->audio.monitors, &monitor);+		pthread_mutex_unlock(&obs->audio.monitoring_mutex);++		bfree(monitor);+	}+}diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.cnew file mode 100644index 000000000..91939ae1b--- /dev/null+++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c@@ -0,0 +1,330 @@+/*+Copyright (C) 2014 by Leonhard Oelke <[email protected]>+Copyright (C) 2017 by Fabio Madia <[email protected]>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation, either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program.  If not, see <http://www.gnu.org/licenses/>.+*/++#include <pthread.h>++#include <pulse/thread-mainloop.h>++#include <util/base.h>+#include <obs.h>++#include "pulseaudio-wrapper.h"++/* global data */+static uint_fast32_t pulseaudio_refs = 0;+static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER;+static pa_threaded_mainloop *pulseaudio_mainloop = NULL;+static pa_context *pulseaudio_context = NULL;++static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i,+		void *userdata)+{+	UNUSED_PARAMETER(c);+	struct pulseaudio_default_output *d =+			(struct pulseaudio_default_output *) userdata;+	d->default_sink_name = bstrdup(i->default_sink_name);+	pulseaudio_signal(0);+}++void get_default_id(char **id)+{+	pulseaudio_init();+	struct pulseaudio_default_output *pdo = bzalloc(+			sizeof(struct pulseaudio_default_output));+	pulseaudio_get_server_info(+			(pa_server_info_cb_t) pulseaudio_default_devices,+			(void *) pdo);+	*id = bzalloc(strlen(pdo->default_sink_name) + 9);+	strcat(*id, pdo->default_sink_name);+	strcat(*id, ".monitor");+	bfree(pdo->default_sink_name);+	bfree(pdo);+	pulseaudio_unref();+}++bool devices_match(const char *id1, const char *id2)+{+	bool match;+	char *name1 = NULL;+	char *name2 = NULL;++	if (!id1 || !id2)+		return false;++	if (strcmp(id1, "default") == 0) {+		get_default_id(&name1);+		id1 = name1;+	}+	if (strcmp(id2, "default") == 0) {+		get_default_id(&name2);+		id2 = name2;+	}++	match = strcmp(id1, id2) == 0;+	bfree(name1);+	bfree(name2);+	return match;+}++/**+ * context status change callback+ *+ * @todo this is currently a noop, we want to reconnect here if the connection+ *       is lost ...+ */+static void pulseaudio_context_state_changed(pa_context *c, void *userdata)+{+	UNUSED_PARAMETER(userdata);+	UNUSED_PARAMETER(c);++	pulseaudio_signal(0);+}++/**+ * get the default properties+ */+static pa_proplist *pulseaudio_properties()+{+	pa_proplist *p = pa_proplist_new();++	pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");+	pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");+	pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");++	return p;+}++/**+ * Initialize the pulse audio context with properties and callback+ */+static void pulseaudio_init_context()+{+	pulseaudio_lock();++	pa_proplist *p = pulseaudio_properties();+	pulseaudio_context = pa_context_new_with_proplist(+			pa_threaded_mainloop_get_api(pulseaudio_mainloop),+			"OBS-Monitor", p);++	pa_context_set_state_callback(pulseaudio_context,+			pulseaudio_context_state_changed, NULL);++	pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN,+			NULL);+	pa_proplist_free(p);++	pulseaudio_unlock();+}++/**+ * wait for context to be ready+ */+static int_fast32_t pulseaudio_context_ready()+{+	pulseaudio_lock();++	if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {+		pulseaudio_unlock();+		return -1;+	}++	while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)+		pulseaudio_wait();++	pulseaudio_unlock();+	return 0;+}++int_fast32_t pulseaudio_init()+{+	pthread_mutex_lock(&pulseaudio_mutex);++	if (pulseaudio_refs == 0) {+		pulseaudio_mainloop = pa_threaded_mainloop_new();+		pa_threaded_mainloop_start(pulseaudio_mainloop);++		pulseaudio_init_context();+	}++	pulseaudio_refs++;++	pthread_mutex_unlock(&pulseaudio_mutex);++	return 0;+}++void pulseaudio_unref()+{+	pthread_mutex_lock(&pulseaudio_mutex);++	if (--pulseaudio_refs == 0) {+		pulseaudio_lock();+		if (pulseaudio_context != NULL) {+			pa_context_disconnect(pulseaudio_context);+			pa_context_unref(pulseaudio_context);+			pulseaudio_context = NULL;+		}+		pulseaudio_unlock();++		if (pulseaudio_mainloop != NULL) {+			pa_threaded_mainloop_stop(pulseaudio_mainloop);+			pa_threaded_mainloop_free(pulseaudio_mainloop);+			pulseaudio_mainloop = NULL;+		}+	}++	pthread_mutex_unlock(&pulseaudio_mutex);+}++void pulseaudio_lock()+{+	pa_threaded_mainloop_lock(pulseaudio_mainloop);+}++void pulseaudio_unlock()+{+	pa_threaded_mainloop_unlock(pulseaudio_mainloop);+}++void pulseaudio_wait()+{+	pa_threaded_mainloop_wait(pulseaudio_mainloop);+}++void pulseaudio_signal(int wait_for_accept)+{+	pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);+}++void pulseaudio_accept()+{+	pa_threaded_mainloop_accept(pulseaudio_mainloop);+}++int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,+		void *userdata)+{+	if (pulseaudio_context_ready() < 0)+		return -1;++	pulseaudio_lock();++	pa_operation *op = pa_context_get_source_info_list(+			pulseaudio_context, cb, userdata);+	if (!op) {+		pulseaudio_unlock();+		return -1;+	}+	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)+		pulseaudio_wait();+	pa_operation_unref(op);++	pulseaudio_unlock();++	return 0;+}++int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,+		const char *name, void *userdata)+{+	if (pulseaudio_context_ready() < 0)+		return -1;++	pulseaudio_lock();++	pa_operation *op = pa_context_get_source_info_by_name(+			pulseaudio_context, name, cb, userdata);+	if (!op) {+		pulseaudio_unlock();+		return -1;+	}+	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)+		pulseaudio_wait();+	pa_operation_unref(op);++	pulseaudio_unlock();++	return 0;+}++int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata)+{+	if (pulseaudio_context_ready() < 0)+		return -1;++	pulseaudio_lock();++	pa_operation *op = pa_context_get_server_info(+			pulseaudio_context, cb, userdata);+	if (!op) {+		pulseaudio_unlock();+		return -1;+	}+	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)+		pulseaudio_wait();+	pa_operation_unref(op);++	pulseaudio_unlock();+	return 0;+}++pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,+		const pa_channel_map *map)+{+	if (pulseaudio_context_ready() < 0)+		return NULL;++	pulseaudio_lock();++	pa_proplist *p = pulseaudio_properties();+	pa_stream *s = pa_stream_new_with_proplist(+			pulseaudio_context, name, ss, map, p);+	pa_proplist_free(p);++	pulseaudio_unlock();+	return s;+}++int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,+		const pa_buffer_attr *attr, pa_stream_flags_t flags)+{+	if (pulseaudio_context_ready() < 0)+		return -1;++	size_t dev_len = strlen(name) - 8;+	char device[dev_len];+	memcpy(device, name, dev_len);+	device[dev_len] = '\0';++	pulseaudio_lock();+	int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags,+			NULL, NULL);+	pulseaudio_unlock();+	return ret;+}++void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,+		void *userdata)+{+	if (pulseaudio_context_ready() < 0)+		return;++	pulseaudio_lock();+	pa_stream_set_write_callback(p, cb, userdata);+	pulseaudio_unlock();+}diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.hnew file mode 100644index 000000000..3630ab0f0--- /dev/null+++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h@@ -0,0 +1,175 @@+/*+Copyright (C) 2014 by Leonhard Oelke <[email protected]>+Copyright (C) 2017 by Fabio Madia <[email protected]>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation, either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program.  If not, see <http://www.gnu.org/licenses/>.+*/++#include <inttypes.h>+#include <pulse/stream.h>+#include <pulse/context.h>+#include <pulse/introspect.h>++#pragma once++struct pulseaudio_default_output {+	char *default_sink_name;+};++struct enum_cb {+	obs_enum_audio_device_cb cb;+	void *data;+	int cont;+};++void get_default_id(char **id);++bool devices_match(const char *id1, const char *id2);++/**+ * Initialize the pulseaudio mainloop and increase the reference count+ */+int_fast32_t pulseaudio_init();++/**+ * Unreference the pulseaudio mainloop, when the reference count reaches+ * zero the mainloop will automatically be destroyed+ */+void pulseaudio_unref();++/**+ * Lock the mainloop+ *+ * In order to allow for multiple threads to use the same mainloop pulseaudio+ * provides it's own locking mechanism. This function should be called before+ * using any pulseaudio function that is in any way related to the mainloop or+ * context.+ *+ * @note use of this function may cause deadlocks+ *+ * @warning do not use with pulseaudio_ wrapper functions+ */+void pulseaudio_lock();++/**+ * Unlock the mainloop+ *+ * @see pulseaudio_lock()+ */+void pulseaudio_unlock();++/**+ * Wait for events to happen+ *+ * This function should be called when waiting for an event to happen.+ */+void pulseaudio_wait();++/**+ * Wait for accept signal from calling thread+ *+ * This function tells the pulseaudio mainloop wheter the data provided to+ * the callback should be retained until the calling thread executes+ * pulseaudio_accept()+ *+ * If wait_for_accept is 0 the function returns and the data is freed.+ */+void pulseaudio_signal(int wait_for_accept);++/**+ * Signal the waiting callback to return+ *+ * This function is used in conjunction with pulseaudio_signal()+ */+void pulseaudio_accept();++/**+ * Request source information+ *+ * The function will block until the operation was executed and the mainloop+ * called the provided callback function.+ *+ * @return negative on error+ *+ * @note The function will block until the server context is ready.+ *+ * @warning call without active locks+ */+int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,+		void *userdata);++/**+ * Request source information from a specific source+ *+ * The function will block until the operation was executed and the mainloop+ * called the provided callback function.+ *+ * @param cb pointer to the callback function+ * @param name the source name to get information for+ * @param userdata pointer to userdata the callback will be called with+ *+ * @return negative on error+ *+ * @note The function will block until the server context is ready.+ *+ * @warning call without active locks+ */+int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,+		const char *name, void *userdata);++/**+ * Request server information+ *+ * The function will block until the operation was executed and the mainloop+ * called the provided callback function.+ *+ * @return negative on error+ *+ * @note The function will block until the server context is ready.+ *+ * @warning call without active locks+ */+int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata);++/**+ * Create a new stream with the default properties+ *+ * @note The function will block until the server context is ready.+ *+ * @warning call without active locks+ */+pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,+		const pa_channel_map *map);++/**+ * Connect to a pulseaudio playback stream+ *+ * @param s pa_stream to connect to. NULL for default+ * @param attr pa_buffer_attr+ * @param name Device name. NULL for default device+ * @param flags pa_stream_flags_t+ * @return negative on error+ */+int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,+		const pa_buffer_attr *attr, pa_stream_flags_t flags);++/**+ * Sets a callback function for when data can be written to the stream+ *+ * @param p pa_stream to connect to. NULL for default+ * @param cb pa_stream_request_cb_t+ * @param userdata pointer to userdata the callback will be called with+ */+void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,+		void *userdata);diff --git a/libobs/obs.c b/libobs/obs.cindex b41c12a8d..ab2fb3d1c 100644--- a/libobs/obs.c+++ b/libobs/obs.c@@ -1898,7 +1898,7 @@ bool obs_set_audio_monitoring_device(const char *name, const char *id) 	if (!obs || !name || !id || !*name || !*id) 		return false; -#ifdef _WIN32+#if defined(_WIN32) || HAVE_PULSEAUDIO 	pthread_mutex_lock(&obs->audio.monitoring_mutex);  	if (strcmp(id, obs->audio.monitoring_device_id) == 0) {diff --git a/libobs/obsconfig.h.in b/libobs/obsconfig.h.inindex f86962d59..130a8b203 100644--- a/libobs/obsconfig.h.in+++ b/libobs/obsconfig.h.in@@ -17,3 +17,4 @@ #define OBS_UNIX_STRUCTURE @OBS_UNIX_STRUCTURE@ #define BUILD_CAPTIONS @BUILD_CAPTIONS@ #define HAVE_DBUS @HAVE_DBUS@+#define HAVE_PULSEAUDIO @HAVE_PULSEAUDIO@

Comments