tags 941455 + patch
thanks

Chris Knadle:
> I think the best I can do is open a bug upstream (I opened issue #3822) about
> the problem so that they can either add a Mumble setting to disable trying to
> contact jackd in the client, or lower/shorten the attempts to contact jackd
> to decrease the startup delay.

Mumble upstream has pulled in a rewritten jackd implementation to fix this
issue, which I've tested and works.  Attached is a patch which combines several
upstream commits that seems to fix the issue; I'm mainly including it in case
someone desires to rebuild the package locally with the fix.  Upstream is fixing
several other things too, so I'm looking forward to uploading the next bugfix
release.

   -- Chris

-- 
Chris Knadle
chris.kna...@coredump.us
From 01b097d6ee05aaafd20174d013b438e51fb584c8 Mon Sep 17 00:00:00 2001
From: Davide Beatrici <davidebeatr...@gmail.com>
Date: Tue, 8 Oct 2019 03:09:22 +0200
Subject: [PATCH] Revamp JackAudio implementation

Some users were encountering issues such as the client taking ~8 seconds to 
start when "jackd" could not be run, due to the library attempting many times 
to connect to the JACK server (https://bugs.debian.org/941455).

While working on a fix I corrected the many warnings emitted by Clang-Tidy and 
I realized that there were many things that could be improved.

This commit almost entirely rewrites the implementation, but here are some of 
the changes:

- Mutexes are used everywhere, race conditions should not be possible anymore.
- The JACK client is not opened until it's required (i.e. "JackAudioInput" 
and/or "JackAudioOutput" start running). The initialization code has been moved 
to a dedicated function, the constructor doesn't execute it anymore. This is 
what fixes the issue mentioned above.
- The JACK client is deactivated and closed automatically when both 
"JackAudioInput" and "JackAudioOutput" are not running.
- Code specific to audio input or audio output has been moved from 
"JackAudioSystem" to the corresponding section ("JackAudioInput" or 
"JackAudioOutput").
- Some variables in "JackAudioSystem" have been replaced with functions which 
retrieve the corresponding value from the JACK server.
- Removed all instances of "delete", raw pointers have been replaced with 
"std::unique_ptr<>()".
- Replaced "NULL" with "nullptr".

--- a/src/mumble/JackAudio.cpp
+++ b/src/mumble/JackAudio.cpp
@@ -5,53 +5,53 @@
 
 #include "JackAudio.h"
 
+// We define a global macro called 'g'. This can lead to issues when included 
code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, 
for now, we have to make this our last include.
 #include "Global.h"
 
-
-static JackAudioSystem *jasys = NULL;
+static std::unique_ptr<JackAudioSystem> jas;
 
 // jackStatusToStringList converts a jack_status_t (a flag type
 // that can contain multiple Jack statuses) to a QStringList.
-QStringList jackStatusToStringList(jack_status_t status) {
+static QStringList jackStatusToStringList(const jack_status_t &status) {
        QStringList statusList;
 
-       if ((status & JackFailure) != 0) {
+       if (status & JackFailure) {
                statusList << QLatin1String("JackFailure - overall operation 
failed");
        }
-       if ((status & JackInvalidOption) != 0) {
+       if (status & JackInvalidOption) {
                statusList << QLatin1String("JackInvalidOption - the operation 
contained an invalid or unsupported option");
        }
-       if ((status & JackNameNotUnique) != 0)  {
+       if (status & JackNameNotUnique)  {
                statusList << QLatin1String("JackNameNotUnique - the desired 
client name is not unique");
        }
-       if ((status & JackServerStarted) != 0) {
+       if (status & JackServerStarted) {
                statusList << QLatin1String("JackServerStarted - the server was 
started as a result of this operation");
        }
-       if ((status & JackServerFailed) != 0) {
+       if (status & JackServerFailed) {
                statusList << QLatin1String("JackServerFailed - unable to 
connect to the JACK server");
        }
-       if ((status & JackServerError) != 0) {
+       if (status & JackServerError) {
                statusList << QLatin1String("JackServerError - communication 
error with the JACK server");
        }
-       if ((status & JackNoSuchClient) != 0) {
+       if (status & JackNoSuchClient) {
                statusList << QLatin1String("JackNoSuchClient - requested 
client does not exist");
        }
-       if ((status & JackLoadFailure) != 0) {
+       if (status & JackLoadFailure) {
                statusList << QLatin1String("JackLoadFailure - unable to load 
initial client");
        }
-       if ((status & JackInitFailure) != 0) {
+       if (status & JackInitFailure) {
                statusList << QLatin1String("JackInitFailure - unable to 
initialize client");
        }
-       if ((status & JackShmFailure) != 0)  {
+       if (status & JackShmFailure)  {
                statusList << QLatin1String("JackShmFailure - unable to access 
shared memory");
        }
-       if ((status & JackVersionError) != 0) {
+       if (status & JackVersionError) {
                statusList << QLatin1String("JackVersionError - client's 
protocol version does not match");
        }
-       if ((status & JackBackendError) != 0) {
+       if (status & JackBackendError) {
                statusList << QLatin1String("JackBackendError - a backend error 
occurred");
        }
-       if ((status & JackClientZombie) != 0) {
+       if (status & JackClientZombie) {
                statusList << QLatin1String("JackClientZombie - client 
zombified");
        }
 
@@ -77,561 +77,736 @@
 
 class JackAudioInit : public DeferInit {
        public:
-               JackAudioInputRegistrar *airJackAudio;
-               JackAudioOutputRegistrar *aorJackAudio;
-               void initialize() {
-                       jasys = new JackAudioSystem();
-                       jasys->qmWait.lock();
-                       jasys->qwcWait.wait(&jasys->qmWait, 1000);
-                       jasys->qmWait.unlock();
-                       if (jasys->bJackIsGood) {
-                               airJackAudio = new JackAudioInputRegistrar();
-                               aorJackAudio = new JackAudioOutputRegistrar();
-                       } else {
-                               airJackAudio = NULL;
-                               aorJackAudio = NULL;
-                               delete jasys;
-                               jasys = NULL;
-                       }
-               }
-
-               void destroy() {
-                       if (airJackAudio)
-                               delete airJackAudio;
-                       if (aorJackAudio)
-                               delete aorJackAudio;
-                       if (jasys) {
-                               delete jasys;
-                               jasys = NULL;
-                       }
-               }
+               std::unique_ptr<JackAudioInputRegistrar> airJackAudio;
+               std::unique_ptr<JackAudioOutputRegistrar> aorJackAudio;
+               void initialize();
+               void destroy();
 };
 
-static JackAudioInit jackinit; // To instantiate the classes (JackAudioSystem, 
JackAudioInputRegistrar and JackAudioOutputRegistrar).
+JackAudioInputRegistrar::JackAudioInputRegistrar() : 
AudioInputRegistrar(QLatin1String("JACK"), 10) {}
 
-JackAudioSystem::JackAudioSystem()
-       : bActive(false)
-       , client(NULL)
-       , in_port(NULL)
-       , output_buffer(NULL)
-       , iBufferSize(0)
-       , bJackIsGood(false)
-       , bInputIsGood(false)
-       , bOutputIsGood(false)
-       , iSampleRate(0)
-{
-       if (g.s.qsJackAudioOutput.isEmpty()) {
-               iOutPorts = 1;
-       } else {
-               iOutPorts = g.s.qsJackAudioOutput.toInt();
+AudioInput *JackAudioInputRegistrar::create() {
+       return new JackAudioInput();
+}
+
+const QList<audioDevice> JackAudioInputRegistrar::getDeviceChoices() {
+       QList<audioDevice> qlReturn;
+
+       auto qlInputDevs = jas->qhInput.keys();
+       std::sort(qlInputDevs.begin(), qlInputDevs.end());
+
+       for (const auto &dev : qlInputDevs) {
+               qlReturn << audioDevice(jas->qhInput.value(dev), dev);
+       }
+
+       return qlReturn;
+}
+
+void JackAudioInputRegistrar::setDeviceChoice(const QVariant &, Settings &) {}
+
+bool JackAudioInputRegistrar::canEcho(const QString &) const {
+       return false;
+}
+
+JackAudioOutputRegistrar::JackAudioOutputRegistrar() : 
AudioOutputRegistrar(QLatin1String("JACK"), 10) {}
+
+AudioOutput *JackAudioOutputRegistrar::create() {
+       return new JackAudioOutput();
+}
+
+const QList<audioDevice> JackAudioOutputRegistrar::getDeviceChoices() {
+       QList<audioDevice> qlReturn;
+
+       QStringList qlOutputDevs = jas->qhOutput.keys();
+       std::sort(qlOutputDevs.begin(), qlOutputDevs.end());
+
+       if (qlOutputDevs.contains(g.s.qsJackAudioOutput)) {
+               qlOutputDevs.removeAll(g.s.qsJackAudioOutput);
+               qlOutputDevs.prepend(g.s.qsJackAudioOutput);
+       }
+
+       foreach(const QString &dev, qlOutputDevs) {
+               qlReturn << audioDevice(jas->qhOutput.value(dev), dev);
        }
-       memset(reinterpret_cast<void *>(&out_ports), 0, sizeof(out_ports));
 
+       return qlReturn;
+}
+
+void JackAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, 
Settings &s) {
+       s.qsJackAudioOutput = choice.toString();
+}
+
+void JackAudioInit::initialize() {
+       jas.reset(new JackAudioSystem());
+       airJackAudio.reset(new JackAudioInputRegistrar());
+       aorJackAudio.reset(new JackAudioOutputRegistrar());
+}
+
+void JackAudioInit::destroy() {
+       airJackAudio.reset();
+       aorJackAudio.reset();
+       jas.reset();
+}
+
+// Instantiate JackAudioSystem, JackAudioInputRegistrar and 
JackAudioOutputRegistrar
+static JackAudioInit jai;
+
+JackAudioSystem::JackAudioSystem()
+    : users(0)
+    , client(nullptr)
+{
        qhInput.insert(QString(), tr("Hardware Ports"));
        qhOutput.insert(QString::number(1), tr("Mono"));
        qhOutput.insert(QString::number(2), tr("Stereo"));
+}
 
-       jack_status_t status = static_cast<jack_status_t>(0);
-       int err = 0;
+JackAudioSystem::~JackAudioSystem() {
+       deinitialize();
+}
+
+bool JackAudioSystem::initialize() {
+       QMutexLocker lock(&qmWait);
 
-       jack_options_t jack_option = g.s.bJackStartServer ? JackNullOption : 
JackNoStartServer;
-       client = jack_client_open(g.s.qsJackClientName.toStdString().c_str(), 
jack_option, &status);
+       if (client) {
+               lock.unlock();
+               deinitialize();
+               lock.relock();
+       }
 
+       jack_status_t status;
+       client = jack_client_open(g.s.qsJackClientName.toStdString().c_str(), 
g.s.bJackStartServer ? JackNullOption : JackNoStartServer, &status);
        if (!client) {
-               QStringList errors = jackStatusToStringList(status);
+               const auto errors = jackStatusToStringList(status);
                qWarning("JackAudioSystem: unable to open client due to %i 
errors:", errors.count());
-               for (int i = 0; i < errors.count(); ++i) {
+               for (auto i = 0; i < errors.count(); ++i) {
                        qWarning("JackAudioSystem: %s", 
qPrintable(errors.at(i)));
                }
 
-               return;
+               return false;
        }
 
-       qWarning("JackAudioSystem: client \"%s\" opened successfully", 
jack_get_client_name(client));
-       iBufferSize = jack_get_buffer_size(client);
-       iSampleRate = jack_get_sample_rate(client);
+       qDebug("JackAudioSystem: client \"%s\" opened successfully", 
jack_get_client_name(client));
 
-       err = jack_set_process_callback(client, process_callback, this);
-       if (err != 0) {
-               qWarning("JackAudioSystem: unable to set process callback - 
jack_set_process_callback() returned %i", err);
-               return;
+       auto ret = jack_set_process_callback(client, processCallback, nullptr);
+       if (ret != 0) {
+               qWarning("JackAudioSystem: unable to set process callback - 
jack_set_process_callback() returned %i", ret);
+               jack_client_close(client);
+               client = nullptr;
+               return false;
        }
 
-       err = jack_set_sample_rate_callback(client, srate_callback, this);
-       if (err != 0) {
-               qWarning("JackAudioSystem: unable to set sample rate callback - 
jack_set_sample_rate_callback() returned %i", err);
+       ret = jack_set_sample_rate_callback(client, sampleRateCallback, 
nullptr);
+       if (ret != 0) {
+               qWarning("JackAudioSystem: unable to set sample rate callback - 
jack_set_sample_rate_callback() returned %i", ret);
+               jack_client_close(client);
+               client = nullptr;
+               return false;
+       }
+
+       ret = jack_set_buffer_size_callback(client, bufferSizeCallback, 
nullptr);
+       if (ret != 0) {
+               qWarning("JackAudioSystem: unable to set buffer size callback - 
jack_set_buffer_size_callback() returned %i", ret);
+               jack_client_close(client);
+               client = nullptr;
+               return false;
+       }
+
+       jack_on_shutdown(client, shutdownCallback, nullptr);
+
+       return true;
+}
+
+void JackAudioSystem::deinitialize() {
+       QMutexLocker lock(&qmWait);
+
+       if (!client) {
                return;
        }
 
-       err = jack_set_buffer_size_callback(client, buffer_size_callback, this);
+       const auto clientName = 
QString::fromLatin1(jack_get_client_name(client));
+
+       const auto err = jack_client_close(client);
        if (err != 0) {
-               qWarning("JackAudioSystem: unable to set buffer size callback - 
jack_set_buffer_size_callback() returned %i", err);
+               qWarning("JackAudioSystem: unable to disconnect from the server 
- jack_client_close() returned %i", err);
                return;
        }
 
-       jack_on_shutdown(client, shutdown_callback, this);
+       client = nullptr;
 
-       // If we made it this far, then everything is okay
-       bJackIsGood = true;
+       qDebug("JackAudioSystem: client \"%s\" closed successfully", 
clientName.toStdString().c_str());
 }
 
-JackAudioSystem::~JackAudioSystem() {
+bool JackAudioSystem::activate() {
        QMutexLocker lock(&qmWait);
 
-       if (client) {
-               int err = 0;
-               err = jack_deactivate(client);
-               if (err != 0)  {
-                       qWarning("JackAudioSystem: unable to remove client from 
the process graph - jack_deactivate() returned %i", err);
-               }
-
-               bActive = false;
+       if (!client) {
+               lock.unlock();
 
-               err = jack_client_close(client);
-               if (err != 0) {
-                       qWarning("JackAudioSystem: unable to disconnect from 
the server - jack_client_close() returned %i", err);
+               if (!initialize()) {
+                       return false;
                }
 
-               delete [] output_buffer;
-               output_buffer = NULL;
+               lock.relock();
+       }
+
+       if (users++ > 0) {
+               // The client is already active, because there is at least a 
user
+               return true;
+       }
 
-               client = NULL;
+       const auto ret = jack_activate(client);
+       if (ret != 0) {
+               qWarning("JackAudioSystem: unable to activate client - 
jack_activate() returned %i", ret);
+               return false;
        }
 
-       bJackIsGood = false;
+       qDebug("JackAudioSystem: client activated");
+
+       return true;
 }
 
-void JackAudioSystem::auto_connect_ports() {
-       if (!(client && g.s.bJackAutoConnect)) {
+void JackAudioSystem::deactivate() {
+       QMutexLocker lock(&qmWait);
+
+       if (!client) {
+               return;
+       }
+
+       if (--users > 0) {
+               // There is still at least a user, we only decrement the counter
                return;
        }
 
-       disconnect_ports();
+       const auto err = jack_deactivate(client);
+       if (err != 0)  {
+               qWarning("JackAudioSystem: unable to remove client from the 
process graph - jack_deactivate() returned %i", err);
+               return;
+       }
 
-       const char **ports = NULL;
-       const int wanted_out_flags = JackPortIsPhysical | JackPortIsOutput;
-       const int wanted_in_flags = JackPortIsPhysical | JackPortIsInput;
-       int err;
-       unsigned int connected_out_ports = 0;
-       unsigned int connected_in_ports = 0;
-
-       ports = jack_get_ports(client, 0, "audio", JackPortIsPhysical);
-       if (ports != NULL) {
-               int i = 0;
-               while (ports[i] != NULL) {
-                       jack_port_t * const port = jack_port_by_name(client, 
ports[i]);
-                       if (port == NULL)  {
-                               qWarning("JackAudioSystem: jack_port_by_name() 
returned an invalid port - skipping it");
-                               continue;
-                       }
+       qDebug("JackAudioSystem: client deactivated");
 
-                       const int port_flags = jack_port_flags(port);
+       lock.unlock();
 
-                       if (bInputIsGood && (port_flags & wanted_out_flags) == 
wanted_out_flags && connected_in_ports < 1) {
-                               err = jack_connect(client, ports[i], 
jack_port_name(in_port));
-                               if (err != 0) {
-                                       qWarning("JackAudioSystem: unable to 
connect port '%s' to '%s' - jack_connect() returned %i", ports[i], 
jack_port_name(in_port), err);
-                               } else {
-                                       connected_in_ports++;
-                               }
-                       } else if (bOutputIsGood && (port_flags & 
wanted_in_flags) == wanted_in_flags && connected_out_ports < iOutPorts) {
-                               err = jack_connect(client, 
jack_port_name(out_ports[connected_out_ports]), ports[i]);
-                               if (err != 0) {
-                                       qWarning("JackAudioSystem: unable to 
connect port '%s' to '%s' - jack_connect() returned %i", 
jack_port_name(out_ports[connected_out_ports]), ports[i], err);
-                               } else {
-                                       connected_out_ports++;
-                               }
-                       }
+       deinitialize();
+}
 
-                       ++i;
-               }
-       }
+bool JackAudioSystem::isOk() {
+       QMutexLocker lock(&qmWait);
+       return client != nullptr;
+}
+
+uint8_t JackAudioSystem::outPorts() {
+       return static_cast<uint8_t>(qBound<unsigned>(1, 
g.s.qsJackAudioOutput.toUInt(), JACK_MAX_OUTPUT_PORTS));
 }
 
-void JackAudioSystem::disconnect_ports() {
+jack_nframes_t JackAudioSystem::sampleRate() {
+       QMutexLocker lock(&qmWait);
+
        if (!client) {
-               return;
+               return 0;
        }
 
-       // Disconnect the input port
-       if (in_port != NULL) {
-               int err = jack_port_disconnect(client, in_port);
-               if (err != 0)  {
-                       qWarning("JackAudioSystem: unable to disconnect in port 
- jack_port_disconnect() returned %i", err);
-               }
-       }
+       return jack_get_sample_rate(client);
+}
 
-       // Disconnect the output ports
-       for (unsigned int i = 0; i < iOutPorts; ++i) {
-               if (out_ports[i] != NULL) {
-                       int err = jack_port_disconnect(client, out_ports[i]);
-                       if (err != 0)  {
-                               qWarning("JackAudioSystem: unable to disconnect 
out port - jack_port_disconnect() returned %i", err);
-                       }
-               }
+jack_nframes_t JackAudioSystem::bufferSize() {
+       QMutexLocker lock(&qmWait);
+
+       if (!client) {
+               return 0;
        }
+
+       return jack_get_buffer_size(client);
 }
 
-void JackAudioSystem::activate() {
+JackPorts JackAudioSystem::getPhysicalPorts(const uint8_t &flags) {
        QMutexLocker lock(&qmWait);
-       if (client) {
-               if (bActive) {
-                       auto_connect_ports();
-                       return;
-               }
 
-               int err = jack_activate(client);
-               if (err != 0) {
-                       qWarning("JackAudioSystem: unable to activate client - 
jack_activate() returned %i", err);
-                       bJackIsGood = false;
-                       return;
-               }
-               bActive = true;
+       if (!client) {
+               return JackPorts();
+       }
 
-               auto_connect_ports();
+       const auto ports = jack_get_ports(client, nullptr, "audio", 
JackPortIsPhysical);
+       if (!ports) {
+               return JackPorts();
        }
-}
 
-int JackAudioSystem::process_callback(jack_nframes_t nframes, void *arg) {
-       JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
+       JackPorts ret;
 
-       if (jas && jas->bJackIsGood) {
-               AudioInputPtr ai = g.ai;
-               AudioOutputPtr ao = g.ao;
-               JackAudioInput * const jai = dynamic_cast<JackAudioInput 
*>(ai.get());
-               JackAudioOutput * const jao = dynamic_cast<JackAudioOutput 
*>(ao.get());
+       for (auto i = 0; ports[i]; ++i) {
+               if (!ports[i]) {
+                       // End of the array
+                       break;
+               }
 
-               if (jai && jai->isRunning() && jai->iMicChannels > 0 && 
!jai->isFinished()) {
-                       QMutexLocker(&jai->qmMutex);
-                       void *input = jack_port_get_buffer(jas->in_port, 
nframes);
-                       if (input != NULL) {
-                               jai->addMic(input, nframes);
-                       }
+               auto port = jack_port_by_name(client, ports[i]);
+               if (!port)  {
+                       qWarning("JackAudioSystem: jack_port_by_name() returned 
an invalid port - skipping it");
+                       continue;
                }
 
-               if (jao && jao->isRunning() && jao->iChannels > 0 && 
!jao->isFinished()) {
-                       QMutexLocker(&jao->qmMutex);
+               if (jack_port_flags(port) & flags) {
+                       ret.append(port);
+               }
+       }
 
-                       jack_default_audio_sample_t 
*port_buffers[JACK_MAX_OUTPUT_PORTS];
-                       for (unsigned int i = 0; i < jao->iChannels; ++i) {
+       jack_free(ports);
 
-                               port_buffers[i] = 
(jack_default_audio_sample_t*)jack_port_get_buffer(jas->out_ports[i], nframes);
-                               if (port_buffers[i] == NULL) {
-                                       return 1;
-                               }
-                       }
+       return ret;
+}
 
-                       jack_default_audio_sample_t * const buffer = 
jas->output_buffer;
-                       memset(buffer, 0, sizeof(jack_default_audio_sample_t) * 
nframes * jao->iChannels);
+jack_port_t *JackAudioSystem::registerPort(const char *name, const uint8_t 
&flags) {
+       QMutexLocker lock(&qmWait);
 
-                       jao->mix(buffer, nframes);
+       if (!client || !name) {
+               return nullptr;
+       }
 
-                       if (jao->iChannels == 1) {
+       return jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, flags, 
0);
+}
 
-                               memcpy(port_buffers[0], buffer, 
sizeof(jack_default_audio_sample_t) * nframes);
-                       } else {
+bool JackAudioSystem::unregisterPort(jack_port_t *port) {
+       QMutexLocker lock(&qmWait);
 
-                               // de-interleave channels
-                               for (unsigned int i = 0; i < nframes * 
jao->iChannels; ++i) {
-                                       port_buffers[i % jao->iChannels][i / 
jao->iChannels] = buffer[i];
-                               }
-                       }
-               }
+       if (!client || !port) {
+               return false;
        }
 
-       return 0;
-}
+       const auto ret = jack_port_unregister(client, port);
+       if (ret != 0)  {
+               qWarning("JackAudioSystem: unable to unregister port - 
jack_port_unregister() returned %i", ret);
+               return false;
+       }
 
-int JackAudioSystem::srate_callback(jack_nframes_t frames, void *arg) {
-       JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
-       jas->iSampleRate = frames;
-       return 0;
+       return true;
 }
 
-void JackAudioSystem::allocOutputBuffer(jack_nframes_t frames) {
-       iBufferSize = frames;
-       AudioOutputPtr ao = g.ao;
-       JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
+bool JackAudioSystem::connectPort(jack_port_t *sourcePort, jack_port_t 
*destinationPort) {
+       QMutexLocker lock(&qmWait);
 
-       if (jao) {
-               jao->qmMutex.lock();
-       }
-       if (output_buffer) {
-               delete [] output_buffer;
-               output_buffer = NULL;
-       }
-       output_buffer = new jack_default_audio_sample_t[frames * iOutPorts];
-       if (output_buffer == NULL) {
-               bJackIsGood = false;
+       if (!client || !sourcePort || !destinationPort) {
+               return false;
        }
 
-       if (jao) {
-               jao->qmMutex.unlock();
+       const auto sourcePortName = jack_port_name(sourcePort);
+       const auto destinationPortName = jack_port_name(destinationPort);
+
+       const auto ret = jack_connect(client, sourcePortName, 
destinationPortName);
+       if (ret != 0)  {
+               qWarning("JackAudioSystem: unable to connect port '%s' to '%s' 
- jack_connect() returned %i", sourcePortName, destinationPortName, ret);
+               return false;
        }
+
+       return true;
 }
 
-void JackAudioSystem::initializeInput() {
+bool JackAudioSystem::disconnectPort(jack_port_t *port) {
        QMutexLocker lock(&qmWait);
 
-       if (!jasys->bJackIsGood) {
-               return;
+       if (!client || !port) {
+               return false;
        }
 
-       AudioInputPtr ai = g.ai;
-       JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get());
-
-       if (jai) {
-               jai->qmMutex.lock();
+       const auto ret = jack_port_disconnect(client, port);
+       if (ret != 0)  {
+               qWarning("JackAudioSystem: unable to disconnect port - 
jack_port_disconnect() returned %i", ret);
+               return false;
        }
 
-       in_port = jack_port_register(client, "input", JACK_DEFAULT_AUDIO_TYPE, 
JackPortIsInput, 0);
-       if (in_port == NULL) {
-               qWarning("JackAudioSystem: unable to register 'input' port");
-               return;
-       }
+       return true;
+}
 
-       bInputIsGood = true;
+int JackAudioSystem::processCallback(jack_nframes_t frames, void *) {
+       QMutexLocker lock(&jas->qmWait);
 
-       if (jai) {
-               jai->qmMutex.unlock();
+       auto const jai = dynamic_cast<JackAudioInput *>(g.ai.get());
+       auto const jao = dynamic_cast<JackAudioOutput *>(g.ao.get());
+
+       const bool input = (jai && jai->isReady());
+       const bool output = (jao && jao->isReady());
+
+       if (input && !jai->process(frames)) {
+               return 1;
+       }
+
+       if (output && !jao->process(frames)) {
+               return 1;
        }
+
+       return 0;
 }
 
-void JackAudioSystem::destroyInput() {
-       AudioInputPtr ai = g.ai;
-       JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get());
+int JackAudioSystem::sampleRateCallback(jack_nframes_t, void *) {
+       auto const jai = dynamic_cast<JackAudioInput *>(g.ai.get());
+       auto const jao = dynamic_cast<JackAudioOutput *>(g.ao.get());
 
        if (jai) {
-               jai->qmMutex.lock();
+               jai->activate();
        }
 
-       if (in_port != NULL) {
-               int err = jack_port_unregister(client, in_port);
-               if (err != 0)  {
-                       qWarning("JackAudioSystem: unable to unregister in port 
- jack_port_unregister() returned %i", err);
-                       return;
-               }
+       if (jao) {
+               jao->activate();
        }
 
-       bInputIsGood = false;
+       return 0;
+}
 
-       if (jai) {
-               jai->qmMutex.unlock();
+int JackAudioSystem::bufferSizeCallback(jack_nframes_t frames, void *) {
+       auto const jao = dynamic_cast<JackAudioOutput *>(g.ao.get());
+       if (jao) {
+               jao->allocBuffer(frames);
        }
+
+       return 0;
+}
+
+void JackAudioSystem::shutdownCallback(void *) {
+       qWarning("JackAudioSystem: server shutdown");
+
+       QMutexLocker lock(&jas->qmWait);
+       jas->client = nullptr;
+       jas->users = 0;
+}
+
+JackAudioInput::JackAudioInput()
+    : port(nullptr)
+{
+       bReady = activate();
+}
+
+JackAudioInput::~JackAudioInput() {
+       // Request interruption
+       qmWait.lock();
+       bReady = false;
+       qwcSleep.wakeAll();
+       qmWait.unlock();
+
+       // Wait for thread to exit
+       wait();
+
+       // Cleanup
+       deactivate();
 }
 
-void JackAudioSystem::initializeOutput() {
+bool JackAudioInput::isReady() {
        QMutexLocker lock(&qmWait);
+       return bReady;
+}
 
-       if (!jasys->bJackIsGood) {
-               return;
+bool JackAudioInput::activate() {
+       QMutexLocker lock(&qmWait);
+
+       if (!jas->activate()) {
+               return false;
        }
 
-       AudioOutputPtr ao = g.ao;
-       JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
+       eMicFormat = SampleFloat;
+       iMicChannels = 1;
+       iMicFreq = jas->sampleRate();
+
+       initializeMixer();
 
-       allocOutputBuffer(iBufferSize);
+       lock.unlock();
 
-       if (jao) {
-               jao->qmMutex.lock();
+       return registerPorts();
+}
+
+void JackAudioInput::deactivate() {
+       unregisterPorts();
+       jas->deactivate();
+}
+
+bool JackAudioInput::registerPorts() {
+       unregisterPorts();
+
+       QMutexLocker lock(&qmWait);
+
+       port = jas->registerPort("input", JackPortIsInput);
+       if (!port) {
+               qWarning("JackAudioInput: unable to register port");
+               return false;
        }
 
-       for (unsigned int i = 0; i < iOutPorts; ++i) {
-               char name[10];
-               snprintf(name, 10, "output_%d", i + 1);
+       return true;
+}
+
+bool JackAudioInput::unregisterPorts() {
+       QMutexLocker lock(&qmWait);
+
+       if (!port) {
+               return false;
+       }
 
-               out_ports[i] = jack_port_register(client, name, 
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
-               if (out_ports[i] == NULL) {
-                       qWarning("JackAudioSystem: unable to register 'output' 
port");
+       if (!jas->unregisterPort(port))  {
+               qWarning("JackAudioInput: unable to unregister port");
+               return false;
+       }
+
+       port = nullptr;
+
+       return true;
+}
+
+void JackAudioInput::connectPorts() {
+       disconnectPorts();
+
+       QMutexLocker lock(&qmWait);
+
+       if (!port) {
+               return;
+       }
+
+       const JackPorts outputPorts = jas->getPhysicalPorts(JackPortIsOutput);
+       for (auto outputPort : outputPorts) {
+               if (jas->connectPort(outputPort, port)) {
                        break;
                }
        }
+}
 
-       bOutputIsGood = true;
+bool JackAudioInput::disconnectPorts() {
+       QMutexLocker lock(&qmWait);
 
-       if (jao) {
-               jao->qmMutex.unlock();
+       if (!port) {
+               return true;
+       }
+
+       if (!jas->disconnectPort(port)) {
+               qWarning("JackAudioInput: unable to disconnect port");
+               return false;
        }
+
+       return true;
 }
 
-void JackAudioSystem::destroyOutput() {
-       AudioOutputPtr ao = g.ao;
-       JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get());
+bool JackAudioInput::process(const jack_nframes_t &frames) {
+       QMutexLocker lock(&qmWait);
 
-       if (jao) {
-               jao->qmMutex.lock();
+       auto input = jack_port_get_buffer(port, frames);
+       if (!input) {
+               return false;
        }
 
-       delete [] output_buffer;
-       output_buffer = NULL;
+       addMic(input, frames);
 
-       for (unsigned int i = 0; i < iOutPorts; ++i) {
-               if (out_ports[i] != NULL) {
-                       int err = jack_port_unregister(client, out_ports[i]);
-                       if (err != 0)  {
-                               qWarning("JackAudioSystem: unable to unregister 
out port - jack_port_unregister() returned %i", err);
-                       }
-                       out_ports[i] = NULL;
-               }
-       }
+       return true;
+}
 
-       bOutputIsGood = false;
+void JackAudioInput::run() {
+       if (!bReady) {
+               return;
+       }
 
-       if (jao) {
-               jao->qmMutex.unlock();
+       // Initialization
+       if (g.s.bJackAutoConnect) {
+               connectPorts();
        }
+
+       // Pause thread until interruption is requested by the destructor
+       qmWait.lock();
+       qwcSleep.wait(&qmWait);
+       qmWait.unlock();
+
+       // Cleanup
+       disconnectPorts();
 }
 
-int JackAudioSystem::buffer_size_callback(jack_nframes_t frames, void *arg) {
-       JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
-       jas->allocOutputBuffer(frames);
-       return 0;
+JackAudioOutput::JackAudioOutput() {
+       bReady = activate();
 }
 
-void JackAudioSystem::shutdown_callback(void *arg) {
-       JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg);
-       jas->bJackIsGood = false;
+JackAudioOutput::~JackAudioOutput() {
+       // Request interruption
+       qmWait.lock();
+       bReady = false;
+       qwcSleep.wakeAll();
+       qmWait.unlock();
+
+       // Wait for thread to exit
+       wait();
+
+       // Cleanup
+       deactivate();
 }
 
-JackAudioInputRegistrar::JackAudioInputRegistrar() : 
AudioInputRegistrar(QLatin1String("JACK"), 10) {
+bool JackAudioOutput::isReady() {
+       QMutexLocker lock(&qmWait);
+       return bReady;
 }
 
-AudioInput *JackAudioInputRegistrar::create() {
-       return new JackAudioInput();
+void JackAudioOutput::allocBuffer(const jack_nframes_t &frames) {
+       QMutexLocker lock(&qmWait);
+       buffer.reset(new jack_default_audio_sample_t[frames * iChannels]);
 }
 
-const QList<audioDevice> JackAudioInputRegistrar::getDeviceChoices() {
-       QList<audioDevice> qlReturn;
+bool JackAudioOutput::activate() {
+       QMutexLocker lock(&qmWait);
+
+       if (!jas->activate()) {
+               return false;
+       }
+
+       eSampleFormat = SampleFloat;
+       iChannels = jas->outPorts();
+       iMixerFreq = jas->sampleRate();
+
+       uint32_t channelsMask[32];
+       channelsMask[0] = SPEAKER_FRONT_LEFT;
+       channelsMask[1] = SPEAKER_FRONT_RIGHT;
+       initializeMixer(channelsMask);
+
+       lock.unlock();
 
-       QStringList qlInputDevs = jasys->qhInput.keys();
-       qSort(qlInputDevs);
+       allocBuffer(jas->bufferSize());
 
-       foreach(const QString &dev, qlInputDevs) {
-               qlReturn << audioDevice(jasys->qhInput.value(dev), dev);
+       if (!registerPorts()) {
+               return false;
        }
 
-       return qlReturn;
+       return true;
 }
 
-void JackAudioInputRegistrar::setDeviceChoice(const QVariant &choice, Settings 
&s) {
-       Q_UNUSED(choice);
-       Q_UNUSED(s);
+void JackAudioOutput::deactivate() {
+       unregisterPorts();
+       jas->deactivate();
+       buffer.reset();
 }
 
-bool JackAudioInputRegistrar::canEcho(const QString &osys) const {
-       Q_UNUSED(osys);
-       return false;
-}
+bool JackAudioOutput::registerPorts() {
+       unregisterPorts();
 
-JackAudioOutputRegistrar::JackAudioOutputRegistrar() : 
AudioOutputRegistrar(QLatin1String("JACK"), 10) {
-}
+       QMutexLocker lock(&qmWait);
 
-AudioOutput *JackAudioOutputRegistrar::create() {
-       return new JackAudioOutput();
+       for (decltype(iChannels) i = 0; i < iChannels; ++i) {
+               char name[10];
+               snprintf(name, sizeof(name), "output_%d", i + 1);
+
+               const auto port = jas->registerPort(name, JackPortIsOutput);
+               if (port == nullptr) {
+                       qWarning("JackAudioOutput: unable to register port 
#%u", i);
+                       return false;
+               }
+
+               ports.append(port);
+       }
+
+       return true;
 }
 
-const QList<audioDevice> JackAudioOutputRegistrar::getDeviceChoices() {
-       QList<audioDevice> qlReturn;
+bool JackAudioOutput::unregisterPorts() {
+       QMutexLocker lock(&qmWait);
 
-       QStringList qlOutputDevs = jasys->qhOutput.keys();
-       qSort(qlOutputDevs);
+       bool ret = true;
 
-       if (qlOutputDevs.contains(g.s.qsJackAudioOutput)) {
-               qlOutputDevs.removeAll(g.s.qsJackAudioOutput);
-               qlOutputDevs.prepend(g.s.qsJackAudioOutput);
-       }
+       for (auto i = 0; i < ports.size(); ++i) {
+               if (!ports[i]) {
+                       continue;
+               }
 
-       foreach(const QString &dev, qlOutputDevs) {
-               qlReturn << audioDevice(jasys->qhOutput.value(dev), dev);
+               if (!jas->unregisterPort(ports[i]))  {
+                       qWarning("JackAudioOutput: unable to unregister port 
#%u", i);
+                       ret = false;
+               }
        }
 
-       return qlReturn;
-}
+       ports.clear();
 
-void JackAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, 
Settings &s) {
-       s.qsJackAudioOutput = choice.toString();
-       jasys->iOutPorts = qBound<unsigned>(1, choice.toInt(), 
JACK_MAX_OUTPUT_PORTS);
+       return ret;
 }
 
-JackAudioInput::JackAudioInput() {
-       bRunning = true;
-       iMicChannels = 0;
-}
+void JackAudioOutput::connectPorts() {
+       disconnectPorts();
 
-JackAudioInput::~JackAudioInput() {
-       bRunning = false;
-       iMicChannels = 0;
-       qmMutex.lock();
-       qwcWait.wakeAll();
-       qmMutex.unlock();
-       wait();
-}
+       QMutexLocker lock(&qmWait);
 
-void JackAudioInput::run() {
-       if (!jasys) {
-               exit(1);
-       }
+       const auto inputPorts = jas->getPhysicalPorts(JackPortIsInput);
+       uint8_t i = 0;
+
+       for (auto inputPort : inputPorts) {
+               if (i == ports.size()) {
+                       break;
+               }
 
-       jasys->initializeInput();
+               if (ports[i]) {
+                       if (!jas->connectPort(ports[i], inputPort)) {
+                               continue;
+                       }
+               }
 
-       if (!jasys->bInputIsGood) {
-               exit(1);
+               ++i;
        }
+}
 
-       iMicFreq = jasys->iSampleRate;
-       iMicChannels = 1;
-       eMicFormat = SampleFloat;
-       initializeMixer();
-       jasys->activate();
+bool JackAudioOutput::disconnectPorts() {
+       QMutexLocker lock(&qmWait);
 
-       qmMutex.lock();
-       while (bRunning)
-               qwcWait.wait(&qmMutex);
-       qmMutex.unlock();
+       bool ret = true;
 
-       jasys->destroyInput();
-}
+       for (auto i = 0; i < ports.size(); ++i) {
+               if (ports[i] && !jas->disconnectPort(ports[i])) {
+                       qWarning("JackAudioOutput: unable to disconnect port 
#%u", i);
+                       ret = false;
+               }
+       }
 
-JackAudioOutput::JackAudioOutput() {
-       bRunning = true;
-       iChannels = 0;
+       return ret;
 }
 
-JackAudioOutput::~JackAudioOutput() {
-       bRunning = false;
-       iChannels = 0;
-       qmMutex.lock();
-       qwcWait.wakeAll();
-       qmMutex.unlock();
-       wait();
-}
+bool JackAudioOutput::process(const jack_nframes_t &frames) {
+       QMutexLocker lock(&qmWait);
 
-void JackAudioOutput::run() {
-       if (!jasys) {
-               exit(1);
+       const auto audioToReproduce = mix(buffer.get(), frames);
+
+       QVector<jack_default_audio_sample_t *> inputBuffers;
+
+       for (decltype(iChannels) currentChannel = 0; currentChannel < 
iChannels; ++currentChannel) {
+               auto inputBuffer = reinterpret_cast<jack_default_audio_sample_t 
*>(jack_port_get_buffer(ports[currentChannel], frames));
+               if (!inputBuffer) {
+                       return false;
+               }
+
+               if (!audioToReproduce) {
+                       // Clear buffer
+                       memset(inputBuffer, 0, 
sizeof(jack_default_audio_sample_t) * frames);
+               }
+
+               inputBuffers.append(inputBuffer);
+       }
+
+       if (!audioToReproduce) {
+               return true;
        }
 
-       jasys->initializeOutput();
+       const auto samples = frames * iChannels;
 
-       if (!jasys->bOutputIsGood) {
-               exit(1);
+       if (samples > frames) {
+               // De-interleave channels
+               for (auto currentSample = decltype(samples){0}; currentSample < 
samples; ++currentSample) {
+                       inputBuffers[currentSample % iChannels][currentSample / 
iChannels] = buffer[currentSample];
+               }
+       } else {
+               // Single channel
+               memcpy(inputBuffers[0], buffer.get(), 
sizeof(jack_default_audio_sample_t) * samples);
        }
 
-       unsigned int chanmasks[32];
+       return true;
+}
 
-       chanmasks[0] = SPEAKER_FRONT_LEFT;
-       chanmasks[1] = SPEAKER_FRONT_RIGHT;
+void JackAudioOutput::run() {
+       if (!bReady) {
+               return;
+       }
 
-       eSampleFormat = SampleFloat;
-       iChannels = jasys->iOutPorts;
-       iMixerFreq = jasys->iSampleRate;
-       initializeMixer(chanmasks);
-       jasys->activate();
-
-       qmMutex.lock();
-       while (bRunning)
-               qwcWait.wait(&qmMutex);
-       qmMutex.unlock();
+       // Initialization
+       if (g.s.bJackAutoConnect) {
+               connectPorts();
+       }
+
+       // Pause thread until interruption is requested by the destructor
+       qmWait.lock();
+       qwcSleep.wait(&qmWait);
+       qmWait.unlock();
 
-       jasys->destroyOutput();
+       // Cleanup
+       disconnectPorts();
 }
--- a/src/mumble/JackAudio.h
+++ b/src/mumble/JackAudio.h
@@ -9,85 +9,110 @@
 #include "AudioInput.h"
 #include "AudioOutput.h"
 
+#include <QtCore/QVector>
 #include <QtCore/QWaitCondition>
 
 #include <jack/jack.h>
 
 #define JACK_MAX_OUTPUT_PORTS 2
 
-class JackAudioOutput;
-class JackAudioInput;
+typedef QVector<jack_port_t *> JackPorts;
+
+class JackAudioInit;
 
 class JackAudioSystem : public QObject {
+               friend JackAudioInit;
+
        private:
                Q_OBJECT
                Q_DISABLE_COPY(JackAudioSystem)
+
        protected:
-               bool bActive;
-               jack_client_t *client;
-               jack_port_t *in_port;
-               jack_port_t *out_ports[JACK_MAX_OUTPUT_PORTS];
-               jack_default_audio_sample_t *output_buffer;
-               jack_nframes_t iBufferSize;
-
-               void allocOutputBuffer(jack_nframes_t frames);
-
-               void auto_connect_ports();
-               void disconnect_ports();
-
-               static int process_callback(jack_nframes_t nframes, void *arg);
-               static int srate_callback(jack_nframes_t frames, void *arg);
-               static int buffer_size_callback(jack_nframes_t frames, void 
*arg);
-               static void shutdown_callback(void *arg);
-       public:
-               QHash<QString, QString> qhInput;
-               QHash<QString, QString> qhOutput;
-               bool bJackIsGood;
-               bool bInputIsGood;
-               bool bOutputIsGood;
-               int iSampleRate;
-               unsigned int iOutPorts;
+               uint8_t users;
                QMutex qmWait;
                QWaitCondition qwcWait;
+               jack_client_t *client;
 
-               void activate();
+               static int processCallback(jack_nframes_t frames, void *);
+               static int sampleRateCallback(jack_nframes_t, void *);
+               static int bufferSizeCallback(jack_nframes_t frames, void *);
+               static void shutdownCallback(void *);
 
-               void initializeInput();
-               void destroyInput();
+       public:
+               QHash<QString, QString> qhInput;
+               QHash<QString, QString> qhOutput;
 
-               void initializeOutput();
-               void destroyOutput();
+               bool isOk();
+               uint8_t outPorts();
+               jack_nframes_t sampleRate();
+               jack_nframes_t bufferSize();
+               JackPorts getPhysicalPorts(const uint8_t &flags);
+               jack_port_t *registerPort(const char *name, const uint8_t 
&flags);
+               bool unregisterPort(jack_port_t *port);
+               bool connectPort(jack_port_t *sourcePort, jack_port_t 
*destinationPort);
+               bool disconnectPort(jack_port_t *port);
+
+               bool initialize();
+               void deinitialize();
+               bool activate();
+               void deactivate();
 
                JackAudioSystem();
                ~JackAudioSystem();
 };
 
 class JackAudioInput : public AudioInput {
-       friend class JackAudioSystem;
        private:
                Q_OBJECT
                Q_DISABLE_COPY(JackAudioInput)
+
        protected:
-               QMutex qmMutex;
-               QWaitCondition qwcWait;
+               bool bReady;
+               QMutex qmWait;
+               QWaitCondition qwcSleep;
+               jack_port_t *port;
+
        public:
+               bool isReady();
+               bool process(const jack_nframes_t &frames);
+               bool activate();
+               void deactivate();
+               bool registerPorts();
+               bool unregisterPorts();
+               void connectPorts();
+               bool disconnectPorts();
+
+               void run() Q_DECL_OVERRIDE;
                JackAudioInput();
                ~JackAudioInput() Q_DECL_OVERRIDE;
-               void run() Q_DECL_OVERRIDE;
 };
 
 class JackAudioOutput : public AudioOutput {
-       friend class JackAudioSystem;
        private:
                Q_OBJECT
                Q_DISABLE_COPY(JackAudioOutput)
+
        protected:
-               QMutex qmMutex;
-               QWaitCondition qwcWait;
+               bool bReady;
+               QMutex qmWait;
+               QWaitCondition qwcSleep;
+               JackPorts ports;
+               std::unique_ptr<jack_default_audio_sample_t[]> buffer;
+
        public:
+               bool isReady();
+               bool process(const jack_nframes_t &frames);
+               void allocBuffer(const jack_nframes_t &frames);
+               bool activate();
+               void deactivate();
+               bool registerPorts();
+               bool unregisterPorts();
+               void connectPorts();
+               bool disconnectPorts();
+
+               void run() Q_DECL_OVERRIDE;
                JackAudioOutput();
                ~JackAudioOutput() Q_DECL_OVERRIDE;
-               void run() Q_DECL_OVERRIDE;
 };
 
 #endif

Reply via email to