[pulseaudio-discuss] [PATCH] [module] Add dynamic JACK bridge

XEdP3X git at xedp3x.de
Wed Dec 24 12:04:20 PST 2014


Create and configure sink and sources if an application connects to pulse and bridge it to JACK audio.
---
 src/Makefile.am                |   9 +-
 src/modules/jack/module-jack.c | 779 +++++++++++++++++++++++++++++++++++++++++
 src/modules/jack/module-jack.h |  64 ++++
 3 files changed, 851 insertions(+), 1 deletion(-)
 create mode 100644 src/modules/jack/module-jack.c
 create mode 100644 src/modules/jack/module-jack.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 88a824e..b966089 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1344,7 +1344,8 @@ endif
 if HAVE_JACK
 modlibexec_LTLIBRARIES += \
 		module-jack-sink.la \
-		module-jack-source.la
+		module-jack-source.la \
+		module-jack.la
 
 if HAVE_DBUS
 modlibexec_LTLIBRARIES += \
@@ -1483,6 +1484,7 @@ SYMDEF_FILES = \
 		module-jackdbus-detect-symdef.h \
 		module-jack-sink-symdef.h \
 		module-jack-source-symdef.h \
+		module-jack-symdef.h \
 		module-volume-restore-symdef.h \
 		module-device-manager-symdef.h \
 		module-device-restore-symdef.h \
@@ -2067,6 +2069,11 @@ module_jack_source_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_jack_source_la_LIBADD = $(MODULE_LIBADD) $(JACK_LIBS)
 module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS)
 
+module_jack_la_SOURCES = modules/jack/module-jack.c
+module_jack_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_jack_la_LIBADD = $(MODULE_LIBADD) $(JACK_LIBS)
+module_jack_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS)
+
 module_hal_detect_la_SOURCES = modules/module-hal-detect-compat.c
 module_hal_detect_la_LIBADD = $(MODULE_LIBADD)
 module_hal_detect_la_CFLAGS = $(AM_CFLAGS)
diff --git a/src/modules/jack/module-jack.c b/src/modules/jack/module-jack.c
new file mode 100644
index 0000000..be74d7f
--- /dev/null
+++ b/src/modules/jack/module-jack.c
@@ -0,0 +1,779 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2015 Mario Krüger
+
+ Some code taken from other parts of PulseAudio, these are
+ Copyright 2006 Lennart Poettering
+ Copyright 2009 Canonical Ltd
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio 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 Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+ ***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <jack/jack.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/rtclock.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/namereg.h>
+
+#include "module-jack-symdef.h"
+#include "module-jack.h"
+
+PA_MODULE_AUTHOR("Mario Krueger");
+PA_MODULE_DESCRIPTION("JACK");
+PA_MODULE_LOAD_ONCE(true);
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_USAGE(
+		"sink_properties=<properties for the card> "
+		"source_properties=<properties for the card> "
+		"server_name=<jack server name> "
+		"connect=<connect new ports to speaker/mic?>"
+		"merge=<merge streams from same application: 0=no, 1=same pid, 2=same binary name, 3=same application name>"
+		"delay=<delay before remove unused application bridge>"
+);
+
+static const char* const valid_modargs[] = {
+	"sink_properties",
+	"source_properties",
+	"server_name",
+	"connect",
+	"merge",
+	"delay",
+	NULL
+};
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+	struct sCard *card = PA_SOURCE(o)->userdata;
+	struct sBase *base = card->base;
+
+	switch (code) {
+		case SOURCE_MESSAGE_POST:
+			/* Handle the new block from the JACK thread */
+			pa_assert(chunk);
+			pa_assert(chunk->length > 0);
+
+			if (card->source->thread_info.state == PA_SOURCE_RUNNING)
+				pa_source_post(card->source, chunk);
+
+			card->saved_frame_time = (jack_nframes_t) offset;
+			card->saved_frame_time_valid = true;
+			return 0;
+
+		case SOURCE_MESSAGE_ON_SHUTDOWN:
+			pa_asyncmsgq_post(card->thread_mq.outq, PA_MSGOBJECT(base->core), PA_CORE_MESSAGE_UNLOAD_MODULE, base->module, 0, NULL, NULL);
+			return 0;
+
+		case PA_SOURCE_MESSAGE_GET_LATENCY: {
+			jack_latency_range_t r;
+			jack_nframes_t l, ft, d;
+			size_t n;
+
+			/* This is the "worst-case" latency */
+			jack_port_get_latency_range(card->port[0], JackCaptureLatency, &r);
+			l = r.max;
+
+			if (card->saved_frame_time_valid) {
+				/* Adjust the worst case latency by the time that
+				 * passed since we last handed data to JACK */
+
+				ft = jack_frame_time(card->jack);
+				d = ft > card->saved_frame_time ? ft - card->saved_frame_time : 0;
+				l += d;
+			}
+
+			/* Convert it to usec */
+			n = l * pa_frame_size(&card->source->sample_spec);
+			*((pa_usec_t*) data) = pa_bytes_to_usec(n, &card->source->sample_spec);
+			return 0;
+		}
+	}
+
+	return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+static int pa_process_sink_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) {
+	struct sCard *card = PA_SINK(o)->userdata;
+	struct sBase *base = card->base;
+
+	switch (code) {
+		case SINK_MESSAGE_RENDER:
+			/* Handle the request from the JACK thread */
+			if (card->sink->thread_info.state == PA_SINK_RUNNING) {
+				pa_memchunk chunk;
+				size_t nbytes;
+				void *p;
+				bool rewind_requested;
+
+				pa_assert(offset > 0);
+				nbytes = (size_t) offset * pa_frame_size(&card->sink->sample_spec);
+
+				rewind_requested = card->sink->thread_info.rewind_requested;
+				card->sink->thread_info.rewind_requested = false;
+				pa_sink_render_full(card->sink, nbytes, &chunk);
+				card->sink->thread_info.rewind_requested = rewind_requested;
+
+				p = pa_memblock_acquire_chunk(&chunk);
+				pa_deinterleave(p, card->buffer, card->channels, sizeof(float),(unsigned) offset);
+				pa_memblock_release(chunk.memblock);
+
+				pa_memblock_unref(chunk.memblock);
+			} else {
+				unsigned c;
+				pa_sample_spec ss;
+
+				/* Humm, we're not RUNNING, hence let's write some silence */
+				/* This can happen if we're paused, or during shutdown (when we're unlinked but jack is still running). */
+
+				ss = card->sink->sample_spec;
+				ss.channels = 1;
+
+				for (c = 0; c < card->channels; c++)
+					pa_silence_memory(card->buffer[c],(size_t) offset * pa_sample_size(&ss), &ss);
+			}
+			card->frames_in_buffer = (jack_nframes_t) offset;
+			card->saved_frame_time = *(jack_nframes_t*) data;
+			card->saved_frame_time_valid = true;
+			return 0;
+
+		case SINK_MESSAGE_BUFFER_SIZE:
+			pa_sink_set_max_request_within_thread(card->sink, (size_t) offset * pa_frame_size(&card->sink->sample_spec));
+			return 0;
+
+		case SINK_MESSAGE_ON_SHUTDOWN:
+			pa_asyncmsgq_post(card->thread_mq.outq, PA_MSGOBJECT(base->core), PA_CORE_MESSAGE_UNLOAD_MODULE, base->module, 0, NULL, NULL);
+			return 0;
+
+		case PA_SINK_MESSAGE_GET_LATENCY: {
+			jack_nframes_t l, ft, d;
+			jack_latency_range_t r;
+			size_t n;
+
+			/* This is the "worst-case" latency */
+			jack_port_get_latency_range(card->port[0], JackPlaybackLatency, &r);
+			l = r.max + card->frames_in_buffer;
+
+			if (card->saved_frame_time_valid) {
+				/* Adjust the worst case latency by the time that
+				 * passed since we last handed data to JACK */
+
+				ft = jack_frame_time(card->jack);
+				d = ft > card->saved_frame_time ? ft - card->saved_frame_time : 0;
+				l = l > d ? l - d : 0;
+			}
+
+			/* Convert it to usec */
+			n = l * pa_frame_size(&card->sink->sample_spec);
+			*((pa_usec_t*) data) = pa_bytes_to_usec(n, &card->sink->sample_spec);
+
+			return 0;
+		}
+	}
+	return pa_sink_process_msg(o, code, data, offset, memchunk);
+}
+
+static int jack_process(jack_nframes_t nframes, void *arg) {
+	struct sCard *card = arg;
+	struct sBase *base = card->base;
+	unsigned c;
+	void *p;
+	const void *buffer[PA_CHANNELS_MAX];
+	jack_nframes_t frame_time;
+	pa_memchunk chunk;
+	pa_assert(card);
+
+	if (card->is_sink) {
+		for (c = 0; c < card->channels; c++)
+			pa_assert_se(card->buffer[c] = jack_port_get_buffer(card->port[c], nframes));
+		frame_time = jack_frame_time(card->jack);
+		pa_assert_se(pa_asyncmsgq_send(card->jack_msgq, PA_MSGOBJECT(card->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0);
+	} else {
+		for (c = 0; c < card->channels; c++)
+			pa_assert_se(buffer[c] = jack_port_get_buffer(card->port[c], nframes));
+
+		pa_memchunk_reset(&chunk);
+		chunk.length = nframes * pa_frame_size(&card->source->sample_spec);
+		chunk.memblock = pa_memblock_new(base->core->mempool, chunk.length);
+		p = pa_memblock_acquire(chunk.memblock);
+		pa_interleave(buffer, card->channels, p, sizeof(float), nframes);
+		pa_memblock_release(chunk.memblock);
+		frame_time = jack_frame_time(card->jack);
+		pa_asyncmsgq_post(card->jack_msgq, PA_MSGOBJECT(card->source),SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL);
+		pa_memblock_unref(chunk.memblock);
+	}
+
+	return 0;
+}
+
+static void thread_func(void *arg) {
+	struct sCard *card = arg;
+	struct sBase *base = card->base;
+
+	pa_assert(card);
+	pa_log_debug("Thread starting up");
+
+	if (base->core->realtime_scheduling)
+		pa_make_realtime(base->core->realtime_priority);
+	pa_thread_mq_install(&card->thread_mq);
+
+	for (;;) {
+		int ret;
+
+		if ((ret = pa_rtpoll_run(card->rtpoll)) < 0)
+			goto fail;
+
+		if (ret == 0)
+			goto finish;
+	}
+
+	fail:
+	/* If this was no regular exit from the loop we have to continue
+	 * processing messages until we received PA_MESSAGE_SHUTDOWN */
+	pa_asyncmsgq_post(card->thread_mq.outq, PA_MSGOBJECT(base->core), PA_CORE_MESSAGE_UNLOAD_MODULE, base->module, 0, NULL, NULL);
+	pa_asyncmsgq_wait_for(card->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+	finish:
+	pa_log_debug("Thread shutting down");
+}
+
+static void jack_error_func(const char*t) {
+	char *s;
+
+	s = pa_xstrndup(t, strcspn(t, "\n\r"));
+	pa_log_warn("JACK error >%s<", s);
+	pa_xfree(s);
+}
+
+static void jack_init(void *arg) {
+	struct sCard *card = arg;
+	struct sBase *base = card->base;
+
+	pa_log_info("JACK thread starting up.");
+
+	if (base->core->realtime_scheduling)
+		pa_make_realtime(base->core->realtime_priority + 4);
+}
+
+static void jack_shutdown(void* arg) {
+	struct sCard *card = arg;
+
+	pa_log_info("JACK thread shutting down..");
+
+	if (card->is_sink) {
+		pa_asyncmsgq_post(card->jack_msgq, PA_MSGOBJECT(card->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
+	} else {
+		pa_asyncmsgq_post(card->jack_msgq, PA_MSGOBJECT(card->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
+	}
+}
+
+void* init_card(void* arg, const char *name, bool is_sink) {
+	struct sCard *card = malloc(sizeof(struct sCard));
+	struct sBase *base = arg;
+	unsigned i;
+	jack_status_t status;
+	jack_latency_range_t r;
+	const char **ports = NULL, **p;
+	pa_sample_spec ss;
+
+	card->name = name;
+	card->base = base;
+	card->is_sink = is_sink;
+
+	card->time_event = pa_core_rttime_new(base->core, PA_USEC_INVALID, timeout_cb, card);
+	card->rtpoll = pa_rtpoll_new();
+	card->saved_frame_time_valid = false;
+
+	pa_thread_mq_init(&card->thread_mq, base->core->mainloop, card->rtpoll);
+
+	/* Jack handler */
+	card->jack_msgq = pa_asyncmsgq_new(0);
+	card->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(card->rtpoll, PA_RTPOLL_EARLY - 1, card->jack_msgq);
+	if (!(card->jack = jack_client_open(card->name, base->server_name ? JackServerName : JackNullOption, &status, base->server_name))) {
+		pa_log("jack_client_open() failed.");
+		goto fail;
+	}
+	pa_log_info("Successfully connected as '%s'", jack_get_client_name(card->jack));
+
+	jack_set_process_callback(card->jack, jack_process, card);
+	jack_on_shutdown(card->jack, jack_shutdown, &card);
+	jack_set_thread_init_callback(card->jack, jack_init, card);
+
+	if (jack_activate(card->jack)) {
+		pa_log("jack_activate() failed");
+		goto fail;
+	}
+
+	/* set sample rate */
+	ss.rate = jack_get_sample_rate(card->jack);
+	ss.format = PA_SAMPLE_FLOAT32NE;
+	card->channels = ss.channels = base->core->default_sample_spec.channels;
+	pa_assert(pa_sample_spec_valid(&ss));
+
+	/* PA handler */
+	if (card->is_sink) {
+		pa_sink_new_data data;
+		pa_sink_new_data_init(&data);
+		data.driver = __FILE__;
+		data.module = base->module;
+
+		pa_sink_new_data_set_name(&data, card->name);
+		pa_sink_new_data_set_sample_spec(&data, &ss);
+
+		if (base->server_name)
+			pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, &base->server_name);
+		pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack (%s)", jack_get_client_name(card->jack));
+		pa_proplist_sets(data.proplist, PA_PROP_JACK_CLIENT, jack_get_client_name(card->jack));
+		pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
+
+		if (pa_modargs_get_proplist(base->ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+			pa_log("Invalid properties");
+			pa_sink_new_data_done(&data);
+			goto fail;
+		}
+
+		card->sink = pa_sink_new(base->core, &data, PA_SINK_LATENCY);
+
+		pa_sink_new_data_done(&data);
+
+		if (!card->sink) {
+			pa_log("Failed to create sink.");
+			goto fail;
+		}
+
+		card->sink->parent.process_msg = pa_process_sink_msg;
+		card->sink->userdata = card;
+		card->source = NULL;
+
+		pa_sink_set_asyncmsgq(card->sink, card->thread_mq.inq);
+		pa_sink_set_rtpoll(card->sink, card->rtpoll);
+		pa_sink_set_max_request(card->sink,jack_get_buffer_size(card->jack)* pa_frame_size(&card->sink->sample_spec));
+	} else {
+		pa_source_new_data data;
+		data.driver = __FILE__;
+		data.module = base->module;
+		pa_source_new_data_init(&data);
+		pa_source_new_data_set_name(&data, card->name);
+		pa_source_new_data_set_sample_spec(&data, &ss);
+
+		if (base->server_name)
+			pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, &base->server_name);
+		pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack (%s)", jack_get_client_name(card->jack));
+		pa_proplist_sets(data.proplist, PA_PROP_JACK_CLIENT, jack_get_client_name(card->jack));
+		pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
+
+		if (pa_modargs_get_proplist(base->ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+			pa_log("Invalid properties");
+			pa_source_new_data_done(&data);
+			goto fail;
+		}
+
+		card->source = pa_source_new(base->core, &data, PA_SOURCE_LATENCY);
+		pa_source_new_data_done(&data);
+
+		if (!card->source) {
+			pa_log("Failed to create source.");
+			goto fail;
+		}
+		card->source->parent.process_msg = source_process_msg;
+		card->source->userdata = card;
+		card->sink = NULL;
+
+		pa_source_set_asyncmsgq(card->source, card->thread_mq.inq);
+		pa_source_set_rtpoll(card->source, card->rtpoll);
+	}
+
+	/* Jack ports */
+	for (i = 0; i < ss.channels; i++) {
+	        if (!(card->port[i] = jack_port_register(card->jack, pa_channel_position_to_string(base->core->default_channel_map.map[i]), JACK_DEFAULT_AUDIO_TYPE, (card->is_sink ? JackPortIsOutput : JackPortIsInput)|JackPortIsTerminal, 0))) {
+	            pa_log("jack_port_register() failed.");
+	            goto fail;
+	        }
+	    }
+
+	if (base->autoconnect) {
+		ports = jack_get_ports(card->jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical | (card->is_sink ? JackPortIsInput : JackPortIsOutput));
+		for (i = 0, p = ports; i < ss.channels; i++, p++) {
+
+			if (!p || !*p) {
+				pa_log("Not enough physical output ports, leaving unconnected.");
+				break;
+			}
+
+			if (jack_connect(card->jack,(card->is_sink ? jack_port_name(card->port[i]) : *p), (card->is_sink ? *p : jack_port_name(card->port[i])))) {
+				pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(card->port[i]), *p);
+				break;
+			}
+		}
+	}
+
+	/* init thread */
+	jack_port_get_latency_range(card->port[0], JackCaptureLatency, &r);
+	if (card->is_sink) {
+		size_t n;
+		n = r.max * pa_frame_size(&card->sink->sample_spec);
+		pa_sink_set_fixed_latency(card->sink,pa_bytes_to_usec(n, &card->sink->sample_spec));
+
+		if (!(card->thread = pa_thread_new(jack_get_client_name(card->jack),thread_func, card))) {
+			pa_log("Failed to create thread.");
+			goto fail;
+		}
+		pa_sink_put(card->sink);
+	} else {
+		size_t n;
+		n = r.max * pa_frame_size(&card->source->sample_spec);
+		pa_source_set_fixed_latency(card->source,pa_bytes_to_usec(n, &card->source->sample_spec));
+
+		if (!(card->thread = pa_thread_new(jack_get_client_name(card->jack),thread_func, card))) {
+			pa_log("Failed to create thread.");
+			goto fail;
+		}
+		pa_source_put(card->source);
+	}
+
+	if (ports)
+		jack_free(ports);
+
+	return card;
+
+fail:
+	pa_log("card_init fatal error");
+	abort();
+	if (ports)
+		jack_free(ports);
+	return NULL;
+}
+
+void unload_card(void* arg,bool forced){
+	struct sCard* card = arg;
+	struct sBase* base = card->base;
+
+	if (!forced){
+		pa_usec_t now;
+		now = pa_rtclock_now();
+		pa_core_rttime_restart(base->core, card->time_event, now + base->delay);
+		return;
+	}
+
+	if(card->is_sink){
+		if (pa_idxset_size(card->sink->inputs) > 0) {
+			pa_sink *def;
+			pa_sink_input *i;
+			uint32_t idx;
+
+			def = pa_namereg_get_default_sink(base->core);
+		    PA_IDXSET_FOREACH(i, card->sink->inputs, idx)
+				pa_sink_input_move_to(i, def, false);
+		}
+		pa_sink_unlink(card->sink);
+	}else{
+		if (pa_idxset_size(card->source->outputs) > 0) {
+			pa_source *def;
+			pa_source_output *o;
+			uint32_t idx;
+
+			def = pa_namereg_get_default_source(base->core);
+			PA_IDXSET_FOREACH(o, card->source->outputs, idx)
+				pa_source_output_move_to(o, def, false);
+		}
+		pa_source_unlink(card->source);
+	}
+	base->core->mainloop->time_free(card->time_event);
+
+	jack_client_close(card->jack);
+	pa_asyncmsgq_send(card->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+	pa_thread_free(card->thread);
+	pa_thread_mq_done(&card->thread_mq);
+
+	if(card->is_sink)
+		pa_sink_unref(card->sink);
+	else
+		pa_source_unref(card->source);
+
+	pa_rtpoll_item_free(card->rtpoll_item);
+	pa_asyncmsgq_unref(card->jack_msgq);
+	pa_rtpoll_free(card->rtpoll);
+	pa_xfree(card);
+}
+
+const char *get_merge_ref(pa_proplist *p, struct sBase *base){
+	switch (base->merge){
+	case 1:
+		return pa_strnull(pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_ID));
+	case 2:
+		return pa_strnull(pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_BINARY));
+	case 3:
+		return pa_strnull(pa_proplist_gets(p, PA_PROP_APPLICATION_NAME));
+	default:
+		return NULL;
+	}
+}
+
+static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_input *i, void *u) {
+    pa_sink *target;
+    target = pa_namereg_get_default_sink(c);
+
+    pa_assert(c);
+    pa_assert(i);
+
+    if (c->state == PA_CORE_SHUTDOWN)
+		return PA_HOOK_OK;
+
+    if (pa_sink_input_finish_move(i, target, false) < 0)
+		return PA_HOOK_OK;
+    else
+		return PA_HOOK_STOP;
+}
+
+static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_source_output *i, void *u) {
+    pa_source *target;
+    target = pa_namereg_get_default_source(c);
+
+    pa_assert(c);
+    pa_assert(i);
+
+    if (c->state == PA_CORE_SHUTDOWN)
+		return PA_HOOK_OK;
+
+    if (pa_source_output_finish_move(i, target, false) < 0)
+		return PA_HOOK_OK;
+    else
+		return PA_HOOK_STOP;
+}
+
+static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink_input *sink_input, struct sBase* base) {
+    /* Don't want to run during startup or shutdown */
+    if (c->state != PA_CORE_RUNNING)
+        return PA_HOOK_OK;
+
+	if (sink_input->flags & PA_SINK_INPUT_DONT_MOVE ){
+		pa_log_info("%s don't own jack-link...",pa_proplist_gets(sink_input->proplist, PA_PROP_APPLICATION_NAME));
+	}else{
+		uint32_t idx;
+		pa_sink *sink;
+		struct sCard* card;
+		const char *merge_ref = get_merge_ref(sink_input->proplist, base);
+
+		if (merge_ref)
+			PA_IDXSET_FOREACH(sink, c->sinks, idx)
+				if (!strcmp(pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_JACK_REF)),merge_ref)){
+					pa_log_info("secend sink from %s...",merge_ref);
+					pa_sink_input_move_to(sink_input, sink, false);
+					return PA_HOOK_OK;
+				}
+
+		card = init_card(base,pa_proplist_gets(sink_input->proplist, PA_PROP_APPLICATION_NAME),true);
+		pa_proplist_sets(card->sink->proplist, PA_PROP_JACK_CLIENT, jack_get_client_name(card->jack));
+		if (merge_ref)
+			pa_proplist_sets(card->sink->proplist, PA_PROP_JACK_REF, merge_ref);
+
+		if (pa_sink_input_move_to(sink_input, card->sink, false) < 0)
+			pa_log_info("Failed to move sink input \"%s\" to %s.", pa_strnull(pa_proplist_gets(sink_input->proplist, PA_PROP_APPLICATION_NAME)), card->sink->name);
+		else
+			pa_log_info("Successfully create sink input %s via %s.", pa_strnull(pa_proplist_gets(sink_input->proplist, PA_PROP_APPLICATION_NAME)), card->sink->name);
+	}
+	return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source_output *source_output, struct sBase* base) {
+    /* Don't want to run during startup or shutdown */
+    if (c->state != PA_CORE_RUNNING)
+        return PA_HOOK_OK;
+
+	if (source_output->flags & PA_SOURCE_OUTPUT_DONT_MOVE ){
+		pa_log_info("%s don't own jack-link...",pa_proplist_gets(source_output->proplist, PA_PROP_APPLICATION_NAME));
+	}else{
+		uint32_t idx;
+		pa_source *source;
+		struct sCard* card;
+		char *name;
+		const char *merge_ref = get_merge_ref(source_output->proplist, base);
+
+		if (merge_ref)
+			PA_IDXSET_FOREACH(source, c->sources, idx){
+				if (!strcmp(pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_JACK_REF)),merge_ref)){
+					pa_log_info("secend source from %s...",merge_ref);
+					pa_source_output_move_to(source_output, source, false);
+					return PA_HOOK_OK;
+				}
+			}
+
+		name = (char *)pa_proplist_gets(source_output->proplist, PA_PROP_APPLICATION_NAME);
+		card= init_card(base,strcat(name,"-mic"),false);
+		pa_proplist_sets(card->source->proplist, PA_PROP_JACK_CLIENT, jack_get_client_name(card->jack));
+		if (merge_ref)
+			pa_proplist_sets(card->source->proplist, PA_PROP_JACK_REF, merge_ref);
+
+		if (pa_source_output_move_to(source_output, card->source, false) < 0)
+			pa_log_info("Failed to move sink input \"%s\" to %s.", pa_strnull(pa_proplist_gets(source_output->proplist, PA_PROP_APPLICATION_NAME)), card->source->name);
+		else
+			pa_log_info("Successfully create source input %s via %s.", pa_strnull(pa_proplist_gets(source_output->proplist, PA_PROP_APPLICATION_NAME)), card->source->name);
+	}
+	return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink_input *sink_input, struct sBase* base) {
+	if (pa_proplist_gets(sink_input->sink->proplist, PA_PROP_JACK_CLIENT) != NULL)
+		unload_card(sink_input->sink->userdata,false);
+	return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source_output *source_output, struct sBase* base) {
+	if (pa_proplist_gets(source_output->source->proplist, PA_PROP_JACK_CLIENT) != NULL)
+		unload_card(source_output->source->userdata,false);
+	return PA_HOOK_OK;
+}
+
+static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
+    struct sCard *card = userdata;
+	struct sBase *base = card->base;
+
+    pa_assert(card);
+    pa_assert(base);
+
+    base->core->mainloop->time_restart(card->time_event, NULL);
+
+	if(card->is_sink){
+		if (pa_idxset_size(card->sink->inputs) > 0)
+			return;
+	}else{
+		if (pa_idxset_size(card->source->outputs) > 0)
+			return;
+	}
+	unload_card(userdata,true);
+}
+
+int pa__init(pa_module*m) {
+	/* init base */
+	struct sBase *base = NULL;
+	struct sCard *card;
+	const char *server_name;
+	uint32_t delay = 5;
+
+	m->userdata = base = pa_xnew0(struct sBase, 1);
+	base->core = m->core;
+	base->module = m;
+
+	/* read Config */
+	if (!(base->ma = pa_modargs_new(m->argument, valid_modargs))) {
+		pa_log("Failed to parse module arguments.");
+		pa__done(m);
+		return -1;
+	}
+
+	base->autoconnect = true;
+	if (pa_modargs_get_value_boolean(base->ma, "connect", &(base->autoconnect)) < 0) {
+		pa_log("Failed to parse connect= argument.");
+		pa__done(m);
+		return -1;
+	}
+
+	base->merge = 1;
+	if (pa_modargs_get_value_u32(base->ma, "merge", &(base->merge)) < 0) {
+		pa_log("Failed to parse merge value.");
+		pa__done(m);
+		return -1;
+	}
+
+	if (pa_modargs_get_value_u32(base->ma, "delay", &delay) < 0) {
+		pa_log("Failed to parse delay value. It must be a number > 0 (in sec.).");
+		pa__done(m);
+		return -1;
+    }
+    base->delay = delay * PA_USEC_PER_SEC;
+
+	/* init Jack */
+	server_name = pa_modargs_get_value(base->ma, "server_name", NULL);
+	if (server_name)
+		base->server_name = *server_name;
+	jack_set_error_function(jack_error_func);
+
+	/* register hooks */
+	base->sink_put_slot					= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT],				PA_HOOK_LATE+30, (pa_hook_cb_t) sink_put_hook_callback, base);
+	base->sink_unlink_slot				= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK],			PA_HOOK_LATE+30, (pa_hook_cb_t) sink_unlink_hook_callback, base);
+	base->source_put_slot				= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT],			PA_HOOK_LATE+30, (pa_hook_cb_t) source_put_hook_callback, base);
+	base->source_unlink_slot			= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK],		PA_HOOK_LATE+30, (pa_hook_cb_t) source_unlink_hook_callback, base);
+	base->sink_input_move_fail_slot		= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL],		PA_HOOK_LATE+20, (pa_hook_cb_t) sink_input_move_fail_hook_callback, base);
+	base->source_output_move_fail_slot	= pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL],	PA_HOOK_LATE+20, (pa_hook_cb_t) source_output_move_fail_hook_callback, base);
+
+	/* fixes the same problems as module-always-sink */
+	card = init_card(base,"Pulse-to-Jack",true);
+	pa_namereg_set_default_sink(base->core,card->sink);
+	card = init_card(base,"Jack-to-Pulse",false);
+	pa_namereg_set_default_source(base->core,card->source);
+
+	return 0;
+}
+
+void pa__done(pa_module*m) {
+	struct sBase *base;
+	uint32_t idx;
+	pa_sink *sink;
+	pa_source *source;
+
+	pa_assert(m);
+
+	if (!(base = m->userdata))
+		return;
+
+	if (base->ma)
+		pa_modargs_free(base->ma);
+
+	if (base->sink_put_slot)
+		pa_hook_slot_free(base->sink_put_slot);
+	if (base->sink_unlink_slot)
+		pa_hook_slot_free(base->sink_unlink_slot);
+	if (base->source_put_slot)
+		pa_hook_slot_free(base->source_put_slot);
+	if (base->source_unlink_slot)
+		pa_hook_slot_free(base->source_unlink_slot);
+	if (base->sink_input_move_fail_slot)
+		pa_hook_slot_free(base->sink_input_move_fail_slot);
+	if (base->source_output_move_fail_slot)
+		pa_hook_slot_free(base->source_output_move_fail_slot);
+
+	PA_IDXSET_FOREACH(sink, base->core->sinks, idx){
+		if (pa_proplist_gets(sink->proplist, PA_PROP_JACK_CLIENT) != NULL){
+			unload_card(sink->userdata,true);
+		}
+	}
+
+	PA_IDXSET_FOREACH(source, base->core->sources, idx){
+		if (pa_proplist_gets(source->proplist, PA_PROP_JACK_CLIENT) != NULL){
+			unload_card(source->userdata,true);
+		}
+	}
+
+	pa_xfree(base);
+}
diff --git a/src/modules/jack/module-jack.h b/src/modules/jack/module-jack.h
new file mode 100644
index 0000000..17a40f6
--- /dev/null
+++ b/src/modules/jack/module-jack.h
@@ -0,0 +1,64 @@
+#define PA_PROP_JACK_CLIENT "jack.name"
+#define PA_PROP_JACK_REF "jack.ref"
+#define PA_USEC_INVALID ((pa_usec_t) -1)
+#define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL)
+
+struct sBase {
+	pa_core *core;
+	pa_module *module;
+	pa_modargs *ma;
+
+	bool autoconnect;
+	char server_name;
+	uint32_t merge;
+	pa_usec_t delay;
+
+    pa_hook_slot
+		*sink_put_slot,
+		*sink_unlink_slot,
+		*source_put_slot,
+		*source_unlink_slot,
+		*sink_input_move_fail_slot,
+		*source_output_move_fail_slot;
+};
+
+struct sCard {
+	void *base;
+	bool is_sink;
+	char const *name;
+
+	pa_sink *sink;
+	pa_source *source;
+
+	pa_time_event *time_event;
+	pa_rtpoll_item *rtpoll_item;
+
+	pa_thread_mq thread_mq;
+	pa_thread *thread;
+	pa_asyncmsgq *jack_msgq;
+	pa_rtpoll *rtpoll;
+
+	jack_client_t *jack;
+	jack_port_t *port[PA_CHANNELS_MAX];
+	jack_nframes_t frames_in_buffer;
+	jack_nframes_t saved_frame_time;
+	bool saved_frame_time_valid;
+
+	unsigned channels;
+	unsigned ports[PA_CHANNELS_MAX];
+	void *buffer[PA_CHANNELS_MAX];
+};
+
+
+enum {
+	SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX,
+	SOURCE_MESSAGE_ON_SHUTDOWN,
+	SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX,
+	SINK_MESSAGE_BUFFER_SIZE,
+	SINK_MESSAGE_ON_SHUTDOWN
+};
+
+void* init_card(void* arg, const char *name, bool is_sink);
+void unload_card(void* arg,bool forced);
+static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata);
+const char* get_merge_ref(pa_proplist *p, struct sBase *base);
-- 
2.1.3



More information about the pulseaudio-discuss mailing list