🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

563 lines
16 KiB

/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/jack.h"
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <thread>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include "ringbuffer.h"
#include "threads.h"
#include "compat.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
namespace {
constexpr ALCchar jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
MAGIC(jack_client_close); \
MAGIC(jack_client_name_size); \
MAGIC(jack_get_client_name); \
MAGIC(jack_connect); \
MAGIC(jack_activate); \
MAGIC(jack_deactivate); \
MAGIC(jack_port_register); \
MAGIC(jack_port_unregister); \
MAGIC(jack_port_get_buffer); \
MAGIC(jack_port_name); \
MAGIC(jack_get_ports); \
MAGIC(jack_free); \
MAGIC(jack_get_sample_rate); \
MAGIC(jack_set_error_function); \
MAGIC(jack_set_process_callback); \
MAGIC(jack_set_buffer_size_callback); \
MAGIC(jack_set_buffer_size); \
MAGIC(jack_get_buffer_size);
void *jack_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
JACK_FUNCS(MAKE_FUNC);
decltype(jack_error_callback) * pjack_error_callback;
#undef MAKE_FUNC
#ifndef IN_IDE_PARSER
#define jack_client_open pjack_client_open
#define jack_client_close pjack_client_close
#define jack_client_name_size pjack_client_name_size
#define jack_get_client_name pjack_get_client_name
#define jack_connect pjack_connect
#define jack_activate pjack_activate
#define jack_deactivate pjack_deactivate
#define jack_port_register pjack_port_register
#define jack_port_unregister pjack_port_unregister
#define jack_port_get_buffer pjack_port_get_buffer
#define jack_port_name pjack_port_name
#define jack_get_ports pjack_get_ports
#define jack_free pjack_free
#define jack_get_sample_rate pjack_get_sample_rate
#define jack_set_error_function pjack_set_error_function
#define jack_set_process_callback pjack_set_process_callback
#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
#define jack_set_buffer_size pjack_set_buffer_size
#define jack_get_buffer_size pjack_get_buffer_size
#define jack_error_callback (*pjack_error_callback)
#endif
#endif
jack_options_t ClientOptions = JackNullOption;
ALCboolean jack_load()
{
ALCboolean error = ALC_FALSE;
#ifdef HAVE_DYNLOAD
if(!jack_handle)
{
std::string missing_funcs;
#ifdef _WIN32
#define JACKLIB "libjack.dll"
#else
#define JACKLIB "libjack.so.0"
#endif
jack_handle = LoadLib(JACKLIB);
if(!jack_handle)
{
WARN("Failed to load %s\n", JACKLIB);
return ALC_FALSE;
}
error = ALC_FALSE;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = ALC_TRUE; \
missing_funcs += "\n" #f; \
} \
} while(0)
JACK_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
/* Optional symbols. These don't exist in all versions of JACK. */
#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
LOAD_SYM(jack_error_callback);
#undef LOAD_SYM
if(error)
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(jack_handle);
jack_handle = nullptr;
}
}
#endif
return !error;
}
struct JackPlayback final : public BackendBase {
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg);
int bufferSizeNotify(jack_nframes_t numframes);
static int processC(jack_nframes_t numframes, void *arg);
int process(jack_nframes_t numframes);
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void stop() override;
ClockLatency getClockLatency() override;
jack_client_t *mClient{nullptr};
jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
RingBufferPtr mRing;
al::semaphore mSem;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "JackPlayback::"; }
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
{
if(!mClient)
return;
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
jack_client_close(mClient);
mClient = nullptr;
}
int JackPlayback::bufferSizeNotifyC(jack_nframes_t numframes, void *arg)
{ return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); }
int JackPlayback::bufferSizeNotify(jack_nframes_t numframes)
{
std::lock_guard<std::mutex> _{mDevice->StateLock};
mDevice->UpdateSize = numframes;
mDevice->BufferSize = numframes*2;
ALuint bufsize{mDevice->UpdateSize};
if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize))
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize);
mRing = nullptr;
mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
if(!mRing)
{
ERR("Failed to reallocate ringbuffer\n");
aluHandleDisconnect(mDevice, "Failed to reallocate %u-sample buffer", bufsize);
}
return 0;
}
int JackPlayback::processC(jack_nframes_t numframes, void *arg)
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
int JackPlayback::process(jack_nframes_t numframes)
{
jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
ALsizei numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, data.first.len)};
std::transform(out, out+numchans, out,
[&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
{
const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf);
std::generate_n(outbuf, todo,
[&in,numchans]() noexcept -> ALfloat
{
ALfloat ret{*in};
in += numchans;
return ret;
}
);
data.first.buf += sizeof(ALfloat);
return outbuf + todo;
}
);
jack_nframes_t total{todo};
todo = minu(numframes-total, data.second.len);
if(todo > 0)
{
std::transform(out, out+numchans, out,
[&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
{
const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf);
std::generate_n(outbuf, todo,
[&in,numchans]() noexcept -> ALfloat
{
ALfloat ret{*in};
in += numchans;
return ret;
}
);
data.second.buf += sizeof(ALfloat);
return outbuf + todo;
}
);
total += todo;
}
mRing->readAdvance(total);
mSem.post();
if(numframes > total)
{
todo = numframes-total;
std::transform(out, out+numchans, out,
[todo](ALfloat *outbuf) -> ALfloat*
{
std::fill_n(outbuf, todo, 0.0f);
return outbuf + todo;
}
);
}
return 0;
}
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
{
unlock();
mSem.wait();
lock();
continue;
}
auto data = mRing->getWriteVector();
auto todo = static_cast<ALuint>(data.first.len + data.second.len);
todo -= todo%mDevice->UpdateSize;
ALuint len1{minu(data.first.len, todo)};
ALuint len2{minu(data.second.len, todo-len1)};
aluMixData(mDevice, data.first.buf, len1);
if(len2 > 0)
aluMixData(mDevice, data.second.buf, len2);
mRing->writeAdvance(todo);
}
unlock();
return 0;
}
ALCenum JackPlayback::open(const ALCchar *name)
{
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
return ALC_INVALID_VALUE;
const char *client_name{"alsoft"};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
{
ERR("jack_client_open() failed, status = 0x%02x\n", status);
return ALC_INVALID_VALUE;
}
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got `%s' instead\n", client_name);
}
jack_set_process_callback(mClient, &JackPlayback::processC, this);
jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this);
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean JackPlayback::reset()
{
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
ALuint bufsize{mDevice->UpdateSize};
if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize))
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
/* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat;
ALsizei numchans{mDevice->channelsFromFmt()};
auto ports_end = std::begin(mPort) + numchans;
auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
[this](jack_port_t *&port) -> bool
{
std::string name{"channel_" + std::to_string(&port - mPort + 1)};
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
return port != nullptr;
}
);
if(bad_port != ports_end)
{
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == std::begin(mPort)) return ALC_FALSE;
if(bad_port == std::begin(mPort)+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
*bad_port = nullptr;
}
mDevice->FmtChans = DevFmtStereo;
}
numchans = std::distance(std::begin(mPort), bad_port);
}
mRing = nullptr;
mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
if(!mRing)
{
ERR("Failed to allocate ringbuffer\n");
return ALC_FALSE;
}
SetDefaultChannelOrder(mDevice);
return ALC_TRUE;
}
ALCboolean JackPlayback::start()
{
if(jack_activate(mClient))
{
ERR("Failed to activate client\n");
return ALC_FALSE;
}
const char **ports{jack_get_ports(mClient, nullptr, nullptr,
JackPortIsPhysical|JackPortIsInput)};
if(ports == nullptr)
{
ERR("No physical playback ports found\n");
jack_deactivate(mClient);
return ALC_FALSE;
}
std::mismatch(std::begin(mPort), std::end(mPort), ports,
[this](const jack_port_t *port, const char *pname) -> bool
{
if(!port) return false;
if(!pname)
{
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
return false;
}
if(jack_connect(mClient, jack_port_name(port), pname))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
pname);
return true;
}
);
jack_free(ports);
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
jack_deactivate(mClient);
return ALC_FALSE;
}
void JackPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mSem.post();
mThread.join();
jack_deactivate(mClient);
}
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
lock();
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace()};
ret.Latency /= mDevice->Frequency;
unlock();
return ret;
}
void jack_msg_handler(const char *message)
{
WARN("%s\n", message);
}
} // namespace
bool JackBackendFactory::init()
{
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
{
WARN("jack_client_open() failed, 0x%02x\n", status);
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
ERR("Unable to connect to JACK server\n");
return false;
}
jack_client_close(client);
return true;
}
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
void JackBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(jackDevice, sizeof(jackDevice));
break;
case DevProbe::Capture:
break;
}
}
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};
return nullptr;
}
BackendFactory &JackBackendFactory::getFactory()
{
static JackBackendFactory factory{};
return factory;
}