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