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
36 #include "alc/alconfig.h"
37 #include "alnumeric.h"
40 #include "althrd_setname.h"
41 #include "core/device.h"
42 #include "core/helpers.h"
43 #include "core/logging.h"
45 #include "ringbuffer.h"
47 #include <jack/jack.h>
48 #include <jack/ringbuffer.h>
53 using namespace std::string_view_literals
;
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); \
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);
78 #define MAKE_FUNC(f) decltype(f) * p##f
80 decltype(jack_error_callback
) * pjack_error_callback
;
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)
108 jack_options_t ClientOptions
= JackNullOption
;
116 #define JACKLIB "libjack.dll"
118 #define JACKLIB "libjack.so.0"
120 jack_handle
= LoadLib(JACKLIB
);
123 WARN("Failed to load %s\n", JACKLIB
);
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; \
132 JACK_FUNCS(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
);
139 if(!missing_funcs
.empty())
141 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
142 CloseLib(jack_handle
);
143 jack_handle
= nullptr;
154 void operator()(void *ptr
) { jack_free(ptr
); }
156 using JackPortsPtr
= std::unique_ptr
<const char*[],JackDeleter
>; /* NOLINT(*-avoid-c-arrays) */
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())
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())
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
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
;
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());
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
;
253 /* Rename entries that have matching names, by appending '#2', '#3',
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
};
264 auto check_name
= [&name
](const DeviceEntry
&entry
) -> bool
265 { return entry
.mName
== name
; };
267 name
= curitem
->mName
;
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
); }
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
{};
305 std::atomic
<bool> mPlaying
{false};
306 bool mRTMixing
{false};
310 std::atomic
<bool> mKillNow
{true};
314 JackPlayback::~JackPlayback()
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
);
324 jack_client_close(mClient
);
329 int JackPlayback::processRt(jack_nframes_t numframes
) noexcept
331 std::array
<jack_default_audio_sample_t
*,MaxOutputChannels
> out
;
333 for(auto port
: mPort
)
335 if(!port
|| numchans
== mDevice
->RealOut
.Buffer
.size())
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
));
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
);
353 int JackPlayback::process(jack_nframes_t numframes
) noexcept
355 std::array
<jack_default_audio_sample_t
*,MaxOutputChannels
> out
;
357 for(auto port
: mPort
)
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
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
);
384 todo
= std::min(numframes
-total
, static_cast<jack_nframes_t
>(data
.second
.len
));
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
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
);
404 mRing
->readAdvance(total
);
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
);
418 int JackPlayback::mixerProc()
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
)
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
);
444 mDevice
->renderSamples(data
.second
.buf
, len2
, frame_step
);
445 mRing
->writeAdvance(todo
);
452 void JackPlayback::open(std::string_view name
)
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
;
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
);
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
);
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;
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
;
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;
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
;
550 ports_end
= mPort
.begin()+2;
551 while(bad_port
!= ports_end
)
553 jack_port_unregister(mClient
, *(--bad_port
));
556 mDevice
->FmtChans
= DevFmtStereo
;
560 setDefaultChannelOrder();
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
,
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
)
585 ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort
[i
]));
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
]),
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;
604 mPlaying
.store(true, std::memory_order_release
);
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);
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())
638 jack_deactivate(mClient
);
639 mPlaying
.store(false, std::memory_order_release
);
644 ClockLatency
JackPlayback::getClockLatency()
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
;
657 void jack_msg_handler(const char *message
)
659 WARN("%s\n", message
);
664 bool JackBackendFactory::init()
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
);
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");
688 jack_client_close(client
);
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
{};
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
);
716 WARN("jack_client_open() failed, 0x%02x\n", status
);
717 std::for_each(PlaybackList
.cbegin(), PlaybackList
.cend(), append_name
);
719 case BackendType::Capture
:
725 BackendPtr
JackBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
727 if(type
== BackendType::Playback
)
728 return BackendPtr
{new JackPlayback
{device
}};
732 BackendFactory
&JackBackendFactory::getFactory()
734 static JackBackendFactory factory
{};