]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/jack.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / jack.cpp
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.
8  *
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.
13  *
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
19  */
20
21 #include "config.h"
22
23 #include "jack.h"
24
25 #include <cstdlib>
26 #include <cstdio>
27 #include <cstring>
28 #include <memory.h>
29
30 #include <array>
31 #include <thread>
32 #include <functional>
33
34 #include "alc/alconfig.h"
35 #include "alnumeric.h"
36 #include "core/device.h"
37 #include "core/helpers.h"
38 #include "core/logging.h"
39 #include "dynload.h"
40 #include "ringbuffer.h"
41 #include "threads.h"
42
43 #include <jack/jack.h>
44 #include <jack/ringbuffer.h>
45
46
47 namespace {
48
49 #ifdef HAVE_DYNLOAD
50 #define JACK_FUNCS(MAGIC)          \
51     MAGIC(jack_client_open);       \
52     MAGIC(jack_client_close);      \
53     MAGIC(jack_client_name_size);  \
54     MAGIC(jack_get_client_name);   \
55     MAGIC(jack_connect);           \
56     MAGIC(jack_activate);          \
57     MAGIC(jack_deactivate);        \
58     MAGIC(jack_port_register);     \
59     MAGIC(jack_port_unregister);   \
60     MAGIC(jack_port_get_buffer);   \
61     MAGIC(jack_port_name);         \
62     MAGIC(jack_get_ports);         \
63     MAGIC(jack_free);              \
64     MAGIC(jack_get_sample_rate);   \
65     MAGIC(jack_set_error_function); \
66     MAGIC(jack_set_process_callback); \
67     MAGIC(jack_set_buffer_size_callback); \
68     MAGIC(jack_set_buffer_size);   \
69     MAGIC(jack_get_buffer_size);
70
71 void *jack_handle;
72 #define MAKE_FUNC(f) decltype(f) * p##f
73 JACK_FUNCS(MAKE_FUNC)
74 decltype(jack_error_callback) * pjack_error_callback;
75 #undef MAKE_FUNC
76
77 #ifndef IN_IDE_PARSER
78 #define jack_client_open pjack_client_open
79 #define jack_client_close pjack_client_close
80 #define jack_client_name_size pjack_client_name_size
81 #define jack_get_client_name pjack_get_client_name
82 #define jack_connect pjack_connect
83 #define jack_activate pjack_activate
84 #define jack_deactivate pjack_deactivate
85 #define jack_port_register pjack_port_register
86 #define jack_port_unregister pjack_port_unregister
87 #define jack_port_get_buffer pjack_port_get_buffer
88 #define jack_port_name pjack_port_name
89 #define jack_get_ports pjack_get_ports
90 #define jack_free pjack_free
91 #define jack_get_sample_rate pjack_get_sample_rate
92 #define jack_set_error_function pjack_set_error_function
93 #define jack_set_process_callback pjack_set_process_callback
94 #define jack_set_buffer_size_callback pjack_set_buffer_size_callback
95 #define jack_set_buffer_size pjack_set_buffer_size
96 #define jack_get_buffer_size pjack_get_buffer_size
97 #define jack_error_callback (*pjack_error_callback)
98 #endif
99 #endif
100
101
102 constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
103
104 jack_options_t ClientOptions = JackNullOption;
105
106 bool jack_load()
107 {
108     bool error{false};
109
110 #ifdef HAVE_DYNLOAD
111     if(!jack_handle)
112     {
113         std::string missing_funcs;
114
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)
122         {
123             WARN("Failed to load %s\n", JACKLIB);
124             return false;
125         }
126
127         error = false;
128 #define LOAD_FUNC(f) do {                                                     \
129     p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f));      \
130     if(p##f == nullptr) {                                                     \
131         error = true;                                                         \
132         missing_funcs += "\n" #f;                                             \
133     }                                                                         \
134 } while(0)
135         JACK_FUNCS(LOAD_FUNC);
136 #undef LOAD_FUNC
137         /* Optional symbols. These don't exist in all versions of JACK. */
138 #define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
139         LOAD_SYM(jack_error_callback);
140 #undef LOAD_SYM
141
142         if(error)
143         {
144             WARN("Missing expected functions:%s\n", missing_funcs.c_str());
145             CloseLib(jack_handle);
146             jack_handle = nullptr;
147         }
148     }
149 #endif
150
151     return !error;
152 }
153
154
155 struct JackDeleter {
156     void operator()(void *ptr) { jack_free(ptr); }
157 };
158 using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
159
160 struct DeviceEntry {
161     std::string mName;
162     std::string mPattern;
163
164     template<typename T, typename U>
165     DeviceEntry(T&& name, U&& pattern)
166         : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
167     { }
168 };
169
170 al::vector<DeviceEntry> PlaybackList;
171
172
173 void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
174 {
175     std::remove_reference_t<decltype(list)>{}.swap(list);
176
177     if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
178     {
179         for(size_t i{0};ports[i];++i)
180         {
181             const char *sep{std::strchr(ports[i], ':')};
182             if(!sep || ports[i] == sep) continue;
183
184             const al::span<const char> portdev{ports[i], sep};
185             auto check_name = [portdev](const DeviceEntry &entry) -> bool
186             {
187                 const size_t len{portdev.size()};
188                 return entry.mName.length() == len
189                     && entry.mName.compare(0, len, portdev.data(), len) == 0;
190             };
191             if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
192                 continue;
193
194             std::string name{portdev.data(), portdev.size()};
195             list.emplace_back(name, name+":");
196             const auto &entry = list.back();
197             TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
198         }
199         /* There are ports but couldn't get device names from them. Add a
200          * generic entry.
201          */
202         if(ports[0] && list.empty())
203         {
204             WARN("No device names found in available ports, adding a generic name.\n");
205             list.emplace_back("JACK", "");
206         }
207     }
208
209     if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
210     {
211         for(size_t strpos{0};strpos < listopt->size();)
212         {
213             size_t nextpos{listopt->find(';', strpos)};
214             size_t seppos{listopt->find('=', strpos)};
215             if(seppos >= nextpos || seppos == strpos)
216             {
217                 const std::string entry{listopt->substr(strpos, nextpos-strpos)};
218                 ERR("Invalid device entry: \"%s\"\n", entry.c_str());
219                 if(nextpos != std::string::npos) ++nextpos;
220                 strpos = nextpos;
221                 continue;
222             }
223
224             const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
225             const al::span<const char> pattern{listopt->data()+(seppos+1),
226                 std::min(nextpos, listopt->size())-(seppos+1)};
227
228             /* Check if this custom pattern already exists in the list. */
229             auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
230             {
231                 const size_t len{pattern.size()};
232                 return entry.mPattern.length() == len
233                     && entry.mPattern.compare(0, len, pattern.data(), len) == 0;
234             };
235             auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
236             if(itemmatch != list.end())
237             {
238                 /* If so, replace the name with this custom one. */
239                 itemmatch->mName.assign(name.data(), name.size());
240                 TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
241                     itemmatch->mPattern.c_str());
242             }
243             else
244             {
245                 /* Otherwise, add a new device entry. */
246                 list.emplace_back(std::string{name.data(), name.size()},
247                     std::string{pattern.data(), pattern.size()});
248                 const auto &entry = list.back();
249                 TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
250             }
251
252             if(nextpos != std::string::npos) ++nextpos;
253             strpos = nextpos;
254         }
255     }
256
257     if(list.size() > 1)
258     {
259         /* Rename entries that have matching names, by appending '#2', '#3',
260          * etc, as needed.
261          */
262         for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
263         {
264             auto check_match = [curitem](const DeviceEntry &entry) -> bool
265             { return entry.mName == curitem->mName; };
266             if(std::find_if(list.begin(), curitem, check_match) != curitem)
267             {
268                 std::string name{curitem->mName};
269                 size_t count{1};
270                 auto check_name = [&name](const DeviceEntry &entry) -> bool
271                 { return entry.mName == name; };
272                 do {
273                     name = curitem->mName;
274                     name += " #";
275                     name += std::to_string(++count);
276                 } while(std::find_if(list.begin(), curitem, check_name) != curitem);
277                 curitem->mName = std::move(name);
278             }
279         }
280     }
281 }
282
283
284 struct JackPlayback final : public BackendBase {
285     JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
286     ~JackPlayback() override;
287
288     int processRt(jack_nframes_t numframes) noexcept;
289     static int processRtC(jack_nframes_t numframes, void *arg) noexcept
290     { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
291
292     int process(jack_nframes_t numframes) noexcept;
293     static int processC(jack_nframes_t numframes, void *arg) noexcept
294     { return static_cast<JackPlayback*>(arg)->process(numframes); }
295
296     int mixerProc();
297
298     void open(const char *name) override;
299     bool reset() override;
300     void start() override;
301     void stop() override;
302     ClockLatency getClockLatency() override;
303
304     std::string mPortPattern;
305
306     jack_client_t *mClient{nullptr};
307     std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
308
309     std::mutex mMutex;
310
311     std::atomic<bool> mPlaying{false};
312     bool mRTMixing{false};
313     RingBufferPtr mRing;
314     al::semaphore mSem;
315
316     std::atomic<bool> mKillNow{true};
317     std::thread mThread;
318
319     DEF_NEWDEL(JackPlayback)
320 };
321
322 JackPlayback::~JackPlayback()
323 {
324     if(!mClient)
325         return;
326
327     auto unregister_port = [this](jack_port_t *port) -> void
328     { if(port) jack_port_unregister(mClient, port); };
329     std::for_each(mPort.begin(), mPort.end(), unregister_port);
330     mPort.fill(nullptr);
331
332     jack_client_close(mClient);
333     mClient = nullptr;
334 }
335
336
337 int JackPlayback::processRt(jack_nframes_t numframes) noexcept
338 {
339     std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
340     size_t numchans{0};
341     for(auto port : mPort)
342     {
343         if(!port || numchans == mDevice->RealOut.Buffer.size())
344             break;
345         out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
346     }
347
348     if(mPlaying.load(std::memory_order_acquire)) LIKELY
349         mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
350     else
351     {
352         auto clear_buf = [numframes](float *outbuf) -> void
353         { std::fill_n(outbuf, numframes, 0.0f); };
354         std::for_each(out.begin(), out.begin()+numchans, clear_buf);
355     }
356
357     return 0;
358 }
359
360
361 int JackPlayback::process(jack_nframes_t numframes) noexcept
362 {
363     std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
364     size_t numchans{0};
365     for(auto port : mPort)
366     {
367         if(!port) break;
368         out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
369     }
370
371     jack_nframes_t total{0};
372     if(mPlaying.load(std::memory_order_acquire)) LIKELY
373     {
374         auto data = mRing->getReadVector();
375         jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
376         auto write_first = [&data,numchans,todo](float *outbuf) -> float*
377         {
378             const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
379             auto deinterlace_input = [&in,numchans]() noexcept -> float
380             {
381                 float ret{*in};
382                 in += numchans;
383                 return ret;
384             };
385             std::generate_n(outbuf, todo, deinterlace_input);
386             data.first.buf += sizeof(float);
387             return outbuf + todo;
388         };
389         std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
390         total += todo;
391
392         todo = minu(numframes-total, static_cast<uint>(data.second.len));
393         if(todo > 0)
394         {
395             auto write_second = [&data,numchans,todo](float *outbuf) -> float*
396             {
397                 const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
398                 auto deinterlace_input = [&in,numchans]() noexcept -> float
399                 {
400                     float ret{*in};
401                     in += numchans;
402                     return ret;
403                 };
404                 std::generate_n(outbuf, todo, deinterlace_input);
405                 data.second.buf += sizeof(float);
406                 return outbuf + todo;
407             };
408             std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
409             total += todo;
410         }
411
412         mRing->readAdvance(total);
413         mSem.post();
414     }
415
416     if(numframes > total)
417     {
418         const jack_nframes_t todo{numframes - total};
419         auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
420         std::for_each(out.begin(), out.begin()+numchans, clear_buf);
421     }
422
423     return 0;
424 }
425
426 int JackPlayback::mixerProc()
427 {
428     SetRTPriority();
429     althrd_setname(MIXER_THREAD_NAME);
430
431     const size_t frame_step{mDevice->channelsFromFmt()};
432
433     while(!mKillNow.load(std::memory_order_acquire)
434         && mDevice->Connected.load(std::memory_order_acquire))
435     {
436         if(mRing->writeSpace() < mDevice->UpdateSize)
437         {
438             mSem.wait();
439             continue;
440         }
441
442         auto data = mRing->getWriteVector();
443         size_t todo{data.first.len + data.second.len};
444         todo -= todo%mDevice->UpdateSize;
445
446         const auto len1 = static_cast<uint>(minz(data.first.len, todo));
447         const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
448
449         std::lock_guard<std::mutex> _{mMutex};
450         mDevice->renderSamples(data.first.buf, len1, frame_step);
451         if(len2 > 0)
452             mDevice->renderSamples(data.second.buf, len2, frame_step);
453         mRing->writeAdvance(todo);
454     }
455
456     return 0;
457 }
458
459
460 void JackPlayback::open(const char *name)
461 {
462     if(!mClient)
463     {
464         const PathNamePair &binname = GetProcBinary();
465         const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
466
467         jack_status_t status;
468         mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
469         if(mClient == nullptr)
470             throw al::backend_exception{al::backend_error::DeviceError,
471                 "Failed to open client connection: 0x%02x", status};
472         if((status&JackServerStarted))
473             TRACE("JACK server started\n");
474         if((status&JackNameNotUnique))
475         {
476             client_name = jack_get_client_name(mClient);
477             TRACE("Client name not unique, got '%s' instead\n", client_name);
478         }
479     }
480
481     if(PlaybackList.empty())
482         EnumerateDevices(mClient, PlaybackList);
483
484     if(!name && !PlaybackList.empty())
485     {
486         name = PlaybackList[0].mName.c_str();
487         mPortPattern = PlaybackList[0].mPattern;
488     }
489     else
490     {
491         auto check_name = [name](const DeviceEntry &entry) -> bool
492         { return entry.mName == name; };
493         auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
494         if(iter == PlaybackList.cend())
495             throw al::backend_exception{al::backend_error::NoDevice,
496                 "Device name \"%s\" not found", name?name:""};
497         mPortPattern = iter->mPattern;
498     }
499
500     mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
501     jack_set_process_callback(mClient,
502         mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
503
504     mDevice->DeviceName = name;
505 }
506
507 bool JackPlayback::reset()
508 {
509     auto unregister_port = [this](jack_port_t *port) -> void
510     { if(port) jack_port_unregister(mClient, port); };
511     std::for_each(mPort.begin(), mPort.end(), unregister_port);
512     mPort.fill(nullptr);
513
514     /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
515      * ready for when requested.
516      */
517     mDevice->Frequency = jack_get_sample_rate(mClient);
518     mDevice->UpdateSize = jack_get_buffer_size(mClient);
519     if(mRTMixing)
520     {
521         /* Assume only two periods when directly mixing. Should try to query
522          * the total port latency when connected.
523          */
524         mDevice->BufferSize = mDevice->UpdateSize * 2;
525     }
526     else
527     {
528         const char *devname{mDevice->DeviceName.c_str()};
529         uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
530         bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
531         mDevice->BufferSize = bufsize + mDevice->UpdateSize;
532     }
533
534     /* Force 32-bit float output. */
535     mDevice->FmtType = DevFmtFloat;
536
537     int port_num{0};
538     auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
539     auto bad_port = mPort.begin();
540     while(bad_port != ports_end)
541     {
542         std::string name{"channel_" + std::to_string(++port_num)};
543         *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
544             JackPortIsOutput | JackPortIsTerminal, 0);
545         if(!*bad_port) break;
546         ++bad_port;
547     }
548     if(bad_port != ports_end)
549     {
550         ERR("Failed to register enough JACK ports for %s output\n",
551             DevFmtChannelsString(mDevice->FmtChans));
552         if(bad_port == mPort.begin()) return false;
553
554         if(bad_port == mPort.begin()+1)
555             mDevice->FmtChans = DevFmtMono;
556         else
557         {
558             ports_end = mPort.begin()+2;
559             while(bad_port != ports_end)
560             {
561                 jack_port_unregister(mClient, *(--bad_port));
562                 *bad_port = nullptr;
563             }
564             mDevice->FmtChans = DevFmtStereo;
565         }
566     }
567
568     setDefaultChannelOrder();
569
570     return true;
571 }
572
573 void JackPlayback::start()
574 {
575     if(jack_activate(mClient))
576         throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
577
578     const char *devname{mDevice->DeviceName.c_str()};
579     if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
580     {
581         JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
582             JackPortIsInput)};
583         if(!pnames)
584         {
585             jack_deactivate(mClient);
586             throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
587         }
588
589         for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
590         {
591             if(!pnames[i])
592             {
593                 ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
594                 break;
595             }
596             if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
597                 ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
598                     pnames[i]);
599         }
600     }
601
602     /* Reconfigure buffer metrics in case the server changed it since the reset
603      * (it won't change again after jack_activate), then allocate the ring
604      * buffer with the appropriate size.
605      */
606     mDevice->Frequency = jack_get_sample_rate(mClient);
607     mDevice->UpdateSize = jack_get_buffer_size(mClient);
608     mDevice->BufferSize = mDevice->UpdateSize * 2;
609
610     mRing = nullptr;
611     if(mRTMixing)
612         mPlaying.store(true, std::memory_order_release);
613     else
614     {
615         uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
616         bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
617         mDevice->BufferSize = bufsize + mDevice->UpdateSize;
618
619         mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
620
621         try {
622             mPlaying.store(true, std::memory_order_release);
623             mKillNow.store(false, std::memory_order_release);
624             mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
625         }
626         catch(std::exception& e) {
627             jack_deactivate(mClient);
628             mPlaying.store(false, std::memory_order_release);
629             throw al::backend_exception{al::backend_error::DeviceError,
630                 "Failed to start mixing thread: %s", e.what()};
631         }
632     }
633 }
634
635 void JackPlayback::stop()
636 {
637     if(mPlaying.load(std::memory_order_acquire))
638     {
639         mKillNow.store(true, std::memory_order_release);
640         if(mThread.joinable())
641         {
642             mSem.post();
643             mThread.join();
644         }
645
646         jack_deactivate(mClient);
647         mPlaying.store(false, std::memory_order_release);
648     }
649 }
650
651
652 ClockLatency JackPlayback::getClockLatency()
653 {
654     ClockLatency ret;
655
656     std::lock_guard<std::mutex> _{mMutex};
657     ret.ClockTime = GetDeviceClockTime(mDevice);
658     ret.Latency  = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
659     ret.Latency /= mDevice->Frequency;
660
661     return ret;
662 }
663
664
665 void jack_msg_handler(const char *message)
666 {
667     WARN("%s\n", message);
668 }
669
670 } // namespace
671
672 bool JackBackendFactory::init()
673 {
674     if(!jack_load())
675         return false;
676
677     if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
678         ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
679
680     const PathNamePair &binname = GetProcBinary();
681     const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
682
683     void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
684     jack_set_error_function(jack_msg_handler);
685     jack_status_t status;
686     jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
687     jack_set_error_function(old_error_cb);
688     if(!client)
689     {
690         WARN("jack_client_open() failed, 0x%02x\n", status);
691         if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
692             ERR("Unable to connect to JACK server\n");
693         return false;
694     }
695
696     jack_client_close(client);
697     return true;
698 }
699
700 bool JackBackendFactory::querySupport(BackendType type)
701 { return (type == BackendType::Playback); }
702
703 std::string JackBackendFactory::probe(BackendType type)
704 {
705     std::string outnames;
706     auto append_name = [&outnames](const DeviceEntry &entry) -> void
707     {
708         /* Includes null char. */
709         outnames.append(entry.mName.c_str(), entry.mName.length()+1);
710     };
711
712     const PathNamePair &binname = GetProcBinary();
713     const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
714     jack_status_t status;
715     switch(type)
716     {
717     case BackendType::Playback:
718         if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
719         {
720             EnumerateDevices(client, PlaybackList);
721             jack_client_close(client);
722         }
723         else
724             WARN("jack_client_open() failed, 0x%02x\n", status);
725         std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
726         break;
727     case BackendType::Capture:
728         break;
729     }
730     return outnames;
731 }
732
733 BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
734 {
735     if(type == BackendType::Playback)
736         return BackendPtr{new JackPlayback{device}};
737     return nullptr;
738 }
739
740 BackendFactory &JackBackendFactory::getFactory()
741 {
742     static JackBackendFactory factory{};
743     return factory;
744 }