mumble-voip_mumble/src/mumble/JackAudio.cpp

1139 lines
26 KiB
C++

// Copyright 2018-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "JackAudio.h"
#include "Utils.h"
#include "Global.h"
#ifdef Q_CC_GNU
# define RESOLVE(var) \
{ \
var = reinterpret_cast< __typeof__(var) >(qlJack.resolve(#var)); \
if (!var) \
return; \
}
#else
# define RESOLVE(var) \
{ \
*reinterpret_cast< void ** >(&var) = static_cast< void * >(qlJack.resolve(#var)); \
if (!var) \
return; \
}
#endif
static std::unique_ptr< JackAudioSystem > jas;
// jackStatusToStringList converts a jack_status_t (a flag type
// that can contain multiple Jack statuses) to a QStringList.
static QStringList jackStatusToStringList(const jack_status_t &status) {
QStringList statusList;
if (status & JackFailure) {
statusList << QLatin1String("JackFailure - overall operation failed");
}
if (status & JackInvalidOption) {
statusList << QLatin1String("JackInvalidOption - the operation contained an invalid or unsupported option");
}
if (status & JackNameNotUnique) {
statusList << QLatin1String("JackNameNotUnique - the desired client name is not unique");
}
if (status & JackServerStarted) {
statusList << QLatin1String("JackServerStarted - the server was started as a result of this operation");
}
if (status & JackServerFailed) {
statusList << QLatin1String("JackServerFailed - unable to connect to the JACK server");
}
if (status & JackServerError) {
statusList << QLatin1String("JackServerError - communication error with the JACK server");
}
if (status & JackNoSuchClient) {
statusList << QLatin1String("JackNoSuchClient - requested client does not exist");
}
if (status & JackLoadFailure) {
statusList << QLatin1String("JackLoadFailure - unable to load initial client");
}
if (status & JackInitFailure) {
statusList << QLatin1String("JackInitFailure - unable to initialize client");
}
if (status & JackShmFailure) {
statusList << QLatin1String("JackShmFailure - unable to access shared memory");
}
if (status & JackVersionError) {
statusList << QLatin1String("JackVersionError - client's protocol version does not match");
}
if (status & JackBackendError) {
statusList << QLatin1String("JackBackendError - a backend error occurred");
}
if (status & JackClientZombie) {
statusList << QLatin1String("JackClientZombie - client zombified");
}
return statusList;
}
class JackAudioInputRegistrar : public AudioInputRegistrar {
private:
AudioInput *create() Q_DECL_OVERRIDE;
const QVariant getDeviceChoice() Q_DECL_OVERRIDE;
const QList< audioDevice > getDeviceChoices() Q_DECL_OVERRIDE;
void setDeviceChoice(const QVariant &, Settings &) Q_DECL_OVERRIDE;
bool canEcho(EchoCancelOptionID echoCancelID, const QString &outputSystem) const Q_DECL_OVERRIDE;
bool isMicrophoneAccessDeniedByOS() Q_DECL_OVERRIDE { return false; };
public:
JackAudioInputRegistrar();
};
class JackAudioOutputRegistrar : public AudioOutputRegistrar {
private:
AudioOutput *create() Q_DECL_OVERRIDE;
const QVariant getDeviceChoice() Q_DECL_OVERRIDE;
const QList< audioDevice > getDeviceChoices() Q_DECL_OVERRIDE;
void setDeviceChoice(const QVariant &, Settings &) Q_DECL_OVERRIDE;
bool usesOutputDelay() const Q_DECL_OVERRIDE;
public:
JackAudioOutputRegistrar();
};
class JackAudioInit : public DeferInit {
private:
std::unique_ptr< JackAudioInputRegistrar > airJackAudio;
std::unique_ptr< JackAudioOutputRegistrar > aorJackAudio;
void initialize() Q_DECL_OVERRIDE;
void destroy() Q_DECL_OVERRIDE;
};
JackAudioInputRegistrar::JackAudioInputRegistrar() : AudioInputRegistrar(QLatin1String("JACK"), 10) {
}
AudioInput *JackAudioInputRegistrar::create() {
return new JackAudioInput();
}
const QVariant JackAudioInputRegistrar::getDeviceChoice() {
return {};
}
const QList< audioDevice > JackAudioInputRegistrar::getDeviceChoices() {
QList< audioDevice > choices;
QStringList keys = jas->qhInput.keys();
std::sort(keys.begin(), keys.end());
for (const auto &key : keys) {
choices << audioDevice(jas->qhInput.value(key), key);
}
return choices;
}
void JackAudioInputRegistrar::setDeviceChoice(const QVariant &, Settings &) {
}
bool JackAudioInputRegistrar::canEcho(EchoCancelOptionID, const QString &) const {
return false;
}
JackAudioOutputRegistrar::JackAudioOutputRegistrar() : AudioOutputRegistrar(QLatin1String("JACK"), 10) {
}
AudioOutput *JackAudioOutputRegistrar::create() {
return new JackAudioOutput();
}
const QVariant JackAudioOutputRegistrar::getDeviceChoice() {
return Global::get().s.qsJackAudioOutput;
}
const QList< audioDevice > JackAudioOutputRegistrar::getDeviceChoices() {
QList< audioDevice > choices;
QStringList keys = jas->qhOutput.keys();
std::sort(keys.begin(), keys.end());
for (const auto &key : keys) {
choices << audioDevice(jas->qhOutput.value(key), key);
}
return choices;
}
void JackAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
s.qsJackAudioOutput = choice.toString();
}
bool JackAudioOutputRegistrar::usesOutputDelay() const {
return false;
}
void JackAudioInit::initialize() {
jas.reset(new JackAudioSystem());
if (jas->bAvailable) {
airJackAudio.reset(new JackAudioInputRegistrar());
aorJackAudio.reset(new JackAudioOutputRegistrar());
} else {
jas.reset();
}
}
void JackAudioInit::destroy() {
airJackAudio.reset();
aorJackAudio.reset();
jas.reset();
}
// Instantiate JackAudioSystem, JackAudioInputRegistrar and JackAudioOutputRegistrar
static JackAudioInit jai;
JackAudioSystem::JackAudioSystem() : bAvailable(false), users(0), client(nullptr) {
QStringList alternatives;
#ifdef Q_OS_WIN
alternatives << QLatin1String("libjack64.dll");
alternatives << QLatin1String("libjack32.dll");
#elif defined(Q_OS_MAC)
alternatives << QLatin1String("libjack.dylib");
alternatives << QLatin1String("libjack.0.dylib");
#else
alternatives << QLatin1String("libjack.so");
alternatives << QLatin1String("libjack.so.0");
#endif
for (const QString &lib : alternatives) {
qlJack.setFileName(lib);
if (qlJack.load()) {
break;
}
}
if (!qlJack.isLoaded()) {
return;
}
RESOLVE(jack_get_version_string)
RESOLVE(jack_free)
RESOLVE(jack_get_client_name)
RESOLVE(jack_client_open)
RESOLVE(jack_client_close)
RESOLVE(jack_activate)
RESOLVE(jack_deactivate)
RESOLVE(jack_get_sample_rate)
RESOLVE(jack_get_buffer_size)
RESOLVE(jack_get_client_name)
RESOLVE(jack_get_ports)
RESOLVE(jack_connect)
RESOLVE(jack_port_disconnect)
RESOLVE(jack_port_register)
RESOLVE(jack_port_unregister)
RESOLVE(jack_port_name)
RESOLVE(jack_port_by_name)
RESOLVE(jack_port_flags)
RESOLVE(jack_port_get_buffer)
RESOLVE(jack_ringbuffer_create)
RESOLVE(jack_ringbuffer_free)
RESOLVE(jack_ringbuffer_mlock)
RESOLVE(jack_ringbuffer_read)
RESOLVE(jack_ringbuffer_read_space)
RESOLVE(jack_ringbuffer_write)
RESOLVE(jack_ringbuffer_write_space)
RESOLVE(jack_ringbuffer_write_advance)
RESOLVE(jack_ringbuffer_get_write_vector)
RESOLVE(jack_set_process_callback)
RESOLVE(jack_set_sample_rate_callback)
RESOLVE(jack_set_buffer_size_callback)
RESOLVE(jack_on_shutdown)
qhInput.insert(QString(), tr("Hardware Ports"));
qhOutput.insert(QString::number(1), tr("Mono"));
qhOutput.insert(QString::number(2), tr("Stereo"));
bAvailable = true;
qDebug("JACK %s from %s", jack_get_version_string(), qPrintable(qlJack.fileName()));
}
JackAudioSystem::~JackAudioSystem() {
deinitialize();
}
bool JackAudioSystem::initialize() {
QMutexLocker lock(&qmWait);
if (client) {
lock.unlock();
deinitialize();
lock.relock();
}
jack_status_t status;
client = jack_client_open(Global::get().s.qsJackClientName.toStdString().c_str(),
Global::get().s.bJackStartServer ? JackNullOption : JackNoStartServer, &status);
if (!client) {
const auto errors = jackStatusToStringList(status);
qWarning("JackAudioSystem: unable to open client due to %i errors:", errors.count());
for (auto i = 0; i < errors.count(); ++i) {
qWarning("JackAudioSystem: %s", qPrintable(errors.at(i)));
}
return false;
}
qDebug("JackAudioSystem: client \"%s\" opened successfully", jack_get_client_name(client));
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;
}
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;
}
const auto clientName = QString::fromLatin1(jack_get_client_name(client));
const auto err = jack_client_close(client);
if (err != 0) {
qWarning("JackAudioSystem: unable to disconnect from the server - jack_client_close() returned %i", err);
return;
}
client = nullptr;
qDebug("JackAudioSystem: client \"%s\" closed successfully", clientName.toStdString().c_str());
}
bool JackAudioSystem::activate() {
QMutexLocker lock(&qmWait);
if (!client) {
lock.unlock();
if (!initialize()) {
return false;
}
lock.relock();
}
if (users++ > 0) {
// The client is already active, because there is at least a user
return true;
}
const auto ret = jack_activate(client);
if (ret != 0) {
qWarning("JackAudioSystem: unable to activate client - jack_activate() returned %i", ret);
return false;
}
qDebug("JackAudioSystem: client activated");
return true;
}
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;
}
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;
}
qDebug("JackAudioSystem: client deactivated");
lock.unlock();
deinitialize();
}
bool JackAudioSystem::isOk() {
QMutexLocker lock(&qmWait);
return (client);
}
uint8_t JackAudioSystem::outPorts() {
return static_cast< uint8_t >(
qBound< unsigned >(1, Global::get().s.qsJackAudioOutput.toUInt(), JACK_MAX_OUTPUT_PORTS));
}
jack_nframes_t JackAudioSystem::sampleRate() {
QMutexLocker lock(&qmWait);
if (!client) {
return 0;
}
return jack_get_sample_rate(client);
}
jack_nframes_t JackAudioSystem::bufferSize() {
QMutexLocker lock(&qmWait);
if (!client) {
return 0;
}
return jack_get_buffer_size(client);
}
JackPorts JackAudioSystem::getPhysicalPorts(const uint8_t flags) {
QMutexLocker lock(&qmWait);
if (!client) {
return JackPorts();
}
const auto ports = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsTerminal);
if (!ports) {
return JackPorts();
}
JackPorts ret;
for (auto i = 0; ports[i]; ++i) {
if (!ports[i]) {
// End of the array
break;
}
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 (jack_port_flags(port) & flags) {
ret.append(port);
}
}
jack_free(ports);
return ret;
}
void *JackAudioSystem::getPortBuffer(jack_port_t *port, const jack_nframes_t frames) {
if (!port) {
return nullptr;
}
return jack_port_get_buffer(port, frames);
}
jack_port_t *JackAudioSystem::registerPort(const char *name, const uint8_t flags) {
QMutexLocker lock(&qmWait);
if (!client || !name) {
return nullptr;
}
return jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, flags, 0);
}
bool JackAudioSystem::unregisterPort(jack_port_t *port) {
QMutexLocker lock(&qmWait);
if (!client || !port) {
return false;
}
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;
}
return true;
}
bool JackAudioSystem::connectPort(jack_port_t *sourcePort, jack_port_t *destinationPort) {
QMutexLocker lock(&qmWait);
if (!client || !sourcePort || !destinationPort) {
return false;
}
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;
}
bool JackAudioSystem::disconnectPort(jack_port_t *port) {
QMutexLocker lock(&qmWait);
if (!client || !port) {
return false;
}
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;
}
return true;
}
// Ringbuffer functions do not have locks.
// They are single-consumer single-producer, lockless.
jack_ringbuffer_t *JackAudioSystem::ringbufferCreate(const size_t size) {
if (size == 0) {
return nullptr;
}
return jack_ringbuffer_create(size);
}
void JackAudioSystem::ringbufferFree(jack_ringbuffer_t *buffer) {
if (!buffer) {
return;
}
jack_ringbuffer_free(buffer);
}
int JackAudioSystem::ringbufferMlock(jack_ringbuffer_t *buffer) {
if (!buffer) {
return -2;
}
return jack_ringbuffer_mlock(buffer);
}
size_t JackAudioSystem::ringbufferRead(jack_ringbuffer_t *buffer, const size_t size, void *destination) {
if (!buffer || size == 0 || !destination) {
return 0;
}
return jack_ringbuffer_read(buffer, static_cast< char * >(destination), size);
}
size_t JackAudioSystem::ringbufferReadSpace(const jack_ringbuffer_t *buffer) {
if (!buffer) {
return 0;
}
return jack_ringbuffer_read_space(buffer);
}
size_t JackAudioSystem::ringbufferWrite(jack_ringbuffer_t *buffer, const size_t size, const void *source) {
if (!buffer || size == 0 || !source) {
return 0;
}
return jack_ringbuffer_write(buffer, static_cast< const char * >(source), size);
}
void JackAudioSystem::ringbufferGetWriteVector(const jack_ringbuffer_t *buffer, jack_ringbuffer_data_t *vector) {
if (!buffer || !vector) {
return;
}
jack_ringbuffer_get_write_vector(buffer, vector);
}
size_t JackAudioSystem::ringbufferWriteSpace(const jack_ringbuffer_t *buffer) {
if (!buffer) {
return 0;
}
return jack_ringbuffer_write_space(buffer);
}
void JackAudioSystem::ringbufferWriteAdvance(jack_ringbuffer_t *buffer, const size_t size) {
if (!buffer || size == 0) {
return;
}
jack_ringbuffer_write_advance(buffer, size);
}
int JackAudioSystem::processCallback(jack_nframes_t frames, void *) {
auto const input = dynamic_cast< JackAudioInput * >(Global::get().ai.get());
auto const output = dynamic_cast< JackAudioOutput * >(Global::get().ao.get());
const bool canInput = (input && input->isReady());
const bool canOutput = (output && output->isReady());
if (canInput && !input->process(frames)) {
return 1;
}
if (canOutput && !output->process(frames)) {
return 1;
}
return 0;
}
int JackAudioSystem::sampleRateCallback(jack_nframes_t, void *) {
auto const input = dynamic_cast< JackAudioInput * >(Global::get().ai.get());
auto const output = dynamic_cast< JackAudioOutput * >(Global::get().ao.get());
if (input) {
input->activate();
}
if (output) {
output->activate();
}
return 0;
}
int JackAudioSystem::bufferSizeCallback(jack_nframes_t frames, void *) {
auto const input = dynamic_cast< JackAudioInput * >(Global::get().ai.get());
auto const output = dynamic_cast< JackAudioOutput * >(Global::get().ao.get());
if (input && !input->allocBuffer(frames)) {
return 1;
}
if (output && !output->allocBuffer(frames)) {
return 1;
}
return 0;
}
void JackAudioSystem::shutdownCallback(void *) {
qWarning("JackAudioSystem: server shutdown");
jas->client = nullptr;
jas->users = 0;
}
JackAudioInput::JackAudioInput() : port(nullptr), buffer(nullptr) {
bReady = activate();
}
JackAudioInput::~JackAudioInput() {
qmWait.lock();
bReady = false;
// Request interruption
qsSleep.release(1);
qmWait.unlock();
// Wait for thread to exit
wait();
// Cleanup
deactivate();
}
bool JackAudioInput::isReady() {
return bReady;
}
bool JackAudioInput::allocBuffer(const jack_nframes_t frames) {
QMutexLocker lock(&qmWait);
bufferSize = frames * sizeof(jack_default_audio_sample_t);
if (buffer) {
jas->ringbufferFree(buffer);
}
buffer = jas->ringbufferCreate(bufferSize * JACK_BUFFER_PERIODS);
if (!buffer) {
return false;
}
jas->ringbufferMlock(buffer);
return true;
}
bool JackAudioInput::activate() {
QMutexLocker lock(&qmWait);
if (!jas->isOk()) {
jas->initialize();
}
eMicFormat = SampleFloat;
iMicChannels = 1;
iMicFreq = jas->sampleRate();
initializeMixer();
lock.unlock();
if (!registerPorts()) {
return false;
}
if (!allocBuffer(jas->bufferSize())) {
return false;
}
if (!jas->activate()) {
return false;
}
return true;
}
void JackAudioInput::deactivate() {
unregisterPorts();
jas->deactivate();
if (buffer) {
jas->ringbufferFree(buffer);
}
}
bool JackAudioInput::registerPorts() {
unregisterPorts();
QMutexLocker lock(&qmWait);
port = jas->registerPort("input", JackPortIsInput);
if (!port) {
qWarning("JackAudioInput: unable to register port");
return false;
}
return true;
}
bool JackAudioInput::unregisterPorts() {
QMutexLocker lock(&qmWait);
if (!port) {
return false;
}
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;
}
}
}
bool JackAudioInput::disconnectPorts() {
QMutexLocker lock(&qmWait);
if (!port) {
return true;
}
if (!jas->disconnectPort(port)) {
qWarning("JackAudioInput: unable to disconnect port");
return false;
}
return true;
}
bool JackAudioInput::process(const jack_nframes_t frames) {
if (!bReady) {
return true;
}
const auto portBuffer = jas->getPortBuffer(port, frames);
qsSleep.release(1);
if (!portBuffer || !buffer) {
return false;
}
// Ringbuffer will not exceed capacity, just drop the frames.
// Since the consumer drains it fully every time, this should never be a problem.
jas->ringbufferWrite(buffer, frames * sizeof(jack_default_audio_sample_t), portBuffer);
return true;
}
void JackAudioInput::run() {
if (!bReady) {
return;
}
// Initialization
if (Global::get().s.bJackAutoConnect) {
connectPorts();
}
// We keep this as the frame size could change, but we are not going to resize this buffer.
qmWait.lock();
std::unique_ptr< uint8_t[] > sampleBuffer(new uint8_t[bufferSize]);
qmWait.unlock();
do {
qmWait.lock();
while (const auto bytes = qMin(jas->ringbufferReadSpace(buffer), bufferSize)) {
jas->ringbufferRead(buffer, bytes, sampleBuffer.get());
addMic(sampleBuffer.get(), static_cast< unsigned int >(bytes / sizeof(jack_default_audio_sample_t)));
}
qmWait.unlock();
qsSleep.acquire(1);
} while (bReady);
}
JackAudioOutput::JackAudioOutput() : buffer(nullptr) {
bReady = activate();
}
JackAudioOutput::~JackAudioOutput() {
// Request interruption
qmWait.lock();
bReady = false;
qsSleep.release(1);
qmWait.unlock();
// Wait for thread to exit
wait();
// Cleanup
deactivate();
}
bool JackAudioOutput::isReady() {
return bReady;
}
bool JackAudioOutput::allocBuffer(const jack_nframes_t frames) {
QMutexLocker lock(&qmWait);
iFrameSize = frames;
if (buffer) {
jas->ringbufferFree(buffer);
}
buffer = jas->ringbufferCreate(iFrameSize * iSampleSize * JACK_BUFFER_PERIODS);
if (!buffer) {
return false;
}
jas->ringbufferMlock(buffer);
return true;
}
bool JackAudioOutput::activate() {
QMutexLocker lock(&qmWait);
if (!jas->isOk()) {
jas->initialize();
}
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();
if (!allocBuffer(jas->bufferSize())) {
return false;
}
if (!registerPorts()) {
return false;
}
if (!jas->activate()) {
return false;
}
return true;
}
void JackAudioOutput::deactivate() {
unregisterPorts();
jas->deactivate();
jas->ringbufferFree(buffer);
}
bool JackAudioOutput::registerPorts() {
unregisterPorts();
QMutexLocker lock(&qmWait);
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) {
qWarning("JackAudioOutput: unable to register port #%u", i);
return false;
}
ports.append(port);
outputBuffers.append(nullptr);
}
return true;
}
bool JackAudioOutput::unregisterPorts() {
QMutexLocker lock(&qmWait);
bool ret = true;
for (auto i = 0; i < ports.size(); ++i) {
if (!ports[i]) {
continue;
}
if (!jas->unregisterPort(ports[i])) {
qWarning("JackAudioOutput: unable to unregister port #%u", i);
ret = false;
}
}
outputBuffers.clear();
ports.clear();
return ret;
}
void JackAudioOutput::connectPorts() {
disconnectPorts();
QMutexLocker lock(&qmWait);
const auto inputPorts = jas->getPhysicalPorts(JackPortIsInput);
uint8_t i = 0;
for (auto inputPort : inputPorts) {
if (i == ports.size()) {
break;
}
if (ports[i]) {
if (!jas->connectPort(ports[i], inputPort)) {
continue;
}
}
++i;
}
}
bool JackAudioOutput::disconnectPorts() {
QMutexLocker lock(&qmWait);
bool ret = true;
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;
}
}
return ret;
}
bool JackAudioOutput::process(const jack_nframes_t frames) {
if (!bReady) {
return true;
}
// FIXME: This is good on Linux, and maybe Windows.
// However, on certain platforms QSemaphore actually uses QWaitCondition, which uses a Mutex internally for locking.
// This is in spite of the fact that on most POSIX systems, QMutex is actually implemented using semaphores.
qsSleep.release(1);
for (decltype(iChannels) currentChannel = 0; currentChannel < iChannels; ++currentChannel) {
auto outputBuffer = jas->getPortBuffer(ports[static_cast< int >(currentChannel)], frames);
if (!outputBuffer) {
return false;
}
outputBuffers.replace(static_cast< int >(currentChannel),
reinterpret_cast< jack_default_audio_sample_t * >(outputBuffer));
}
const auto avail = jas->ringbufferReadSpace(buffer);
if (avail == 0) {
for (decltype(iChannels) currentChannel = 0; currentChannel < iChannels; ++currentChannel) {
memset(outputBuffers[static_cast< int >(currentChannel)], 0, frames * sizeof(jack_default_audio_sample_t));
}
return true;
}
const size_t needed = frames * iSampleSize;
if (iChannels == 1) {
jas->ringbufferRead(buffer, avail, reinterpret_cast< char * >(outputBuffers[0]));
if (avail < needed) {
memset(reinterpret_cast< char * >(&(outputBuffers[static_cast< int >(avail)])), 0, needed - avail);
}
return true;
}
auto samples = qMin(jas->ringbufferReadSpace(buffer), needed) / sizeof(jack_default_audio_sample_t);
for (auto currentSample = decltype(samples){ 0 }; currentSample < samples; ++currentSample) {
jas->ringbufferRead(
buffer, sizeof(jack_default_audio_sample_t),
reinterpret_cast< char * >(
&outputBuffers[static_cast< int >(currentSample % iChannels)][currentSample / iChannels]));
}
if ((samples / iChannels) < frames) {
for (decltype(iChannels) currentChannel = 0; currentChannel < iChannels; ++currentChannel) {
memset(&outputBuffers[static_cast< int >(currentChannel)][avail / samples], 0,
(needed - avail) / iChannels);
}
}
return true;
}
void JackAudioOutput::run() {
if (!bReady) {
return;
}
// Initialization
if (Global::get().s.bJackAutoConnect) {
connectPorts();
}
std::unique_ptr< uint8_t[] > spareSample(new uint8_t[iSampleSize]);
jack_ringbuffer_data_t _writeVector[2];
jack_ringbuffer_data_t *writeVector = _writeVector;
// Keep feeding more data into the buffer
do {
qmWait.lock();
if (jas->ringbufferWriteSpace(buffer) < (iFrameSize * iSampleSize)) {
qmWait.unlock();
qsSleep.acquire(1);
continue;
}
jas->ringbufferGetWriteVector(buffer, writeVector);
auto bOk = true;
size_t writtenFrames = 0;
unsigned int wanted =
qMin(static_cast< unsigned int >(writeVector->len) / iSampleSize, static_cast< unsigned int >(iFrameSize));
if (wanted > 0) {
bOk = mix(writeVector->buf, wanted);
writtenFrames += bOk ? wanted : 0;
}
const auto gap = writeVector->len - (wanted * iSampleSize);
if (!bOk || wanted == iFrameSize) {
goto next;
}
// Corner case where one sample wraps around the buffer
if (gap != 0) {
writeVector->buf += wanted * iSampleSize;
bOk = mix(spareSample.get(), 1);
if (bOk) {
memcpy(writeVector->buf, spareSample.get(), gap);
++writeVector;
memcpy(writeVector->buf, spareSample.get() + gap, iSampleSize - gap);
++wanted;
++writtenFrames;
} else {
goto next;
}
writeVector->buf += iSampleSize - gap;
} else {
++writeVector;
}
if (wanted == iFrameSize) {
goto next;
}
bOk = mix(writeVector->buf, iFrameSize - wanted);
writtenFrames += bOk ? (iFrameSize - wanted) : 0;
next:
jas->ringbufferWriteAdvance(buffer, writtenFrames * iSampleSize);
qmWait.unlock();
qsSleep.acquire(1);
} while (bReady);
}
#undef RESOLVE