1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5 
6 #include "JackAudio.h"
7 
8 #include "Global.h"
9 
10 
11 static JackAudioSystem *jasys = NULL;
12 
13 // jackStatusToStringList converts a jack_status_t (a flag type
14 // that can contain multiple Jack statuses) to a QStringList.
jackStatusToStringList(jack_status_t status)15 QStringList jackStatusToStringList(jack_status_t status) {
16 	QStringList statusList;
17 
18 	if ((status & JackFailure) != 0) {
19 		statusList << QLatin1String("JackFailure - overall operation failed");
20 	}
21 	if ((status & JackInvalidOption) != 0) {
22 		statusList << QLatin1String("JackInvalidOption - the operation contained an invalid or unsupported option");
23 	}
24 	if ((status & JackNameNotUnique) != 0)  {
25 		statusList << QLatin1String("JackNameNotUnique - the desired client name is not unique");
26 	}
27 	if ((status & JackServerStarted) != 0) {
28 		statusList << QLatin1String("JackServerStarted - the server was started as a result of this operation");
29 	}
30 	if ((status & JackServerFailed) != 0) {
31 		statusList << QLatin1String("JackServerFailed - unable to connect to the JACK server");
32 	}
33 	if ((status & JackServerError) != 0) {
34 		statusList << QLatin1String("JackServerError - communication error with the JACK server");
35 	}
36 	if ((status & JackNoSuchClient) != 0) {
37 		statusList << QLatin1String("JackNoSuchClient - requested client does not exist");
38 	}
39 	if ((status & JackLoadFailure) != 0) {
40 		statusList << QLatin1String("JackLoadFailure - unable to load initial client");
41 	}
42 	if ((status & JackInitFailure) != 0) {
43 		statusList << QLatin1String("JackInitFailure - unable to initialize client");
44 	}
45 	if ((status & JackShmFailure) != 0)  {
46 		statusList << QLatin1String("JackShmFailure - unable to access shared memory");
47 	}
48 	if ((status & JackVersionError) != 0) {
49 		statusList << QLatin1String("JackVersionError - client's protocol version does not match");
50 	}
51 	if ((status & JackBackendError) != 0) {
52 		statusList << QLatin1String("JackBackendError - a backend error occurred");
53 	}
54 	if ((status & JackClientZombie) != 0) {
55 		statusList << QLatin1String("JackClientZombie - client zombified");
56 	}
57 
58 	return statusList;
59 }
60 
61 class JackAudioInputRegistrar : public AudioInputRegistrar {
62 	public:
63 		JackAudioInputRegistrar();
64 		virtual AudioInput *create();
65 		virtual const QList<audioDevice> getDeviceChoices();
66 		virtual void setDeviceChoice(const QVariant &, Settings &);
67 		virtual bool canEcho(const QString &) const;
68 };
69 
70 class JackAudioOutputRegistrar : public AudioOutputRegistrar {
71 	public:
72 		JackAudioOutputRegistrar();
73 		virtual AudioOutput *create();
74 		virtual const QList<audioDevice> getDeviceChoices();
75 		virtual void setDeviceChoice(const QVariant &, Settings &);
76 };
77 
78 class JackAudioInit : public DeferInit {
79 	public:
80 		JackAudioInputRegistrar *airJackAudio;
81 		JackAudioOutputRegistrar *aorJackAudio;
initialize()82 		void initialize() {
83 			jasys = new JackAudioSystem();
84 			jasys->qmWait.lock();
85 			jasys->qwcWait.wait(&jasys->qmWait, 1000);
86 			jasys->qmWait.unlock();
87 			if (jasys->bJackIsGood) {
88 				airJackAudio = new JackAudioInputRegistrar();
89 				aorJackAudio = new JackAudioOutputRegistrar();
90 			} else {
91 				airJackAudio = NULL;
92 				aorJackAudio = NULL;
93 				delete jasys;
94 				jasys = NULL;
95 			}
96 		}
97 
destroy()98 		void destroy() {
99 			if (airJackAudio)
100 				delete airJackAudio;
101 			if (aorJackAudio)
102 				delete aorJackAudio;
103 			if (jasys) {
104 				delete jasys;
105 				jasys = NULL;
106 			}
107 		}
108 };
109 
110 static JackAudioInit jackinit; // To instantiate the classes (JackAudioSystem, JackAudioInputRegistrar and JackAudioOutputRegistrar).
111 
JackAudioSystem()112 JackAudioSystem::JackAudioSystem()
113 	: bActive(false)
114 	, client(NULL)
115 	, in_port(NULL)
116 	, output_buffer(NULL)
117 	, iBufferSize(0)
118 	, bJackIsGood(false)
119 	, bInputIsGood(false)
120 	, bOutputIsGood(false)
121 	, iSampleRate(0)
122 {
123 	if (g.s.qsJackAudioOutput.isEmpty()) {
124 		iOutPorts = 1;
125 	} else {
126 		iOutPorts = g.s.qsJackAudioOutput.toInt();
127 	}
128 	memset(reinterpret_cast<void *>(&out_ports), 0, sizeof(out_ports));
129 
130 	qhInput.insert(QString(), tr("Hardware Ports"));
131 	qhOutput.insert(QString::number(1), tr("Mono"));
132 	qhOutput.insert(QString::number(2), tr("Stereo"));
133 
134 	jack_status_t status = static_cast<jack_status_t>(0);
135 	int err = 0;
136 
137 	jack_options_t jack_option = g.s.bJackStartServer ? JackNullOption : JackNoStartServer;
138 	client = jack_client_open(g.s.qsJackClientName.toStdString().c_str(), jack_option, &status);
139 
140 	if (!client) {
141 		QStringList errors = jackStatusToStringList(status);
142 		qWarning("JackAudioSystem: unable to open client due to %i errors:", errors.count());
143 		for (int i = 0; i < errors.count(); ++i) {
144 			qWarning("JackAudioSystem: %s", qPrintable(errors.at(i)));
145 		}
146 
147 		return;
148 	}
149 
150 	qWarning("JackAudioSystem: client \"%s\" opened successfully", jack_get_client_name(client));
151 	iBufferSize = jack_get_buffer_size(client);
152 	iSampleRate = jack_get_sample_rate(client);
153 
154 	err = jack_set_process_callback(client, process_callback, this);
155 	if (err != 0) {
156 		qWarning("JackAudioSystem: unable to set process callback - jack_set_process_callback() returned %i", err);
157 		return;
158 	}
159 
160 	err = jack_set_sample_rate_callback(client, srate_callback, this);
161 	if (err != 0) {
162 		qWarning("JackAudioSystem: unable to set sample rate callback - jack_set_sample_rate_callback() returned %i", err);
163 		return;
164 	}
165 
166 	err = jack_set_buffer_size_callback(client, buffer_size_callback, this);
167 	if (err != 0) {
168 		qWarning("JackAudioSystem: unable to set buffer size callback - jack_set_buffer_size_callback() returned %i", err);
169 		return;
170 	}
171 
172 	jack_on_shutdown(client, shutdown_callback, this);
173 
174 	// If we made it this far, then everything is okay
175 	bJackIsGood = true;
176 }
177 
~JackAudioSystem()178 JackAudioSystem::~JackAudioSystem() {
179 	QMutexLocker lock(&qmWait);
180 
181 	if (client) {
182 		int err = 0;
183 		err = jack_deactivate(client);
184 		if (err != 0)  {
185 			qWarning("JackAudioSystem: unable to remove client from the process graph - jack_deactivate() returned %i", err);
186 		}
187 
188 		bActive = false;
189 
190 		err = jack_client_close(client);
191 		if (err != 0) {
192 			qWarning("JackAudioSystem: unable to disconnect from the server - jack_client_close() returned %i", err);
193 		}
194 
195 		delete [] output_buffer;
196 		output_buffer = NULL;
197 
198 		client = NULL;
199 	}
200 
201 	bJackIsGood = false;
202 }
203 
auto_connect_ports()204 void JackAudioSystem::auto_connect_ports() {
205 	if (!(client && g.s.bJackAutoConnect)) {
206 		return;
207 	}
208 
209 	disconnect_ports();
210 
211 	const char **ports = NULL;
212 	const int wanted_out_flags = JackPortIsPhysical | JackPortIsOutput;
213 	const int wanted_in_flags = JackPortIsPhysical | JackPortIsInput;
214 	int err;
215 	unsigned int connected_out_ports = 0;
216 	unsigned int connected_in_ports = 0;
217 
218 	ports = jack_get_ports(client, 0, "audio", JackPortIsPhysical);
219 	if (ports != NULL) {
220 		int i = 0;
221 		while (ports[i] != NULL) {
222 			jack_port_t * const port = jack_port_by_name(client, ports[i]);
223 			if (port == NULL)  {
224 				qWarning("JackAudioSystem: jack_port_by_name() returned an invalid port - skipping it");
225 				continue;
226 			}
227 
228 			const int port_flags = jack_port_flags(port);
229 
230 			if (bInputIsGood && (port_flags & wanted_out_flags) == wanted_out_flags && connected_in_ports < 1) {
231 				err = jack_connect(client, ports[i], jack_port_name(in_port));
232 				if (err != 0) {
233 					qWarning("JackAudioSystem: unable to connect port '%s' to '%s' - jack_connect() returned %i", ports[i], jack_port_name(in_port), err);
234 				} else {
235 					connected_in_ports++;
236 				}
237 			} else if (bOutputIsGood && (port_flags & wanted_in_flags) == wanted_in_flags && connected_out_ports < iOutPorts) {
238 				err = jack_connect(client, jack_port_name(out_ports[connected_out_ports]), ports[i]);
239 				if (err != 0) {
240 					qWarning("JackAudioSystem: unable to connect port '%s' to '%s' - jack_connect() returned %i", jack_port_name(out_ports[connected_out_ports]), ports[i], err);
241 				} else {
242 					connected_out_ports++;
243 				}
244 			}
245 
246 			++i;
247 		}
248 	}
249 }
250 
disconnect_ports()251 void JackAudioSystem::disconnect_ports() {
252 	if (!client) {
253 		return;
254 	}
255 
256 	// Disconnect the input port
257 	if (in_port != NULL) {
258 		int err = jack_port_disconnect(client, in_port);
259 		if (err != 0)  {
260 			qWarning("JackAudioSystem: unable to disconnect in port - jack_port_disconnect() returned %i", err);
261 		}
262 	}
263 
264 	// Disconnect the output ports
265 	for (unsigned int i = 0; i < iOutPorts; ++i) {
266 		if (out_ports[i] != NULL) {
267 			int err = jack_port_disconnect(client, out_ports[i]);
268 			if (err != 0)  {
269 				qWarning("JackAudioSystem: unable to disconnect out port - jack_port_disconnect() returned %i", err);
270 			}
271 		}
272 	}
273 }
274 
activate()275 void JackAudioSystem::activate() {
276 	QMutexLocker lock(&qmWait);
277 	if (client) {
278 		if (bActive) {
279 			auto_connect_ports();
280 			return;
281 		}
282 
283 		int err = jack_activate(client);
284 		if (err != 0) {
285 			qWarning("JackAudioSystem: unable to activate client - jack_activate() returned %i", err);
286 			bJackIsGood = false;
287 			return;
288 		}
289 		bActive = true;
290 
291 		auto_connect_ports();
292 	}
293 }
294 
process_callback(jack_nframes_t nframes,void * arg)295 int JackAudioSystem::process_callback(jack_nframes_t nframes, void *arg) {
296 	JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
297 
298 	if (jas && jas->bJackIsGood) {
299 		AudioInputPtr ai = g.ai;
300 		AudioOutputPtr ao = g.ao;
301 		JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get());
302 		JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
303 
304 		if (jai && jai->isRunning() && jai->iMicChannels > 0 && !jai->isFinished()) {
305 			QMutexLocker(&jai->qmMutex);
306 			void *input = jack_port_get_buffer(jas->in_port, nframes);
307 			if (input != NULL) {
308 				jai->addMic(input, nframes);
309 			}
310 		}
311 
312 		if (jao && jao->isRunning() && jao->iChannels > 0 && !jao->isFinished()) {
313 			QMutexLocker(&jao->qmMutex);
314 
315 			jack_default_audio_sample_t *port_buffers[JACK_MAX_OUTPUT_PORTS];
316 			for (unsigned int i = 0; i < jao->iChannels; ++i) {
317 
318 				port_buffers[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(jas->out_ports[i], nframes);
319 				if (port_buffers[i] == NULL) {
320 					return 1;
321 				}
322 			}
323 
324 			jack_default_audio_sample_t * const buffer = jas->output_buffer;
325 			memset(buffer, 0, sizeof(jack_default_audio_sample_t) * nframes * jao->iChannels);
326 
327 			jao->mix(buffer, nframes);
328 
329 			if (jao->iChannels == 1) {
330 
331 				memcpy(port_buffers[0], buffer, sizeof(jack_default_audio_sample_t) * nframes);
332 			} else {
333 
334 				// de-interleave channels
335 				for (unsigned int i = 0; i < nframes * jao->iChannels; ++i) {
336 					port_buffers[i % jao->iChannels][i / jao->iChannels] = buffer[i];
337 				}
338 			}
339 		}
340 	}
341 
342 	return 0;
343 }
344 
srate_callback(jack_nframes_t frames,void * arg)345 int JackAudioSystem::srate_callback(jack_nframes_t frames, void *arg) {
346 	JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
347 	jas->iSampleRate = frames;
348 	return 0;
349 }
350 
allocOutputBuffer(jack_nframes_t frames)351 void JackAudioSystem::allocOutputBuffer(jack_nframes_t frames) {
352 	iBufferSize = frames;
353 	AudioOutputPtr ao = g.ao;
354 	JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
355 
356 	if (jao) {
357 		jao->qmMutex.lock();
358 	}
359 	if (output_buffer) {
360 		delete [] output_buffer;
361 		output_buffer = NULL;
362 	}
363 	output_buffer = new jack_default_audio_sample_t[frames * iOutPorts];
364 	if (output_buffer == NULL) {
365 		bJackIsGood = false;
366 	}
367 
368 	if (jao) {
369 		jao->qmMutex.unlock();
370 	}
371 }
372 
initializeInput()373 void JackAudioSystem::initializeInput() {
374 	QMutexLocker lock(&qmWait);
375 
376 	if (!jasys->bJackIsGood) {
377 		return;
378 	}
379 
380 	AudioInputPtr ai = g.ai;
381 	JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get());
382 
383 	if (jai) {
384 		jai->qmMutex.lock();
385 	}
386 
387 	in_port = jack_port_register(client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
388 	if (in_port == NULL) {
389 		qWarning("JackAudioSystem: unable to register 'input' port");
390 		return;
391 	}
392 
393 	bInputIsGood = true;
394 
395 	if (jai) {
396 		jai->qmMutex.unlock();
397 	}
398 }
399 
destroyInput()400 void JackAudioSystem::destroyInput() {
401 	AudioInputPtr ai = g.ai;
402 	JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get());
403 
404 	if (jai) {
405 		jai->qmMutex.lock();
406 	}
407 
408 	if (in_port != NULL) {
409 		int err = jack_port_unregister(client, in_port);
410 		if (err != 0)  {
411 			qWarning("JackAudioSystem: unable to unregister in port - jack_port_unregister() returned %i", err);
412 			return;
413 		}
414 	}
415 
416 	bInputIsGood = false;
417 
418 	if (jai) {
419 		jai->qmMutex.unlock();
420 	}
421 }
422 
initializeOutput()423 void JackAudioSystem::initializeOutput() {
424 	QMutexLocker lock(&qmWait);
425 
426 	if (!jasys->bJackIsGood) {
427 		return;
428 	}
429 
430 	AudioOutputPtr ao = g.ao;
431 	JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
432 
433 	allocOutputBuffer(iBufferSize);
434 
435 	if (jao) {
436 		jao->qmMutex.lock();
437 	}
438 
439 	for (unsigned int i = 0; i < iOutPorts; ++i) {
440 		char name[10];
441 		snprintf(name, 10, "output_%d", i + 1);
442 
443 		out_ports[i] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
444 		if (out_ports[i] == NULL) {
445 			qWarning("JackAudioSystem: unable to register 'output' port");
446 			break;
447 		}
448 	}
449 
450 	bOutputIsGood = true;
451 
452 	if (jao) {
453 		jao->qmMutex.unlock();
454 	}
455 }
456 
destroyOutput()457 void JackAudioSystem::destroyOutput() {
458 	AudioOutputPtr ao = g.ao;
459 	JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
460 
461 	if (jao) {
462 		jao->qmMutex.lock();
463 	}
464 
465 	delete [] output_buffer;
466 	output_buffer = NULL;
467 
468 	for (unsigned int i = 0; i < iOutPorts; ++i) {
469 		if (out_ports[i] != NULL) {
470 			int err = jack_port_unregister(client, out_ports[i]);
471 			if (err != 0)  {
472 				qWarning("JackAudioSystem: unable to unregister out port - jack_port_unregister() returned %i", err);
473 			}
474 			out_ports[i] = NULL;
475 		}
476 	}
477 
478 	bOutputIsGood = false;
479 
480 	if (jao) {
481 		jao->qmMutex.unlock();
482 	}
483 }
484 
buffer_size_callback(jack_nframes_t frames,void * arg)485 int JackAudioSystem::buffer_size_callback(jack_nframes_t frames, void *arg) {
486 	JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
487 	jas->allocOutputBuffer(frames);
488 	return 0;
489 }
490 
shutdown_callback(void * arg)491 void JackAudioSystem::shutdown_callback(void *arg) {
492 	JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
493 	jas->bJackIsGood = false;
494 }
495 
JackAudioInputRegistrar()496 JackAudioInputRegistrar::JackAudioInputRegistrar() : AudioInputRegistrar(QLatin1String("JACK"), 10) {
497 }
498 
create()499 AudioInput *JackAudioInputRegistrar::create() {
500 	return new JackAudioInput();
501 }
502 
getDeviceChoices()503 const QList<audioDevice> JackAudioInputRegistrar::getDeviceChoices() {
504 	QList<audioDevice> qlReturn;
505 
506 	QStringList qlInputDevs = jasys->qhInput.keys();
507 	qSort(qlInputDevs);
508 
509 	foreach(const QString &dev, qlInputDevs) {
510 		qlReturn << audioDevice(jasys->qhInput.value(dev), dev);
511 	}
512 
513 	return qlReturn;
514 }
515 
setDeviceChoice(const QVariant & choice,Settings & s)516 void JackAudioInputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
517 	Q_UNUSED(choice);
518 	Q_UNUSED(s);
519 }
520 
canEcho(const QString & osys) const521 bool JackAudioInputRegistrar::canEcho(const QString &osys) const {
522 	Q_UNUSED(osys);
523 	return false;
524 }
525 
JackAudioOutputRegistrar()526 JackAudioOutputRegistrar::JackAudioOutputRegistrar() : AudioOutputRegistrar(QLatin1String("JACK"), 10) {
527 }
528 
create()529 AudioOutput *JackAudioOutputRegistrar::create() {
530 	return new JackAudioOutput();
531 }
532 
getDeviceChoices()533 const QList<audioDevice> JackAudioOutputRegistrar::getDeviceChoices() {
534 	QList<audioDevice> qlReturn;
535 
536 	QStringList qlOutputDevs = jasys->qhOutput.keys();
537 	qSort(qlOutputDevs);
538 
539 	if (qlOutputDevs.contains(g.s.qsJackAudioOutput)) {
540 		qlOutputDevs.removeAll(g.s.qsJackAudioOutput);
541 		qlOutputDevs.prepend(g.s.qsJackAudioOutput);
542 	}
543 
544 	foreach(const QString &dev, qlOutputDevs) {
545 		qlReturn << audioDevice(jasys->qhOutput.value(dev), dev);
546 	}
547 
548 	return qlReturn;
549 }
550 
setDeviceChoice(const QVariant & choice,Settings & s)551 void JackAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
552 	s.qsJackAudioOutput = choice.toString();
553 	jasys->iOutPorts = qBound<unsigned>(1, choice.toInt(), JACK_MAX_OUTPUT_PORTS);
554 }
555 
JackAudioInput()556 JackAudioInput::JackAudioInput() {
557 	bRunning = true;
558 	iMicChannels = 0;
559 }
560 
~JackAudioInput()561 JackAudioInput::~JackAudioInput() {
562 	bRunning = false;
563 	iMicChannels = 0;
564 	qmMutex.lock();
565 	qwcWait.wakeAll();
566 	qmMutex.unlock();
567 	wait();
568 }
569 
run()570 void JackAudioInput::run() {
571 	if (!jasys) {
572 		exit(1);
573 	}
574 
575 	jasys->initializeInput();
576 
577 	if (!jasys->bInputIsGood) {
578 		exit(1);
579 	}
580 
581 	iMicFreq = jasys->iSampleRate;
582 	iMicChannels = 1;
583 	eMicFormat = SampleFloat;
584 	initializeMixer();
585 	jasys->activate();
586 
587 	qmMutex.lock();
588 	while (bRunning)
589 		qwcWait.wait(&qmMutex);
590 	qmMutex.unlock();
591 
592 	jasys->destroyInput();
593 }
594 
JackAudioOutput()595 JackAudioOutput::JackAudioOutput() {
596 	bRunning = true;
597 	iChannels = 0;
598 }
599 
~JackAudioOutput()600 JackAudioOutput::~JackAudioOutput() {
601 	bRunning = false;
602 	iChannels = 0;
603 	qmMutex.lock();
604 	qwcWait.wakeAll();
605 	qmMutex.unlock();
606 	wait();
607 }
608 
run()609 void JackAudioOutput::run() {
610 	if (!jasys) {
611 		exit(1);
612 	}
613 
614 	jasys->initializeOutput();
615 
616 	if (!jasys->bOutputIsGood) {
617 		exit(1);
618 	}
619 
620 	unsigned int chanmasks[32];
621 
622 	chanmasks[0] = SPEAKER_FRONT_LEFT;
623 	chanmasks[1] = SPEAKER_FRONT_RIGHT;
624 
625 	eSampleFormat = SampleFloat;
626 	iChannels = jasys->iOutPorts;
627 	iMixerFreq = jasys->iSampleRate;
628 	initializeMixer(chanmasks);
629 	jasys->activate();
630 
631 	qmMutex.lock();
632 	while (bRunning)
633 		qwcWait.wait(&qmMutex);
634 	qmMutex.unlock();
635 
636 	jasys->destroyOutput();
637 }
638