From c1ae33f9343490855fc0fd07721f130fc0a8c9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Schieli?= Date: Wed, 29 Oct 2014 19:09:56 +0100 Subject: [PATCH 1/3] Split JackWaitThreadedDriver's Execute method This makes it possible to use JackWaitThreadedDriver as a base class for a non-threaded version. --- common/JackWaitThreadedDriver.cpp | 21 +++++++++++++-------- common/JackWaitThreadedDriver.h | 8 ++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/common/JackWaitThreadedDriver.cpp b/common/JackWaitThreadedDriver.cpp index 09c2cae4..bbe70877 100644 --- a/common/JackWaitThreadedDriver.cpp +++ b/common/JackWaitThreadedDriver.cpp @@ -37,16 +37,21 @@ bool JackWaitThreadedDriver::Init() bool JackWaitThreadedDriver::Execute() { - try { + SetRealTime(); - SetRealTime(); + // Process a null cycle until NetDriver has started + while (!fStarter.fRunning && fThread.GetStatus() == JackThread::kRunning) { + // Use base class method + assert(static_cast(fDriver)); + static_cast(fDriver)->ProcessNull(); + } - // Process a null cycle until NetDriver has started - while (!fStarter.fRunning && fThread.GetStatus() == JackThread::kRunning) { - // Use base class method - assert(static_cast(fDriver)); - static_cast(fDriver)->ProcessNull(); - } + return ExecuteReal(); +} + +bool JackWaitThreadedDriver::ExecuteReal() +{ + try { // Switch to keep running even in case of error while (fThread.GetStatus() == JackThread::kRunning) { diff --git a/common/JackWaitThreadedDriver.h b/common/JackWaitThreadedDriver.h index 6eee399a..d9cda652 100644 --- a/common/JackWaitThreadedDriver.h +++ b/common/JackWaitThreadedDriver.h @@ -28,10 +28,10 @@ namespace Jack { /*! -\brief To be used as a wrapper of JackNetDriver. +\brief Wrapper for a restartable threaded driver (e.g. JackNetDriver). The idea is to behave as the "dummy" driver, until the network connection is really started and processing starts. -The Execute method will call the Process method from the base JackTimedDriver, until the decorated driver Init method returns. +The Execute method will call the ProcessNull method from the base JackWaiterDriver, until the decorated driver Initialize method returns. A helper JackDriverStarter thread is used for that purpose. */ @@ -89,6 +89,10 @@ class SERVER_EXPORT JackWaitThreadedDriver : public JackThreadedDriver // JackRunnableInterface interface bool Init(); bool Execute(); + + protected: + + virtual bool ExecuteReal(); /*!< Real work to be done when the decorated driver has finish initializing */ }; From 1cd25cb975e000be0d7628f51e14861c30db3f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Schieli?= Date: Thu, 30 Oct 2014 10:24:58 +0100 Subject: [PATCH 2/3] Add JackWaitCallbackDriver This wrapper driver has the same usage as its parent JackWaitThreadedDriver, but for non-threaded (callback) drivers. After waiting for Initialize to return, its main thread simply ends instead of calling the driver's Process method in a loop. The decorated driver, which must extends JackRestarerDriver instead of JackWaiterDriver, can restart the wait cycle by calling its RestartWait method. --- common/JackTimedDriver.cpp | 14 +++++++++ common/JackTimedDriver.h | 26 ++++++++++++++++ common/JackWaitCallbackDriver.cpp | 39 ++++++++++++++++++++++++ common/JackWaitCallbackDriver.h | 50 +++++++++++++++++++++++++++++++ common/wscript | 1 + 5 files changed, 130 insertions(+) create mode 100644 common/JackWaitCallbackDriver.cpp create mode 100644 common/JackWaitCallbackDriver.h diff --git a/common/JackTimedDriver.cpp b/common/JackTimedDriver.cpp index 2a6dec89..7fccfd01 100644 --- a/common/JackTimedDriver.cpp +++ b/common/JackTimedDriver.cpp @@ -86,4 +86,18 @@ int JackWaiterDriver::ProcessNull() return 0; } +void JackRestarterDriver::SetRestartDriver(JackDriver* driver) +{ + fRestartDriver = driver; +} + +int JackRestarterDriver::RestartWait() +{ + if (!fRestartDriver) { + jack_error("JackRestartedDriver::RestartWait driver not set"); + return -1; + } + return fRestartDriver->Start(); +} + } // end of namespace diff --git a/common/JackTimedDriver.h b/common/JackTimedDriver.h index e2d26ca1..0a594e75 100644 --- a/common/JackTimedDriver.h +++ b/common/JackTimedDriver.h @@ -75,6 +75,32 @@ class SERVER_EXPORT JackWaiterDriver : public JackTimedDriver }; +/*! +\brief A restartable driver. + +When wrapped into a JackWaitCallbackDriver, this driver can restart the +wrapper thread which will wait again for the Initialize method to return. +*/ +class SERVER_EXPORT JackRestarterDriver : public JackWaiterDriver +{ + + private: + + JackDriver* fRestartDriver; /*!< The wrapper driver */ + + public: + + JackRestarterDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table) + : JackWaiterDriver(name, alias, engine, table), fRestartDriver(NULL) + {} + virtual ~JackRestarterDriver() + {} + + void SetRestartDriver(JackDriver* driver); /*!< Let the wrapper register itself */ + int RestartWait(); /*!< Restart the wrapper thread */ + +}; + } // end of namespace #endif diff --git a/common/JackWaitCallbackDriver.cpp b/common/JackWaitCallbackDriver.cpp new file mode 100644 index 00000000..6ab9944d --- /dev/null +++ b/common/JackWaitCallbackDriver.cpp @@ -0,0 +1,39 @@ +/* + Copyright (C) 2014 Cédric Schieli + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include "JackWaitCallbackDriver.h" + +namespace Jack +{ + +JackWaitCallbackDriver::JackWaitCallbackDriver(JackRestarterDriver* driver) + : JackWaitThreadedDriver(driver) +{ + // Self register with the decorated driver so it can restart us + assert(driver); + driver->SetRestartDriver((JackDriver*)this); +} + +bool JackWaitCallbackDriver::ExecuteReal() +{ + // End the thread and let the callback driver do its job + return false; +} + +} // end of namespace diff --git a/common/JackWaitCallbackDriver.h b/common/JackWaitCallbackDriver.h new file mode 100644 index 00000000..1474472d --- /dev/null +++ b/common/JackWaitCallbackDriver.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2014 Cédric Schieli + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef __JackWaitCallbackDriver__ +#define __JackWaitCallbackDriver__ + +#include "JackWaitThreadedDriver.h" + +namespace Jack +{ + +/*! +\brief Wrapper for a restartable non-threaded driver (e.g. JackProxyDriver). + +Simply ends its thread when the decorated driver Initialize method returns. +Self register with the supplied JackRestarterDriver so it can restart the thread. +*/ + +class SERVER_EXPORT JackWaitCallbackDriver : public JackWaitThreadedDriver +{ + public: + + JackWaitCallbackDriver(JackRestarterDriver* driver); + + protected: + + bool ExecuteReal(); +}; + + +} // end of namespace + + +#endif diff --git a/common/wscript b/common/wscript index 10ed9c25..b5359ade 100644 --- a/common/wscript +++ b/common/wscript @@ -223,6 +223,7 @@ def build(bld): 'JackThreadedDriver.cpp', 'JackRestartThreadedDriver.cpp', 'JackWaitThreadedDriver.cpp', + 'JackWaitCallbackDriver.cpp', 'JackServerAPI.cpp', 'JackDriverLoader.cpp', 'JackServerGlobals.cpp', From 8f6c3c6d1f5e7b7bd18123538229bb99cb1eb82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Schieli?= Date: Fri, 10 Oct 2014 22:17:17 +0200 Subject: [PATCH 3/3] Add JackProxyDriver This driver is very similar to the JackNetDriver, but instead of connecting through the network, it connects to its upstream server through standard jack API. So it can only reach local servers which must be running as the same user or in promiscuous mode. The main use case is the multi-user, multi-session, shared workstation: - a classic server with hw driver is launched system-wide at boot time, in promiscuous mode, optionaly restricted to the audio group - in each user session, a jackdbus server is automatically started with JackProxyDriver as master driver, automatically connected to the system-wide one - optionaly, each user run PulseAudio with a pulse-jack bridge --- common/JackProxyDriver.cpp | 608 +++++++++++++++++++++++++++++++++++++ common/JackProxyDriver.h | 181 +++++++++++ linux/wscript | 1 + macosx/wscript | 2 + solaris/wscript | 2 + windows/wscript | 2 + 6 files changed, 796 insertions(+) create mode 100644 common/JackProxyDriver.cpp create mode 100644 common/JackProxyDriver.h diff --git a/common/JackProxyDriver.cpp b/common/JackProxyDriver.cpp new file mode 100644 index 00000000..43910776 --- /dev/null +++ b/common/JackProxyDriver.cpp @@ -0,0 +1,608 @@ +/* + Copyright (C) 2014 Cédric Schieli + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include "JackCompilerDeps.h" +#include "driver_interface.h" +#include "JackEngineControl.h" +#include "JackLockedEngine.h" +#include "JackWaitCallbackDriver.h" +#include "JackProxyDriver.h" + +using namespace std; + +namespace Jack +{ + JackProxyDriver::JackProxyDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, + const char* upstream, const char* promiscuous, + char* client_name, bool auto_connect, bool auto_save) + : JackRestarterDriver(name, alias, engine, table) + { + jack_log("JackProxyDriver::JackProxyDriver upstream %s", upstream); + + assert(strlen(upstream) < JACK_CLIENT_NAME_SIZE); + strcpy(fUpstream, upstream); + + assert(strlen(client_name) < JACK_CLIENT_NAME_SIZE); + strcpy(fClientName, client_name); + + if (promiscuous) { + fPromiscuous = strdup(promiscuous); + } + + fAutoConnect = auto_connect; + fAutoSave = auto_save; + } + + JackProxyDriver::~JackProxyDriver() + { + if (fHandle) { + UnloadJackModule(fHandle); + } + } + + int JackProxyDriver::LoadClientLib() + { + // Already loaded + if (fHandle) { + return 0; + } + fHandle = LoadJackModule(JACK_PROXY_CLIENT_LIB); + if (!fHandle) { + return -1; + } + LoadSymbols(); + return 0; + } + +//open, close, attach and detach------------------------------------------------------ + + int JackProxyDriver::Open(jack_nframes_t buffer_size, + jack_nframes_t samplerate, + bool capturing, + bool playing, + int inchannels, + int outchannels, + bool monitor, + const char* capture_driver_name, + const char* playback_driver_name, + jack_nframes_t capture_latency, + jack_nframes_t playback_latency) + { + fDetectPlaybackChannels = (outchannels == -1); + fDetectCaptureChannels = (inchannels == -1); + + if (LoadClientLib() != 0) { + jack_error("Cannot dynamically load client library !"); + return -1; + } + + return JackWaiterDriver::Open(buffer_size, samplerate, + capturing, playing, + inchannels, outchannels, + monitor, + capture_driver_name, playback_driver_name, + capture_latency, playback_latency); + } + + int JackProxyDriver::Close() + { + FreePorts(); + return JackWaiterDriver::Close(); + } + + // Attach and Detach are defined as empty methods: port allocation is done when driver actually start (that is in Init) + int JackProxyDriver::Attach() + { + return 0; + } + + int JackProxyDriver::Detach() + { + return 0; + } + +//init and restart-------------------------------------------------------------------- + + /* + JackProxyDriver is wrapped in a JackWaitCallbackDriver decorator that behaves + as a "dummy driver, until Initialize method returns. + */ + bool JackProxyDriver::Initialize() + { + jack_log("JackProxyDriver::Initialize"); + + // save existing local connections if needed + if (fAutoSave) { + SaveConnections(0); + } + + // new loading, but existing client, restart the driver + if (fClient) { + jack_info("JackProxyDriver restarting..."); + jack_client_close(fClient); + } + FreePorts(); + + // display some additional infos + jack_info("JackProxyDriver started in %s mode.", + (fEngineControl->fSyncMode) ? "sync" : "async"); + + do { + jack_status_t status; + char *old = NULL; + + if (fPromiscuous) { + // as we are fiddling with the environment variable content, save it + const char* tmp = getenv("JACK_PROMISCUOUS_SERVER"); + if (tmp) { + old = strdup(tmp); + } + // temporary enable promiscuous mode + if (setenv("JACK_PROMISCUOUS_SERVER", fPromiscuous, 1) < 0) { + free(old); + jack_error("Error allocating memory."); + return false; + } + } + + jack_info("JackProxyDriver connecting to %s", fUpstream); + fClient = jack_client_open(fClientName, static_cast(JackNoStartServer|JackServerName), &status, fUpstream); + + if (fPromiscuous) { + // restore previous environment variable content + if (old) { + if (setenv("JACK_PROMISCUOUS_SERVER", old, 1) < 0) { + free(old); + jack_error("Error allocating memory."); + return false; + } + free(old); + } else { + unsetenv("JACK_PROMISCUOUS_SERVER"); + } + } + + // the connection failed, try again later + if (!fClient) { + JackSleep(1000000); + } + + } while (!fClient); + jack_info("JackProxyDriver connected to %s", fUpstream); + + // we are connected, let's register some callbacks + + jack_on_shutdown(fClient, shutdown_callback, this); + + if (jack_set_process_callback(fClient, process_callback, this) != 0) { + jack_error("Cannot set process callback."); + return false; + } + + if (jack_set_buffer_size_callback(fClient, bufsize_callback, this) != 0) { + jack_error("Cannot set buffer size callback."); + return false; + } + + if (jack_set_sample_rate_callback(fClient, srate_callback, this) != 0) { + jack_error("Cannot set sample rate callback."); + return false; + } + + if (jack_set_port_connect_callback(fClient, connect_callback, this) != 0) { + jack_error("Cannot set port connect callback."); + return false; + } + + // detect upstream physical playback ports if needed + if (fDetectPlaybackChannels) { + fPlaybackChannels = CountIO(JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical | JackPortIsOutput); + } + + // detect upstream physical capture ports if needed + if (fDetectCaptureChannels) { + fCaptureChannels = CountIO(JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical | JackPortIsInput); + } + + if (AllocPorts() != 0) { + jack_error("Can't allocate ports."); + return false; + } + + bufsize_callback(jack_get_buffer_size(fClient)); + srate_callback(jack_get_sample_rate(fClient)); + + // restore local connections if needed + if (fAutoSave) { + LoadConnections(0); + } + + // everything is ready, start upstream processing + if (jack_activate(fClient) != 0) { + jack_error("Cannot activate jack client."); + return false; + } + + // connect upstream ports if needed + if (fAutoConnect) { + ConnectPorts(); + } + + return true; + } + + int JackProxyDriver::Stop() + { + if (fClient && (jack_deactivate(fClient) != 0)) { + jack_error("Cannot deactivate jack client."); + return -1; + } + return 0; + } + +//client callbacks--------------------------------------------------------------------------- + + int JackProxyDriver::process_callback(jack_nframes_t nframes, void* arg) + { + assert(static_cast(arg)); + return static_cast(arg)->Process(); + } + + int JackProxyDriver::bufsize_callback(jack_nframes_t nframes, void* arg) + { + assert(static_cast(arg)); + return static_cast(arg)->bufsize_callback(nframes); + } + int JackProxyDriver::bufsize_callback(jack_nframes_t nframes) + { + if (JackTimedDriver::SetBufferSize(nframes) == 0) { + return -1; + } + JackDriver::NotifyBufferSize(nframes); + return 0; + } + + int JackProxyDriver::srate_callback(jack_nframes_t nframes, void* arg) + { + assert(static_cast(arg)); + return static_cast(arg)->srate_callback(nframes); + } + int JackProxyDriver::srate_callback(jack_nframes_t nframes) + { + if (JackTimedDriver::SetSampleRate(nframes) == 0) { + return -1; + } + JackDriver::NotifySampleRate(nframes); + return 0; + } + + void JackProxyDriver::connect_callback(jack_port_id_t a, jack_port_id_t b, int connect, void* arg) + { + assert(static_cast(arg)); + static_cast(arg)->connect_callback(a, b, connect); + } + void JackProxyDriver::connect_callback(jack_port_id_t a, jack_port_id_t b, int connect) + { + jack_port_t* port; + int i; + + // skip port if not our own + port = jack_port_by_id(fClient, a); + if (!jack_port_is_mine(fClient, port)) { + port = jack_port_by_id(fClient, b); + if (!jack_port_is_mine(fClient, port)) { + return; + } + } + + for (i = 0; i < fCaptureChannels; i++) { + if (fUpstreamPlaybackPorts[i] == port) { + fUpstreamPlaybackPortConnected[i] = connect; + } + } + + for (i = 0; i < fPlaybackChannels; i++) { + if (fUpstreamCapturePorts[i] == port) { + fUpstreamCapturePortConnected[i] = connect; + } + } + } + + void JackProxyDriver::shutdown_callback(void* arg) + { + assert(static_cast(arg)); + static_cast(arg)->RestartWait(); + } + +//jack ports and buffers-------------------------------------------------------------- + + int JackProxyDriver::CountIO(const char* type, int flags) + { + int count = 0; + const char** ports = jack_get_ports(fClient, NULL, type, flags); + if (ports != NULL) { + while (ports[count]) { count++; } + jack_free(ports); + } + return count; + } + + int JackProxyDriver::AllocPorts() + { + jack_log("JackProxyDriver::AllocPorts fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate); + + char proxy[REAL_JACK_PORT_NAME_SIZE]; + int i; + + fUpstreamPlaybackPorts = new jack_port_t* [fCaptureChannels]; + fUpstreamPlaybackPortConnected = new int [fCaptureChannels]; + for (i = 0; i < fCaptureChannels; i++) { + snprintf(proxy, sizeof(proxy), "%s:to_client_%d", fClientName, i + 1); + fUpstreamPlaybackPorts[i] = jack_port_register(fClient, proxy, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0); + if (fUpstreamPlaybackPorts[i] == NULL) { + jack_error("driver: cannot register upstream port %s", proxy); + return -1; + } + fUpstreamPlaybackPortConnected[i] = 0; + } + + fUpstreamCapturePorts = new jack_port_t* [fPlaybackChannels]; + fUpstreamCapturePortConnected = new int [fPlaybackChannels]; + for (i = 0; i < fPlaybackChannels; i++) { + snprintf(proxy, sizeof(proxy), "%s:from_client_%d", fClientName, i + 1); + fUpstreamCapturePorts[i] = jack_port_register(fClient, proxy, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); + if (fUpstreamCapturePorts[i] == NULL) { + jack_error("driver: cannot register upstream port %s", proxy); + return -1; + } + fUpstreamCapturePortConnected[i] = 0; + } + + // local ports are registered here + return JackAudioDriver::Attach(); + } + + int JackProxyDriver::FreePorts() + { + jack_log("JackProxyDriver::FreePorts"); + + int i; + + for (i = 0; i < fCaptureChannels; i++) { + if (fCapturePortList[i] > 0) { + fEngine->PortUnRegister(fClientControl.fRefNum, fCapturePortList[i]); + fCapturePortList[i] = 0; + } + if (fUpstreamPlaybackPorts && fUpstreamPlaybackPorts[i]) { + fUpstreamPlaybackPorts[i] = NULL; + } + } + + for (i = 0; i < fPlaybackChannels; i++) { + if (fPlaybackPortList[i] > 0) { + fEngine->PortUnRegister(fClientControl.fRefNum, fPlaybackPortList[i]); + fPlaybackPortList[i] = 0; + } + if (fUpstreamCapturePorts && fUpstreamCapturePorts[i]) { + fUpstreamCapturePorts[i] = NULL; + } + } + + delete[] fUpstreamPlaybackPorts; + delete[] fUpstreamPlaybackPortConnected; + delete[] fUpstreamCapturePorts; + delete[] fUpstreamCapturePortConnected; + + fUpstreamPlaybackPorts = NULL; + fUpstreamPlaybackPortConnected = NULL; + fUpstreamCapturePorts = NULL; + fUpstreamCapturePortConnected = NULL; + + return 0; + } + + void JackProxyDriver::ConnectPorts() + { + jack_log("JackProxyDriver::ConnectPorts"); + const char** ports = jack_get_ports(fClient, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical | JackPortIsOutput); + if (ports != NULL) { + for (int i = 0; i < fCaptureChannels && ports[i]; i++) { + jack_connect(fClient, ports[i], jack_port_name(fUpstreamPlaybackPorts[i])); + } + jack_free(ports); + } + + ports = jack_get_ports(fClient, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical | JackPortIsInput); + if (ports != NULL) { + for (int i = 0; i < fPlaybackChannels && ports[i]; i++) { + jack_connect(fClient, jack_port_name(fUpstreamCapturePorts[i]), ports[i]); + } + jack_free(ports); + } + } + +//driver processes-------------------------------------------------------------------- + + int JackProxyDriver::Read() + { + // take the time at the beginning of the cycle + JackDriver::CycleTakeBeginTime(); + + int i; + void *from, *to; + size_t buflen = sizeof(jack_default_audio_sample_t) * fEngineControl->fBufferSize; + + for (i = 0; i < fCaptureChannels; i++) { + if (fUpstreamPlaybackPortConnected[i]) { + from = jack_port_get_buffer(fUpstreamPlaybackPorts[i], fEngineControl->fBufferSize); + to = GetInputBuffer(i); + memcpy(to, from, buflen); + } + } + + return 0; + } + + int JackProxyDriver::Write() + { + int i; + void *from, *to; + size_t buflen = sizeof(jack_default_audio_sample_t) * fEngineControl->fBufferSize; + + for (i = 0; i < fPlaybackChannels; i++) { + if (fUpstreamCapturePortConnected[i]) { + to = jack_port_get_buffer(fUpstreamCapturePorts[i], fEngineControl->fBufferSize); + from = GetOutputBuffer(i); + memcpy(to, from, buflen); + } + } + + return 0; + } + +//driver loader----------------------------------------------------------------------- + +#ifdef __cplusplus + extern "C" + { +#endif + + SERVER_EXPORT jack_driver_desc_t* driver_get_descriptor() + { + jack_driver_desc_t * desc; + jack_driver_desc_filler_t filler; + jack_driver_param_value_t value; + + desc = jack_driver_descriptor_construct("proxy", JackDriverMaster, "proxy backend", &filler); + + strcpy(value.str, DEFAULT_UPSTREAM); + jack_driver_descriptor_add_parameter(desc, &filler, "upstream", 'u', JackDriverParamString, &value, NULL, "Name of the upstream jack server", NULL); + + strcpy(value.str, ""); + jack_driver_descriptor_add_parameter(desc, &filler, "promiscuous", 'p', JackDriverParamString, &value, NULL, "Promiscuous group", NULL); + + value.i = -1; + jack_driver_descriptor_add_parameter(desc, &filler, "input-ports", 'C', JackDriverParamInt, &value, NULL, "Number of audio input ports", "Number of audio input ports. If -1, audio physical input from the master"); + jack_driver_descriptor_add_parameter(desc, &filler, "output-ports", 'P', JackDriverParamInt, &value, NULL, "Number of audio output ports", "Number of audio output ports. If -1, audio physical output from the master"); + + strcpy(value.str, "proxy"); + jack_driver_descriptor_add_parameter(desc, &filler, "client-name", 'n', JackDriverParamString, &value, NULL, "Name of the jack client", NULL); + + value.i = false; + jack_driver_descriptor_add_parameter(desc, &filler, "use-username", 'U', JackDriverParamBool, &value, NULL, "Use current username as client name", NULL); + + value.i = false; + jack_driver_descriptor_add_parameter(desc, &filler, "auto-connect", 'c', JackDriverParamBool, &value, NULL, "Auto connect proxy to upstream system ports", NULL); + + value.i = false; + jack_driver_descriptor_add_parameter(desc, &filler, "auto-save", 's', JackDriverParamBool, &value, NULL, "Save/restore connection state when restarting", NULL); + + return desc; + } + + SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) + { + char upstream[JACK_CLIENT_NAME_SIZE + 1]; + char promiscuous[JACK_CLIENT_NAME_SIZE + 1] = {0}; + char client_name[JACK_CLIENT_NAME_SIZE + 1]; + jack_nframes_t period_size = 1024; // to be used while waiting for master period_size + jack_nframes_t sample_rate = 48000; // to be used while waiting for master sample_rate + int capture_ports = -1; + int playback_ports = -1; + const JSList* node; + const jack_driver_param_t* param; + bool auto_connect = false; + bool auto_save = false; + bool use_promiscuous = false; + + // Possibly use env variable for upstream name + const char* default_upstream = getenv("JACK_PROXY_UPSTREAM"); + strcpy(upstream, (default_upstream) ? default_upstream : DEFAULT_UPSTREAM); + + // Possibly use env variable for upstream promiscuous + const char* default_promiscuous = getenv("JACK_PROXY_PROMISCUOUS"); + strcpy(promiscuous, (default_promiscuous) ? default_promiscuous : ""); + + // Possibly use env variable for client name + const char* default_client_name = getenv("JACK_PROXY_CLIENT_NAME"); + strcpy(client_name, (default_client_name) ? default_client_name : DEFAULT_CLIENT_NAME); + +#ifdef WIN32 + const char* username = getenv("USERNAME"); +#else + const char* username = getenv("LOGNAME"); +#endif + + for (node = params; node; node = jack_slist_next(node)) { + param = (const jack_driver_param_t*) node->data; + switch (param->character) + { + case 'u' : + assert(strlen(param->value.str) < JACK_CLIENT_NAME_SIZE); + strcpy(upstream, param->value.str); + break; + case 'p': + assert(strlen(param->value.str) < JACK_CLIENT_NAME_SIZE); + use_promiscuous = true; + strcpy(promiscuous, param->value.str); + break; + case 'C': + capture_ports = param->value.i; + break; + case 'P': + playback_ports = param->value.i; + break; + case 'n' : + assert(strlen(param->value.str) < JACK_CLIENT_NAME_SIZE); + strncpy(client_name, param->value.str, JACK_CLIENT_NAME_SIZE); + break; + case 'U' : + if (username && *username) { + assert(strlen(username) < JACK_CLIENT_NAME_SIZE); + strncpy(client_name, username, JACK_CLIENT_NAME_SIZE); + } + case 'c': + auto_connect = true; + break; + case 's': + auto_save = true; + break; + } + } + + try { + + Jack::JackDriverClientInterface* driver = new Jack::JackWaitCallbackDriver( + new Jack::JackProxyDriver("system", "proxy_pcm", engine, table, upstream, use_promiscuous ? promiscuous : NULL, client_name, auto_connect, auto_save)); + if (driver->Open(period_size, sample_rate, 1, 1, capture_ports, playback_ports, false, "capture_", "playback_", 0, 0) == 0) { + return driver; + } else { + delete driver; + return NULL; + } + + } catch (...) { + return NULL; + } + } + +#ifdef __cplusplus + } +#endif +} diff --git a/common/JackProxyDriver.h b/common/JackProxyDriver.h new file mode 100644 index 00000000..f7b25e41 --- /dev/null +++ b/common/JackProxyDriver.h @@ -0,0 +1,181 @@ +/* + Copyright (C) 2014 Cédric Schieli + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef __JackProxyDriver__ +#define __JackProxyDriver__ + +#include "JackTimedDriver.h" + +#define DEFAULT_UPSTREAM "default" /*!< Default upstream Jack server to connect to */ +#define DEFAULT_CLIENT_NAME "proxy" /*!< Default client name to use when connecting to upstream Jack server */ + +#ifdef __APPLE__ + #define JACK_PROXY_CLIENT_LIB "libjack.0.dylib" +#elif defined(WIN32) + #ifdef _WIN64 + #define JACK_PROXY_CLIENT_LIB "libjack64.dll" + #else + #define JACK_PROXY_CLIENT_LIB "libjack.dll" + #endif +#else + #define JACK_PROXY_CLIENT_LIB "libjack.so.0" +#endif + +#define PROXY_DEF_SYMBOL(ret,name,...) ret (*name) (__VA_ARGS__) +#define PROXY_LOAD_SYMBOL(ret,name,...) name = (ret (*) (__VA_ARGS__)) GetJackProc(fHandle, #name); assert(name) + +namespace Jack +{ + /*! \Brief This class describes the Proxy Backend + + It uses plain Jack API to connect to an upstream server. The latter is + either running as the same user, or is running in promiscuous mode. + + The main use case is the multi-user, multi-session, shared workstation: + + - a classic server with hw driver is launched system-wide at boot time, in + promiscuous mode, optionaly restricted to the audio group + - in each user session, a jackdbus server is automatically started with + JackProxyDriver as master driver, automatically connected to the + system-wide one + - optionaly, each user run PulseAudio with a pulse-jack bridge + */ + + class JackProxyDriver : public JackRestarterDriver + { + + private: + + char fUpstream[JACK_CLIENT_NAME_SIZE]; /*