2 Copyright (C) 2014 Cédric Schieli
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "JackCompilerDeps.h"
21 #include "driver_interface.h"
22 #include "JackEngineControl.h"
23 #include "JackLockedEngine.h"
24 #include "JackWaitCallbackDriver.h"
25 #include "JackProxyDriver.h"
31 JackProxyDriver::JackProxyDriver(const char* name
, const char* alias
, JackLockedEngine
* engine
, JackSynchro
* table
,
32 const char* upstream
, const char* promiscuous
,
33 char* client_name
, bool auto_connect
, bool auto_save
)
34 : JackRestarterDriver(name
, alias
, engine
, table
)
36 jack_log("JackProxyDriver::JackProxyDriver upstream %s", upstream
);
38 assert(strlen(upstream
) < JACK_CLIENT_NAME_SIZE
);
39 strcpy(fUpstream
, upstream
);
41 assert(strlen(client_name
) < JACK_CLIENT_NAME_SIZE
);
42 strcpy(fClientName
, client_name
);
45 fPromiscuous
= strdup(promiscuous
);
48 fAutoConnect
= auto_connect
;
49 fAutoSave
= auto_save
;
52 JackProxyDriver::~JackProxyDriver()
55 UnloadJackModule(fHandle
);
59 int JackProxyDriver::LoadClientLib()
65 fHandle
= LoadJackModule(JACK_PROXY_CLIENT_LIB
);
73 //open, close, attach and detach------------------------------------------------------
75 int JackProxyDriver::Open(jack_nframes_t buffer_size
,
76 jack_nframes_t samplerate
,
82 const char* capture_driver_name
,
83 const char* playback_driver_name
,
84 jack_nframes_t capture_latency
,
85 jack_nframes_t playback_latency
)
87 fDetectPlaybackChannels
= (outchannels
== -1);
88 fDetectCaptureChannels
= (inchannels
== -1);
90 if (LoadClientLib() != 0) {
91 jack_error("Cannot dynamically load client library !");
95 return JackWaiterDriver::Open(buffer_size
, samplerate
,
97 inchannels
, outchannels
,
99 capture_driver_name
, playback_driver_name
,
100 capture_latency
, playback_latency
);
103 int JackProxyDriver::Close()
106 return JackWaiterDriver::Close();
109 // Attach and Detach are defined as empty methods: port allocation is done when driver actually start (that is in Init)
110 int JackProxyDriver::Attach()
115 int JackProxyDriver::Detach()
120 //init and restart--------------------------------------------------------------------
123 JackProxyDriver is wrapped in a JackWaitCallbackDriver decorator that behaves
124 as a "dummy driver, until Initialize method returns.
126 bool JackProxyDriver::Initialize()
128 jack_log("JackProxyDriver::Initialize");
130 // save existing local connections if needed
135 // new loading, but existing client, restart the driver
137 jack_info("JackProxyDriver restarting...");
138 jack_client_close(fClient
);
142 // display some additional infos
143 jack_info("JackProxyDriver started in %s mode.",
144 (fEngineControl
->fSyncMode
) ? "sync" : "async");
147 jack_status_t status
;
151 // as we are fiddling with the environment variable content, save it
152 const char* tmp
= getenv("JACK_PROMISCUOUS_SERVER");
156 // temporary enable promiscuous mode
157 if (setenv("JACK_PROMISCUOUS_SERVER", fPromiscuous
, 1) < 0) {
159 jack_error("Error allocating memory.");
164 jack_info("JackProxyDriver connecting to %s", fUpstream
);
165 fClient
= jack_client_open(fClientName
, static_cast<jack_options_t
>(JackNoStartServer
|JackServerName
), &status
, fUpstream
);
168 // restore previous environment variable content
170 if (setenv("JACK_PROMISCUOUS_SERVER", old
, 1) < 0) {
172 jack_error("Error allocating memory.");
177 unsetenv("JACK_PROMISCUOUS_SERVER");
181 // the connection failed, try again later
187 jack_info("JackProxyDriver connected to %s", fUpstream
);
189 // we are connected, let's register some callbacks
191 jack_on_shutdown(fClient
, shutdown_callback
, this);
193 if (jack_set_process_callback(fClient
, process_callback
, this) != 0) {
194 jack_error("Cannot set process callback.");
198 if (jack_set_buffer_size_callback(fClient
, bufsize_callback
, this) != 0) {
199 jack_error("Cannot set buffer size callback.");
203 if (jack_set_sample_rate_callback(fClient
, srate_callback
, this) != 0) {
204 jack_error("Cannot set sample rate callback.");
208 if (jack_set_port_connect_callback(fClient
, connect_callback
, this) != 0) {
209 jack_error("Cannot set port connect callback.");
213 // detect upstream physical playback ports if needed
214 if (fDetectPlaybackChannels
) {
215 fPlaybackChannels
= CountIO(JACK_DEFAULT_AUDIO_TYPE
, JackPortIsPhysical
| JackPortIsOutput
);
218 // detect upstream physical capture ports if needed
219 if (fDetectCaptureChannels
) {
220 fCaptureChannels
= CountIO(JACK_DEFAULT_AUDIO_TYPE
, JackPortIsPhysical
| JackPortIsInput
);
223 if (AllocPorts() != 0) {
224 jack_error("Can't allocate ports.");
228 bufsize_callback(jack_get_buffer_size(fClient
));
229 srate_callback(jack_get_sample_rate(fClient
));
231 // restore local connections if needed
236 // everything is ready, start upstream processing
237 if (jack_activate(fClient
) != 0) {
238 jack_error("Cannot activate jack client.");
242 // connect upstream ports if needed
250 int JackProxyDriver::Stop()
252 if (fClient
&& (jack_deactivate(fClient
) != 0)) {
253 jack_error("Cannot deactivate jack client.");
259 //client callbacks---------------------------------------------------------------------------
261 int JackProxyDriver::process_callback(jack_nframes_t nframes
, void* arg
)
263 assert(static_cast<JackProxyDriver
*>(arg
));
264 return static_cast<JackProxyDriver
*>(arg
)->Process();
267 int JackProxyDriver::bufsize_callback(jack_nframes_t nframes
, void* arg
)
269 assert(static_cast<JackProxyDriver
*>(arg
));
270 return static_cast<JackProxyDriver
*>(arg
)->bufsize_callback(nframes
);
272 int JackProxyDriver::bufsize_callback(jack_nframes_t nframes
)
274 if (JackTimedDriver::SetBufferSize(nframes
) == 0) {
277 JackDriver::NotifyBufferSize(nframes
);
281 int JackProxyDriver::srate_callback(jack_nframes_t nframes
, void* arg
)
283 assert(static_cast<JackProxyDriver
*>(arg
));
284 return static_cast<JackProxyDriver
*>(arg
)->srate_callback(nframes
);
286 int JackProxyDriver::srate_callback(jack_nframes_t nframes
)
288 if (JackTimedDriver::SetSampleRate(nframes
) == 0) {
291 JackDriver::NotifySampleRate(nframes
);
295 void JackProxyDriver::connect_callback(jack_port_id_t a
, jack_port_id_t b
, int connect
, void* arg
)
297 assert(static_cast<JackProxyDriver
*>(arg
));
298 static_cast<JackProxyDriver
*>(arg
)->connect_callback(a
, b
, connect
);
300 void JackProxyDriver::connect_callback(jack_port_id_t a
, jack_port_id_t b
, int connect
)
305 // skip port if not our own
306 port
= jack_port_by_id(fClient
, a
);
307 if (!jack_port_is_mine(fClient
, port
)) {
308 port
= jack_port_by_id(fClient
, b
);
309 if (!jack_port_is_mine(fClient
, port
)) {
314 for (i
= 0; i
< fCaptureChannels
; i
++) {
315 if (fUpstreamPlaybackPorts
[i
] == port
) {
316 fUpstreamPlaybackPortConnected
[i
] = connect
;
320 for (i
= 0; i
< fPlaybackChannels
; i
++) {
321 if (fUpstreamCapturePorts
[i
] == port
) {
322 fUpstreamCapturePortConnected
[i
] = connect
;
327 void JackProxyDriver::shutdown_callback(void* arg
)
329 assert(static_cast<JackProxyDriver
*>(arg
));
330 static_cast<JackProxyDriver
*>(arg
)->RestartWait();
333 //jack ports and buffers--------------------------------------------------------------
335 int JackProxyDriver::CountIO(const char* type
, int flags
)
338 const char** ports
= jack_get_ports(fClient
, NULL
, type
, flags
);
340 while (ports
[count
]) { count
++; }
346 int JackProxyDriver::AllocPorts()
348 jack_log("JackProxyDriver::AllocPorts fBufferSize = %ld fSampleRate = %ld", fEngineControl
->fBufferSize
, fEngineControl
->fSampleRate
);
350 char proxy
[REAL_JACK_PORT_NAME_SIZE
];
353 fUpstreamPlaybackPorts
= new jack_port_t
* [fCaptureChannels
];
354 fUpstreamPlaybackPortConnected
= new int [fCaptureChannels
];
355 for (i
= 0; i
< fCaptureChannels
; i
++) {
356 snprintf(proxy
, sizeof(proxy
), "%s:to_client_%d", fClientName
, i
+ 1);
357 fUpstreamPlaybackPorts
[i
] = jack_port_register(fClient
, proxy
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsInput
| JackPortIsTerminal
, 0);
358 if (fUpstreamPlaybackPorts
[i
] == NULL
) {
359 jack_error("driver: cannot register upstream port %s", proxy
);
362 fUpstreamPlaybackPortConnected
[i
] = 0;
365 fUpstreamCapturePorts
= new jack_port_t
* [fPlaybackChannels
];
366 fUpstreamCapturePortConnected
= new int [fPlaybackChannels
];
367 for (i
= 0; i
< fPlaybackChannels
; i
++) {
368 snprintf(proxy
, sizeof(proxy
), "%s:from_client_%d", fClientName
, i
+ 1);
369 fUpstreamCapturePorts
[i
] = jack_port_register(fClient
, proxy
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsOutput
| JackPortIsTerminal
, 0);
370 if (fUpstreamCapturePorts
[i
] == NULL
) {
371 jack_error("driver: cannot register upstream port %s", proxy
);
374 fUpstreamCapturePortConnected
[i
] = 0;
377 // local ports are registered here
378 return JackAudioDriver::Attach();
381 int JackProxyDriver::FreePorts()
383 jack_log("JackProxyDriver::FreePorts");
387 for (i
= 0; i
< fCaptureChannels
; i
++) {
388 if (fCapturePortList
[i
] > 0) {
389 fEngine
->PortUnRegister(fClientControl
.fRefNum
, fCapturePortList
[i
]);
390 fCapturePortList
[i
] = 0;
392 if (fUpstreamPlaybackPorts
&& fUpstreamPlaybackPorts
[i
]) {
393 fUpstreamPlaybackPorts
[i
] = NULL
;
397 for (i
= 0; i
< fPlaybackChannels
; i
++) {
398 if (fPlaybackPortList
[i
] > 0) {
399 fEngine
->PortUnRegister(fClientControl
.fRefNum
, fPlaybackPortList
[i
]);
400 fPlaybackPortList
[i
] = 0;
402 if (fUpstreamCapturePorts
&& fUpstreamCapturePorts
[i
]) {
403 fUpstreamCapturePorts
[i
] = NULL
;
407 delete[] fUpstreamPlaybackPorts
;
408 delete[] fUpstreamPlaybackPortConnected
;
409 delete[] fUpstreamCapturePorts
;
410 delete[] fUpstreamCapturePortConnected
;
412 fUpstreamPlaybackPorts
= NULL
;
413 fUpstreamPlaybackPortConnected
= NULL
;
414 fUpstreamCapturePorts
= NULL
;
415 fUpstreamCapturePortConnected
= NULL
;
420 void JackProxyDriver::ConnectPorts()
422 jack_log("JackProxyDriver::ConnectPorts");
423 const char** ports
= jack_get_ports(fClient
, NULL
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsPhysical
| JackPortIsOutput
);
425 for (int i
= 0; i
< fCaptureChannels
&& ports
[i
]; i
++) {
426 jack_connect(fClient
, ports
[i
], jack_port_name(fUpstreamPlaybackPorts
[i
]));
431 ports
= jack_get_ports(fClient
, NULL
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsPhysical
| JackPortIsInput
);
433 for (int i
= 0; i
< fPlaybackChannels
&& ports
[i
]; i
++) {
434 jack_connect(fClient
, jack_port_name(fUpstreamCapturePorts
[i
]), ports
[i
]);
440 //driver processes--------------------------------------------------------------------
442 int JackProxyDriver::Read()
444 // take the time at the beginning of the cycle
445 JackDriver::CycleTakeBeginTime();
449 size_t buflen
= sizeof(jack_default_audio_sample_t
) * fEngineControl
->fBufferSize
;
451 for (i
= 0; i
< fCaptureChannels
; i
++) {
452 if (fUpstreamPlaybackPortConnected
[i
]) {
453 from
= jack_port_get_buffer(fUpstreamPlaybackPorts
[i
], fEngineControl
->fBufferSize
);
454 to
= GetInputBuffer(i
);
455 memcpy(to
, from
, buflen
);
462 int JackProxyDriver::Write()
466 size_t buflen
= sizeof(jack_default_audio_sample_t
) * fEngineControl
->fBufferSize
;
468 for (i
= 0; i
< fPlaybackChannels
; i
++) {
469 if (fUpstreamCapturePortConnected
[i
]) {
470 to
= jack_port_get_buffer(fUpstreamCapturePorts
[i
], fEngineControl
->fBufferSize
);
471 from
= GetOutputBuffer(i
);
472 memcpy(to
, from
, buflen
);
479 //driver loader-----------------------------------------------------------------------
486 SERVER_EXPORT jack_driver_desc_t
* driver_get_descriptor()
488 jack_driver_desc_t
* desc
;
489 jack_driver_desc_filler_t filler
;
490 jack_driver_param_value_t value
;
492 desc
= jack_driver_descriptor_construct("proxy", JackDriverMaster
, "proxy backend", &filler
);
494 strcpy(value
.str
, DEFAULT_UPSTREAM
);
495 jack_driver_descriptor_add_parameter(desc
, &filler
, "upstream", 'u', JackDriverParamString
, &value
, NULL
, "Name of the upstream jack server", NULL
);
497 strcpy(value
.str
, "");
498 jack_driver_descriptor_add_parameter(desc
, &filler
, "promiscuous", 'p', JackDriverParamString
, &value
, NULL
, "Promiscuous group", NULL
);
501 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");
502 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");
504 strcpy(value
.str
, "proxy");
505 jack_driver_descriptor_add_parameter(desc
, &filler
, "client-name", 'n', JackDriverParamString
, &value
, NULL
, "Name of the jack client", NULL
);
508 jack_driver_descriptor_add_parameter(desc
, &filler
, "use-username", 'U', JackDriverParamBool
, &value
, NULL
, "Use current username as client name", NULL
);
511 jack_driver_descriptor_add_parameter(desc
, &filler
, "auto-connect", 'c', JackDriverParamBool
, &value
, NULL
, "Auto connect proxy to upstream system ports", NULL
);
514 jack_driver_descriptor_add_parameter(desc
, &filler
, "auto-save", 's', JackDriverParamBool
, &value
, NULL
, "Save/restore connection state when restarting", NULL
);
519 SERVER_EXPORT
Jack::JackDriverClientInterface
* driver_initialize(Jack::JackLockedEngine
* engine
, Jack::JackSynchro
* table
, const JSList
* params
)
521 char upstream
[JACK_CLIENT_NAME_SIZE
+ 1];
522 char promiscuous
[JACK_CLIENT_NAME_SIZE
+ 1] = {0};
523 char client_name
[JACK_CLIENT_NAME_SIZE
+ 1];
524 jack_nframes_t period_size
= 1024; // to be used while waiting for master period_size
525 jack_nframes_t sample_rate
= 48000; // to be used while waiting for master sample_rate
526 int capture_ports
= -1;
527 int playback_ports
= -1;
529 const jack_driver_param_t
* param
;
530 bool auto_connect
= false;
531 bool auto_save
= false;
532 bool use_promiscuous
= false;
534 // Possibly use env variable for upstream name
535 const char* default_upstream
= getenv("JACK_PROXY_UPSTREAM");
536 strcpy(upstream
, (default_upstream
) ? default_upstream
: DEFAULT_UPSTREAM
);
538 // Possibly use env variable for upstream promiscuous
539 const char* default_promiscuous
= getenv("JACK_PROXY_PROMISCUOUS");
540 strcpy(promiscuous
, (default_promiscuous
) ? default_promiscuous
: "");
542 // Possibly use env variable for client name
543 const char* default_client_name
= getenv("JACK_PROXY_CLIENT_NAME");
544 strcpy(client_name
, (default_client_name
) ? default_client_name
: DEFAULT_CLIENT_NAME
);
547 const char* username
= getenv("USERNAME");
549 const char* username
= getenv("LOGNAME");
552 for (node
= params
; node
; node
= jack_slist_next(node
)) {
553 param
= (const jack_driver_param_t
*) node
->data
;
554 switch (param
->character
)
557 assert(strlen(param
->value
.str
) < JACK_CLIENT_NAME_SIZE
);
558 strcpy(upstream
, param
->value
.str
);
561 assert(strlen(param
->value
.str
) < JACK_CLIENT_NAME_SIZE
);
562 use_promiscuous
= true;
563 strcpy(promiscuous
, param
->value
.str
);
566 capture_ports
= param
->value
.i
;
569 playback_ports
= param
->value
.i
;
572 assert(strlen(param
->value
.str
) < JACK_CLIENT_NAME_SIZE
);
573 strncpy(client_name
, param
->value
.str
, JACK_CLIENT_NAME_SIZE
);
576 if (username
&& *username
) {
577 assert(strlen(username
) < JACK_CLIENT_NAME_SIZE
);
578 strncpy(client_name
, username
, JACK_CLIENT_NAME_SIZE
);
591 Jack::JackDriverClientInterface
* driver
= new Jack::JackWaitCallbackDriver(
592 new Jack::JackProxyDriver("system", "proxy_pcm", engine
, table
, upstream
, use_promiscuous
? promiscuous
: NULL
, client_name
, auto_connect
, auto_save
));
593 if (driver
->Open(period_size
, sample_rate
, 1, 1, capture_ports
, playback_ports
, false, "capture_", "playback_", 0, 0) == 0) {