Don't call log10 with values too close to 0
[openal-soft.git] / alc / backends / pipewire.cpp
blob5d41a8e1aabaa9a729fbd3a208e7c06756bfcbc2
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 struct PodDynamicBuilder {
136 private:
137 std::vector<std::byte> mStorage;
138 spa_pod_builder mPod{};
140 int overflow(uint32_t size) noexcept
142 try {
143 mStorage.resize(size);
145 catch(...) {
146 ERR("Failed to resize POD storage\n");
147 return -ENOMEM;
149 mPod.data = mStorage.data();
150 mPod.size = size;
151 return 0;
154 public:
155 PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize)
156 , mPod{make_pod_builder(mStorage.data(), initSize)}
158 static constexpr auto callbacks{[]
160 spa_pod_builder_callbacks cb{};
161 cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS;
162 cb.overflow = [](void *data, uint32_t size) noexcept
163 { return static_cast<PodDynamicBuilder*>(data)->overflow(size); };
164 return cb;
165 }()};
167 spa_pod_builder_set_callbacks(&mPod, &callbacks, this);
170 spa_pod_builder *get() noexcept { return &mPod; }
173 /* Added in 0.3.33, but we currently only require 0.3.23. */
174 #ifndef PW_KEY_NODE_RATE
175 #define PW_KEY_NODE_RATE "node.rate"
176 #endif
178 using namespace std::string_view_literals;
179 using std::chrono::seconds;
180 using std::chrono::milliseconds;
181 using std::chrono::nanoseconds;
182 using uint = unsigned int;
185 bool check_version(const char *version)
187 /* There doesn't seem to be a function to get the version as an integer, so
188 * instead we have to parse the string, which hopefully won't break in the
189 * future.
191 int major{0}, minor{0}, revision{0};
192 int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
193 return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
194 || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO));
197 #ifdef HAVE_DYNLOAD
198 #define PWIRE_FUNCS(MAGIC) \
199 MAGIC(pw_context_connect) \
200 MAGIC(pw_context_destroy) \
201 MAGIC(pw_context_new) \
202 MAGIC(pw_core_disconnect) \
203 MAGIC(pw_get_library_version) \
204 MAGIC(pw_init) \
205 MAGIC(pw_properties_free) \
206 MAGIC(pw_properties_new) \
207 MAGIC(pw_properties_set) \
208 MAGIC(pw_properties_setf) \
209 MAGIC(pw_proxy_add_object_listener) \
210 MAGIC(pw_proxy_destroy) \
211 MAGIC(pw_proxy_get_user_data) \
212 MAGIC(pw_stream_add_listener) \
213 MAGIC(pw_stream_connect) \
214 MAGIC(pw_stream_dequeue_buffer) \
215 MAGIC(pw_stream_destroy) \
216 MAGIC(pw_stream_get_state) \
217 MAGIC(pw_stream_new) \
218 MAGIC(pw_stream_queue_buffer) \
219 MAGIC(pw_stream_set_active) \
220 MAGIC(pw_thread_loop_new) \
221 MAGIC(pw_thread_loop_destroy) \
222 MAGIC(pw_thread_loop_get_loop) \
223 MAGIC(pw_thread_loop_start) \
224 MAGIC(pw_thread_loop_stop) \
225 MAGIC(pw_thread_loop_lock) \
226 MAGIC(pw_thread_loop_wait) \
227 MAGIC(pw_thread_loop_signal) \
228 MAGIC(pw_thread_loop_unlock)
229 #if PW_CHECK_VERSION(0,3,50)
230 #define PWIRE_FUNCS2(MAGIC) \
231 MAGIC(pw_stream_get_time_n)
232 #else
233 #define PWIRE_FUNCS2(MAGIC) \
234 MAGIC(pw_stream_get_time)
235 #endif
237 void *pwire_handle;
238 #define MAKE_FUNC(f) decltype(f) * p##f;
239 PWIRE_FUNCS(MAKE_FUNC)
240 PWIRE_FUNCS2(MAKE_FUNC)
241 #undef MAKE_FUNC
243 bool pwire_load()
245 if(pwire_handle)
246 return true;
248 const char *pwire_library{"libpipewire-0.3.so.0"};
249 std::string missing_funcs;
251 pwire_handle = LoadLib(pwire_library);
252 if(!pwire_handle)
254 WARN("Failed to load %s\n", pwire_library);
255 return false;
258 #define LOAD_FUNC(f) do { \
259 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
260 if(p##f == nullptr) missing_funcs += "\n" #f; \
261 } while(0);
262 PWIRE_FUNCS(LOAD_FUNC)
263 PWIRE_FUNCS2(LOAD_FUNC)
264 #undef LOAD_FUNC
266 if(!missing_funcs.empty())
268 WARN("Missing expected functions:%s\n", missing_funcs.c_str());
269 CloseLib(pwire_handle);
270 pwire_handle = nullptr;
271 return false;
274 return true;
277 #ifndef IN_IDE_PARSER
278 #define pw_context_connect ppw_context_connect
279 #define pw_context_destroy ppw_context_destroy
280 #define pw_context_new ppw_context_new
281 #define pw_core_disconnect ppw_core_disconnect
282 #define pw_get_library_version ppw_get_library_version
283 #define pw_init ppw_init
284 #define pw_properties_free ppw_properties_free
285 #define pw_properties_new ppw_properties_new
286 #define pw_properties_set ppw_properties_set
287 #define pw_properties_setf ppw_properties_setf
288 #define pw_proxy_add_object_listener ppw_proxy_add_object_listener
289 #define pw_proxy_destroy ppw_proxy_destroy
290 #define pw_proxy_get_user_data ppw_proxy_get_user_data
291 #define pw_stream_add_listener ppw_stream_add_listener
292 #define pw_stream_connect ppw_stream_connect
293 #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
294 #define pw_stream_destroy ppw_stream_destroy
295 #define pw_stream_get_state ppw_stream_get_state
296 #define pw_stream_new ppw_stream_new
297 #define pw_stream_queue_buffer ppw_stream_queue_buffer
298 #define pw_stream_set_active ppw_stream_set_active
299 #define pw_thread_loop_destroy ppw_thread_loop_destroy
300 #define pw_thread_loop_get_loop ppw_thread_loop_get_loop
301 #define pw_thread_loop_lock ppw_thread_loop_lock
302 #define pw_thread_loop_new ppw_thread_loop_new
303 #define pw_thread_loop_signal ppw_thread_loop_signal
304 #define pw_thread_loop_start ppw_thread_loop_start
305 #define pw_thread_loop_stop ppw_thread_loop_stop
306 #define pw_thread_loop_unlock ppw_thread_loop_unlock
307 #define pw_thread_loop_wait ppw_thread_loop_wait
308 #if PW_CHECK_VERSION(0,3,50)
309 #define pw_stream_get_time_n ppw_stream_get_time_n
310 #else
311 inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
312 { return ppw_stream_get_time(stream, ptime); }
313 #endif
314 #endif
316 #else
318 constexpr bool pwire_load() { return true; }
319 #endif
321 /* Helpers for retrieving values from params */
322 template<uint32_t T> struct PodInfo { };
324 template<>
325 struct PodInfo<SPA_TYPE_Int> {
326 using Type = int32_t;
327 static auto get_value(const spa_pod *pod, int32_t *val)
328 { return spa_pod_get_int(pod, val); }
330 template<>
331 struct PodInfo<SPA_TYPE_Id> {
332 using Type = uint32_t;
333 static auto get_value(const spa_pod *pod, uint32_t *val)
334 { return spa_pod_get_id(pod, val); }
337 template<uint32_t T>
338 using Pod_t = typename PodInfo<T>::Type;
340 template<uint32_t T>
341 al::span<const Pod_t<T>> get_array_span(const spa_pod *pod)
343 uint32_t nvals{};
344 if(void *v{spa_pod_get_array(pod, &nvals)})
346 if(get_array_value_type(pod) == T)
347 return {static_cast<const Pod_t<T>*>(v), nvals};
349 return {};
352 template<uint32_t T>
353 std::optional<Pod_t<T>> get_value(const spa_pod *value)
355 Pod_t<T> val{};
356 if(PodInfo<T>::get_value(value, &val) == 0)
357 return val;
358 return std::nullopt;
361 /* Internally, PipeWire types "inherit" from each other, but this is hidden
362 * from the API and the caller is expected to C-style cast to inherited types
363 * as needed. It's also not made very clear what types a given type can be
364 * casted to. To make it a bit safer, this as() method allows casting pw_*
365 * types to known inherited types, generating a compile-time error for
366 * unexpected/invalid casts.
368 template<typename To, typename From>
369 To as(From) noexcept = delete;
371 /* pw_proxy
372 * - pw_registry
373 * - pw_node
374 * - pw_metadata
376 template<>
377 pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); }
378 template<>
379 pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); }
380 template<>
381 pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); }
384 struct PwContextDeleter {
385 void operator()(pw_context *context) const { pw_context_destroy(context); }
387 using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>;
389 struct PwCoreDeleter {
390 void operator()(pw_core *core) const { pw_core_disconnect(core); }
392 using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>;
394 struct PwRegistryDeleter {
395 void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); }
397 using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>;
399 struct PwNodeDeleter {
400 void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); }
402 using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>;
404 struct PwMetadataDeleter {
405 void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); }
407 using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>;
409 struct PwStreamDeleter {
410 void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
412 using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
414 /* Enums for bitflags... again... *sigh* */
415 constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
416 { return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
418 constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
419 { lhs = lhs | rhs; return lhs; }
421 class ThreadMainloop {
422 pw_thread_loop *mLoop{};
424 public:
425 ThreadMainloop() = default;
426 ThreadMainloop(const ThreadMainloop&) = delete;
427 ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
428 explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
429 ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
431 ThreadMainloop& operator=(const ThreadMainloop&) = delete;
432 ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
433 { std::swap(mLoop, rhs.mLoop); return *this; }
434 ThreadMainloop& operator=(std::nullptr_t) noexcept
436 if(mLoop)
437 pw_thread_loop_destroy(mLoop);
438 mLoop = nullptr;
439 return *this;
442 explicit operator bool() const noexcept { return mLoop != nullptr; }
444 [[nodiscard]]
445 auto start() const { return pw_thread_loop_start(mLoop); }
446 auto stop() const { return pw_thread_loop_stop(mLoop); }
448 [[nodiscard]]
449 auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
451 auto lock() const { return pw_thread_loop_lock(mLoop); }
452 auto unlock() const { return pw_thread_loop_unlock(mLoop); }
454 auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
456 auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) const
457 { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; }
459 static auto Create(const char *name, spa_dict *props=nullptr)
460 { return ThreadMainloop{pw_thread_loop_new(name, props)}; }
462 friend struct MainloopUniqueLock;
464 struct MainloopUniqueLock : public std::unique_lock<ThreadMainloop> {
465 using std::unique_lock<ThreadMainloop>::unique_lock;
466 MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
468 auto wait() const -> void
469 { pw_thread_loop_wait(mutex()->mLoop); }
471 template<typename Predicate>
472 auto wait(Predicate done_waiting) const -> void
473 { while(!done_waiting()) wait(); }
475 using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
478 /* There's quite a mess here, but the purpose is to track active devices and
479 * their default formats, so playback devices can be configured to match. The
480 * device list is updated asynchronously, so it will have the latest list of
481 * devices provided by the server.
484 /* A generic PipeWire node proxy object used to track changes to sink and
485 * source nodes.
487 struct NodeProxy {
488 static constexpr pw_node_events CreateNodeEvents()
490 pw_node_events ret{};
491 ret.version = PW_VERSION_NODE_EVENTS;
492 ret.info = infoCallback;
493 ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept
494 { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); };
495 return ret;
498 uint32_t mId{};
500 PwNodePtr mNode{};
501 spa_hook mListener{};
503 NodeProxy(uint32_t id, PwNodePtr node)
504 : mId{id}, mNode{std::move(node)}
506 static constexpr pw_node_events nodeEvents{CreateNodeEvents()};
507 ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this);
509 /* Track changes to the enumerable and current formats (indicates the
510 * default and active format, which is what we're interested in).
512 std::array<uint32_t,2> fmtids{{SPA_PARAM_EnumFormat, SPA_PARAM_Format}};
513 ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size());
515 ~NodeProxy()
516 { spa_hook_remove(&mListener); }
519 static void infoCallback(void *object, const pw_node_info *info) noexcept;
520 void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) const noexcept;
523 /* A metadata proxy object used to query the default sink and source. */
524 struct MetadataProxy {
525 static constexpr pw_metadata_events CreateMetadataEvents()
527 pw_metadata_events ret{};
528 ret.version = PW_VERSION_METADATA_EVENTS;
529 ret.property = propertyCallback;
530 return ret;
533 uint32_t mId{};
535 PwMetadataPtr mMetadata{};
536 spa_hook mListener{};
538 MetadataProxy(uint32_t id, PwMetadataPtr mdata)
539 : mId{id}, mMetadata{std::move(mdata)}
541 static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()};
542 ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this);
544 ~MetadataProxy()
545 { spa_hook_remove(&mListener); }
547 static auto propertyCallback(void *object, uint32_t id, const char *key, const char *type,
548 const char *value) noexcept -> int;
552 /* The global thread watching for global events. This particular class responds
553 * to objects being added to or removed from the registry.
555 struct EventManager {
556 ThreadMainloop mLoop{};
557 PwContextPtr mContext{};
558 PwCorePtr mCore{};
559 PwRegistryPtr mRegistry{};
560 spa_hook mRegistryListener{};
561 spa_hook mCoreListener{};
563 /* A list of proxy objects watching for events about changes to objects in
564 * the registry.
566 std::vector<std::unique_ptr<NodeProxy>> mNodeList;
567 std::optional<MetadataProxy> mDefaultMetadata;
569 /* Initialization handling. When init() is called, mInitSeq is set to a
570 * SequenceID that marks the end of populating the registry. As objects of
571 * interest are found, events to parse them are generated and mInitSeq is
572 * updated with a newer ID. When mInitSeq stops being updated and the event
573 * corresponding to it is reached, mInitDone will be set to true.
575 std::atomic<bool> mInitDone{false};
576 std::atomic<bool> mHasAudio{false};
577 int mInitSeq{};
579 ~EventManager() { if(mLoop) mLoop.stop(); }
581 bool init();
583 void kill();
585 auto lock() const { return mLoop.lock(); }
586 auto unlock() const { return mLoop.unlock(); }
588 [[nodiscard]] inline
589 bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept
590 { return mInitDone.load(m); }
593 * Waits for initialization to finish. The event manager must *NOT* be
594 * locked when calling this.
596 void waitForInit()
598 if(!initIsDone(std::memory_order_acquire)) UNLIKELY
600 MainloopUniqueLock plock{mLoop};
601 plock.wait([this](){ return initIsDone(std::memory_order_acquire); });
606 * Waits for audio support to be detected, or initialization to finish,
607 * whichever is first. Returns true if audio support was detected. The
608 * event manager must *NOT* be locked when calling this.
610 bool waitForAudio()
612 MainloopUniqueLock plock{mLoop};
613 bool has_audio{};
614 plock.wait([this,&has_audio]()
616 has_audio = mHasAudio.load(std::memory_order_acquire);
617 return has_audio || initIsDone(std::memory_order_acquire);
619 return has_audio;
622 void syncInit()
624 /* If initialization isn't done, update the sequence ID so it won't
625 * complete until after currently scheduled events.
627 if(!initIsDone(std::memory_order_relaxed))
628 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq);
631 void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version,
632 const spa_dict *props) noexcept;
634 void removeCallback(uint32_t id) noexcept;
636 static constexpr pw_registry_events CreateRegistryEvents()
638 pw_registry_events ret{};
639 ret.version = PW_VERSION_REGISTRY_EVENTS;
640 ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept
641 { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); };
642 ret.global_remove = [](void *object, uint32_t id) noexcept
643 { static_cast<EventManager*>(object)->removeCallback(id); };
644 return ret;
647 void coreCallback(uint32_t id, int seq) noexcept;
649 static constexpr pw_core_events CreateCoreEvents()
651 pw_core_events ret{};
652 ret.version = PW_VERSION_CORE_EVENTS;
653 ret.done = [](void *object, uint32_t id, int seq) noexcept
654 { static_cast<EventManager*>(object)->coreCallback(id, seq); };
655 return ret;
658 using EventWatcherUniqueLock = std::unique_lock<EventManager>;
659 using EventWatcherLockGuard = std::lock_guard<EventManager>;
661 EventManager gEventHandler;
663 /* Enumerated devices. This is updated asynchronously as the app runs, and the
664 * gEventHandler thread loop must be locked when accessing the list.
666 enum class NodeType : unsigned char {
667 Sink, Source, Duplex
669 constexpr auto InvalidChannelConfig = DevFmtChannels(255);
670 struct DeviceNode {
671 uint32_t mId{};
673 uint64_t mSerial{};
674 std::string mName;
675 std::string mDevName;
677 NodeType mType{};
678 bool mIsHeadphones{};
679 bool mIs51Rear{};
681 uint mSampleRate{};
682 DevFmtChannels mChannels{InvalidChannelConfig};
684 static std::vector<DeviceNode> sList;
685 static DeviceNode &Add(uint32_t id);
686 static DeviceNode *Find(uint32_t id);
687 static DeviceNode *FindByDevName(std::string_view devname);
688 static void Remove(uint32_t id);
689 static auto GetList() noexcept { return al::span{sList}; }
691 void parseSampleRate(const spa_pod *value, bool force_update) noexcept;
692 void parsePositions(const spa_pod *value, bool force_update) noexcept;
693 void parseChannelCount(const spa_pod *value, bool force_update) noexcept;
695 void callEvent(alc::EventType type, std::string_view message) const
697 /* Source nodes aren't recognized for playback, only Sink and Duplex
698 * nodes are. All node types are recognized for capture.
700 if(mType != NodeType::Source)
701 alc::Event(type, alc::DeviceType::Playback, message);
702 alc::Event(type, alc::DeviceType::Capture, message);
705 std::vector<DeviceNode> DeviceNode::sList;
706 std::string DefaultSinkDevice;
707 std::string DefaultSourceDevice;
709 const char *AsString(NodeType type) noexcept
711 switch(type)
713 case NodeType::Sink: return "sink";
714 case NodeType::Source: return "source";
715 case NodeType::Duplex: return "duplex";
717 return "<unknown>";
720 DeviceNode &DeviceNode::Add(uint32_t id)
722 auto match_id = [id](DeviceNode &n) noexcept -> bool
723 { return n.mId == id; };
725 /* If the node is already in the list, return the existing entry. */
726 auto match = std::find_if(sList.begin(), sList.end(), match_id);
727 if(match != sList.end()) return *match;
729 auto &n = sList.emplace_back();
730 n.mId = id;
731 return n;
734 DeviceNode *DeviceNode::Find(uint32_t id)
736 auto match_id = [id](DeviceNode &n) noexcept -> bool
737 { return n.mId == id; };
739 auto match = std::find_if(sList.begin(), sList.end(), match_id);
740 if(match != sList.end()) return al::to_address(match);
742 return nullptr;
745 DeviceNode *DeviceNode::FindByDevName(std::string_view devname)
747 auto match_id = [devname](DeviceNode &n) noexcept -> bool
748 { return n.mDevName == devname; };
750 auto match = std::find_if(sList.begin(), sList.end(), match_id);
751 if(match != sList.end()) return al::to_address(match);
753 return nullptr;
756 void DeviceNode::Remove(uint32_t id)
758 auto match_id = [id](DeviceNode &n) noexcept -> bool
760 if(n.mId != id)
761 return false;
762 TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
763 if(gEventHandler.initIsDone(std::memory_order_relaxed))
765 const std::string msg{"Device removed: "+n.mName};
766 n.callEvent(alc::EventType::DeviceRemoved, msg);
768 return true;
771 auto end = std::remove_if(sList.begin(), sList.end(), match_id);
772 sList.erase(end, sList.end());
776 constexpr std::array MonoMap{
777 SPA_AUDIO_CHANNEL_MONO
779 constexpr std::array StereoMap{
780 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
782 constexpr std::array QuadMap{
783 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
785 constexpr std::array X51Map{
786 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
787 SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
789 constexpr std::array X51RearMap{
790 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
791 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
793 constexpr std::array X61Map{
794 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
795 SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
797 constexpr std::array X71Map{
798 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
799 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
801 constexpr std::array X714Map{
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,
804 SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
808 * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
809 * to or a superset of map1).
811 bool MatchChannelMap(const al::span<const uint32_t> map0,
812 const al::span<const spa_audio_channel> map1)
814 if(map0.size() < map1.size())
815 return false;
817 auto find_channel = [map0](const spa_audio_channel chid) -> bool
818 { return std::find(map0.begin(), map0.end(), chid) != map0.end(); };
819 return std::all_of(map1.cbegin(), map1.cend(), find_channel);
822 void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept
824 /* TODO: Can this be anything else? Long, Float, Double? */
825 uint32_t nvals{}, choiceType{};
826 value = spa_pod_get_values(value, &nvals, &choiceType);
828 const uint podType{get_pod_type(value)};
829 if(podType != SPA_TYPE_Int)
831 WARN(" Unhandled sample rate POD type: %u\n", podType);
832 return;
835 if(choiceType == SPA_CHOICE_Range)
837 if(nvals != 3)
839 WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals);
840 return;
842 auto srates = get_pod_body<int32_t,3>(value);
844 /* [0] is the default, [1] is the min, and [2] is the max. */
845 TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]);
846 if(!mSampleRate || force_update)
847 mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
848 MaxOutputRate));
849 return;
852 if(choiceType == SPA_CHOICE_Enum)
854 if(nvals == 0)
856 WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals);
857 return;
859 auto srates = get_pod_body<int32_t>(value, nvals);
861 /* [0] is the default, [1...size()-1] are available selections. */
862 std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}};
863 for(size_t i{2};i < srates.size();++i)
865 others += ", ";
866 others += std::to_string(srates[i]);
868 TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str());
869 /* Pick the first rate listed that's within the allowed range (default
870 * rate if possible).
872 for(const auto &rate : srates)
874 if(rate >= int{MinOutputRate} && rate <= int{MaxOutputRate})
876 if(!mSampleRate || force_update)
877 mSampleRate = static_cast<uint>(rate);
878 break;
881 return;
884 if(choiceType == SPA_CHOICE_None)
886 if(nvals != 1)
888 WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals);
889 return;
891 auto srates = get_pod_body<int32_t,1>(value);
893 TRACE(" sample rate: %d\n", srates[0]);
894 if(!mSampleRate || force_update)
895 mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
896 MaxOutputRate));
897 return;
900 WARN(" Unhandled sample rate choice type: %u\n", choiceType);
903 void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept
905 uint32_t choiceCount{}, choiceType{};
906 value = spa_pod_get_values(value, &choiceCount, &choiceType);
908 if(choiceType != SPA_CHOICE_None || choiceCount != 1)
910 ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
911 return;
914 const auto chanmap = get_array_span<SPA_TYPE_Id>(value);
915 if(chanmap.empty()) return;
917 if(mChannels == InvalidChannelConfig || force_update)
919 mIs51Rear = false;
921 if(MatchChannelMap(chanmap, X714Map))
922 mChannels = DevFmtX714;
923 else if(MatchChannelMap(chanmap, X71Map))
924 mChannels = DevFmtX71;
925 else if(MatchChannelMap(chanmap, X61Map))
926 mChannels = DevFmtX61;
927 else if(MatchChannelMap(chanmap, X51Map))
928 mChannels = DevFmtX51;
929 else if(MatchChannelMap(chanmap, X51RearMap))
931 mChannels = DevFmtX51;
932 mIs51Rear = true;
934 else if(MatchChannelMap(chanmap, QuadMap))
935 mChannels = DevFmtQuad;
936 else if(MatchChannelMap(chanmap, StereoMap))
937 mChannels = DevFmtStereo;
938 else
939 mChannels = DevFmtMono;
941 TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s",
942 DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
945 void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept
947 /* As a fallback with just a channel count, just assume mono or stereo. */
948 uint32_t choiceCount{}, choiceType{};
949 value = spa_pod_get_values(value, &choiceCount, &choiceType);
951 if(choiceType != SPA_CHOICE_None || choiceCount != 1)
953 ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
954 return;
957 const auto chancount = get_value<SPA_TYPE_Int>(value);
958 if(!chancount) return;
960 if(mChannels == InvalidChannelConfig || force_update)
962 mIs51Rear = false;
964 if(*chancount >= 2)
965 mChannels = DevFmtStereo;
966 else if(*chancount >= 1)
967 mChannels = DevFmtMono;
969 TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s",
970 DevFmtChannelsString(mChannels));
974 [[nodiscard]] constexpr auto GetMonitorPrefix() noexcept { return "Monitor of "sv; }
975 [[nodiscard]] constexpr auto GetMonitorSuffix() noexcept { return ".monitor"sv; }
976 [[nodiscard]] constexpr auto GetAudioSinkClassName() noexcept { return "Audio/Sink"sv; }
977 [[nodiscard]] constexpr auto GetAudioSourceClassName() noexcept { return "Audio/Source"sv; }
978 [[nodiscard]] constexpr auto GetAudioDuplexClassName() noexcept { return "Audio/Duplex"sv; }
979 [[nodiscard]] constexpr auto GetAudioSourceVirtualClassName() noexcept
980 { return "Audio/Source/Virtual"sv; }
983 void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
985 /* We only care about property changes here (media class, name/desc).
986 * Format changes will automatically invoke the param callback.
988 * TODO: Can the media class or name/desc change without being removed and
989 * readded?
991 if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS))
993 /* Can this actually change? */
994 const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
995 if(!media_class) UNLIKELY return;
996 const std::string_view className{media_class};
998 NodeType ntype{};
999 if(al::case_compare(className, GetAudioSinkClassName()) == 0)
1000 ntype = NodeType::Sink;
1001 else if(al::case_compare(className, GetAudioSourceClassName()) == 0
1002 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0)
1003 ntype = NodeType::Source;
1004 else if(al::case_compare(className, GetAudioDuplexClassName()) == 0)
1005 ntype = NodeType::Duplex;
1006 else
1008 TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
1009 DeviceNode::Remove(info->id);
1010 return;
1013 const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)};
1014 const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)};
1015 if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
1016 if(!nodeName || !*nodeName) nodeName = devName;
1018 uint64_t serial_id{info->id};
1019 #ifdef PW_KEY_OBJECT_SERIAL
1020 if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
1022 errno = 0;
1023 char *serial_end{};
1024 serial_id = std::strtoull(serial_str, &serial_end, 0);
1025 if(*serial_end != '\0' || errno == ERANGE)
1027 ERR("Unexpected object serial: %s\n", serial_str);
1028 serial_id = info->id;
1031 #endif
1033 std::string name;
1034 if(nodeName && *nodeName) name = nodeName;
1035 else name = "PipeWire node #"+std::to_string(info->id);
1037 const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
1038 TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
1039 form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
1040 TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id);
1042 DeviceNode &node = DeviceNode::Add(info->id);
1043 node.mSerial = serial_id;
1044 /* This method is called both to notify about a new sink/source node,
1045 * and update properties for the node. It's unclear what properties can
1046 * change for an existing node without being removed first, so err on
1047 * the side of caution: send a DeviceRemoved event if it had a name
1048 * that's being changed, and send a DeviceAdded event when the name
1049 * differs or it didn't have one.
1051 * The DeviceRemoved event needs to be called before the potentially
1052 * new NodeType is set, so the removal event is called for the previous
1053 * device type, while the DeviceAdded event needs to be called after.
1055 * This is overkill if the node type, name, and devname can't change.
1057 bool notifyAdd{false};
1058 if(node.mName != name)
1060 if(gEventHandler.initIsDone(std::memory_order_relaxed))
1062 if(!node.mName.empty())
1064 const std::string msg{"Device removed: "+node.mName};
1065 node.callEvent(alc::EventType::DeviceRemoved, msg);
1067 notifyAdd = true;
1069 node.mName = std::move(name);
1071 node.mDevName = devName ? devName : "";
1072 node.mType = ntype;
1073 node.mIsHeadphones = form_factor && (al::case_compare(form_factor, "headphones"sv) == 0
1074 || al::case_compare(form_factor, "headset"sv) == 0);
1075 if(notifyAdd)
1077 const std::string msg{"Device added: "+node.mName};
1078 node.callEvent(alc::EventType::DeviceAdded, msg);
1083 void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) const noexcept
1085 if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format)
1087 DeviceNode *node{DeviceNode::Find(mId)};
1088 if(!node) UNLIKELY return;
1090 TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial,
1091 (id == SPA_PARAM_EnumFormat) ? "available" : "current",
1092 (id == SPA_PARAM_EnumFormat) ? "s" : "");
1094 const bool force_update{id == SPA_PARAM_Format};
1095 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
1096 node->parseSampleRate(&prop->value, force_update);
1098 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)})
1099 node->parsePositions(&prop->value, force_update);
1100 else
1102 prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels);
1103 if(prop) node->parseChannelCount(&prop->value, force_update);
1109 auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const char *type,
1110 const char *value) noexcept -> int
1112 if(id != PW_ID_CORE)
1113 return 0;
1115 bool isCapture{};
1116 if("default.audio.sink"sv == key)
1117 isCapture = false;
1118 else if("default.audio.source"sv == key)
1119 isCapture = true;
1120 else
1121 return 0;
1123 if(!type)
1125 TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
1126 if(!isCapture) DefaultSinkDevice.clear();
1127 else DefaultSourceDevice.clear();
1128 return 0;
1130 if("Spa:String:JSON"sv != type)
1132 ERR("Unexpected %s property type: %s\n", key, type);
1133 return 0;
1136 std::array<spa_json,2> it{};
1137 spa_json_init(it.data(), value, strlen(value));
1138 if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0)
1139 return 0;
1141 auto get_json_string = [](spa_json *iter)
1143 std::optional<std::string> str;
1145 const char *val{};
1146 int len{spa_json_next(iter, &val)};
1147 if(len <= 0) return str;
1149 str.emplace(static_cast<uint>(len), '\0');
1150 if(spa_json_parse_string(val, len, str->data()) <= 0)
1151 str.reset();
1152 else while(!str->empty() && str->back() == '\0')
1153 str->pop_back();
1154 return str;
1156 while(auto propKey = get_json_string(&std::get<1>(it)))
1158 if("name"sv == *propKey)
1160 auto propValue = get_json_string(&std::get<1>(it));
1161 if(!propValue) break;
1163 TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
1164 propValue->c_str());
1165 if(!isCapture && DefaultSinkDevice != *propValue)
1167 if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
1169 auto entry = DeviceNode::FindByDevName(*propValue);
1170 const std::string message{"Default playback device changed: "+
1171 (entry ? entry->mName : std::string{})};
1172 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
1173 message);
1175 DefaultSinkDevice = std::move(*propValue);
1177 else if(isCapture && DefaultSourceDevice != *propValue)
1179 if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
1181 auto entry = DeviceNode::FindByDevName(*propValue);
1182 const std::string message{"Default capture device changed: "+
1183 (entry ? entry->mName : std::string{})};
1184 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
1185 message);
1187 DefaultSourceDevice = std::move(*propValue);
1190 else
1192 const char *v{};
1193 if(spa_json_next(&std::get<1>(it), &v) <= 0)
1194 break;
1197 return 0;
1201 bool EventManager::init()
1203 mLoop = ThreadMainloop::Create("PWEventThread");
1204 if(!mLoop)
1206 ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
1207 return false;
1210 mContext = mLoop.newContext();
1211 if(!mContext)
1213 ERR("Failed to create PipeWire event context (errno: %d)\n", errno);
1214 return false;
1217 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1218 if(!mCore)
1220 ERR("Failed to connect PipeWire event context (errno: %d)\n", errno);
1221 return false;
1224 mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
1225 if(!mRegistry)
1227 ERR("Failed to get PipeWire event registry (errno: %d)\n", errno);
1228 return false;
1231 static constexpr pw_core_events coreEvents{CreateCoreEvents()};
1232 static constexpr pw_registry_events registryEvents{CreateRegistryEvents()};
1234 ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this);
1235 ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, &registryEvents, this);
1237 /* Set an initial sequence ID for initialization, to trigger after the
1238 * registry is first populated.
1240 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0);
1242 if(int res{mLoop.start()})
1244 ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
1245 return false;
1248 return true;
1251 void EventManager::kill()
1253 if(mLoop) mLoop.stop();
1255 mDefaultMetadata.reset();
1256 mNodeList.clear();
1258 mRegistry = nullptr;
1259 mCore = nullptr;
1260 mContext = nullptr;
1261 mLoop = nullptr;
1264 void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version,
1265 const spa_dict *props) noexcept
1267 /* We're only interested in interface nodes. */
1268 if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
1270 const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)};
1271 if(!media_class) return;
1272 const std::string_view className{media_class};
1274 /* Specifically, audio sinks and sources (and duplexes). */
1275 const bool isGood{al::case_compare(className, GetAudioSinkClassName()) == 0
1276 || al::case_compare(className, GetAudioSourceClassName()) == 0
1277 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0
1278 || al::case_compare(className, GetAudioDuplexClassName()) == 0};
1279 if(!isGood)
1281 if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv))
1282 TRACE("Ignoring node class %s\n", media_class);
1283 return;
1286 /* Create the proxy object. */
1287 auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type,
1288 version, 0))};
1289 if(!node)
1291 ERR("Failed to create node proxy object (errno: %d)\n", errno);
1292 return;
1295 /* Initialize the NodeProxy to hold the node object, add it to the
1296 * active node list, and update the sync point.
1298 mNodeList.emplace_back(std::make_unique<NodeProxy>(id, std::move(node)));
1299 syncInit();
1301 /* Signal any waiters that we have found a source or sink for audio
1302 * support.
1304 if(!mHasAudio.exchange(true, std::memory_order_acq_rel))
1305 mLoop.signal(false);
1307 else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0)
1309 const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)};
1310 if(!data_class) return;
1312 if("default"sv != data_class)
1314 TRACE("Ignoring metadata \"%s\"\n", data_class);
1315 return;
1318 if(mDefaultMetadata)
1320 ERR("Duplicate default metadata\n");
1321 return;
1324 auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id,
1325 type, version, 0))};
1326 if(!mdata)
1328 ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
1329 return;
1332 mDefaultMetadata.emplace(id, std::move(mdata));
1333 syncInit();
1337 void EventManager::removeCallback(uint32_t id) noexcept
1339 DeviceNode::Remove(id);
1341 auto clear_node = [id](std::unique_ptr<NodeProxy> &node) noexcept
1342 { return node->mId == id; };
1343 auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node);
1344 mNodeList.erase(node_end, mNodeList.end());
1346 if(mDefaultMetadata && mDefaultMetadata->mId == id)
1347 mDefaultMetadata.reset();
1350 void EventManager::coreCallback(uint32_t id, int seq) noexcept
1352 if(id == PW_ID_CORE && seq == mInitSeq)
1354 /* Initialization done. Remove this callback and signal anyone that may
1355 * be waiting.
1357 spa_hook_remove(&mCoreListener);
1359 mInitDone.store(true);
1360 mLoop.signal(false);
1365 enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
1366 spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p)
1368 spa_audio_info_raw info{};
1369 if(use_f32p)
1371 device->FmtType = DevFmtFloat;
1372 info.format = SPA_AUDIO_FORMAT_F32P;
1374 else switch(device->FmtType)
1376 case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break;
1377 case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break;
1378 case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break;
1379 case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break;
1380 case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break;
1381 case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break;
1382 case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
1385 info.rate = device->Frequency;
1387 al::span<const spa_audio_channel> map{};
1388 switch(device->FmtChans)
1390 case DevFmtMono: map = MonoMap; break;
1391 case DevFmtStereo: map = StereoMap; break;
1392 case DevFmtQuad: map = QuadMap; break;
1393 case DevFmtX51:
1394 if(is51rear) map = X51RearMap;
1395 else map = X51Map;
1396 break;
1397 case DevFmtX61: map = X61Map; break;
1398 case DevFmtX71: map = X71Map; break;
1399 case DevFmtX714: map = X714Map; break;
1400 case DevFmtX3D71: map = X71Map; break;
1401 case DevFmtX7144:
1402 case DevFmtAmbi3D:
1403 info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
1404 info.channels = device->channelsFromFmt();
1405 break;
1407 if(!map.empty())
1409 info.channels = static_cast<uint32_t>(map.size());
1410 std::copy(map.begin(), map.end(), std::begin(info.position));
1413 return info;
1416 class PipeWirePlayback final : public BackendBase {
1417 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept;
1418 void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept;
1419 void outputCallback() noexcept;
1421 void open(std::string_view name) override;
1422 bool reset() override;
1423 void start() override;
1424 void stop() override;
1425 ClockLatency getClockLatency() override;
1427 uint64_t mTargetId{PwIdAny};
1428 nanoseconds mTimeBase{0};
1429 ThreadMainloop mLoop;
1430 PwContextPtr mContext;
1431 PwCorePtr mCore;
1432 PwStreamPtr mStream;
1433 spa_hook mStreamListener{};
1434 spa_io_rate_match *mRateMatch{};
1435 std::vector<void*> mChannelPtrs;
1437 static constexpr pw_stream_events CreateEvents()
1439 pw_stream_events ret{};
1440 ret.version = PW_VERSION_STREAM_EVENTS;
1441 ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept
1442 { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); };
1443 ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept
1444 { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); };
1445 ret.process = [](void *data) noexcept
1446 { static_cast<PipeWirePlayback*>(data)->outputCallback(); };
1447 return ret;
1450 public:
1451 PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
1452 ~PipeWirePlayback() final
1454 /* Stop the mainloop so the stream can be properly destroyed. */
1455 if(mLoop) mLoop.stop();
1460 void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept
1461 { mLoop.signal(false); }
1463 void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept
1465 switch(id)
1467 case SPA_IO_RateMatch:
1468 if(size >= sizeof(spa_io_rate_match))
1469 mRateMatch = static_cast<spa_io_rate_match*>(area);
1470 else
1471 mRateMatch = nullptr;
1472 break;
1476 void PipeWirePlayback::outputCallback() noexcept
1478 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1479 if(!pw_buf) UNLIKELY return;
1481 const al::span<spa_data> datas{pw_buf->buffer->datas,
1482 std::min(mChannelPtrs.size(), size_t{pw_buf->buffer->n_datas})};
1483 #if PW_CHECK_VERSION(0,3,49)
1484 /* In 0.3.49, pw_buffer::requested specifies the number of samples needed
1485 * by the resampler/graph for this audio update.
1487 uint length{static_cast<uint>(pw_buf->requested)};
1488 #else
1489 /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
1490 * of samples per update.
1492 uint length{mRateMatch ? mRateMatch->size : 0u};
1493 #endif
1494 /* If no length is specified, use the device's update size as a fallback. */
1495 if(!length) UNLIKELY length = mDevice->UpdateSize;
1497 /* For planar formats, each datas[] seems to contain one channel, so store
1498 * the pointers in an array. Limit the render length in case the available
1499 * buffer length in any one channel is smaller than we wanted (shouldn't
1500 * be, but just in case).
1502 auto chanptr_end = mChannelPtrs.begin();
1503 for(const auto &data : datas)
1505 length = std::min(length, data.maxsize/uint{sizeof(float)});
1506 *chanptr_end = data.data;
1507 ++chanptr_end;
1509 data.chunk->offset = 0;
1510 data.chunk->stride = sizeof(float);
1511 data.chunk->size = length * sizeof(float);
1514 mDevice->renderSamples(mChannelPtrs, length);
1516 pw_buf->size = length;
1517 pw_stream_queue_buffer(mStream.get(), pw_buf);
1521 void PipeWirePlayback::open(std::string_view name)
1523 static std::atomic<uint> OpenCount{0};
1525 uint64_t targetid{PwIdAny};
1526 std::string devname{};
1527 gEventHandler.waitForInit();
1528 if(name.empty())
1530 EventWatcherLockGuard evtlock{gEventHandler};
1531 auto&& devlist = DeviceNode::GetList();
1533 auto match = devlist.cend();
1534 if(!DefaultSinkDevice.empty())
1536 auto match_default = [](const DeviceNode &n) -> bool
1537 { return n.mDevName == DefaultSinkDevice; };
1538 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1540 if(match == devlist.cend())
1542 auto match_playback = [](const DeviceNode &n) -> bool
1543 { return n.mType != NodeType::Source; };
1544 match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback);
1545 if(match == devlist.cend())
1546 throw al::backend_exception{al::backend_error::NoDevice,
1547 "No PipeWire playback device found"};
1550 targetid = match->mSerial;
1551 devname = match->mName;
1553 else
1555 EventWatcherLockGuard evtlock{gEventHandler};
1556 auto&& devlist = DeviceNode::GetList();
1558 auto match_name = [name](const DeviceNode &n) -> bool
1559 { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); };
1560 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
1561 if(match == devlist.cend())
1562 throw al::backend_exception{al::backend_error::NoDevice,
1563 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
1565 targetid = match->mSerial;
1566 devname = match->mName;
1569 if(!mLoop)
1571 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
1572 const std::string thread_name{"ALSoftP" + std::to_string(count)};
1573 mLoop = ThreadMainloop::Create(thread_name.c_str());
1574 if(!mLoop)
1575 throw al::backend_exception{al::backend_error::DeviceError,
1576 "Failed to create PipeWire mainloop (errno: %d)", errno};
1577 if(int res{mLoop.start()})
1578 throw al::backend_exception{al::backend_error::DeviceError,
1579 "Failed to start PipeWire mainloop (res: %d)", res};
1581 MainloopUniqueLock mlock{mLoop};
1582 if(!mContext)
1584 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
1585 mContext = mLoop.newContext(cprops);
1586 if(!mContext)
1587 throw al::backend_exception{al::backend_error::DeviceError,
1588 "Failed to create PipeWire event context (errno: %d)\n", errno};
1590 if(!mCore)
1592 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1593 if(!mCore)
1594 throw al::backend_exception{al::backend_error::DeviceError,
1595 "Failed to connect PipeWire event context (errno: %d)\n", errno};
1597 mlock.unlock();
1599 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1601 mTargetId = targetid;
1602 if(!devname.empty())
1603 mDevice->DeviceName = std::move(devname);
1604 else
1605 mDevice->DeviceName = "PipeWire Output"sv;
1608 bool PipeWirePlayback::reset()
1610 if(mStream)
1612 MainloopLockGuard looplock{mLoop};
1613 mStream = nullptr;
1615 mStreamListener = {};
1616 mRateMatch = nullptr;
1617 mTimeBase = mDevice->getClockTime();
1619 /* If connecting to a specific device, update various device parameters to
1620 * match its format.
1622 bool is51rear{false};
1623 mDevice->Flags.reset(DirectEar);
1624 if(mTargetId != PwIdAny)
1626 EventWatcherLockGuard evtlock{gEventHandler};
1627 auto&& devlist = DeviceNode::GetList();
1629 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
1630 { return targetid == n.mSerial; };
1631 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
1632 if(match != devlist.cend())
1634 if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
1636 /* Scale the update size if the sample rate changes. */
1637 const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
1638 const double updatesize{std::round(mDevice->UpdateSize * scale)};
1639 const double buffersize{std::round(mDevice->BufferSize * scale)};
1641 mDevice->Frequency = match->mSampleRate;
1642 mDevice->UpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0));
1643 mDevice->BufferSize = static_cast<uint>(std::max(buffersize, 128.0));
1645 if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
1646 mDevice->FmtChans = match->mChannels;
1647 if(match->mChannels == DevFmtStereo && match->mIsHeadphones)
1648 mDevice->Flags.set(DirectEar);
1649 is51rear = match->mIs51Rear;
1652 /* Force planar 32-bit float output for playback. This is what PipeWire
1653 * handles internally, and it's easier for us too.
1655 spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)};
1657 static constexpr uint32_t pod_buffer_size{1024};
1658 PodDynamicBuilder b(pod_buffer_size);
1660 const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)};
1661 if(!params)
1662 throw al::backend_exception{al::backend_error::DeviceError,
1663 "Failed to set PipeWire audio format parameters"};
1665 /* TODO: Which properties are actually needed here? Any others that could
1666 * be useful?
1668 auto&& binary = GetProcBinary();
1669 const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
1670 pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
1671 PW_KEY_NODE_DESCRIPTION, appname,
1672 PW_KEY_MEDIA_TYPE, "Audio",
1673 PW_KEY_MEDIA_CATEGORY, "Playback",
1674 PW_KEY_MEDIA_ROLE, "Game",
1675 PW_KEY_NODE_ALWAYS_PROCESS, "true",
1676 nullptr)};
1677 if(!props)
1678 throw al::backend_exception{al::backend_error::DeviceError,
1679 "Failed to create PipeWire stream properties (errno: %d)", errno};
1681 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
1682 mDevice->Frequency);
1683 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
1684 #ifdef PW_KEY_TARGET_OBJECT
1685 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
1686 #else
1687 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
1688 #endif
1690 MainloopUniqueLock plock{mLoop};
1691 /* The stream takes overship of 'props', even in the case of failure. */
1692 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
1693 if(!mStream)
1694 throw al::backend_exception{al::backend_error::NoDevice,
1695 "Failed to create PipeWire stream (errno: %d)", errno};
1696 static constexpr pw_stream_events streamEvents{CreateEvents()};
1697 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
1699 pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
1700 | PW_STREAM_FLAG_MAP_BUFFERS};
1701 if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false))
1702 flags |= PW_STREAM_FLAG_RT_PROCESS;
1703 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)})
1704 throw al::backend_exception{al::backend_error::DeviceError,
1705 "Error connecting PipeWire stream (res: %d)", res};
1707 /* Wait for the stream to become paused (ready to start streaming). */
1708 plock.wait([stream=mStream.get()]()
1710 const char *error{};
1711 pw_stream_state state{pw_stream_get_state(stream, &error)};
1712 if(state == PW_STREAM_STATE_ERROR)
1713 throw al::backend_exception{al::backend_error::DeviceError,
1714 "Error connecting PipeWire stream: \"%s\"", error};
1715 return state == PW_STREAM_STATE_PAUSED;
1718 /* TODO: Update mDevice->UpdateSize with the stream's quantum, and
1719 * mDevice->BufferSize with the total known buffering delay from the head
1720 * of this playback stream to the tail of the device output.
1722 * This info is apparently not available until after the stream starts.
1724 plock.unlock();
1726 mChannelPtrs.resize(mDevice->channelsFromFmt());
1728 setDefaultWFXChannelOrder();
1730 return true;
1733 void PipeWirePlayback::start()
1735 MainloopUniqueLock plock{mLoop};
1736 if(int res{pw_stream_set_active(mStream.get(), true)})
1737 throw al::backend_exception{al::backend_error::DeviceError,
1738 "Failed to start PipeWire stream (res: %d)", res};
1740 /* Wait for the stream to start playing (would be nice to not, but we need
1741 * the actual update size which is only available after starting).
1743 plock.wait([stream=mStream.get()]()
1745 const char *error{};
1746 pw_stream_state state{pw_stream_get_state(stream, &error)};
1747 if(state == PW_STREAM_STATE_ERROR)
1748 throw al::backend_exception{al::backend_error::DeviceError,
1749 "PipeWire stream error: %s", error ? error : "(unknown)"};
1750 return state == PW_STREAM_STATE_STREAMING;
1753 /* HACK: Try to work out the update size and total buffering size. There's
1754 * no actual query for this, so we have to work it out from the stream time
1755 * info, and assume it stays accurate with future updates. The stream time
1756 * info may also not be available right away, so we have to wait until it
1757 * is (up to about 2 seconds).
1759 int wait_count{100};
1760 do {
1761 pw_time ptime{};
1762 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1764 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1765 break;
1768 /* The rate match size is the update size for each buffer. */
1769 const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
1770 #if PW_CHECK_VERSION(0,3,50)
1771 /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
1772 * queue size.
1774 if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
1776 const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
1778 /* Ensure the delay is in sample frames. */
1779 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1780 ptime.rate.num / ptime.rate.denom};
1782 mDevice->UpdateSize = updatesize;
1783 mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
1784 uint64_t{totalbuffers}*updatesize);
1785 break;
1787 #else
1788 /* Prior to 0.3.50, we can only measure the delay with the update size,
1789 * assuming one buffer and no resample buffering.
1791 if(ptime.rate.denom > 0 && updatesize > 0)
1793 /* Ensure the delay is in sample frames. */
1794 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1795 ptime.rate.num / ptime.rate.denom};
1797 mDevice->UpdateSize = updatesize;
1798 mDevice->BufferSize = static_cast<uint>(delay + updatesize);
1799 break;
1801 #endif
1802 if(!--wait_count)
1804 ERR("Timeout getting PipeWire stream buffering info\n");
1805 break;
1808 plock.unlock();
1809 std::this_thread::sleep_for(milliseconds{20});
1810 plock.lock();
1811 } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
1814 void PipeWirePlayback::stop()
1816 MainloopUniqueLock plock{mLoop};
1817 if(int res{pw_stream_set_active(mStream.get(), false)})
1818 ERR("Failed to stop PipeWire stream (res: %d)\n", res);
1820 /* Wait for the stream to stop playing. */
1821 plock.wait([stream=mStream.get()]()
1822 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
1825 ClockLatency PipeWirePlayback::getClockLatency()
1827 /* Given a real-time low-latency output, this is rather complicated to get
1828 * accurate timing. So, here we go.
1831 /* First, get the stream time info (tick delay, ticks played, and the
1832 * CLOCK_MONOTONIC time closest to when that last tick was played).
1834 pw_time ptime{};
1835 if(mStream)
1837 MainloopLockGuard looplock{mLoop};
1838 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1839 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1842 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1843 * monotonic clock closest to 'now', and the last mixer time at 'now').
1845 nanoseconds mixtime{};
1846 timespec tspec{};
1847 uint refcount;
1848 do {
1849 refcount = mDevice->waitForMix();
1850 mixtime = mDevice->getClockTime();
1851 clock_gettime(CLOCK_MONOTONIC, &tspec);
1852 std::atomic_thread_fence(std::memory_order_acquire);
1853 } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
1855 /* Convert the monotonic clock, stream ticks, and stream delay to
1856 * nanoseconds.
1858 nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
1859 nanoseconds curtic{}, delay{};
1860 if(ptime.rate.denom < 1) UNLIKELY
1862 /* If there's no stream rate, the stream hasn't had a chance to get
1863 * going and return time info yet. Just use dummy values.
1865 ptime.now = monoclock.count();
1866 curtic = mixtime;
1867 delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
1869 else
1871 /* The stream gets recreated with each reset, so include the time that
1872 * had already passed with previous streams.
1874 curtic = mTimeBase;
1875 /* More safely scale the ticks to avoid overflowing the pre-division
1876 * temporary as it gets larger.
1878 curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
1879 curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
1880 ptime.rate.denom;
1882 /* The delay should be small enough to not worry about overflow. */
1883 delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
1886 /* If the mixer time is ahead of the stream time, there's that much more
1887 * delay relative to the stream delay.
1889 if(mixtime > curtic)
1890 delay += mixtime - curtic;
1891 /* Reduce the delay according to how much time has passed since the known
1892 * stream time. This isn't 100% accurate since the system monotonic clock
1893 * doesn't tick at the exact same rate as the audio device, but it should
1894 * be good enough with ptime.now being constantly updated every few
1895 * milliseconds with ptime.ticks.
1897 delay -= monoclock - nanoseconds{ptime.now};
1899 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1900 * in case timer drift got that severe.
1902 ClockLatency ret{};
1903 ret.ClockTime = mixtime;
1904 ret.Latency = std::max(delay, nanoseconds{});
1906 return ret;
1910 class PipeWireCapture final : public BackendBase {
1911 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept;
1912 void inputCallback() noexcept;
1914 void open(std::string_view name) override;
1915 void start() override;
1916 void stop() override;
1917 void captureSamples(std::byte *buffer, uint samples) override;
1918 uint availableSamples() override;
1920 uint64_t mTargetId{PwIdAny};
1921 ThreadMainloop mLoop;
1922 PwContextPtr mContext;
1923 PwCorePtr mCore;
1924 PwStreamPtr mStream;
1925 spa_hook mStreamListener{};
1927 RingBufferPtr mRing{};
1929 static constexpr pw_stream_events CreateEvents()
1931 pw_stream_events ret{};
1932 ret.version = PW_VERSION_STREAM_EVENTS;
1933 ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept
1934 { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); };
1935 ret.process = [](void *data) noexcept
1936 { static_cast<PipeWireCapture*>(data)->inputCallback(); };
1937 return ret;
1940 public:
1941 PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
1942 ~PipeWireCapture() final { if(mLoop) mLoop.stop(); }
1946 void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept
1947 { mLoop.signal(false); }
1949 void PipeWireCapture::inputCallback() noexcept
1951 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1952 if(!pw_buf) UNLIKELY return;
1954 spa_data *bufdata{pw_buf->buffer->datas};
1955 const uint offset{bufdata->chunk->offset % bufdata->maxsize};
1956 const auto input = al::span{static_cast<const char*>(bufdata->data), bufdata->maxsize}
1957 .subspan(offset, std::min(bufdata->chunk->size, bufdata->maxsize - offset));
1959 std::ignore = mRing->write(input.data(), input.size() / mRing->getElemSize());
1961 pw_stream_queue_buffer(mStream.get(), pw_buf);
1965 void PipeWireCapture::open(std::string_view name)
1967 static std::atomic<uint> OpenCount{0};
1969 uint64_t targetid{PwIdAny};
1970 std::string devname{};
1971 gEventHandler.waitForInit();
1972 if(name.empty())
1974 EventWatcherLockGuard evtlock{gEventHandler};
1975 auto&& devlist = DeviceNode::GetList();
1977 auto match = devlist.cend();
1978 if(!DefaultSourceDevice.empty())
1980 auto match_default = [](const DeviceNode &n) -> bool
1981 { return n.mDevName == DefaultSourceDevice; };
1982 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1984 if(match == devlist.cend())
1986 auto match_capture = [](const DeviceNode &n) -> bool
1987 { return n.mType != NodeType::Sink; };
1988 match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture);
1990 if(match == devlist.cend())
1992 match = devlist.cbegin();
1993 if(match == devlist.cend())
1994 throw al::backend_exception{al::backend_error::NoDevice,
1995 "No PipeWire capture device found"};
1998 targetid = match->mSerial;
1999 if(match->mType != NodeType::Sink) devname = match->mName;
2000 else devname = std::string{GetMonitorPrefix()}+match->mName;
2002 else
2004 EventWatcherLockGuard evtlock{gEventHandler};
2005 auto&& devlist = DeviceNode::GetList();
2006 const std::string_view prefix{GetMonitorPrefix()};
2007 const std::string_view suffix{GetMonitorSuffix()};
2009 auto match_name = [name](const DeviceNode &n) -> bool
2010 { return n.mType != NodeType::Sink && n.mName == name; };
2011 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
2012 if(match == devlist.cend() && al::starts_with(name, prefix))
2014 const std::string_view sinkname{name.substr(prefix.length())};
2015 auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
2016 { return n.mType == NodeType::Sink && n.mName == sinkname; };
2017 match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
2019 else if(match == devlist.cend() && al::ends_with(name, suffix))
2021 const std::string_view sinkname{name.substr(0, name.size()-suffix.size())};
2022 auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
2023 { return n.mType == NodeType::Sink && n.mDevName == sinkname; };
2024 match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
2026 if(match == devlist.cend())
2027 throw al::backend_exception{al::backend_error::NoDevice,
2028 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
2030 targetid = match->mSerial;
2031 if(match->mType != NodeType::Sink) devname = match->mName;
2032 else devname = std::string{GetMonitorPrefix()}+match->mName;
2035 if(!mLoop)
2037 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
2038 const std::string thread_name{"ALSoftC" + std::to_string(count)};
2039 mLoop = ThreadMainloop::Create(thread_name.c_str());
2040 if(!mLoop)
2041 throw al::backend_exception{al::backend_error::DeviceError,
2042 "Failed to create PipeWire mainloop (errno: %d)", errno};
2043 if(int res{mLoop.start()})
2044 throw al::backend_exception{al::backend_error::DeviceError,
2045 "Failed to start PipeWire mainloop (res: %d)", res};
2047 MainloopUniqueLock mlock{mLoop};
2048 if(!mContext)
2050 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
2051 mContext = mLoop.newContext(cprops);
2052 if(!mContext)
2053 throw al::backend_exception{al::backend_error::DeviceError,
2054 "Failed to create PipeWire event context (errno: %d)\n", errno};
2056 if(!mCore)
2058 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
2059 if(!mCore)
2060 throw al::backend_exception{al::backend_error::DeviceError,
2061 "Failed to connect PipeWire event context (errno: %d)\n", errno};
2063 mlock.unlock();
2065 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
2067 mTargetId = targetid;
2068 if(!devname.empty())
2069 mDevice->DeviceName = std::move(devname);
2070 else
2071 mDevice->DeviceName = "PipeWire Input"sv;
2074 bool is51rear{false};
2075 if(mTargetId != PwIdAny)
2077 EventWatcherLockGuard evtlock{gEventHandler};
2078 auto&& devlist = DeviceNode::GetList();
2080 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
2081 { return targetid == n.mSerial; };
2082 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
2083 if(match != devlist.cend())
2084 is51rear = match->mIs51Rear;
2086 spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)};
2088 static constexpr uint32_t pod_buffer_size{1024};
2089 PodDynamicBuilder b(pod_buffer_size);
2091 std::array params{static_cast<const spa_pod*>(spa_format_audio_raw_build(b.get(),
2092 SPA_PARAM_EnumFormat, &info))};
2093 if(!params[0])
2094 throw al::backend_exception{al::backend_error::DeviceError,
2095 "Failed to set PipeWire audio format parameters"};
2097 auto&& binary = GetProcBinary();
2098 const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
2099 pw_properties *props{pw_properties_new(
2100 PW_KEY_NODE_NAME, appname,
2101 PW_KEY_NODE_DESCRIPTION, appname,
2102 PW_KEY_MEDIA_TYPE, "Audio",
2103 PW_KEY_MEDIA_CATEGORY, "Capture",
2104 PW_KEY_MEDIA_ROLE, "Game",
2105 PW_KEY_NODE_ALWAYS_PROCESS, "true",
2106 nullptr)};
2107 if(!props)
2108 throw al::backend_exception{al::backend_error::DeviceError,
2109 "Failed to create PipeWire stream properties (errno: %d)", errno};
2111 /* We don't actually care what the latency/update size is, as long as it's
2112 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
2113 * around 40ms, which isn't great. So request 20ms instead.
2115 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
2116 mDevice->Frequency);
2117 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
2118 #ifdef PW_KEY_TARGET_OBJECT
2119 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
2120 #else
2121 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
2122 #endif
2124 MainloopUniqueLock plock{mLoop};
2125 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
2126 if(!mStream)
2127 throw al::backend_exception{al::backend_error::NoDevice,
2128 "Failed to create PipeWire stream (errno: %d)", errno};
2129 static constexpr pw_stream_events streamEvents{CreateEvents()};
2130 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
2132 constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
2133 | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
2134 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)})
2135 throw al::backend_exception{al::backend_error::DeviceError,
2136 "Error connecting PipeWire stream (res: %d)", res};
2138 /* Wait for the stream to become paused (ready to start streaming). */
2139 plock.wait([stream=mStream.get()]()
2141 const char *error{};
2142 pw_stream_state state{pw_stream_get_state(stream, &error)};
2143 if(state == PW_STREAM_STATE_ERROR)
2144 throw al::backend_exception{al::backend_error::DeviceError,
2145 "Error connecting PipeWire stream: \"%s\"", error};
2146 return state == PW_STREAM_STATE_PAUSED;
2148 plock.unlock();
2150 setDefaultWFXChannelOrder();
2152 /* Ensure at least a 100ms capture buffer. */
2153 mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize),
2154 mDevice->frameSizeFromFmt(), false);
2158 void PipeWireCapture::start()
2160 MainloopUniqueLock plock{mLoop};
2161 if(int res{pw_stream_set_active(mStream.get(), true)})
2162 throw al::backend_exception{al::backend_error::DeviceError,
2163 "Failed to start PipeWire stream (res: %d)", res};
2165 plock.wait([stream=mStream.get()]()
2167 const char *error{};
2168 pw_stream_state state{pw_stream_get_state(stream, &error)};
2169 if(state == PW_STREAM_STATE_ERROR)
2170 throw al::backend_exception{al::backend_error::DeviceError,
2171 "PipeWire stream error: %s", error ? error : "(unknown)"};
2172 return state == PW_STREAM_STATE_STREAMING;
2176 void PipeWireCapture::stop()
2178 MainloopUniqueLock plock{mLoop};
2179 if(int res{pw_stream_set_active(mStream.get(), false)})
2180 ERR("Failed to stop PipeWire stream (res: %d)\n", res);
2182 plock.wait([stream=mStream.get()]()
2183 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
2186 uint PipeWireCapture::availableSamples()
2187 { return static_cast<uint>(mRing->readSpace()); }
2189 void PipeWireCapture::captureSamples(std::byte *buffer, uint samples)
2190 { std::ignore = mRing->read(buffer, samples); }
2192 } // namespace
2195 bool PipeWireBackendFactory::init()
2197 if(!pwire_load())
2198 return false;
2200 const char *version{pw_get_library_version()};
2201 if(!check_version(version))
2203 WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
2204 pw_get_headers_version());
2205 return false;
2207 TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
2209 pw_init(nullptr, nullptr);
2210 if(!gEventHandler.init())
2211 return false;
2213 if(!GetConfigValueBool({}, "pipewire", "assume-audio", false)
2214 && !gEventHandler.waitForAudio())
2216 gEventHandler.kill();
2217 /* TODO: Temporary warning, until PipeWire gets a proper way to report
2218 * audio support.
2220 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
2221 return false;
2223 return true;
2226 bool PipeWireBackendFactory::querySupport(BackendType type)
2227 { return type == BackendType::Playback || type == BackendType::Capture; }
2229 auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
2231 std::vector<std::string> outnames;
2233 gEventHandler.waitForInit();
2234 EventWatcherLockGuard evtlock{gEventHandler};
2235 auto&& devlist = DeviceNode::GetList();
2237 auto match_defsink = [](const DeviceNode &n) -> bool
2238 { return n.mDevName == DefaultSinkDevice; };
2239 auto match_defsource = [](const DeviceNode &n) -> bool
2240 { return n.mDevName == DefaultSourceDevice; };
2242 auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool
2243 { return lhs.mId < rhs.mId; };
2244 std::sort(devlist.begin(), devlist.end(), sort_devnode);
2246 auto defmatch = devlist.cbegin();
2247 switch(type)
2249 case BackendType::Playback:
2250 defmatch = std::find_if(defmatch, devlist.cend(), match_defsink);
2251 if(defmatch != devlist.cend())
2252 outnames.emplace_back(defmatch->mName);
2253 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2255 if(iter != defmatch && iter->mType != NodeType::Source)
2256 outnames.emplace_back(iter->mName);
2258 break;
2259 case BackendType::Capture:
2260 outnames.reserve(devlist.size());
2261 defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
2262 if(defmatch != devlist.cend())
2264 if(defmatch->mType == NodeType::Sink)
2265 outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName);
2266 else
2267 outnames.emplace_back(defmatch->mName);
2269 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2271 if(iter != defmatch)
2273 if(iter->mType == NodeType::Sink)
2274 outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName);
2275 else
2276 outnames.emplace_back(iter->mName);
2279 break;
2282 return outnames;
2286 BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
2288 if(type == BackendType::Playback)
2289 return BackendPtr{new PipeWirePlayback{device}};
2290 if(type == BackendType::Capture)
2291 return BackendPtr{new PipeWireCapture{device}};
2292 return nullptr;
2295 BackendFactory &PipeWireBackendFactory::getFactory()
2297 static PipeWireBackendFactory factory{};
2298 return factory;
2301 alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
2303 switch(eventType)
2305 case alc::EventType::DefaultDeviceChanged:
2306 case alc::EventType::DeviceAdded:
2307 case alc::EventType::DeviceRemoved:
2308 return alc::EventSupport::FullSupport;
2310 case alc::EventType::Count:
2311 break;
2313 return alc::EventSupport::NoSupport;