Don't lock the device state when preparing a new effect state
[openal-soft.git] / alc / backends / jack.cpp
blob39cfeb39cd5c97c360fb018ab8b837ed35ac8805
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 "alc/alconfig.h"
36 #include "alnumeric.h"
37 #include "alsem.h"
38 #include "alstring.h"
39 #include "althrd_setname.h"
40 #include "core/device.h"
41 #include "core/helpers.h"
42 #include "core/logging.h"
43 #include "dynload.h"
44 #include "ringbuffer.h"
46 #include <jack/jack.h>
47 #include <jack/ringbuffer.h>
50 namespace {
52 using namespace std::string_view_literals;
54 #ifdef HAVE_DYNLOAD
55 #define JACK_FUNCS(MAGIC) \
56 MAGIC(jack_client_open); \
57 MAGIC(jack_client_close); \
58 MAGIC(jack_client_name_size); \
59 MAGIC(jack_get_client_name); \
60 MAGIC(jack_connect); \
61 MAGIC(jack_activate); \
62 MAGIC(jack_deactivate); \
63 MAGIC(jack_port_register); \
64 MAGIC(jack_port_unregister); \
65 MAGIC(jack_port_get_buffer); \
66 MAGIC(jack_port_name); \
67 MAGIC(jack_get_ports); \
68 MAGIC(jack_free); \
69 MAGIC(jack_get_sample_rate); \
70 MAGIC(jack_set_error_function); \
71 MAGIC(jack_set_process_callback); \
72 MAGIC(jack_set_buffer_size_callback); \
73 MAGIC(jack_set_buffer_size); \
74 MAGIC(jack_get_buffer_size);
76 void *jack_handle;
77 #define MAKE_FUNC(f) decltype(f) * p##f
78 JACK_FUNCS(MAKE_FUNC)
79 decltype(jack_error_callback) * pjack_error_callback;
80 #undef MAKE_FUNC
82 #ifndef IN_IDE_PARSER
83 #define jack_client_open pjack_client_open
84 #define jack_client_close pjack_client_close
85 #define jack_client_name_size pjack_client_name_size
86 #define jack_get_client_name pjack_get_client_name
87 #define jack_connect pjack_connect
88 #define jack_activate pjack_activate
89 #define jack_deactivate pjack_deactivate
90 #define jack_port_register pjack_port_register
91 #define jack_port_unregister pjack_port_unregister
92 #define jack_port_get_buffer pjack_port_get_buffer
93 #define jack_port_name pjack_port_name
94 #define jack_get_ports pjack_get_ports
95 #define jack_free pjack_free
96 #define jack_get_sample_rate pjack_get_sample_rate
97 #define jack_set_error_function pjack_set_error_function
98 #define jack_set_process_callback pjack_set_process_callback
99 #define jack_set_buffer_size_callback pjack_set_buffer_size_callback
100 #define jack_set_buffer_size pjack_set_buffer_size
101 #define jack_get_buffer_size pjack_get_buffer_size
102 #define jack_error_callback (*pjack_error_callback)
103 #endif
104 #endif
107 jack_options_t ClientOptions = JackNullOption;
109 bool jack_load()
111 #ifdef HAVE_DYNLOAD
112 if(!jack_handle)
114 #ifdef _WIN32
115 #define JACKLIB "libjack.dll"
116 #else
117 #define JACKLIB "libjack.so.0"
118 #endif
119 jack_handle = LoadLib(JACKLIB);
120 if(!jack_handle)
122 WARN("Failed to load %s\n", JACKLIB);
123 return false;
126 std::string missing_funcs;
127 #define LOAD_FUNC(f) do { \
128 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
129 if(p##f == nullptr) missing_funcs += "\n" #f; \
130 } while(0)
131 JACK_FUNCS(LOAD_FUNC);
132 #undef LOAD_FUNC
133 /* Optional symbols. These don't exist in all versions of JACK. */
134 #define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
135 LOAD_SYM(jack_error_callback);
136 #undef LOAD_SYM
138 if(!missing_funcs.empty())
140 WARN("Missing expected functions:%s\n", missing_funcs.c_str());
141 CloseLib(jack_handle);
142 jack_handle = nullptr;
143 return false;
146 #endif
148 return true;
152 struct JackDeleter {
153 void operator()(void *ptr) { jack_free(ptr); }
155 using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
157 struct DeviceEntry {
158 std::string mName;
159 std::string mPattern;
161 DeviceEntry() = default;
162 DeviceEntry(const DeviceEntry&) = default;
163 DeviceEntry(DeviceEntry&&) = default;
164 template<typename T, typename U>
165 DeviceEntry(T&& name, U&& pattern)
166 : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
168 ~DeviceEntry();
170 DeviceEntry& operator=(const DeviceEntry&) = default;
171 DeviceEntry& operator=(DeviceEntry&&) = default;
173 DeviceEntry::~DeviceEntry() = default;
175 std::vector<DeviceEntry> PlaybackList;
178 void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
180 std::remove_reference_t<decltype(list)>{}.swap(list);
182 if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
184 for(size_t i{0};ports[i];++i)
186 const std::string_view portname{ports[i]};
187 const size_t seppos{portname.find(':')};
188 if(seppos == 0 || seppos >= portname.size())
189 continue;
191 const auto portdev = portname.substr(0, seppos);
192 auto check_name = [portdev](const DeviceEntry &entry) -> bool
193 { return entry.mName == portdev; };
194 if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
195 continue;
197 const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
198 TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
200 /* There are ports but couldn't get device names from them. Add a
201 * generic entry.
203 if(ports[0] && list.empty())
205 WARN("No device names found in available ports, adding a generic name.\n");
206 list.emplace_back("JACK"sv, ""sv);
210 if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
212 for(size_t strpos{0};strpos < listopt->size();)
214 size_t nextpos{listopt->find(';', strpos)};
215 size_t seppos{listopt->find('=', strpos)};
216 if(seppos >= nextpos || seppos == strpos)
218 const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
219 ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data());
220 if(nextpos != std::string::npos) ++nextpos;
221 strpos = nextpos;
222 continue;
225 const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos);
226 const auto pattern = std::string_view{*listopt}.substr(seppos+1,
227 std::min(nextpos, listopt->size())-(seppos+1));
229 /* Check if this custom pattern already exists in the list. */
230 auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
231 { return entry.mPattern == pattern; };
232 auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
233 if(itemmatch != list.end())
235 /* If so, replace the name with this custom one. */
236 itemmatch->mName = name;
237 TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
238 itemmatch->mPattern.c_str());
240 else
242 /* Otherwise, add a new device entry. */
243 const auto &entry = list.emplace_back(name, pattern);
244 TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
247 if(nextpos != std::string::npos) ++nextpos;
248 strpos = nextpos;
252 if(list.size() > 1)
254 /* Rename entries that have matching names, by appending '#2', '#3',
255 * etc, as needed.
257 for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
259 auto check_match = [curitem](const DeviceEntry &entry) -> bool
260 { return entry.mName == curitem->mName; };
261 if(std::find_if(list.begin(), curitem, check_match) != curitem)
263 std::string name{curitem->mName};
264 size_t count{1};
265 auto check_name = [&name](const DeviceEntry &entry) -> bool
266 { return entry.mName == name; };
267 do {
268 name = curitem->mName;
269 name += " #";
270 name += std::to_string(++count);
271 } while(std::find_if(list.begin(), curitem, check_name) != curitem);
272 curitem->mName = std::move(name);
279 struct JackPlayback final : public BackendBase {
280 JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
281 ~JackPlayback() override;
283 int processRt(jack_nframes_t numframes) noexcept;
284 static int processRtC(jack_nframes_t numframes, void *arg) noexcept
285 { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
287 int process(jack_nframes_t numframes) noexcept;
288 static int processC(jack_nframes_t numframes, void *arg) noexcept
289 { return static_cast<JackPlayback*>(arg)->process(numframes); }
291 int mixerProc();
293 void open(std::string_view name) override;
294 bool reset() override;
295 void start() override;
296 void stop() override;
297 ClockLatency getClockLatency() override;
299 std::string mPortPattern;
301 jack_client_t *mClient{nullptr};
302 std::array<jack_port_t*,MaxOutputChannels> mPort{};
304 std::mutex mMutex;
306 std::atomic<bool> mPlaying{false};
307 bool mRTMixing{false};
308 RingBufferPtr mRing;
309 al::semaphore mSem;
311 std::atomic<bool> mKillNow{true};
312 std::thread mThread;
315 JackPlayback::~JackPlayback()
317 if(!mClient)
318 return;
320 auto unregister_port = [this](jack_port_t *port) -> void
321 { if(port) jack_port_unregister(mClient, port); };
322 std::for_each(mPort.begin(), mPort.end(), unregister_port);
323 mPort.fill(nullptr);
325 jack_client_close(mClient);
326 mClient = nullptr;
330 int JackPlayback::processRt(jack_nframes_t numframes) noexcept
332 auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{};
333 auto numchans = size_t{0};
334 for(auto port : mPort)
336 if(!port || numchans == mDevice->RealOut.Buffer.size())
337 break;
338 outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
341 const auto dst = al::span{outptrs}.first(numchans);
342 if(mPlaying.load(std::memory_order_acquire)) LIKELY
343 mDevice->renderSamples(dst, static_cast<uint>(numframes));
344 else
346 std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void
347 { std::fill_n(outbuf, numframes, 0.0f); });
350 return 0;
354 int JackPlayback::process(jack_nframes_t numframes) noexcept
356 std::array<al::span<float>,MaxOutputChannels> out;
357 size_t numchans{0};
358 for(auto port : mPort)
360 if(!port) break;
361 out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
364 size_t total{0};
365 if(mPlaying.load(std::memory_order_acquire)) LIKELY
367 auto data = mRing->getReadVector();
368 const auto update_size = size_t{mDevice->UpdateSize};
370 const auto outlen = size_t{numframes / update_size};
371 const auto len1 = size_t{std::min(data.first.len/update_size, outlen)};
372 const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)};
374 auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans};
375 for(size_t i{0};i < len1;++i)
377 for(size_t c{0};c < numchans;++c)
379 const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
380 out[c] = {iter, out[c].end()};
381 src = src.subspan(update_size);
383 total += update_size;
386 src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans};
387 for(size_t i{0};i < len2;++i)
389 for(size_t c{0};c < numchans;++c)
391 const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
392 out[c] = {iter, out[c].end()};
393 src = src.subspan(update_size);
395 total += update_size;
398 mRing->readAdvance(total);
399 mSem.post();
402 if(numframes > total)
404 auto clear_buf = [](const al::span<float> outbuf) -> void
405 { std::fill(outbuf.begin(), outbuf.end(), 0.0f); };
406 std::for_each(out.begin(), out.begin()+numchans, clear_buf);
409 return 0;
412 int JackPlayback::mixerProc()
414 SetRTPriority();
415 althrd_setname(GetMixerThreadName());
417 const auto update_size = uint{mDevice->UpdateSize};
418 const auto num_channels = size_t{mDevice->channelsFromFmt()};
419 auto outptrs = std::vector<float*>(num_channels);
421 while(!mKillNow.load(std::memory_order_acquire)
422 && mDevice->Connected.load(std::memory_order_acquire))
424 if(mRing->writeSpace() < update_size)
426 mSem.wait();
427 continue;
430 auto data = mRing->getWriteVector();
431 const auto len1 = size_t{data.first.len / update_size};
432 const auto len2 = size_t{data.second.len / update_size};
434 std::lock_guard<std::mutex> dlock{mMutex};
435 auto buffer = al::span{reinterpret_cast<float*>(data.first.buf),
436 data.first.len*num_channels};
437 auto bufiter = buffer.begin();
438 for(size_t i{0};i < len1;++i)
440 std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
442 auto ret = al::to_address(bufiter);
443 bufiter += ptrdiff_t(update_size);
444 return ret;
446 mDevice->renderSamples(outptrs, update_size);
448 if(len2 > 0)
450 buffer = al::span{reinterpret_cast<float*>(data.second.buf),
451 data.second.len*num_channels};
452 bufiter = buffer.begin();
453 for(size_t i{0};i < len2;++i)
455 std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
457 auto ret = al::to_address(bufiter);
458 bufiter += ptrdiff_t(update_size);
459 return ret;
461 mDevice->renderSamples(outptrs, update_size);
464 mRing->writeAdvance((len1+len2) * update_size);
467 return 0;
471 void JackPlayback::open(std::string_view name)
473 if(!mClient)
475 const PathNamePair &binname = GetProcBinary();
476 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
478 jack_status_t status{};
479 mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
480 if(mClient == nullptr)
481 throw al::backend_exception{al::backend_error::DeviceError,
482 "Failed to open client connection: 0x%02x", status};
483 if((status&JackServerStarted))
484 TRACE("JACK server started\n");
485 if((status&JackNameNotUnique))
487 client_name = jack_get_client_name(mClient);
488 TRACE("Client name not unique, got '%s' instead\n", client_name);
492 if(PlaybackList.empty())
493 EnumerateDevices(mClient, PlaybackList);
495 if(name.empty() && !PlaybackList.empty())
497 name = PlaybackList[0].mName;
498 mPortPattern = PlaybackList[0].mPattern;
500 else
502 auto check_name = [name](const DeviceEntry &entry) -> bool
503 { return entry.mName == name; };
504 auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
505 if(iter == PlaybackList.cend())
506 throw al::backend_exception{al::backend_error::NoDevice,
507 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
508 mPortPattern = iter->mPattern;
511 mDevice->DeviceName = name;
514 bool JackPlayback::reset()
516 auto unregister_port = [this](jack_port_t *port) -> void
517 { if(port) jack_port_unregister(mClient, port); };
518 std::for_each(mPort.begin(), mPort.end(), unregister_port);
519 mPort.fill(nullptr);
521 mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
522 jack_set_process_callback(mClient,
523 mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
525 /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
526 * ready for when requested.
528 mDevice->Frequency = jack_get_sample_rate(mClient);
529 mDevice->UpdateSize = jack_get_buffer_size(mClient);
530 if(mRTMixing)
532 /* Assume only two periods when directly mixing. Should try to query
533 * the total port latency when connected.
535 mDevice->BufferSize = mDevice->UpdateSize * 2;
537 else
539 const std::string_view devname{mDevice->DeviceName};
540 uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
541 bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
542 mDevice->BufferSize = bufsize + mDevice->UpdateSize;
545 /* Force 32-bit float output. */
546 mDevice->FmtType = DevFmtFloat;
548 int port_num{0};
549 auto ports = al::span{mPort}.first(mDevice->channelsFromFmt());
550 auto bad_port = ports.begin();
551 while(bad_port != ports.end())
553 std::string name{"channel_" + std::to_string(++port_num)};
554 *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
555 JackPortIsOutput | JackPortIsTerminal, 0);
556 if(!*bad_port) break;
557 ++bad_port;
559 if(bad_port != ports.end())
561 ERR("Failed to register enough JACK ports for %s output\n",
562 DevFmtChannelsString(mDevice->FmtChans));
563 if(bad_port == ports.begin()) return false;
565 if(bad_port == ports.begin()+1)
566 mDevice->FmtChans = DevFmtMono;
567 else
569 const auto ports_end = ports.begin()+2;
570 while(bad_port != ports_end)
572 jack_port_unregister(mClient, *(--bad_port));
573 *bad_port = nullptr;
575 mDevice->FmtChans = DevFmtStereo;
579 setDefaultChannelOrder();
581 return true;
584 void JackPlayback::start()
586 if(jack_activate(mClient))
587 throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
589 const std::string_view devname{mDevice->DeviceName};
590 if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
592 JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
593 JackPortIsInput)};
594 if(!pnames)
596 jack_deactivate(mClient);
597 throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
600 for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
602 if(!pnames[i])
604 ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
605 break;
607 if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
608 ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
609 pnames[i]);
613 /* Reconfigure buffer metrics in case the server changed it since the reset
614 * (it won't change again after jack_activate), then allocate the ring
615 * buffer with the appropriate size.
617 mDevice->Frequency = jack_get_sample_rate(mClient);
618 mDevice->UpdateSize = jack_get_buffer_size(mClient);
619 mDevice->BufferSize = mDevice->UpdateSize * 2;
621 mRing = nullptr;
622 if(mRTMixing)
623 mPlaying.store(true, std::memory_order_release);
624 else
626 uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
627 bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
628 mDevice->BufferSize = bufsize + mDevice->UpdateSize;
630 mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
632 try {
633 mPlaying.store(true, std::memory_order_release);
634 mKillNow.store(false, std::memory_order_release);
635 mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
637 catch(std::exception& e) {
638 jack_deactivate(mClient);
639 mPlaying.store(false, std::memory_order_release);
640 throw al::backend_exception{al::backend_error::DeviceError,
641 "Failed to start mixing thread: %s", e.what()};
646 void JackPlayback::stop()
648 if(mPlaying.load(std::memory_order_acquire))
650 mKillNow.store(true, std::memory_order_release);
651 if(mThread.joinable())
653 mSem.post();
654 mThread.join();
657 jack_deactivate(mClient);
658 mPlaying.store(false, std::memory_order_release);
663 ClockLatency JackPlayback::getClockLatency()
665 std::lock_guard<std::mutex> dlock{mMutex};
666 ClockLatency ret{};
667 ret.ClockTime = mDevice->getClockTime();
668 ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
669 ret.Latency /= mDevice->Frequency;
671 return ret;
675 void jack_msg_handler(const char *message)
677 WARN("%s\n", message);
680 } // namespace
682 bool JackBackendFactory::init()
684 if(!jack_load())
685 return false;
687 if(!GetConfigValueBool({}, "jack", "spawn-server", false))
688 ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
690 const PathNamePair &binname = GetProcBinary();
691 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
693 void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
694 jack_set_error_function(jack_msg_handler);
695 jack_status_t status{};
696 jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
697 jack_set_error_function(old_error_cb);
698 if(!client)
700 WARN("jack_client_open() failed, 0x%02x\n", status);
701 if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
702 ERR("Unable to connect to JACK server\n");
703 return false;
706 jack_client_close(client);
707 return true;
710 bool JackBackendFactory::querySupport(BackendType type)
711 { return (type == BackendType::Playback); }
713 auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
715 std::vector<std::string> outnames;
716 auto append_name = [&outnames](const DeviceEntry &entry) -> void
717 { outnames.emplace_back(entry.mName); };
719 const PathNamePair &binname = GetProcBinary();
720 const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
721 jack_status_t status{};
722 switch(type)
724 case BackendType::Playback:
725 if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
727 EnumerateDevices(client, PlaybackList);
728 jack_client_close(client);
730 else
731 WARN("jack_client_open() failed, 0x%02x\n", status);
732 outnames.reserve(PlaybackList.size());
733 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
734 break;
735 case BackendType::Capture:
736 break;
738 return outnames;
741 BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
743 if(type == BackendType::Playback)
744 return BackendPtr{new JackPlayback{device}};
745 return nullptr;
748 BackendFactory &JackBackendFactory::getFactory()
750 static JackBackendFactory factory{};
751 return factory;