Silence and fix some clang-tidy warnings
[openal-soft.git] / alc / backends / pipewire.cpp
blob5d2ce65021b24c02a2e798536e4968d9bde77a60
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 2010 by Chris Robinson
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 "pipewire.h"
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <bitset>
29 #include <cinttypes>
30 #include <cmath>
31 #include <cstddef>
32 #include <cstdio>
33 #include <cstdlib>
34 #include <cstring>
35 #include <cerrno>
36 #include <chrono>
37 #include <ctime>
38 #include <iterator>
39 #include <memory>
40 #include <mutex>
41 #include <optional>
42 #include <string_view>
43 #include <thread>
44 #include <tuple>
45 #include <utility>
47 #include "alc/alconfig.h"
48 #include "alc/backends/base.h"
49 #include "almalloc.h"
50 #include "alspan.h"
51 #include "alstring.h"
52 #include "core/devformat.h"
53 #include "core/device.h"
54 #include "core/helpers.h"
55 #include "core/logging.h"
56 #include "dynload.h"
57 #include "opthelpers.h"
58 #include "ringbuffer.h"
60 /* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC
61 * doesn't support ignoring -Weverything, so we have the list the individual
62 * warnings to ignore (and ignoring -Winline doesn't seem to work).
64 _Pragma("GCC diagnostic push")
65 _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
66 _Pragma("GCC diagnostic ignored \"-Wconversion\"")
67 _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
68 _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
69 _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
70 _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
71 _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
72 _Pragma("GCC diagnostic ignored \"-Winline\"")
73 _Pragma("GCC diagnostic ignored \"-Wpragmas\"")
74 _Pragma("GCC diagnostic ignored \"-Weverything\"")
75 #include "pipewire/pipewire.h"
76 #include "pipewire/extensions/metadata.h"
77 #include "spa/buffer/buffer.h"
78 #include "spa/param/audio/format-utils.h"
79 #include "spa/param/audio/raw.h"
80 #include "spa/param/format.h"
81 #include "spa/param/param.h"
82 #include "spa/pod/builder.h"
83 #include "spa/utils/json.h"
85 /* NOLINTBEGIN : All kinds of unsafe C stuff here from PipeWire headers
86 * (function-like macros, C style casts in macros, etc), which we can't do
87 * anything about except wrap into inline functions.
89 namespace {
90 /* Wrap some nasty macros here too... */
91 template<typename ...Args>
92 auto ppw_core_add_listener(pw_core *core, Args&& ...args)
93 { return pw_core_add_listener(core, std::forward<Args>(args)...); }
94 template<typename ...Args>
95 auto ppw_core_sync(pw_core *core, Args&& ...args)
96 { return pw_core_sync(core, std::forward<Args>(args)...); }
97 template<typename ...Args>
98 auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args)
99 { return pw_registry_add_listener(reg, std::forward<Args>(args)...); }
100 template<typename ...Args>
101 auto ppw_node_add_listener(pw_node *node, Args&& ...args)
102 { return pw_node_add_listener(node, std::forward<Args>(args)...); }
103 template<typename ...Args>
104 auto ppw_node_subscribe_params(pw_node *node, Args&& ...args)
105 { return pw_node_subscribe_params(node, std::forward<Args>(args)...); }
106 template<typename ...Args>
107 auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args)
108 { return pw_metadata_add_listener(mdata, std::forward<Args>(args)...); }
111 constexpr auto get_pod_type(const spa_pod *pod) noexcept
112 { return SPA_POD_TYPE(pod); }
114 template<typename T>
115 constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept
116 { return al::span<T>{static_cast<T*>(SPA_POD_BODY(pod)), count}; }
117 template<typename T, size_t N>
118 constexpr auto get_pod_body(const spa_pod *pod) noexcept
119 { return al::span<T,N>{static_cast<T*>(SPA_POD_BODY(pod)), N}; }
121 constexpr auto get_array_value_type(const spa_pod *pod) noexcept
122 { return SPA_POD_ARRAY_VALUE_TYPE(pod); }
124 constexpr auto make_pod_builder(void *data, uint32_t size) noexcept
125 { return SPA_POD_BUILDER_INIT(data, size); }
127 constexpr auto PwIdAny = PW_ID_ANY;
129 } // namespace
130 /* NOLINTEND */
131 _Pragma("GCC diagnostic pop")
133 namespace {
135 template<typename T> [[nodiscard]] constexpr
136 auto as_const_ptr(T *ptr) noexcept -> std::add_const_t<T>* { return ptr; }
138 struct PodDynamicBuilder {
139 private:
140 std::vector<std::byte> mStorage;
141 spa_pod_builder mPod{};
143 int overflow(uint32_t size) noexcept
145 try {
146 mStorage.resize(size);
148 catch(...) {
149 ERR("Failed to resize POD storage\n");
150 return -ENOMEM;
152 mPod.data = mStorage.data();
153 mPod.size = size;
154 return 0;
157 public:
158 PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize)
159 , mPod{make_pod_builder(mStorage.data(), initSize)}
161 static constexpr auto callbacks{[]
163 spa_pod_builder_callbacks cb{};
164 cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS;
165 cb.overflow = [](void *data, uint32_t size) noexcept
166 { return static_cast<PodDynamicBuilder*>(data)->overflow(size); };
167 return cb;
168 }()};
170 spa_pod_builder_set_callbacks(&mPod, &callbacks, this);
173 spa_pod_builder *get() noexcept { return &mPod; }
176 /* Added in 0.3.33, but we currently only require 0.3.23. */
177 #ifndef PW_KEY_NODE_RATE
178 #define PW_KEY_NODE_RATE "node.rate"
179 #endif
181 using namespace std::string_view_literals;
182 using std::chrono::seconds;
183 using std::chrono::milliseconds;
184 using std::chrono::nanoseconds;
185 using uint = unsigned int;
188 bool check_version(const char *version)
190 /* There doesn't seem to be a function to get the version as an integer, so
191 * instead we have to parse the string, which hopefully won't break in the
192 * future.
194 int major{0}, minor{0}, revision{0};
195 int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
196 return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
197 || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO));
200 #ifdef HAVE_DYNLOAD
201 #define PWIRE_FUNCS(MAGIC) \
202 MAGIC(pw_context_connect) \
203 MAGIC(pw_context_destroy) \
204 MAGIC(pw_context_new) \
205 MAGIC(pw_core_disconnect) \
206 MAGIC(pw_get_library_version) \
207 MAGIC(pw_init) \
208 MAGIC(pw_properties_free) \
209 MAGIC(pw_properties_new) \
210 MAGIC(pw_properties_set) \
211 MAGIC(pw_properties_setf) \
212 MAGIC(pw_proxy_add_object_listener) \
213 MAGIC(pw_proxy_destroy) \
214 MAGIC(pw_proxy_get_user_data) \
215 MAGIC(pw_stream_add_listener) \
216 MAGIC(pw_stream_connect) \
217 MAGIC(pw_stream_dequeue_buffer) \
218 MAGIC(pw_stream_destroy) \
219 MAGIC(pw_stream_get_state) \
220 MAGIC(pw_stream_new) \
221 MAGIC(pw_stream_queue_buffer) \
222 MAGIC(pw_stream_set_active) \
223 MAGIC(pw_thread_loop_new) \
224 MAGIC(pw_thread_loop_destroy) \
225 MAGIC(pw_thread_loop_get_loop) \
226 MAGIC(pw_thread_loop_start) \
227 MAGIC(pw_thread_loop_stop) \
228 MAGIC(pw_thread_loop_lock) \
229 MAGIC(pw_thread_loop_wait) \
230 MAGIC(pw_thread_loop_signal) \
231 MAGIC(pw_thread_loop_unlock)
232 #if PW_CHECK_VERSION(0,3,50)
233 #define PWIRE_FUNCS2(MAGIC) \
234 MAGIC(pw_stream_get_time_n)
235 #else
236 #define PWIRE_FUNCS2(MAGIC) \
237 MAGIC(pw_stream_get_time)
238 #endif
240 void *pwire_handle;
241 #define MAKE_FUNC(f) decltype(f) * p##f;
242 PWIRE_FUNCS(MAKE_FUNC)
243 PWIRE_FUNCS2(MAKE_FUNC)
244 #undef MAKE_FUNC
246 bool pwire_load()
248 if(pwire_handle)
249 return true;
251 const char *pwire_library{"libpipewire-0.3.so.0"};
252 std::string missing_funcs;
254 pwire_handle = LoadLib(pwire_library);
255 if(!pwire_handle)
257 WARN("Failed to load %s\n", pwire_library);
258 return false;
261 #define LOAD_FUNC(f) do { \
262 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
263 if(p##f == nullptr) missing_funcs += "\n" #f; \
264 } while(0);
265 PWIRE_FUNCS(LOAD_FUNC)
266 PWIRE_FUNCS2(LOAD_FUNC)
267 #undef LOAD_FUNC
269 if(!missing_funcs.empty())
271 WARN("Missing expected functions:%s\n", missing_funcs.c_str());
272 CloseLib(pwire_handle);
273 pwire_handle = nullptr;
274 return false;
277 return true;
280 #ifndef IN_IDE_PARSER
281 #define pw_context_connect ppw_context_connect
282 #define pw_context_destroy ppw_context_destroy
283 #define pw_context_new ppw_context_new
284 #define pw_core_disconnect ppw_core_disconnect
285 #define pw_get_library_version ppw_get_library_version
286 #define pw_init ppw_init
287 #define pw_properties_free ppw_properties_free
288 #define pw_properties_new ppw_properties_new
289 #define pw_properties_set ppw_properties_set
290 #define pw_properties_setf ppw_properties_setf
291 #define pw_proxy_add_object_listener ppw_proxy_add_object_listener
292 #define pw_proxy_destroy ppw_proxy_destroy
293 #define pw_proxy_get_user_data ppw_proxy_get_user_data
294 #define pw_stream_add_listener ppw_stream_add_listener
295 #define pw_stream_connect ppw_stream_connect
296 #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
297 #define pw_stream_destroy ppw_stream_destroy
298 #define pw_stream_get_state ppw_stream_get_state
299 #define pw_stream_new ppw_stream_new
300 #define pw_stream_queue_buffer ppw_stream_queue_buffer
301 #define pw_stream_set_active ppw_stream_set_active
302 #define pw_thread_loop_destroy ppw_thread_loop_destroy
303 #define pw_thread_loop_get_loop ppw_thread_loop_get_loop
304 #define pw_thread_loop_lock ppw_thread_loop_lock
305 #define pw_thread_loop_new ppw_thread_loop_new
306 #define pw_thread_loop_signal ppw_thread_loop_signal
307 #define pw_thread_loop_start ppw_thread_loop_start
308 #define pw_thread_loop_stop ppw_thread_loop_stop
309 #define pw_thread_loop_unlock ppw_thread_loop_unlock
310 #define pw_thread_loop_wait ppw_thread_loop_wait
311 #if PW_CHECK_VERSION(0,3,50)
312 #define pw_stream_get_time_n ppw_stream_get_time_n
313 #else
314 inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
315 { return ppw_stream_get_time(stream, ptime); }
316 #endif
317 #endif
319 #else
321 constexpr bool pwire_load() { return true; }
322 #endif
324 /* Helpers for retrieving values from params */
325 template<uint32_t T> struct PodInfo { };
327 template<>
328 struct PodInfo<SPA_TYPE_Int> {
329 using Type = int32_t;
330 static auto get_value(const spa_pod *pod, int32_t *val)
331 { return spa_pod_get_int(pod, val); }
333 template<>
334 struct PodInfo<SPA_TYPE_Id> {
335 using Type = uint32_t;
336 static auto get_value(const spa_pod *pod, uint32_t *val)
337 { return spa_pod_get_id(pod, val); }
340 template<uint32_t T>
341 using Pod_t = typename PodInfo<T>::Type;
343 template<uint32_t T>
344 al::span<const Pod_t<T>> get_array_span(const spa_pod *pod)
346 uint32_t nvals{};
347 if(void *v{spa_pod_get_array(pod, &nvals)})
349 if(get_array_value_type(pod) == T)
350 return {static_cast<const Pod_t<T>*>(v), nvals};
352 return {};
355 template<uint32_t T>
356 std::optional<Pod_t<T>> get_value(const spa_pod *value)
358 Pod_t<T> val{};
359 if(PodInfo<T>::get_value(value, &val) == 0)
360 return val;
361 return std::nullopt;
364 /* Internally, PipeWire types "inherit" from each other, but this is hidden
365 * from the API and the caller is expected to C-style cast to inherited types
366 * as needed. It's also not made very clear what types a given type can be
367 * casted to. To make it a bit safer, this as() method allows casting pw_*
368 * types to known inherited types, generating a compile-time error for
369 * unexpected/invalid casts.
371 template<typename To, typename From>
372 To as(From) noexcept = delete;
374 /* pw_proxy
375 * - pw_registry
376 * - pw_node
377 * - pw_metadata
379 template<>
380 pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); }
381 template<>
382 pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); }
383 template<>
384 pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); }
387 struct PwContextDeleter {
388 void operator()(pw_context *context) const { pw_context_destroy(context); }
390 using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>;
392 struct PwCoreDeleter {
393 void operator()(pw_core *core) const { pw_core_disconnect(core); }
395 using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>;
397 struct PwRegistryDeleter {
398 void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); }
400 using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>;
402 struct PwNodeDeleter {
403 void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); }
405 using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>;
407 struct PwMetadataDeleter {
408 void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); }
410 using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>;
412 struct PwStreamDeleter {
413 void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
415 using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
417 /* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */
418 constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
419 { return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
420 /* NOLINTEND(*EnumCastOutOfRange) */
422 constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
423 { lhs = lhs | rhs; return lhs; }
425 class ThreadMainloop {
426 pw_thread_loop *mLoop{};
428 public:
429 ThreadMainloop() = default;
430 ThreadMainloop(const ThreadMainloop&) = delete;
431 ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
432 explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
433 ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
435 ThreadMainloop& operator=(const ThreadMainloop&) = delete;
436 ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
437 { std::swap(mLoop, rhs.mLoop); return *this; }
438 ThreadMainloop& operator=(std::nullptr_t) noexcept
440 if(mLoop)
441 pw_thread_loop_destroy(mLoop);
442 mLoop = nullptr;
443 return *this;
446 explicit operator bool() const noexcept { return mLoop != nullptr; }
448 [[nodiscard]]
449 auto start() const { return pw_thread_loop_start(mLoop); }
450 auto stop() const { return pw_thread_loop_stop(mLoop); }
452 [[nodiscard]]
453 auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
455 auto lock() const { return pw_thread_loop_lock(mLoop); }
456 auto unlock() const { return pw_thread_loop_unlock(mLoop); }
458 auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
460 auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) const
461 { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; }
463 static auto Create(const char *name, spa_dict *props=nullptr)
464 { return ThreadMainloop{pw_thread_loop_new(name, props)}; }
466 friend struct MainloopUniqueLock;
468 struct MainloopUniqueLock : public std::unique_lock<ThreadMainloop> {
469 using std::unique_lock<ThreadMainloop>::unique_lock;
470 MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
472 auto wait() const -> void
473 { pw_thread_loop_wait(mutex()->mLoop); }
475 template<typename Predicate>
476 auto wait(Predicate done_waiting) const -> void
477 { while(!done_waiting()) wait(); }
479 using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
482 /* There's quite a mess here, but the purpose is to track active devices and
483 * their default formats, so playback devices can be configured to match. The
484 * device list is updated asynchronously, so it will have the latest list of
485 * devices provided by the server.
488 /* A generic PipeWire node proxy object used to track changes to sink and
489 * source nodes.
491 struct NodeProxy {
492 static constexpr pw_node_events CreateNodeEvents()
494 pw_node_events ret{};
495 ret.version = PW_VERSION_NODE_EVENTS;
496 ret.info = infoCallback;
497 ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept
498 { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); };
499 return ret;
502 uint32_t mId{};
504 PwNodePtr mNode{};
505 spa_hook mListener{};
507 NodeProxy(uint32_t id, PwNodePtr node)
508 : mId{id}, mNode{std::move(node)}
510 static constexpr pw_node_events nodeEvents{CreateNodeEvents()};
511 ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this);
513 /* Track changes to the enumerable and current formats (indicates the
514 * default and active format, which is what we're interested in).
516 std::array<uint32_t,2> fmtids{{SPA_PARAM_EnumFormat, SPA_PARAM_Format}};
517 ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size());
519 ~NodeProxy()
520 { spa_hook_remove(&mListener); }
523 static void infoCallback(void *object, const pw_node_info *info) noexcept;
524 void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) const noexcept;
527 /* A metadata proxy object used to query the default sink and source. */
528 struct MetadataProxy {
529 static constexpr pw_metadata_events CreateMetadataEvents()
531 pw_metadata_events ret{};
532 ret.version = PW_VERSION_METADATA_EVENTS;
533 ret.property = propertyCallback;
534 return ret;
537 uint32_t mId{};
539 PwMetadataPtr mMetadata{};
540 spa_hook mListener{};
542 MetadataProxy(uint32_t id, PwMetadataPtr mdata)
543 : mId{id}, mMetadata{std::move(mdata)}
545 static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()};
546 ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this);
548 ~MetadataProxy()
549 { spa_hook_remove(&mListener); }
551 static auto propertyCallback(void *object, uint32_t id, const char *key, const char *type,
552 const char *value) noexcept -> int;
556 /* The global thread watching for global events. This particular class responds
557 * to objects being added to or removed from the registry.
559 struct EventManager {
560 ThreadMainloop mLoop{};
561 PwContextPtr mContext{};
562 PwCorePtr mCore{};
563 PwRegistryPtr mRegistry{};
564 spa_hook mRegistryListener{};
565 spa_hook mCoreListener{};
567 /* A list of proxy objects watching for events about changes to objects in
568 * the registry.
570 std::vector<std::unique_ptr<NodeProxy>> mNodeList;
571 std::optional<MetadataProxy> mDefaultMetadata;
573 /* Initialization handling. When init() is called, mInitSeq is set to a
574 * SequenceID that marks the end of populating the registry. As objects of
575 * interest are found, events to parse them are generated and mInitSeq is
576 * updated with a newer ID. When mInitSeq stops being updated and the event
577 * corresponding to it is reached, mInitDone will be set to true.
579 std::atomic<bool> mInitDone{false};
580 std::atomic<bool> mHasAudio{false};
581 int mInitSeq{};
583 ~EventManager() { if(mLoop) mLoop.stop(); }
585 bool init();
587 void kill();
589 auto lock() const { return mLoop.lock(); }
590 auto unlock() const { return mLoop.unlock(); }
592 [[nodiscard]] inline
593 bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept
594 { return mInitDone.load(m); }
597 * Waits for initialization to finish. The event manager must *NOT* be
598 * locked when calling this.
600 void waitForInit()
602 if(!initIsDone(std::memory_order_acquire)) UNLIKELY
604 MainloopUniqueLock plock{mLoop};
605 plock.wait([this](){ return initIsDone(std::memory_order_acquire); });
610 * Waits for audio support to be detected, or initialization to finish,
611 * whichever is first. Returns true if audio support was detected. The
612 * event manager must *NOT* be locked when calling this.
614 bool waitForAudio()
616 MainloopUniqueLock plock{mLoop};
617 bool has_audio{};
618 plock.wait([this,&has_audio]()
620 has_audio = mHasAudio.load(std::memory_order_acquire);
621 return has_audio || initIsDone(std::memory_order_acquire);
623 return has_audio;
626 void syncInit()
628 /* If initialization isn't done, update the sequence ID so it won't
629 * complete until after currently scheduled events.
631 if(!initIsDone(std::memory_order_relaxed))
632 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq);
635 void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version,
636 const spa_dict *props) noexcept;
638 void removeCallback(uint32_t id) noexcept;
640 static constexpr pw_registry_events CreateRegistryEvents()
642 pw_registry_events ret{};
643 ret.version = PW_VERSION_REGISTRY_EVENTS;
644 ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept
645 { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); };
646 ret.global_remove = [](void *object, uint32_t id) noexcept
647 { static_cast<EventManager*>(object)->removeCallback(id); };
648 return ret;
651 void coreCallback(uint32_t id, int seq) noexcept;
653 static constexpr pw_core_events CreateCoreEvents()
655 pw_core_events ret{};
656 ret.version = PW_VERSION_CORE_EVENTS;
657 ret.done = [](void *object, uint32_t id, int seq) noexcept
658 { static_cast<EventManager*>(object)->coreCallback(id, seq); };
659 return ret;
662 using EventWatcherUniqueLock = std::unique_lock<EventManager>;
663 using EventWatcherLockGuard = std::lock_guard<EventManager>;
665 EventManager gEventHandler;
667 /* Enumerated devices. This is updated asynchronously as the app runs, and the
668 * gEventHandler thread loop must be locked when accessing the list.
670 enum class NodeType : unsigned char {
671 Sink, Source, Duplex
673 constexpr auto InvalidChannelConfig = DevFmtChannels(255);
674 struct DeviceNode {
675 uint32_t mId{};
677 uint64_t mSerial{};
678 std::string mName;
679 std::string mDevName;
681 NodeType mType{};
682 bool mIsHeadphones{};
683 bool mIs51Rear{};
685 uint mSampleRate{};
686 DevFmtChannels mChannels{InvalidChannelConfig};
688 static std::vector<DeviceNode> sList;
689 static DeviceNode &Add(uint32_t id);
690 static DeviceNode *Find(uint32_t id);
691 static DeviceNode *FindByDevName(std::string_view devname);
692 static void Remove(uint32_t id);
693 static auto GetList() noexcept { return al::span{sList}; }
695 void parseSampleRate(const spa_pod *value, bool force_update) noexcept;
696 void parsePositions(const spa_pod *value, bool force_update) noexcept;
697 void parseChannelCount(const spa_pod *value, bool force_update) noexcept;
699 void callEvent(alc::EventType type, std::string_view message) const
701 /* Source nodes aren't recognized for playback, only Sink and Duplex
702 * nodes are. All node types are recognized for capture.
704 if(mType != NodeType::Source)
705 alc::Event(type, alc::DeviceType::Playback, message);
706 alc::Event(type, alc::DeviceType::Capture, message);
709 std::vector<DeviceNode> DeviceNode::sList;
710 std::string DefaultSinkDevice;
711 std::string DefaultSourceDevice;
713 const char *AsString(NodeType type) noexcept
715 switch(type)
717 case NodeType::Sink: return "sink";
718 case NodeType::Source: return "source";
719 case NodeType::Duplex: return "duplex";
721 return "<unknown>";
724 DeviceNode &DeviceNode::Add(uint32_t id)
726 auto match_id = [id](DeviceNode &n) noexcept -> bool
727 { return n.mId == id; };
729 /* If the node is already in the list, return the existing entry. */
730 auto match = std::find_if(sList.begin(), sList.end(), match_id);
731 if(match != sList.end()) return *match;
733 auto &n = sList.emplace_back();
734 n.mId = id;
735 return n;
738 DeviceNode *DeviceNode::Find(uint32_t id)
740 auto match_id = [id](DeviceNode &n) noexcept -> bool
741 { return n.mId == id; };
743 auto match = std::find_if(sList.begin(), sList.end(), match_id);
744 if(match != sList.end()) return al::to_address(match);
746 return nullptr;
749 DeviceNode *DeviceNode::FindByDevName(std::string_view devname)
751 auto match_id = [devname](DeviceNode &n) noexcept -> bool
752 { return n.mDevName == devname; };
754 auto match = std::find_if(sList.begin(), sList.end(), match_id);
755 if(match != sList.end()) return al::to_address(match);
757 return nullptr;
760 void DeviceNode::Remove(uint32_t id)
762 auto match_id = [id](DeviceNode &n) noexcept -> bool
764 if(n.mId != id)
765 return false;
766 TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
767 if(gEventHandler.initIsDone(std::memory_order_relaxed))
769 const std::string msg{"Device removed: "+n.mName};
770 n.callEvent(alc::EventType::DeviceRemoved, msg);
772 return true;
775 auto end = std::remove_if(sList.begin(), sList.end(), match_id);
776 sList.erase(end, sList.end());
780 constexpr std::array MonoMap{
781 SPA_AUDIO_CHANNEL_MONO
783 constexpr std::array StereoMap{
784 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
786 constexpr std::array QuadMap{
787 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
789 constexpr std::array X51Map{
790 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
791 SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
793 constexpr std::array X51RearMap{
794 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
795 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
797 constexpr std::array X61Map{
798 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
799 SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
801 constexpr std::array X71Map{
802 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
803 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
805 constexpr std::array X714Map{
806 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
807 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR,
808 SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
812 * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
813 * to or a superset of map1).
815 bool MatchChannelMap(const al::span<const uint32_t> map0,
816 const al::span<const spa_audio_channel> map1)
818 if(map0.size() < map1.size())
819 return false;
821 auto find_channel = [map0](const spa_audio_channel chid) -> bool
822 { return std::find(map0.begin(), map0.end(), chid) != map0.end(); };
823 return std::all_of(map1.cbegin(), map1.cend(), find_channel);
826 void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept
828 /* TODO: Can this be anything else? Long, Float, Double? */
829 uint32_t nvals{}, choiceType{};
830 value = spa_pod_get_values(value, &nvals, &choiceType);
832 const uint podType{get_pod_type(value)};
833 if(podType != SPA_TYPE_Int)
835 WARN(" Unhandled sample rate POD type: %u\n", podType);
836 return;
839 if(choiceType == SPA_CHOICE_Range)
841 if(nvals != 3)
843 WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals);
844 return;
846 auto srates = get_pod_body<int32_t,3>(value);
848 /* [0] is the default, [1] is the min, and [2] is the max. */
849 TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]);
850 if(!mSampleRate || force_update)
851 mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
852 MaxOutputRate));
853 return;
856 if(choiceType == SPA_CHOICE_Enum)
858 if(nvals == 0)
860 WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals);
861 return;
863 auto srates = get_pod_body<int32_t>(value, nvals);
865 /* [0] is the default, [1...size()-1] are available selections. */
866 std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}};
867 for(size_t i{2};i < srates.size();++i)
869 others += ", ";
870 others += std::to_string(srates[i]);
872 TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str());
873 /* Pick the first rate listed that's within the allowed range (default
874 * rate if possible).
876 for(const auto &rate : srates)
878 if(rate >= int{MinOutputRate} && rate <= int{MaxOutputRate})
880 if(!mSampleRate || force_update)
881 mSampleRate = static_cast<uint>(rate);
882 break;
885 return;
888 if(choiceType == SPA_CHOICE_None)
890 if(nvals != 1)
892 WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals);
893 return;
895 auto srates = get_pod_body<int32_t,1>(value);
897 TRACE(" sample rate: %d\n", srates[0]);
898 if(!mSampleRate || force_update)
899 mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
900 MaxOutputRate));
901 return;
904 WARN(" Unhandled sample rate choice type: %u\n", choiceType);
907 void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept
909 uint32_t choiceCount{}, choiceType{};
910 value = spa_pod_get_values(value, &choiceCount, &choiceType);
912 if(choiceType != SPA_CHOICE_None || choiceCount != 1)
914 ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
915 return;
918 const auto chanmap = get_array_span<SPA_TYPE_Id>(value);
919 if(chanmap.empty()) return;
921 if(mChannels == InvalidChannelConfig || force_update)
923 mIs51Rear = false;
925 if(MatchChannelMap(chanmap, X714Map))
926 mChannels = DevFmtX714;
927 else if(MatchChannelMap(chanmap, X71Map))
928 mChannels = DevFmtX71;
929 else if(MatchChannelMap(chanmap, X61Map))
930 mChannels = DevFmtX61;
931 else if(MatchChannelMap(chanmap, X51Map))
932 mChannels = DevFmtX51;
933 else if(MatchChannelMap(chanmap, X51RearMap))
935 mChannels = DevFmtX51;
936 mIs51Rear = true;
938 else if(MatchChannelMap(chanmap, QuadMap))
939 mChannels = DevFmtQuad;
940 else if(MatchChannelMap(chanmap, StereoMap))
941 mChannels = DevFmtStereo;
942 else
943 mChannels = DevFmtMono;
945 TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s",
946 DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
949 void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept
951 /* As a fallback with just a channel count, just assume mono or stereo. */
952 uint32_t choiceCount{}, choiceType{};
953 value = spa_pod_get_values(value, &choiceCount, &choiceType);
955 if(choiceType != SPA_CHOICE_None || choiceCount != 1)
957 ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
958 return;
961 const auto chancount = get_value<SPA_TYPE_Int>(value);
962 if(!chancount) return;
964 if(mChannels == InvalidChannelConfig || force_update)
966 mIs51Rear = false;
968 if(*chancount >= 2)
969 mChannels = DevFmtStereo;
970 else if(*chancount >= 1)
971 mChannels = DevFmtMono;
973 TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s",
974 DevFmtChannelsString(mChannels));
978 [[nodiscard]] constexpr auto GetMonitorPrefix() noexcept { return "Monitor of "sv; }
979 [[nodiscard]] constexpr auto GetMonitorSuffix() noexcept { return ".monitor"sv; }
980 [[nodiscard]] constexpr auto GetAudioSinkClassName() noexcept { return "Audio/Sink"sv; }
981 [[nodiscard]] constexpr auto GetAudioSourceClassName() noexcept { return "Audio/Source"sv; }
982 [[nodiscard]] constexpr auto GetAudioDuplexClassName() noexcept { return "Audio/Duplex"sv; }
983 [[nodiscard]] constexpr auto GetAudioSourceVirtualClassName() noexcept
984 { return "Audio/Source/Virtual"sv; }
987 void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
989 /* We only care about property changes here (media class, name/desc).
990 * Format changes will automatically invoke the param callback.
992 * TODO: Can the media class or name/desc change without being removed and
993 * readded?
995 if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS))
997 /* Can this actually change? */
998 const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
999 if(!media_class) UNLIKELY return;
1000 const std::string_view className{media_class};
1002 NodeType ntype{};
1003 if(al::case_compare(className, GetAudioSinkClassName()) == 0)
1004 ntype = NodeType::Sink;
1005 else if(al::case_compare(className, GetAudioSourceClassName()) == 0
1006 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0)
1007 ntype = NodeType::Source;
1008 else if(al::case_compare(className, GetAudioDuplexClassName()) == 0)
1009 ntype = NodeType::Duplex;
1010 else
1012 TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
1013 DeviceNode::Remove(info->id);
1014 return;
1017 const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)};
1018 const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)};
1019 if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
1020 if(!nodeName || !*nodeName) nodeName = devName;
1022 uint64_t serial_id{info->id};
1023 #ifdef PW_KEY_OBJECT_SERIAL
1024 if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
1026 errno = 0;
1027 char *serial_end{};
1028 serial_id = std::strtoull(serial_str, &serial_end, 0);
1029 if(*serial_end != '\0' || errno == ERANGE)
1031 ERR("Unexpected object serial: %s\n", serial_str);
1032 serial_id = info->id;
1035 #endif
1037 std::string name;
1038 if(nodeName && *nodeName) name = nodeName;
1039 else name = "PipeWire node #"+std::to_string(info->id);
1041 const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
1042 TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
1043 form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
1044 TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id);
1046 DeviceNode &node = DeviceNode::Add(info->id);
1047 node.mSerial = serial_id;
1048 /* This method is called both to notify about a new sink/source node,
1049 * and update properties for the node. It's unclear what properties can
1050 * change for an existing node without being removed first, so err on
1051 * the side of caution: send a DeviceRemoved event if it had a name
1052 * that's being changed, and send a DeviceAdded event when the name
1053 * differs or it didn't have one.
1055 * The DeviceRemoved event needs to be called before the potentially
1056 * new NodeType is set, so the removal event is called for the previous
1057 * device type, while the DeviceAdded event needs to be called after.
1059 * This is overkill if the node type, name, and devname can't change.
1061 bool notifyAdd{false};
1062 if(node.mName != name)
1064 if(gEventHandler.initIsDone(std::memory_order_relaxed))
1066 if(!node.mName.empty())
1068 const std::string msg{"Device removed: "+node.mName};
1069 node.callEvent(alc::EventType::DeviceRemoved, msg);
1071 notifyAdd = true;
1073 node.mName = std::move(name);
1075 node.mDevName = devName ? devName : "";
1076 node.mType = ntype;
1077 node.mIsHeadphones = form_factor && (al::case_compare(form_factor, "headphones"sv) == 0
1078 || al::case_compare(form_factor, "headset"sv) == 0);
1079 if(notifyAdd)
1081 const std::string msg{"Device added: "+node.mName};
1082 node.callEvent(alc::EventType::DeviceAdded, msg);
1087 void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) const noexcept
1089 if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format)
1091 DeviceNode *node{DeviceNode::Find(mId)};
1092 if(!node) UNLIKELY return;
1094 TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial,
1095 (id == SPA_PARAM_EnumFormat) ? "available" : "current",
1096 (id == SPA_PARAM_EnumFormat) ? "s" : "");
1098 const bool force_update{id == SPA_PARAM_Format};
1099 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
1100 node->parseSampleRate(&prop->value, force_update);
1102 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)})
1103 node->parsePositions(&prop->value, force_update);
1104 else
1106 prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels);
1107 if(prop) node->parseChannelCount(&prop->value, force_update);
1113 auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const char *type,
1114 const char *value) noexcept -> int
1116 if(id != PW_ID_CORE)
1117 return 0;
1119 bool isCapture{};
1120 if("default.audio.sink"sv == key)
1121 isCapture = false;
1122 else if("default.audio.source"sv == key)
1123 isCapture = true;
1124 else
1125 return 0;
1127 if(!type)
1129 TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
1130 if(!isCapture) DefaultSinkDevice.clear();
1131 else DefaultSourceDevice.clear();
1132 return 0;
1134 if("Spa:String:JSON"sv != type)
1136 ERR("Unexpected %s property type: %s\n", key, type);
1137 return 0;
1140 std::array<spa_json,2> it{};
1141 spa_json_init(it.data(), value, strlen(value));
1142 if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0)
1143 return 0;
1145 auto get_json_string = [](spa_json *iter)
1147 std::optional<std::string> str;
1149 const char *val{};
1150 int len{spa_json_next(iter, &val)};
1151 if(len <= 0) return str;
1153 str.emplace(static_cast<uint>(len), '\0');
1154 if(spa_json_parse_string(val, len, str->data()) <= 0)
1155 str.reset();
1156 else while(!str->empty() && str->back() == '\0')
1157 str->pop_back();
1158 return str;
1160 while(auto propKey = get_json_string(&std::get<1>(it)))
1162 if("name"sv == *propKey)
1164 auto propValue = get_json_string(&std::get<1>(it));
1165 if(!propValue) break;
1167 TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
1168 propValue->c_str());
1169 if(!isCapture && DefaultSinkDevice != *propValue)
1171 if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
1173 auto entry = DeviceNode::FindByDevName(*propValue);
1174 const std::string message{"Default playback device changed: "+
1175 (entry ? entry->mName : std::string{})};
1176 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
1177 message);
1179 DefaultSinkDevice = std::move(*propValue);
1181 else if(isCapture && DefaultSourceDevice != *propValue)
1183 if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
1185 auto entry = DeviceNode::FindByDevName(*propValue);
1186 const std::string message{"Default capture device changed: "+
1187 (entry ? entry->mName : std::string{})};
1188 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
1189 message);
1191 DefaultSourceDevice = std::move(*propValue);
1194 else
1196 const char *v{};
1197 if(spa_json_next(&std::get<1>(it), &v) <= 0)
1198 break;
1201 return 0;
1205 bool EventManager::init()
1207 mLoop = ThreadMainloop::Create("PWEventThread");
1208 if(!mLoop)
1210 ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
1211 return false;
1214 mContext = mLoop.newContext();
1215 if(!mContext)
1217 ERR("Failed to create PipeWire event context (errno: %d)\n", errno);
1218 return false;
1221 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1222 if(!mCore)
1224 ERR("Failed to connect PipeWire event context (errno: %d)\n", errno);
1225 return false;
1228 mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
1229 if(!mRegistry)
1231 ERR("Failed to get PipeWire event registry (errno: %d)\n", errno);
1232 return false;
1235 static constexpr pw_core_events coreEvents{CreateCoreEvents()};
1236 static constexpr pw_registry_events registryEvents{CreateRegistryEvents()};
1238 ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this);
1239 ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, &registryEvents, this);
1241 /* Set an initial sequence ID for initialization, to trigger after the
1242 * registry is first populated.
1244 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0);
1246 if(int res{mLoop.start()})
1248 ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
1249 return false;
1252 return true;
1255 void EventManager::kill()
1257 if(mLoop) mLoop.stop();
1259 mDefaultMetadata.reset();
1260 mNodeList.clear();
1262 mRegistry = nullptr;
1263 mCore = nullptr;
1264 mContext = nullptr;
1265 mLoop = nullptr;
1268 void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version,
1269 const spa_dict *props) noexcept
1271 /* We're only interested in interface nodes. */
1272 if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
1274 const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)};
1275 if(!media_class) return;
1276 const std::string_view className{media_class};
1278 /* Specifically, audio sinks and sources (and duplexes). */
1279 const bool isGood{al::case_compare(className, GetAudioSinkClassName()) == 0
1280 || al::case_compare(className, GetAudioSourceClassName()) == 0
1281 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0
1282 || al::case_compare(className, GetAudioDuplexClassName()) == 0};
1283 if(!isGood)
1285 if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv))
1286 TRACE("Ignoring node class %s\n", media_class);
1287 return;
1290 /* Create the proxy object. */
1291 auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type,
1292 version, 0))};
1293 if(!node)
1295 ERR("Failed to create node proxy object (errno: %d)\n", errno);
1296 return;
1299 /* Initialize the NodeProxy to hold the node object, add it to the
1300 * active node list, and update the sync point.
1302 mNodeList.emplace_back(std::make_unique<NodeProxy>(id, std::move(node)));
1303 syncInit();
1305 /* Signal any waiters that we have found a source or sink for audio
1306 * support.
1308 if(!mHasAudio.exchange(true, std::memory_order_acq_rel))
1309 mLoop.signal(false);
1311 else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0)
1313 const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)};
1314 if(!data_class) return;
1316 if("default"sv != data_class)
1318 TRACE("Ignoring metadata \"%s\"\n", data_class);
1319 return;
1322 if(mDefaultMetadata)
1324 ERR("Duplicate default metadata\n");
1325 return;
1328 auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id,
1329 type, version, 0))};
1330 if(!mdata)
1332 ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
1333 return;
1336 mDefaultMetadata.emplace(id, std::move(mdata));
1337 syncInit();
1341 void EventManager::removeCallback(uint32_t id) noexcept
1343 DeviceNode::Remove(id);
1345 auto clear_node = [id](std::unique_ptr<NodeProxy> &node) noexcept
1346 { return node->mId == id; };
1347 auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node);
1348 mNodeList.erase(node_end, mNodeList.end());
1350 if(mDefaultMetadata && mDefaultMetadata->mId == id)
1351 mDefaultMetadata.reset();
1354 void EventManager::coreCallback(uint32_t id, int seq) noexcept
1356 if(id == PW_ID_CORE && seq == mInitSeq)
1358 /* Initialization done. Remove this callback and signal anyone that may
1359 * be waiting.
1361 spa_hook_remove(&mCoreListener);
1363 mInitDone.store(true);
1364 mLoop.signal(false);
1369 enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
1370 spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p)
1372 spa_audio_info_raw info{};
1373 if(use_f32p)
1375 device->FmtType = DevFmtFloat;
1376 info.format = SPA_AUDIO_FORMAT_F32P;
1378 else switch(device->FmtType)
1380 case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break;
1381 case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break;
1382 case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break;
1383 case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break;
1384 case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break;
1385 case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break;
1386 case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
1389 info.rate = device->Frequency;
1391 al::span<const spa_audio_channel> map{};
1392 switch(device->FmtChans)
1394 case DevFmtMono: map = MonoMap; break;
1395 case DevFmtStereo: map = StereoMap; break;
1396 case DevFmtQuad: map = QuadMap; break;
1397 case DevFmtX51:
1398 if(is51rear) map = X51RearMap;
1399 else map = X51Map;
1400 break;
1401 case DevFmtX61: map = X61Map; break;
1402 case DevFmtX71: map = X71Map; break;
1403 case DevFmtX714: map = X714Map; break;
1404 case DevFmtX3D71: map = X71Map; break;
1405 case DevFmtX7144:
1406 case DevFmtAmbi3D:
1407 info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
1408 info.channels = device->channelsFromFmt();
1409 break;
1411 if(!map.empty())
1413 info.channels = static_cast<uint32_t>(map.size());
1414 std::copy(map.begin(), map.end(), std::begin(info.position));
1417 return info;
1420 class PipeWirePlayback final : public BackendBase {
1421 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept;
1422 void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept;
1423 void outputCallback() noexcept;
1425 void open(std::string_view name) override;
1426 bool reset() override;
1427 void start() override;
1428 void stop() override;
1429 ClockLatency getClockLatency() override;
1431 uint64_t mTargetId{PwIdAny};
1432 nanoseconds mTimeBase{0};
1433 ThreadMainloop mLoop;
1434 PwContextPtr mContext;
1435 PwCorePtr mCore;
1436 PwStreamPtr mStream;
1437 spa_hook mStreamListener{};
1438 spa_io_rate_match *mRateMatch{};
1439 std::vector<void*> mChannelPtrs;
1441 static constexpr pw_stream_events CreateEvents()
1443 pw_stream_events ret{};
1444 ret.version = PW_VERSION_STREAM_EVENTS;
1445 ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept
1446 { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); };
1447 ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept
1448 { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); };
1449 ret.process = [](void *data) noexcept
1450 { static_cast<PipeWirePlayback*>(data)->outputCallback(); };
1451 return ret;
1454 public:
1455 PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
1456 ~PipeWirePlayback() final
1458 /* Stop the mainloop so the stream can be properly destroyed. */
1459 if(mLoop) mLoop.stop();
1464 void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept
1465 { mLoop.signal(false); }
1467 void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept
1469 switch(id)
1471 case SPA_IO_RateMatch:
1472 if(size >= sizeof(spa_io_rate_match))
1473 mRateMatch = static_cast<spa_io_rate_match*>(area);
1474 else
1475 mRateMatch = nullptr;
1476 break;
1480 void PipeWirePlayback::outputCallback() noexcept
1482 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1483 if(!pw_buf) UNLIKELY return;
1485 const al::span<spa_data> datas{pw_buf->buffer->datas,
1486 std::min(mChannelPtrs.size(), size_t{pw_buf->buffer->n_datas})};
1487 #if PW_CHECK_VERSION(0,3,49)
1488 /* In 0.3.49, pw_buffer::requested specifies the number of samples needed
1489 * by the resampler/graph for this audio update.
1491 uint length{static_cast<uint>(pw_buf->requested)};
1492 #else
1493 /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
1494 * of samples per update.
1496 uint length{mRateMatch ? mRateMatch->size : 0u};
1497 #endif
1498 /* If no length is specified, use the device's update size as a fallback. */
1499 if(!length) UNLIKELY length = mDevice->UpdateSize;
1501 /* For planar formats, each datas[] seems to contain one channel, so store
1502 * the pointers in an array. Limit the render length in case the available
1503 * buffer length in any one channel is smaller than we wanted (shouldn't
1504 * be, but just in case).
1506 auto chanptr_end = mChannelPtrs.begin();
1507 for(const auto &data : datas)
1509 length = std::min(length, data.maxsize/uint{sizeof(float)});
1510 *chanptr_end = data.data;
1511 ++chanptr_end;
1513 data.chunk->offset = 0;
1514 data.chunk->stride = sizeof(float);
1515 data.chunk->size = length * sizeof(float);
1518 mDevice->renderSamples(mChannelPtrs, length);
1520 pw_buf->size = length;
1521 pw_stream_queue_buffer(mStream.get(), pw_buf);
1525 void PipeWirePlayback::open(std::string_view name)
1527 static std::atomic<uint> OpenCount{0};
1529 uint64_t targetid{PwIdAny};
1530 std::string devname{};
1531 gEventHandler.waitForInit();
1532 if(name.empty())
1534 EventWatcherLockGuard evtlock{gEventHandler};
1535 auto&& devlist = DeviceNode::GetList();
1537 auto match = devlist.cend();
1538 if(!DefaultSinkDevice.empty())
1540 auto match_default = [](const DeviceNode &n) -> bool
1541 { return n.mDevName == DefaultSinkDevice; };
1542 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1544 if(match == devlist.cend())
1546 auto match_playback = [](const DeviceNode &n) -> bool
1547 { return n.mType != NodeType::Source; };
1548 match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback);
1549 if(match == devlist.cend())
1550 throw al::backend_exception{al::backend_error::NoDevice,
1551 "No PipeWire playback device found"};
1554 targetid = match->mSerial;
1555 devname = match->mName;
1557 else
1559 EventWatcherLockGuard evtlock{gEventHandler};
1560 auto&& devlist = DeviceNode::GetList();
1562 auto match_name = [name](const DeviceNode &n) -> bool
1563 { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); };
1564 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
1565 if(match == devlist.cend())
1566 throw al::backend_exception{al::backend_error::NoDevice,
1567 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
1569 targetid = match->mSerial;
1570 devname = match->mName;
1573 if(!mLoop)
1575 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
1576 const std::string thread_name{"ALSoftP" + std::to_string(count)};
1577 mLoop = ThreadMainloop::Create(thread_name.c_str());
1578 if(!mLoop)
1579 throw al::backend_exception{al::backend_error::DeviceError,
1580 "Failed to create PipeWire mainloop (errno: %d)", errno};
1581 if(int res{mLoop.start()})
1582 throw al::backend_exception{al::backend_error::DeviceError,
1583 "Failed to start PipeWire mainloop (res: %d)", res};
1585 MainloopUniqueLock mlock{mLoop};
1586 if(!mContext)
1588 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
1589 mContext = mLoop.newContext(cprops);
1590 if(!mContext)
1591 throw al::backend_exception{al::backend_error::DeviceError,
1592 "Failed to create PipeWire event context (errno: %d)\n", errno};
1594 if(!mCore)
1596 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1597 if(!mCore)
1598 throw al::backend_exception{al::backend_error::DeviceError,
1599 "Failed to connect PipeWire event context (errno: %d)\n", errno};
1601 mlock.unlock();
1603 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1605 mTargetId = targetid;
1606 if(!devname.empty())
1607 mDeviceName = std::move(devname);
1608 else
1609 mDeviceName = "PipeWire Output"sv;
1612 bool PipeWirePlayback::reset()
1614 if(mStream)
1616 MainloopLockGuard looplock{mLoop};
1617 mStream = nullptr;
1619 mStreamListener = {};
1620 mRateMatch = nullptr;
1621 mTimeBase = mDevice->getClockTime();
1623 /* If connecting to a specific device, update various device parameters to
1624 * match its format.
1626 bool is51rear{false};
1627 mDevice->Flags.reset(DirectEar);
1628 if(mTargetId != PwIdAny)
1630 EventWatcherLockGuard evtlock{gEventHandler};
1631 auto&& devlist = DeviceNode::GetList();
1633 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
1634 { return targetid == n.mSerial; };
1635 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
1636 if(match != devlist.cend())
1638 if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
1640 /* Scale the update size if the sample rate changes. */
1641 const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
1642 const double updatesize{std::round(mDevice->UpdateSize * scale)};
1643 const double buffersize{std::round(mDevice->BufferSize * scale)};
1645 mDevice->Frequency = match->mSampleRate;
1646 mDevice->UpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0));
1647 mDevice->BufferSize = static_cast<uint>(std::max(buffersize, 128.0));
1649 if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
1650 mDevice->FmtChans = match->mChannels;
1651 if(match->mChannels == DevFmtStereo && match->mIsHeadphones)
1652 mDevice->Flags.set(DirectEar);
1653 is51rear = match->mIs51Rear;
1656 /* Force planar 32-bit float output for playback. This is what PipeWire
1657 * handles internally, and it's easier for us too.
1659 auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)};
1661 auto b = PodDynamicBuilder{};
1662 auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
1663 if(!params)
1664 throw al::backend_exception{al::backend_error::DeviceError,
1665 "Failed to set PipeWire audio format parameters"};
1667 /* TODO: Which properties are actually needed here? Any others that could
1668 * be useful?
1670 auto&& binary = GetProcBinary();
1671 const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
1672 pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
1673 PW_KEY_NODE_DESCRIPTION, appname,
1674 PW_KEY_MEDIA_TYPE, "Audio",
1675 PW_KEY_MEDIA_CATEGORY, "Playback",
1676 PW_KEY_MEDIA_ROLE, "Game",
1677 PW_KEY_NODE_ALWAYS_PROCESS, "true",
1678 nullptr)};
1679 if(!props)
1680 throw al::backend_exception{al::backend_error::DeviceError,
1681 "Failed to create PipeWire stream properties (errno: %d)", errno};
1683 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
1684 mDevice->Frequency);
1685 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
1686 #ifdef PW_KEY_TARGET_OBJECT
1687 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
1688 #else
1689 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
1690 #endif
1692 MainloopUniqueLock plock{mLoop};
1693 /* The stream takes overship of 'props', even in the case of failure. */
1694 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
1695 if(!mStream)
1696 throw al::backend_exception{al::backend_error::NoDevice,
1697 "Failed to create PipeWire stream (errno: %d)", errno};
1698 static constexpr pw_stream_events streamEvents{CreateEvents()};
1699 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
1701 pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
1702 | PW_STREAM_FLAG_MAP_BUFFERS};
1703 if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false))
1704 flags |= PW_STREAM_FLAG_RT_PROCESS;
1705 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)})
1706 throw al::backend_exception{al::backend_error::DeviceError,
1707 "Error connecting PipeWire stream (res: %d)", res};
1709 /* Wait for the stream to become paused (ready to start streaming). */
1710 plock.wait([stream=mStream.get()]()
1712 const char *error{};
1713 pw_stream_state state{pw_stream_get_state(stream, &error)};
1714 if(state == PW_STREAM_STATE_ERROR)
1715 throw al::backend_exception{al::backend_error::DeviceError,
1716 "Error connecting PipeWire stream: \"%s\"", error};
1717 return state == PW_STREAM_STATE_PAUSED;
1720 /* TODO: Update mDevice->UpdateSize with the stream's quantum, and
1721 * mDevice->BufferSize with the total known buffering delay from the head
1722 * of this playback stream to the tail of the device output.
1724 * This info is apparently not available until after the stream starts.
1726 plock.unlock();
1728 mChannelPtrs.resize(mDevice->channelsFromFmt());
1730 setDefaultWFXChannelOrder();
1732 return true;
1735 void PipeWirePlayback::start()
1737 MainloopUniqueLock plock{mLoop};
1738 if(int res{pw_stream_set_active(mStream.get(), true)})
1739 throw al::backend_exception{al::backend_error::DeviceError,
1740 "Failed to start PipeWire stream (res: %d)", res};
1742 /* Wait for the stream to start playing (would be nice to not, but we need
1743 * the actual update size which is only available after starting).
1745 plock.wait([stream=mStream.get()]()
1747 const char *error{};
1748 pw_stream_state state{pw_stream_get_state(stream, &error)};
1749 if(state == PW_STREAM_STATE_ERROR)
1750 throw al::backend_exception{al::backend_error::DeviceError,
1751 "PipeWire stream error: %s", error ? error : "(unknown)"};
1752 return state == PW_STREAM_STATE_STREAMING;
1755 /* HACK: Try to work out the update size and total buffering size. There's
1756 * no actual query for this, so we have to work it out from the stream time
1757 * info, and assume it stays accurate with future updates. The stream time
1758 * info may also not be available right away, so we have to wait until it
1759 * is (up to about 2 seconds).
1761 int wait_count{100};
1762 do {
1763 pw_time ptime{};
1764 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1766 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1767 break;
1770 /* The rate match size is the update size for each buffer. */
1771 const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
1772 #if PW_CHECK_VERSION(0,3,50)
1773 /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
1774 * queue size.
1776 if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
1778 const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
1780 /* Ensure the delay is in sample frames. */
1781 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1782 ptime.rate.num / ptime.rate.denom};
1784 mDevice->UpdateSize = updatesize;
1785 mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
1786 uint64_t{totalbuffers}*updatesize);
1787 break;
1789 #else
1790 /* Prior to 0.3.50, we can only measure the delay with the update size,
1791 * assuming one buffer and no resample buffering.
1793 if(ptime.rate.denom > 0 && updatesize > 0)
1795 /* Ensure the delay is in sample frames. */
1796 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1797 ptime.rate.num / ptime.rate.denom};
1799 mDevice->UpdateSize = updatesize;
1800 mDevice->BufferSize = static_cast<uint>(delay + updatesize);
1801 break;
1803 #endif
1804 if(!--wait_count)
1806 ERR("Timeout getting PipeWire stream buffering info\n");
1807 break;
1810 plock.unlock();
1811 std::this_thread::sleep_for(milliseconds{20});
1812 plock.lock();
1813 } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
1816 void PipeWirePlayback::stop()
1818 MainloopUniqueLock plock{mLoop};
1819 if(int res{pw_stream_set_active(mStream.get(), false)})
1820 ERR("Failed to stop PipeWire stream (res: %d)\n", res);
1822 /* Wait for the stream to stop playing. */
1823 plock.wait([stream=mStream.get()]()
1824 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
1827 ClockLatency PipeWirePlayback::getClockLatency()
1829 /* Given a real-time low-latency output, this is rather complicated to get
1830 * accurate timing. So, here we go.
1833 /* First, get the stream time info (tick delay, ticks played, and the
1834 * CLOCK_MONOTONIC time closest to when that last tick was played).
1836 pw_time ptime{};
1837 if(mStream)
1839 MainloopLockGuard looplock{mLoop};
1840 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1841 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1844 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1845 * monotonic clock closest to 'now', and the last mixer time at 'now').
1847 nanoseconds mixtime{};
1848 timespec tspec{};
1849 uint refcount;
1850 do {
1851 refcount = mDevice->waitForMix();
1852 mixtime = mDevice->getClockTime();
1853 clock_gettime(CLOCK_MONOTONIC, &tspec);
1854 std::atomic_thread_fence(std::memory_order_acquire);
1855 } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
1857 /* Convert the monotonic clock, stream ticks, and stream delay to
1858 * nanoseconds.
1860 nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
1861 nanoseconds curtic{}, delay{};
1862 if(ptime.rate.denom < 1) UNLIKELY
1864 /* If there's no stream rate, the stream hasn't had a chance to get
1865 * going and return time info yet. Just use dummy values.
1867 ptime.now = monoclock.count();
1868 curtic = mixtime;
1869 delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
1871 else
1873 /* The stream gets recreated with each reset, so include the time that
1874 * had already passed with previous streams.
1876 curtic = mTimeBase;
1877 /* More safely scale the ticks to avoid overflowing the pre-division
1878 * temporary as it gets larger.
1880 curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
1881 curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
1882 ptime.rate.denom;
1884 /* The delay should be small enough to not worry about overflow. */
1885 delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
1888 /* If the mixer time is ahead of the stream time, there's that much more
1889 * delay relative to the stream delay.
1891 if(mixtime > curtic)
1892 delay += mixtime - curtic;
1893 /* Reduce the delay according to how much time has passed since the known
1894 * stream time. This isn't 100% accurate since the system monotonic clock
1895 * doesn't tick at the exact same rate as the audio device, but it should
1896 * be good enough with ptime.now being constantly updated every few
1897 * milliseconds with ptime.ticks.
1899 delay -= monoclock - nanoseconds{ptime.now};
1901 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1902 * in case timer drift got that severe.
1904 ClockLatency ret{};
1905 ret.ClockTime = mixtime;
1906 ret.Latency = std::max(delay, nanoseconds{});
1908 return ret;
1912 class PipeWireCapture final : public BackendBase {
1913 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept;
1914 void inputCallback() noexcept;
1916 void open(std::string_view name) override;
1917 void start() override;
1918 void stop() override;
1919 void captureSamples(std::byte *buffer, uint samples) override;
1920 uint availableSamples() override;
1922 uint64_t mTargetId{PwIdAny};
1923 ThreadMainloop mLoop;
1924 PwContextPtr mContext;
1925 PwCorePtr mCore;
1926 PwStreamPtr mStream;
1927 spa_hook mStreamListener{};
1929 RingBufferPtr mRing{};
1931 static constexpr pw_stream_events CreateEvents()
1933 pw_stream_events ret{};
1934 ret.version = PW_VERSION_STREAM_EVENTS;
1935 ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept
1936 { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); };
1937 ret.process = [](void *data) noexcept
1938 { static_cast<PipeWireCapture*>(data)->inputCallback(); };
1939 return ret;
1942 public:
1943 PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
1944 ~PipeWireCapture() final { if(mLoop) mLoop.stop(); }
1948 void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept
1949 { mLoop.signal(false); }
1951 void PipeWireCapture::inputCallback() noexcept
1953 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1954 if(!pw_buf) UNLIKELY return;
1956 spa_data *bufdata{pw_buf->buffer->datas};
1957 const uint offset{bufdata->chunk->offset % bufdata->maxsize};
1958 const auto input = al::span{static_cast<const char*>(bufdata->data), bufdata->maxsize}
1959 .subspan(offset, std::min(bufdata->chunk->size, bufdata->maxsize - offset));
1961 std::ignore = mRing->write(input.data(), input.size() / mRing->getElemSize());
1963 pw_stream_queue_buffer(mStream.get(), pw_buf);
1967 void PipeWireCapture::open(std::string_view name)
1969 static std::atomic<uint> OpenCount{0};
1971 uint64_t targetid{PwIdAny};
1972 std::string devname{};
1973 gEventHandler.waitForInit();
1974 if(name.empty())
1976 EventWatcherLockGuard evtlock{gEventHandler};
1977 auto&& devlist = DeviceNode::GetList();
1979 auto match = devlist.cend();
1980 if(!DefaultSourceDevice.empty())
1982 auto match_default = [](const DeviceNode &n) -> bool
1983 { return n.mDevName == DefaultSourceDevice; };
1984 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1986 if(match == devlist.cend())
1988 auto match_capture = [](const DeviceNode &n) -> bool
1989 { return n.mType != NodeType::Sink; };
1990 match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture);
1992 if(match == devlist.cend())
1994 match = devlist.cbegin();
1995 if(match == devlist.cend())
1996 throw al::backend_exception{al::backend_error::NoDevice,
1997 "No PipeWire capture device found"};
2000 targetid = match->mSerial;
2001 if(match->mType != NodeType::Sink) devname = match->mName;
2002 else devname = std::string{GetMonitorPrefix()}+match->mName;
2004 else
2006 EventWatcherLockGuard evtlock{gEventHandler};
2007 auto&& devlist = DeviceNode::GetList();
2008 const std::string_view prefix{GetMonitorPrefix()};
2009 const std::string_view suffix{GetMonitorSuffix()};
2011 auto match_name = [name](const DeviceNode &n) -> bool
2012 { return n.mType != NodeType::Sink && n.mName == name; };
2013 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
2014 if(match == devlist.cend() && al::starts_with(name, prefix))
2016 const std::string_view sinkname{name.substr(prefix.length())};
2017 auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
2018 { return n.mType == NodeType::Sink && n.mName == sinkname; };
2019 match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
2021 else if(match == devlist.cend() && al::ends_with(name, suffix))
2023 const std::string_view sinkname{name.substr(0, name.size()-suffix.size())};
2024 auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
2025 { return n.mType == NodeType::Sink && n.mDevName == sinkname; };
2026 match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
2028 if(match == devlist.cend())
2029 throw al::backend_exception{al::backend_error::NoDevice,
2030 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
2032 targetid = match->mSerial;
2033 if(match->mType != NodeType::Sink) devname = match->mName;
2034 else devname = std::string{GetMonitorPrefix()}+match->mName;
2037 if(!mLoop)
2039 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
2040 const std::string thread_name{"ALSoftC" + std::to_string(count)};
2041 mLoop = ThreadMainloop::Create(thread_name.c_str());
2042 if(!mLoop)
2043 throw al::backend_exception{al::backend_error::DeviceError,
2044 "Failed to create PipeWire mainloop (errno: %d)", errno};
2045 if(int res{mLoop.start()})
2046 throw al::backend_exception{al::backend_error::DeviceError,
2047 "Failed to start PipeWire mainloop (res: %d)", res};
2049 MainloopUniqueLock mlock{mLoop};
2050 if(!mContext)
2052 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
2053 mContext = mLoop.newContext(cprops);
2054 if(!mContext)
2055 throw al::backend_exception{al::backend_error::DeviceError,
2056 "Failed to create PipeWire event context (errno: %d)\n", errno};
2058 if(!mCore)
2060 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
2061 if(!mCore)
2062 throw al::backend_exception{al::backend_error::DeviceError,
2063 "Failed to connect PipeWire event context (errno: %d)\n", errno};
2065 mlock.unlock();
2067 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
2069 mTargetId = targetid;
2070 if(!devname.empty())
2071 mDeviceName = std::move(devname);
2072 else
2073 mDeviceName = "PipeWire Input"sv;
2076 bool is51rear{false};
2077 if(mTargetId != PwIdAny)
2079 EventWatcherLockGuard evtlock{gEventHandler};
2080 auto&& devlist = DeviceNode::GetList();
2082 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
2083 { return targetid == n.mSerial; };
2084 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
2085 if(match != devlist.cend())
2086 is51rear = match->mIs51Rear;
2088 auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)};
2090 auto b = PodDynamicBuilder{};
2091 auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
2092 if(!params)
2093 throw al::backend_exception{al::backend_error::DeviceError,
2094 "Failed to set PipeWire audio format parameters"};
2096 auto&& binary = GetProcBinary();
2097 const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
2098 pw_properties *props{pw_properties_new(
2099 PW_KEY_NODE_NAME, appname,
2100 PW_KEY_NODE_DESCRIPTION, appname,
2101 PW_KEY_MEDIA_TYPE, "Audio",
2102 PW_KEY_MEDIA_CATEGORY, "Capture",
2103 PW_KEY_MEDIA_ROLE, "Game",
2104 PW_KEY_NODE_ALWAYS_PROCESS, "true",
2105 nullptr)};
2106 if(!props)
2107 throw al::backend_exception{al::backend_error::DeviceError,
2108 "Failed to create PipeWire stream properties (errno: %d)", errno};
2110 /* We don't actually care what the latency/update size is, as long as it's
2111 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
2112 * around 40ms, which isn't great. So request 20ms instead.
2114 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
2115 mDevice->Frequency);
2116 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
2117 #ifdef PW_KEY_TARGET_OBJECT
2118 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
2119 #else
2120 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
2121 #endif
2123 MainloopUniqueLock plock{mLoop};
2124 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
2125 if(!mStream)
2126 throw al::backend_exception{al::backend_error::NoDevice,
2127 "Failed to create PipeWire stream (errno: %d)", errno};
2128 static constexpr pw_stream_events streamEvents{CreateEvents()};
2129 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
2131 constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
2132 | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
2133 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, &params, 1)})
2134 throw al::backend_exception{al::backend_error::DeviceError,
2135 "Error connecting PipeWire stream (res: %d)", res};
2137 /* Wait for the stream to become paused (ready to start streaming). */
2138 plock.wait([stream=mStream.get()]()
2140 const char *error{};
2141 pw_stream_state state{pw_stream_get_state(stream, &error)};
2142 if(state == PW_STREAM_STATE_ERROR)
2143 throw al::backend_exception{al::backend_error::DeviceError,
2144 "Error connecting PipeWire stream: \"%s\"", error};
2145 return state == PW_STREAM_STATE_PAUSED;
2147 plock.unlock();
2149 setDefaultWFXChannelOrder();
2151 /* Ensure at least a 100ms capture buffer. */
2152 mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize),
2153 mDevice->frameSizeFromFmt(), false);
2157 void PipeWireCapture::start()
2159 MainloopUniqueLock plock{mLoop};
2160 if(int res{pw_stream_set_active(mStream.get(), true)})
2161 throw al::backend_exception{al::backend_error::DeviceError,
2162 "Failed to start PipeWire stream (res: %d)", res};
2164 plock.wait([stream=mStream.get()]()
2166 const char *error{};
2167 pw_stream_state state{pw_stream_get_state(stream, &error)};
2168 if(state == PW_STREAM_STATE_ERROR)
2169 throw al::backend_exception{al::backend_error::DeviceError,
2170 "PipeWire stream error: %s", error ? error : "(unknown)"};
2171 return state == PW_STREAM_STATE_STREAMING;
2175 void PipeWireCapture::stop()
2177 MainloopUniqueLock plock{mLoop};
2178 if(int res{pw_stream_set_active(mStream.get(), false)})
2179 ERR("Failed to stop PipeWire stream (res: %d)\n", res);
2181 plock.wait([stream=mStream.get()]()
2182 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
2185 uint PipeWireCapture::availableSamples()
2186 { return static_cast<uint>(mRing->readSpace()); }
2188 void PipeWireCapture::captureSamples(std::byte *buffer, uint samples)
2189 { std::ignore = mRing->read(buffer, samples); }
2191 } // namespace
2194 bool PipeWireBackendFactory::init()
2196 if(!pwire_load())
2197 return false;
2199 const char *version{pw_get_library_version()};
2200 if(!check_version(version))
2202 WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
2203 pw_get_headers_version());
2204 return false;
2206 TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
2208 pw_init(nullptr, nullptr);
2209 if(!gEventHandler.init())
2210 return false;
2212 if(!GetConfigValueBool({}, "pipewire", "assume-audio", false)
2213 && !gEventHandler.waitForAudio())
2215 gEventHandler.kill();
2216 /* TODO: Temporary warning, until PipeWire gets a proper way to report
2217 * audio support.
2219 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
2220 return false;
2222 return true;
2225 bool PipeWireBackendFactory::querySupport(BackendType type)
2226 { return type == BackendType::Playback || type == BackendType::Capture; }
2228 auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
2230 std::vector<std::string> outnames;
2232 gEventHandler.waitForInit();
2233 EventWatcherLockGuard evtlock{gEventHandler};
2234 auto&& devlist = DeviceNode::GetList();
2236 auto match_defsink = [](const DeviceNode &n) -> bool
2237 { return n.mDevName == DefaultSinkDevice; };
2238 auto match_defsource = [](const DeviceNode &n) -> bool
2239 { return n.mDevName == DefaultSourceDevice; };
2241 auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool
2242 { return lhs.mId < rhs.mId; };
2243 std::sort(devlist.begin(), devlist.end(), sort_devnode);
2245 auto defmatch = devlist.cbegin();
2246 switch(type)
2248 case BackendType::Playback:
2249 defmatch = std::find_if(defmatch, devlist.cend(), match_defsink);
2250 if(defmatch != devlist.cend())
2251 outnames.emplace_back(defmatch->mName);
2252 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2254 if(iter != defmatch && iter->mType != NodeType::Source)
2255 outnames.emplace_back(iter->mName);
2257 break;
2258 case BackendType::Capture:
2259 outnames.reserve(devlist.size());
2260 defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
2261 if(defmatch != devlist.cend())
2263 if(defmatch->mType == NodeType::Sink)
2264 outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName);
2265 else
2266 outnames.emplace_back(defmatch->mName);
2268 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2270 if(iter != defmatch)
2272 if(iter->mType == NodeType::Sink)
2273 outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName);
2274 else
2275 outnames.emplace_back(iter->mName);
2278 break;
2281 return outnames;
2285 BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
2287 if(type == BackendType::Playback)
2288 return BackendPtr{new PipeWirePlayback{device}};
2289 if(type == BackendType::Capture)
2290 return BackendPtr{new PipeWireCapture{device}};
2291 return nullptr;
2294 BackendFactory &PipeWireBackendFactory::getFactory()
2296 static PipeWireBackendFactory factory{};
2297 return factory;
2300 alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
2302 switch(eventType)
2304 case alc::EventType::DefaultDeviceChanged:
2305 case alc::EventType::DeviceAdded:
2306 case alc::EventType::DeviceRemoved:
2307 return alc::EventSupport::FullSupport;
2309 case alc::EventType::Count:
2310 break;
2312 return alc::EventSupport::NoSupport;