Remove custom minz/maxz functions
[openal-soft.git] / alc / backends / jack.cpp
blobb9790f0399e24493a60eaa0a66ac5d1c23d03c9a
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include "jack.h"
25 #include <array>
26 #include <cstdlib>
27 #include <cstdio>
28 #include <cstring>
29 #include <memory.h>
30 #include <mutex>
31 #include <thread>
32 #include <functional>
33 #include <vector>
35 #include "albit.h"
36 #include "alc/alconfig.h"
37 #include "alnumeric.h"
38 #include "alsem.h"
39 #include "alstring.h"
40 #include "althrd_setname.h"
41 #include "core/device.h"
42 #include "core/helpers.h"
43 #include "core/logging.h"
44 #include "dynload.h"
45 #include "ringbuffer.h"
47 #include <jack/jack.h>
48 #include <jack/ringbuffer.h>
51 namespace {
53 using namespace std::string_view_literals;
55 #ifdef HAVE_DYNLOAD
56 #define JACK_FUNCS(MAGIC) \
57 MAGIC(jack_client_open); \
58 MAGIC(jack_client_close); \
59 MAGIC(jack_client_name_size); \
60 MAGIC(jack_get_client_name); \
61 MAGIC(jack_connect); \
62 MAGIC(jack_activate); \
63 MAGIC(jack_deactivate); \
64 MAGIC(jack_port_register); \
65 MAGIC(jack_port_unregister); \
66 MAGIC(jack_port_get_buffer); \
67 MAGIC(jack_port_name); \
68 MAGIC(jack_get_ports); \
69 MAGIC(jack_free); \
70 MAGIC(jack_get_sample_rate); \
71 MAGIC(jack_set_error_function); \
72 MAGIC(jack_set_process_callback); \
73 MAGIC(jack_set_buffer_size_callback); \
74 MAGIC(jack_set_buffer_size); \
75 MAGIC(jack_get_buffer_size);
77 void *jack_handle;
78 #define MAKE_FUNC(f) decltype(f) * p##f
79 JACK_FUNCS(MAKE_FUNC)
80 decltype(jack_error_callback) * pjack_error_callback;
81 #undef MAKE_FUNC
83 #ifndef IN_IDE_PARSER
84 #define jack_client_open pjack_client_open
85 #define jack_client_close pjack_client_close
86 #define jack_client_name_size pjack_client_name_size
87 #define jack_get_client_name pjack_get_client_name
88 #define jack_connect pjack_connect
89 #define jack_activate pjack_activate
90 #define jack_deactivate pjack_deactivate
91 #define jack_port_register pjack_port_register
92 #define jack_port_unregister pjack_port_unregister
93 #define jack_port_get_buffer pjack_port_get_buffer
94 #define jack_port_name pjack_port_name
95 #define jack_get_ports pjack_get_ports
96 #define jack_free pjack_free
97 #define jack_get_sample_rate pjack_get_sample_rate
98 #define jack_set_error_function pjack_set_error_function
99 #define jack_set_process_callback pjack_set_process_callback
100 #define jack_set_buffer_size_callback pjack_set_buffer_size_callback
101 #define jack_set_buffer_size pjack_set_buffer_size
102 #define jack_get_buffer_size pjack_get_buffer_size
103 #define jack_error_callback (*pjack_error_callback)
104 #endif
105 #endif
108 jack_options_t ClientOptions = JackNullOption;
110 bool jack_load()
112 #ifdef HAVE_DYNLOAD
113 if(!jack_handle)
115 #ifdef _WIN32
116 #define JACKLIB "libjack.dll"
117 #else
118 #define JACKLIB "libjack.so.0"
119 #endif
120 jack_handle = LoadLib(JACKLIB);
121 if(!jack_handle)
123 WARN("Failed to load %s\n", JACKLIB);
124 return false;
127 std::string missing_funcs;
128 #define LOAD_FUNC(f) do { \
129 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
130 if(p##f == nullptr) missing_funcs += "\n" #f; \
131 } while(0)
132 JACK_FUNCS(LOAD_FUNC);
133 #undef LOAD_FUNC
134 /* Optional symbols. These don't exist in all versions of JACK. */
135 #define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
136 LOAD_SYM(jack_error_callback);
137 #undef LOAD_SYM
139 if(!missing_funcs.empty())
141 WARN("Missing expected functions:%s\n", missing_funcs.c_str());
142 CloseLib(jack_handle);
143 jack_handle = nullptr;
144 return false;
147 #endif
149 return true;
153 struct JackDeleter {
154 void operator()(void *ptr) { jack_free(ptr); }
156 using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
158 struct DeviceEntry {
159 std::string mName;
160 std::string mPattern;
162 template<typename T, typename U>
163 DeviceEntry(T&& name, U&& pattern)
164 : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
168 std::vector<DeviceEntry> PlaybackList;
171 void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
173 std::remove_reference_t<decltype(list)>{}.swap(list);
175 if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
177 for(size_t i{0};ports[i];++i)
179 const std::string_view portname{ports[i]};
180 const size_t seppos{portname.find(':')};
181 if(seppos == 0 || seppos >= portname.size())
182 continue;
184 const std::string_view portdev{ports[i], seppos};
185 auto check_name = [portdev](const DeviceEntry &entry) -> bool
186 { return entry.mName == portdev; };
187 if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
188 continue;
190 const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
191 TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
193 /* There are ports but couldn't get device names from them. Add a
194 * generic entry.
196 if(ports[0] && list.empty())
198 WARN("No device names found in available ports, adding a generic name.\n");
199 list.emplace_back("JACK"sv, ""sv);
203 if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
205 for(size_t strpos{0};strpos < listopt->size();)
207 size_t nextpos{listopt->find(';', strpos)};
208 size_t seppos{listopt->find('=', strpos)};
209 if(seppos >= nextpos || seppos == strpos)
211 const std::string entry{listopt->substr(strpos, nextpos-strpos)};
212 ERR("Invalid device entry: \"%s\"\n", entry.c_str());
213 if(nextpos != std::string::npos) ++nextpos;
214 strpos = nextpos;
215 continue;
218 const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
219 const al::span<const char> pattern{listopt->data()+(seppos+1),
220 std::min(nextpos, listopt->size())-(seppos+1)};
222 /* Check if this custom pattern already exists in the list. */
223 auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
225 const size_t len{pattern.size()};
226 return entry.mPattern.length() == len
227 && entry.mPattern.compare(0, len, pattern.data(), len) == 0;
229 auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
230 if(itemmatch != list.end())
232 /* If so, replace the name with this custom one. */
233 itemmatch->mName.assign(name.data(), name.size());
234 TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
235 itemmatch->mPattern.c_str());
237 else
239 /* Otherwise, add a new device entry. */
240 list.emplace_back(std::string{name.data(), name.size()},
241 std::string{pattern.data(), pattern.size()});
242 const auto &entry = list.back();
243 TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
246 if(nextpos != std::string::npos) ++nextpos;
247 strpos = nextpos;
251 if(list.size() > 1)
253 /* Rename entries that have matching names, by appending '#2', '#3',
254 * etc, as needed.
256 for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
258 auto check_match = [curitem](const DeviceEntry &entry) -> bool
259 { return entry.mName == curitem->mName; };
260 if(std::find_if(list.begin(), curitem, check_match) != curitem)
262 std::string name{curitem->mName};
263 size_t count{1};
264 auto check_name = [&name](const DeviceEntry &entry) -> bool
265 { return entry.mName == name; };
266 do {
267 name = curitem->mName;
268 name += " #";
269 name += std::to_string(++count);
270 } while(std::find_if(list.begin(), curitem, check_name) != curitem);
271 curitem->mName = std::move(name);
278 struct JackPlayback final : public BackendBase {
279 JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
280 ~JackPlayback() override;
282 int processRt(jack_nframes_t numframes) noexcept;
283 static int processRtC(jack_nframes_t numframes, void *arg) noexcept
284 { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
286 int process(jack_nframes_t numframes) noexcept;
287 static int processC(jack_nframes_t numframes, void *arg) noexcept
288 { return static_cast<JackPlayback*>(arg)->process(numframes); }
290 int mixerProc();
292 void open(std::string_view name) override;
293 bool reset() override;
294 void start() override;
295 void stop() override;
296 ClockLatency getClockLatency() override;
298 std::string mPortPattern;
300 jack_client_t *mClient{nullptr};
301 std::array<jack_port_t*,MaxOutputChannels> mPort{};
303 std::mutex mMutex;
305 std::atomic<bool> mPlaying{false};
306 bool mRTMixing{false};
307 RingBufferPtr mRing;
308 al::semaphore mSem;
310 std::atomic<bool> mKillNow{true};
311 std::thread mThread;
314 JackPlayback::~JackPlayback()
316 if(!mClient)
317 return;
319 auto unregister_port = [this](jack_port_t *port) -> void
320 { if(port) jack_port_unregister(mClient, port); };
321 std::for_each(mPort.begin(), mPort.end(), unregister_port);
322 mPort.fill(nullptr);
324 jack_client_close(mClient);
325 mClient = nullptr;
329 int JackPlayback::processRt(jack_nframes_t numframes) noexcept
331 std::array<jack_default_audio_sample_t*,MaxOutputChannels> out;
332 size_t numchans{0};
333 for(auto port : mPort)
335 if(!port || numchans == mDevice->RealOut.Buffer.size())
336 break;
337 out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
340 if(mPlaying.load(std::memory_order_acquire)) LIKELY
341 mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
342 else
344 auto clear_buf = [numframes](float *outbuf) -> void
345 { std::fill_n(outbuf, numframes, 0.0f); };
346 std::for_each(out.begin(), out.begin()+numchans, clear_buf);
349 return 0;
353 int JackPlayback::process(jack_nframes_t numframes) noexcept
355 std::array<jack_default_audio_sample_t*,MaxOutputChannels> out;
356 size_t numchans{0};
357 for(auto port : mPort)
359 if(!port) break;
360 out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
363 jack_nframes_t total{0};
364 if(mPlaying.load(std::memory_order_acquire)) LIKELY
366 auto data = mRing->getReadVector();
367 jack_nframes_t todo{std::min(numframes, static_cast<jack_nframes_t>(data.first.len))};
368 auto write_first = [&data,numchans,todo](float *outbuf) -> float*
370 const auto *RESTRICT in = reinterpret_cast<const float*>(data.first.buf);
371 auto deinterlace_input = [&in,numchans]() noexcept -> float
373 float ret{*in};
374 in += numchans;
375 return ret;
377 std::generate_n(outbuf, todo, deinterlace_input);
378 data.first.buf += sizeof(float);
379 return outbuf + todo;
381 std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
382 total += todo;
384 todo = std::min(numframes-total, static_cast<jack_nframes_t>(data.second.len));
385 if(todo > 0)
387 auto write_second = [&data,numchans,todo](float *outbuf) -> float*
389 const auto *RESTRICT in = reinterpret_cast<const float*>(data.second.buf);
390 auto deinterlace_input = [&in,numchans]() noexcept -> float
392 float ret{*in};
393 in += numchans;
394 return ret;
396 std::generate_n(outbuf, todo, deinterlace_input);
397 data.second.buf += sizeof(float);
398 return outbuf + todo;
400 std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
401 total += todo;
404 mRing->readAdvance(total);
405 mSem.post();
408 if(numframes > total)
410 const jack_nframes_t todo{numframes - total};
411 auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
412 std::for_each(out.begin(), out.begin()+numchans, clear_buf);
415 return 0;
418 int JackPlayback::mixerProc()
420 SetRTPriority();
421 althrd_setname(GetMixerThreadName());
423 const size_t frame_step{mDevice->channelsFromFmt()};
425 while(!mKillNow.load(std::memory_order_acquire)
426 && mDevice->Connected.load(std::memory_order_acquire))
428 if(mRing->writeSpace() < mDevice->UpdateSize)
430 mSem.wait();
431 continue;
434 auto data = mRing->getWriteVector();
435 size_t todo{data.first.len + data.second.len};
436 todo -= todo%mDevice->UpdateSize;
438 const auto len1 = static_cast<uint>(std::min(data.first.len, todo));
439 const auto len2 = static_cast<uint>(std::min(data.second.len, todo-len1));
441 std::lock_guard<std::mutex> dlock{mMutex};
442 mDevice->renderSamples(data.first.buf, len1, frame_step);
443 if(len2 > 0)
444 mDevice->renderSamples(data.second.buf, len2, frame_step);
445 mRing->writeAdvance(todo);
448 return 0;
452 void JackPlayback::open(std::string_view name)
454 if(!mClient)
456 const PathNamePair &binname = GetProcBinary();
457 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
459 jack_status_t status{};
460 mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
461 if(mClient == nullptr)
462 throw al::backend_exception{al::backend_error::DeviceError,
463 "Failed to open client connection: 0x%02x", status};
464 if((status&JackServerStarted))
465 TRACE("JACK server started\n");
466 if((status&JackNameNotUnique))
468 client_name = jack_get_client_name(mClient);
469 TRACE("Client name not unique, got '%s' instead\n", client_name);
473 if(PlaybackList.empty())
474 EnumerateDevices(mClient, PlaybackList);
476 if(name.empty() && !PlaybackList.empty())
478 name = PlaybackList[0].mName;
479 mPortPattern = PlaybackList[0].mPattern;
481 else
483 auto check_name = [name](const DeviceEntry &entry) -> bool
484 { return entry.mName == name; };
485 auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
486 if(iter == PlaybackList.cend())
487 throw al::backend_exception{al::backend_error::NoDevice,
488 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
489 mPortPattern = iter->mPattern;
492 mDevice->DeviceName = name;
495 bool JackPlayback::reset()
497 auto unregister_port = [this](jack_port_t *port) -> void
498 { if(port) jack_port_unregister(mClient, port); };
499 std::for_each(mPort.begin(), mPort.end(), unregister_port);
500 mPort.fill(nullptr);
502 mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
503 jack_set_process_callback(mClient,
504 mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
506 /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
507 * ready for when requested.
509 mDevice->Frequency = jack_get_sample_rate(mClient);
510 mDevice->UpdateSize = jack_get_buffer_size(mClient);
511 if(mRTMixing)
513 /* Assume only two periods when directly mixing. Should try to query
514 * the total port latency when connected.
516 mDevice->BufferSize = mDevice->UpdateSize * 2;
518 else
520 const std::string_view devname{mDevice->DeviceName};
521 uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
522 bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
523 mDevice->BufferSize = bufsize + mDevice->UpdateSize;
526 /* Force 32-bit float output. */
527 mDevice->FmtType = DevFmtFloat;
529 int port_num{0};
530 auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
531 auto bad_port = mPort.begin();
532 while(bad_port != ports_end)
534 std::string name{"channel_" + std::to_string(++port_num)};
535 *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
536 JackPortIsOutput | JackPortIsTerminal, 0);
537 if(!*bad_port) break;
538 ++bad_port;
540 if(bad_port != ports_end)
542 ERR("Failed to register enough JACK ports for %s output\n",
543 DevFmtChannelsString(mDevice->FmtChans));
544 if(bad_port == mPort.begin()) return false;
546 if(bad_port == mPort.begin()+1)
547 mDevice->FmtChans = DevFmtMono;
548 else
550 ports_end = mPort.begin()+2;
551 while(bad_port != ports_end)
553 jack_port_unregister(mClient, *(--bad_port));
554 *bad_port = nullptr;
556 mDevice->FmtChans = DevFmtStereo;
560 setDefaultChannelOrder();
562 return true;
565 void JackPlayback::start()
567 if(jack_activate(mClient))
568 throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
570 const std::string_view devname{mDevice->DeviceName};
571 if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
573 JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
574 JackPortIsInput)};
575 if(!pnames)
577 jack_deactivate(mClient);
578 throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
581 for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
583 if(!pnames[i])
585 ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
586 break;
588 if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
589 ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
590 pnames[i]);
594 /* Reconfigure buffer metrics in case the server changed it since the reset
595 * (it won't change again after jack_activate), then allocate the ring
596 * buffer with the appropriate size.
598 mDevice->Frequency = jack_get_sample_rate(mClient);
599 mDevice->UpdateSize = jack_get_buffer_size(mClient);
600 mDevice->BufferSize = mDevice->UpdateSize * 2;
602 mRing = nullptr;
603 if(mRTMixing)
604 mPlaying.store(true, std::memory_order_release);
605 else
607 uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
608 bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
609 mDevice->BufferSize = bufsize + mDevice->UpdateSize;
611 mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
613 try {
614 mPlaying.store(true, std::memory_order_release);
615 mKillNow.store(false, std::memory_order_release);
616 mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
618 catch(std::exception& e) {
619 jack_deactivate(mClient);
620 mPlaying.store(false, std::memory_order_release);
621 throw al::backend_exception{al::backend_error::DeviceError,
622 "Failed to start mixing thread: %s", e.what()};
627 void JackPlayback::stop()
629 if(mPlaying.load(std::memory_order_acquire))
631 mKillNow.store(true, std::memory_order_release);
632 if(mThread.joinable())
634 mSem.post();
635 mThread.join();
638 jack_deactivate(mClient);
639 mPlaying.store(false, std::memory_order_release);
644 ClockLatency JackPlayback::getClockLatency()
646 ClockLatency ret;
648 std::lock_guard<std::mutex> dlock{mMutex};
649 ret.ClockTime = mDevice->getClockTime();
650 ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
651 ret.Latency /= mDevice->Frequency;
653 return ret;
657 void jack_msg_handler(const char *message)
659 WARN("%s\n", message);
662 } // namespace
664 bool JackBackendFactory::init()
666 if(!jack_load())
667 return false;
669 if(!GetConfigValueBool({}, "jack", "spawn-server", false))
670 ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
672 const PathNamePair &binname = GetProcBinary();
673 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
675 void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
676 jack_set_error_function(jack_msg_handler);
677 jack_status_t status{};
678 jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
679 jack_set_error_function(old_error_cb);
680 if(!client)
682 WARN("jack_client_open() failed, 0x%02x\n", status);
683 if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
684 ERR("Unable to connect to JACK server\n");
685 return false;
688 jack_client_close(client);
689 return true;
692 bool JackBackendFactory::querySupport(BackendType type)
693 { return (type == BackendType::Playback); }
695 std::string JackBackendFactory::probe(BackendType type)
697 std::string outnames;
698 auto append_name = [&outnames](const DeviceEntry &entry) -> void
700 /* Includes null char. */
701 outnames.append(entry.mName.c_str(), entry.mName.length()+1);
704 const PathNamePair &binname = GetProcBinary();
705 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
706 jack_status_t status{};
707 switch(type)
709 case BackendType::Playback:
710 if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
712 EnumerateDevices(client, PlaybackList);
713 jack_client_close(client);
715 else
716 WARN("jack_client_open() failed, 0x%02x\n", status);
717 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
718 break;
719 case BackendType::Capture:
720 break;
722 return outnames;
725 BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
727 if(type == BackendType::Playback)
728 return BackendPtr{new JackPlayback{device}};
729 return nullptr;
732 BackendFactory &JackBackendFactory::getFactory()
734 static JackBackendFactory factory{};
735 return factory;