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
36 #include <type_traits>
40 #include "alc/alconfig.h"
42 #include "alnumeric.h"
43 #include "aloptional.h"
46 #include "core/devformat.h"
47 #include "core/device.h"
48 #include "core/helpers.h"
49 #include "core/logging.h"
51 #include "opthelpers.h"
52 #include "ringbuffer.h"
54 /* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC
55 * doesn't support ignoring -Weverything, so we have the list the individual
56 * warnings to ignore (and ignoring -Winline doesn't seem to work).
58 _Pragma("GCC diagnostic push")
59 _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
60 _Pragma("GCC diagnostic ignored \"-Wconversion\"")
61 _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
62 _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
63 _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
64 _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
65 _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
66 _Pragma("GCC diagnostic ignored \"-Winline\"")
67 _Pragma("GCC diagnostic ignored \"-Wpragmas\"")
68 _Pragma("GCC diagnostic ignored \"-Weverything\"")
69 #include "pipewire/pipewire.h"
70 #include "pipewire/extensions/metadata.h"
71 #include "spa/buffer/buffer.h"
72 #include "spa/param/audio/format-utils.h"
73 #include "spa/param/audio/raw.h"
74 #include "spa/param/param.h"
75 #include "spa/pod/builder.h"
76 #include "spa/utils/json.h"
79 /* Wrap some nasty macros here too... */
80 template<typename ...Args>
81 auto ppw_core_add_listener(pw_core *core, Args&& ...args)
82 { return pw_core_add_listener(core, std::forward<Args>(args)...); }
83 template<typename ...Args>
84 auto ppw_core_sync(pw_core *core, Args&& ...args)
85 { return pw_core_sync(core, std::forward<Args>(args)...); }
86 template<typename ...Args>
87 auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args)
88 { return pw_registry_add_listener(reg, std::forward<Args>(args)...); }
89 template<typename ...Args>
90 auto ppw_node_add_listener(pw_node *node, Args&& ...args)
91 { return pw_node_add_listener(node, std::forward<Args>(args)...); }
92 template<typename ...Args>
93 auto ppw_node_subscribe_params(pw_node *node, Args&& ...args)
94 { return pw_node_subscribe_params(node, std::forward<Args>(args)...); }
95 template<typename ...Args>
96 auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args)
97 { return pw_metadata_add_listener(mdata, std::forward<Args>(args)...); }
100 constexpr auto get_pod_type(const spa_pod *pod) noexcept
101 { return SPA_POD_TYPE(pod); }
104 constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept
105 { return al::span<T>{static_cast<T*>(SPA_POD_BODY(pod)), count}; }
106 template<typename T, size_t N>
107 constexpr auto get_pod_body(const spa_pod *pod) noexcept
108 { return al::span<T,N>{static_cast<T*>(SPA_POD_BODY(pod)), N}; }
110 constexpr auto make_pod_builder(void *data, uint32_t size) noexcept
111 { return SPA_POD_BUILDER_INIT(data, size); }
113 constexpr auto get_array_value_type(const spa_pod *pod) noexcept
114 { return SPA_POD_ARRAY_VALUE_TYPE(pod); }
116 constexpr auto PwIdAny = PW_ID_ANY;
119 _Pragma("GCC diagnostic pop")
123 /* Added in 0.3.33, but we currently only require 0.3.23. */
124 #ifndef PW_KEY_NODE_RATE
125 #define PW_KEY_NODE_RATE "node.rate"
128 using std::chrono::seconds;
129 using std::chrono::milliseconds;
130 using std::chrono::nanoseconds;
131 using uint = unsigned int;
133 constexpr char pwireDevice[] = "PipeWire Output";
134 constexpr char pwireInput[] = "PipeWire Input";
137 bool check_version(const char *version)
139 /* There doesn't seem to be a function to get the version as an integer, so
140 * instead we have to parse the string, which hopefully won't break in the
143 int major{0}, minor{0}, revision{0};
144 int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
145 if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
146 || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)))
152 #define PWIRE_FUNCS(MAGIC) \
153 MAGIC(pw_context_connect) \
154 MAGIC(pw_context_destroy) \
155 MAGIC(pw_context_new) \
156 MAGIC(pw_core_disconnect) \
157 MAGIC(pw_get_library_version) \
159 MAGIC(pw_properties_free) \
160 MAGIC(pw_properties_new) \
161 MAGIC(pw_properties_set) \
162 MAGIC(pw_properties_setf) \
163 MAGIC(pw_proxy_add_object_listener) \
164 MAGIC(pw_proxy_destroy) \
165 MAGIC(pw_proxy_get_user_data) \
166 MAGIC(pw_stream_add_listener) \
167 MAGIC(pw_stream_connect) \
168 MAGIC(pw_stream_dequeue_buffer) \
169 MAGIC(pw_stream_destroy) \
170 MAGIC(pw_stream_get_state) \
171 MAGIC(pw_stream_new) \
172 MAGIC(pw_stream_queue_buffer) \
173 MAGIC(pw_stream_set_active) \
174 MAGIC(pw_thread_loop_new) \
175 MAGIC(pw_thread_loop_destroy) \
176 MAGIC(pw_thread_loop_get_loop) \
177 MAGIC(pw_thread_loop_start) \
178 MAGIC(pw_thread_loop_stop) \
179 MAGIC(pw_thread_loop_lock) \
180 MAGIC(pw_thread_loop_wait) \
181 MAGIC(pw_thread_loop_signal) \
182 MAGIC(pw_thread_loop_unlock)
183 #if PW_CHECK_VERSION(0,3,50)
184 #define PWIRE_FUNCS2(MAGIC) \
185 MAGIC(pw_stream_get_time_n)
187 #define PWIRE_FUNCS2(MAGIC) \
188 MAGIC(pw_stream_get_time)
192 #define MAKE_FUNC(f) decltype(f) * p##f;
193 PWIRE_FUNCS(MAKE_FUNC)
194 PWIRE_FUNCS2(MAKE_FUNC)
202 static constexpr char pwire_library[] = "libpipewire-0.3.so.0";
203 std::string missing_funcs;
205 pwire_handle = LoadLib(pwire_library);
208 WARN("Failed to load %s\n", pwire_library);
212 #define LOAD_FUNC(f) do { \
213 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
214 if(p##f == nullptr) missing_funcs += "\n" #f; \
216 PWIRE_FUNCS(LOAD_FUNC)
217 PWIRE_FUNCS2(LOAD_FUNC)
220 if(!missing_funcs.empty())
222 WARN("Missing expected functions:%s\n", missing_funcs.c_str());
223 CloseLib(pwire_handle);
224 pwire_handle = nullptr;
231 #ifndef IN_IDE_PARSER
232 #define pw_context_connect ppw_context_connect
233 #define pw_context_destroy ppw_context_destroy
234 #define pw_context_new ppw_context_new
235 #define pw_core_disconnect ppw_core_disconnect
236 #define pw_get_library_version ppw_get_library_version
237 #define pw_init ppw_init
238 #define pw_properties_free ppw_properties_free
239 #define pw_properties_new ppw_properties_new
240 #define pw_properties_set ppw_properties_set
241 #define pw_properties_setf ppw_properties_setf
242 #define pw_proxy_add_object_listener ppw_proxy_add_object_listener
243 #define pw_proxy_destroy ppw_proxy_destroy
244 #define pw_proxy_get_user_data ppw_proxy_get_user_data
245 #define pw_stream_add_listener ppw_stream_add_listener
246 #define pw_stream_connect ppw_stream_connect
247 #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
248 #define pw_stream_destroy ppw_stream_destroy
249 #define pw_stream_get_state ppw_stream_get_state
250 #define pw_stream_new ppw_stream_new
251 #define pw_stream_queue_buffer ppw_stream_queue_buffer
252 #define pw_stream_set_active ppw_stream_set_active
253 #define pw_thread_loop_destroy ppw_thread_loop_destroy
254 #define pw_thread_loop_get_loop ppw_thread_loop_get_loop
255 #define pw_thread_loop_lock ppw_thread_loop_lock
256 #define pw_thread_loop_new ppw_thread_loop_new
257 #define pw_thread_loop_signal ppw_thread_loop_signal
258 #define pw_thread_loop_start ppw_thread_loop_start
259 #define pw_thread_loop_stop ppw_thread_loop_stop
260 #define pw_thread_loop_unlock ppw_thread_loop_unlock
261 #define pw_thread_loop_wait ppw_thread_loop_wait
262 #if PW_CHECK_VERSION(0,3,50)
263 #define pw_stream_get_time_n ppw_stream_get_time_n
265 inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
266 { return ppw_stream_get_time(stream, ptime); }
272 constexpr bool pwire_load() { return true; }
275 /* Helpers for retrieving values from params */
276 template<uint32_t T> struct PodInfo { };
279 struct PodInfo<SPA_TYPE_Int> {
280 using Type = int32_t;
281 static auto get_value(const spa_pod *pod, int32_t *val)
282 { return spa_pod_get_int(pod, val); }
285 struct PodInfo<SPA_TYPE_Id> {
286 using Type = uint32_t;
287 static auto get_value(const spa_pod *pod, uint32_t *val)
288 { return spa_pod_get_id(pod, val); }
292 using Pod_t = typename PodInfo<T>::Type;
295 al::span<const Pod_t<T>> get_array_span(const spa_pod *pod)
298 if(void *v{spa_pod_get_array(pod, &nvals)})
300 if(get_array_value_type(pod) == T)
301 return {static_cast<const Pod_t<T>*>(v), nvals};
307 al::optional<Pod_t<T>> get_value(const spa_pod *value)
310 if(PodInfo<T>::get_value(value, &val) == 0)
315 /* Internally, PipeWire types "inherit" from each other, but this is hidden
316 * from the API and the caller is expected to C-style cast to inherited types
317 * as needed. It's also not made very clear what types a given type can be
318 * casted to. To make it a bit safer, this as() method allows casting pw_*
319 * types to known inherited types, generating a compile-time error for
320 * unexpected/invalid casts.
322 template<typename To, typename From>
323 To as(From) noexcept = delete;
331 pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); }
333 pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); }
335 pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); }
338 struct PwContextDeleter {
339 void operator()(pw_context *context) const { pw_context_destroy(context); }
341 using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>;
343 struct PwCoreDeleter {
344 void operator()(pw_core *core) const { pw_core_disconnect(core); }
346 using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>;
348 struct PwRegistryDeleter {
349 void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); }
351 using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>;
353 struct PwNodeDeleter {
354 void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); }
356 using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>;
358 struct PwMetadataDeleter {
359 void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); }
361 using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>;
363 struct PwStreamDeleter {
364 void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
366 using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
368 /* Enums for bitflags... again... *sigh* */
369 constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
370 { return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
372 constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
373 { lhs = lhs | rhs; return lhs; }
375 class ThreadMainloop {
376 pw_thread_loop *mLoop{};
379 ThreadMainloop() = default;
380 ThreadMainloop(const ThreadMainloop&) = delete;
381 ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
382 explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
383 ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
385 ThreadMainloop& operator=(const ThreadMainloop&) = delete;
386 ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
387 { std::swap(mLoop, rhs.mLoop); return *this; }
388 ThreadMainloop& operator=(std::nullptr_t) noexcept
391 pw_thread_loop_destroy(mLoop);
396 explicit operator bool() const noexcept { return mLoop != nullptr; }
398 auto start() const { return pw_thread_loop_start(mLoop); }
399 auto stop() const { return pw_thread_loop_stop(mLoop); }
401 auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
403 auto lock() const { return pw_thread_loop_lock(mLoop); }
404 auto unlock() const { return pw_thread_loop_unlock(mLoop); }
406 auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
408 auto newContext(pw_properties *props=nullptr, size_t user_data_size=0)
409 { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; }
411 static auto Create(const char *name, spa_dict *props=nullptr)
412 { return ThreadMainloop{pw_thread_loop_new(name, props)}; }
414 friend struct MainloopUniqueLock;
416 struct MainloopUniqueLock : public std::unique_lock<ThreadMainloop> {
417 using std::unique_lock<ThreadMainloop>::unique_lock;
418 MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
420 auto wait() const -> void
421 { pw_thread_loop_wait(mutex()->mLoop); }
423 template<typename Predicate>
424 auto wait(Predicate done_waiting) const -> void
425 { while(!done_waiting()) wait(); }
427 using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
430 /* There's quite a mess here, but the purpose is to track active devices and
431 * their default formats, so playback devices can be configured to match. The
432 * device list is updated asynchronously, so it will have the latest list of
433 * devices provided by the server.
437 struct MetadataProxy;
439 /* The global thread watching for global events. This particular class responds
440 * to objects being added to or removed from the registry.
442 struct EventManager {
443 ThreadMainloop mLoop{};
444 PwContextPtr mContext{};
446 PwRegistryPtr mRegistry{};
447 spa_hook mRegistryListener{};
448 spa_hook mCoreListener{};
450 /* A list of proxy objects watching for events about changes to objects in
453 std::vector<NodeProxy*> mNodeList;
454 MetadataProxy *mDefaultMetadata{nullptr};
456 /* Initialization handling. When init() is called, mInitSeq is set to a
457 * SequenceID that marks the end of populating the registry. As objects of
458 * interest are found, events to parse them are generated and mInitSeq is
459 * updated with a newer ID. When mInitSeq stops being updated and the event
460 * corresponding to it is reached, mInitDone will be set to true.
462 std::atomic<bool> mInitDone{false};
463 std::atomic<bool> mHasAudio{false};
471 auto lock() const { return mLoop.lock(); }
472 auto unlock() const { return mLoop.unlock(); }
475 * Waits for initialization to finish. The event manager must *NOT* be
476 * locked when calling this.
480 if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY
482 MainloopUniqueLock plock{mLoop};
483 plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); });
488 * Waits for audio support to be detected, or initialization to finish,
489 * whichever is first. Returns true if audio support was detected. The
490 * event manager must *NOT* be locked when calling this.
494 MainloopUniqueLock plock{mLoop};
496 plock.wait([this,&has_audio]()
498 has_audio = mHasAudio.load(std::memory_order_acquire);
499 return has_audio || mInitDone.load(std::memory_order_acquire);
506 /* If initialization isn't done, update the sequence ID so it won't
507 * complete until after currently scheduled events.
509 if(!mInitDone.load(std::memory_order_relaxed))
510 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq);
513 void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version,
514 const spa_dict *props);
515 static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type,
516 uint32_t version, const spa_dict *props)
517 { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); }
519 void removeCallback(uint32_t id);
520 static void removeCallbackC(void *object, uint32_t id)
521 { static_cast<EventManager*>(object)->removeCallback(id); }
523 static constexpr pw_registry_events CreateRegistryEvents()
525 pw_registry_events ret{};
526 ret.version = PW_VERSION_REGISTRY_EVENTS;
527 ret.global = &EventManager::addCallbackC;
528 ret.global_remove = &EventManager::removeCallbackC;
532 void coreCallback(uint32_t id, int seq);
533 static void coreCallbackC(void *object, uint32_t id, int seq)
534 { static_cast<EventManager*>(object)->coreCallback(id, seq); }
536 static constexpr pw_core_events CreateCoreEvents()
538 pw_core_events ret{};
539 ret.version = PW_VERSION_CORE_EVENTS;
540 ret.done = &EventManager::coreCallbackC;
544 using EventWatcherUniqueLock = std::unique_lock<EventManager>;
545 using EventWatcherLockGuard = std::lock_guard<EventManager>;
547 EventManager gEventHandler;
549 /* Enumerated devices. This is updated asynchronously as the app runs, and the
550 * gEventHandler thread loop must be locked when accessing the list.
552 enum class NodeType : unsigned char {
555 constexpr auto InvalidChannelConfig = DevFmtChannels(255);
561 std::string mDevName;
564 bool mIsHeadphones{};
568 DevFmtChannels mChannels{InvalidChannelConfig};
570 static std::vector<DeviceNode> sList;
571 static DeviceNode &Add(uint32_t id);
572 static DeviceNode *Find(uint32_t id);
573 static void Remove(uint32_t id);
574 static std::vector<DeviceNode> &GetList() noexcept { return sList; }
576 void parseSampleRate(const spa_pod *value) noexcept;
577 void parsePositions(const spa_pod *value) noexcept;
578 void parseChannelCount(const spa_pod *value) noexcept;
580 std::vector<DeviceNode> DeviceNode::sList;
581 std::string DefaultSinkDevice;
582 std::string DefaultSourceDevice;
584 const char *AsString(NodeType type) noexcept
588 case NodeType::Sink: return "sink";
589 case NodeType::Source: return "source";
590 case NodeType::Duplex: return "duplex";
595 DeviceNode &DeviceNode::Add(uint32_t id)
597 auto match_id = [id](DeviceNode &n) noexcept -> bool
598 { return n.mId == id; };
600 /* If the node is already in the list, return the existing entry. */
601 auto match = std::find_if(sList.begin(), sList.end(), match_id);
602 if(match != sList.end()) return *match;
604 sList.emplace_back();
605 auto &n = sList.back();
610 DeviceNode *DeviceNode::Find(uint32_t id)
612 auto match_id = [id](DeviceNode &n) noexcept -> bool
613 { return n.mId == id; };
615 auto match = std::find_if(sList.begin(), sList.end(), match_id);
616 if(match != sList.end()) return al::to_address(match);
621 void DeviceNode::Remove(uint32_t id)
623 auto match_id = [id](DeviceNode &n) noexcept -> bool
627 TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
631 auto end = std::remove_if(sList.begin(), sList.end(), match_id);
632 sList.erase(end, sList.end());
636 const spa_audio_channel MonoMap[]{
637 SPA_AUDIO_CHANNEL_MONO
639 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
641 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
643 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
644 SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
646 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
647 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
649 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
650 SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
652 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
653 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
655 SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
656 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR,
657 SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
661 * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
662 * to or a superset of map1).
665 bool MatchChannelMap(const al::span<const uint32_t> map0, const spa_audio_channel (&map1)[N])
669 for(const spa_audio_channel chid : map1)
671 if(std::find(map0.begin(), map0.end(), chid) == map0.end())
677 void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
679 /* TODO: Can this be anything else? Long, Float, Double? */
680 uint32_t nvals{}, choiceType{};
681 value = spa_pod_get_values(value, &nvals, &choiceType);
683 const uint podType{get_pod_type(value)};
684 if(podType != SPA_TYPE_Int)
686 WARN("Unhandled sample rate POD type: %u\n", podType);
690 if(choiceType == SPA_CHOICE_Range)
694 WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals);
697 auto srates = get_pod_body<int32_t,3>(value);
699 /* [0] is the default, [1] is the min, and [2] is the max. */
700 TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0],
701 srates[1], srates[2]);
702 mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
706 if(choiceType == SPA_CHOICE_Enum)
710 WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals);
713 auto srates = get_pod_body<int32_t>(value, nvals);
715 /* [0] is the default, [1...size()-1] are available selections. */
716 std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}};
717 for(size_t i{2};i < srates.size();++i)
720 others += std::to_string(srates[i]);
722 TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str());
723 /* Pick the first rate listed that's within the allowed range (default
726 for(const auto &rate : srates)
728 if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE)
730 mSampleRate = static_cast<uint>(rate);
737 if(choiceType == SPA_CHOICE_None)
741 WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals);
744 auto srates = get_pod_body<int32_t,1>(value);
746 TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]);
747 mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
751 WARN("Unhandled sample rate choice type: %u\n", choiceType);
754 void DeviceNode::parsePositions(const spa_pod *value) noexcept
756 const auto chanmap = get_array_span<SPA_TYPE_Id>(value);
757 if(chanmap.empty()) return;
761 if(MatchChannelMap(chanmap, X714Map))
762 mChannels = DevFmtX714;
763 else if(MatchChannelMap(chanmap, X71Map))
764 mChannels = DevFmtX71;
765 else if(MatchChannelMap(chanmap, X61Map))
766 mChannels = DevFmtX61;
767 else if(MatchChannelMap(chanmap, X51Map))
768 mChannels = DevFmtX51;
769 else if(MatchChannelMap(chanmap, X51RearMap))
771 mChannels = DevFmtX51;
774 else if(MatchChannelMap(chanmap, QuadMap))
775 mChannels = DevFmtQuad;
776 else if(MatchChannelMap(chanmap, StereoMap))
777 mChannels = DevFmtStereo;
779 mChannels = DevFmtMono;
780 TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(),
781 (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
784 void DeviceNode::parseChannelCount(const spa_pod *value) noexcept
786 /* As a fallback with just a channel count, just assume mono or stereo. */
787 const auto chancount = get_value<SPA_TYPE_Int>(value);
788 if(!chancount) return;
793 mChannels = DevFmtStereo;
794 else if(*chancount >= 1)
795 mChannels = DevFmtMono;
796 TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount,
797 (*chancount==1)?"":"s", DevFmtChannelsString(mChannels));
801 constexpr char MonitorPrefix[]{"Monitor of "};
802 constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1;
803 constexpr char AudioSinkClass[]{"Audio/Sink"};
804 constexpr char AudioSourceClass[]{"Audio/Source"};
805 constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"};
806 constexpr char AudioDuplexClass[]{"Audio/Duplex"};
807 constexpr char StreamClass[]{"Stream/"};
809 /* A generic PipeWire node proxy object used to track changes to sink and
813 static constexpr pw_node_events CreateNodeEvents()
815 pw_node_events ret{};
816 ret.version = PW_VERSION_NODE_EVENTS;
817 ret.info = &NodeProxy::infoCallbackC;
818 ret.param = &NodeProxy::paramCallbackC;
825 spa_hook mListener{};
827 NodeProxy(uint32_t id, PwNodePtr node)
828 : mId{id}, mNode{std::move(node)}
830 static constexpr pw_node_events nodeEvents{CreateNodeEvents()};
831 ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this);
833 /* Track changes to the enumerable formats (indicates the default
834 * format, which is what we're interested in).
836 uint32_t fmtids[]{SPA_PARAM_EnumFormat};
837 ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids));
840 { spa_hook_remove(&mListener); }
843 void infoCallback(const pw_node_info *info);
844 static void infoCallbackC(void *object, const pw_node_info *info)
845 { static_cast<NodeProxy*>(object)->infoCallback(info); }
847 void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param);
848 static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next,
849 const spa_pod *param)
850 { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); }
853 void NodeProxy::infoCallback(const pw_node_info *info)
855 /* We only care about property changes here (media class, name/desc).
856 * Format changes will automatically invoke the param callback.
858 * TODO: Can the media class or name/desc change without being removed and
861 if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS))
863 /* Can this actually change? */
864 const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
865 if(!media_class) UNLIKELY return;
868 if(al::strcasecmp(media_class, AudioSinkClass) == 0)
869 ntype = NodeType::Sink;
870 else if(al::strcasecmp(media_class, AudioSourceClass) == 0
871 || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0)
872 ntype = NodeType::Source;
873 else if(al::strcasecmp(media_class, AudioDuplexClass) == 0)
874 ntype = NodeType::Duplex;
877 TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
878 DeviceNode::Remove(info->id);
882 const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)};
883 const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)};
884 if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
885 if(!nodeName || !*nodeName) nodeName = devName;
887 uint64_t serial_id{info->id};
888 #ifdef PW_KEY_OBJECT_SERIAL
889 if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
892 serial_id = std::strtoull(serial_str, &serial_end, 0);
893 if(*serial_end != '\0' || errno == ERANGE)
895 ERR("Unexpected object serial: %s\n", serial_str);
896 serial_id = info->id;
901 const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
902 TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
903 form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
904 TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id);
906 DeviceNode &node = DeviceNode::Add(info->id);
907 node.mSerial = serial_id;
908 if(nodeName && *nodeName) node.mName = nodeName;
909 else node.mName = "PipeWire node #"+std::to_string(info->id);
910 node.mDevName = devName ? devName : "";
912 node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0
913 || al::strcasecmp(form_factor, "headset") == 0);
917 void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param)
919 if(id == SPA_PARAM_EnumFormat)
921 DeviceNode *node{DeviceNode::Find(mId)};
922 if(!node) UNLIKELY return;
924 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
925 node->parseSampleRate(&prop->value);
927 if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)})
928 node->parsePositions(&prop->value);
929 else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr)
930 node->parseChannelCount(&prop->value);
935 /* A metadata proxy object used to query the default sink and source. */
936 struct MetadataProxy {
937 static constexpr pw_metadata_events CreateMetadataEvents()
939 pw_metadata_events ret{};
940 ret.version = PW_VERSION_METADATA_EVENTS;
941 ret.property = &MetadataProxy::propertyCallbackC;
947 PwMetadataPtr mMetadata{};
948 spa_hook mListener{};
950 MetadataProxy(uint32_t id, PwMetadataPtr mdata)
951 : mId{id}, mMetadata{std::move(mdata)}
953 static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()};
954 ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this);
957 { spa_hook_remove(&mListener); }
960 int propertyCallback(uint32_t id, const char *key, const char *type, const char *value);
961 static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type,
963 { return static_cast<MetadataProxy*>(object)->propertyCallback(id, key, type, value); }
966 int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type,
973 if(std::strcmp(key, "default.audio.sink") == 0)
975 else if(std::strcmp(key, "default.audio.source") == 0)
982 TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
983 if(!isCapture) DefaultSinkDevice.clear();
984 else DefaultSourceDevice.clear();
987 if(std::strcmp(type, "Spa:String:JSON") != 0)
989 ERR("Unexpected %s property type: %s\n", key, type);
994 spa_json_init(&it[0], value, strlen(value));
995 if(spa_json_enter_object(&it[0], &it[1]) <= 0)
998 auto get_json_string = [](spa_json *iter)
1000 al::optional<std::string> str;
1003 int len{spa_json_next(iter, &val)};
1004 if(len <= 0) return str;
1006 str.emplace().resize(static_cast<uint>(len), '\0');
1007 if(spa_json_parse_string(val, len, &str->front()) <= 0)
1009 else while(!str->empty() && str->back() == '\0')
1013 while(auto propKey = get_json_string(&it[1]))
1015 if(*propKey == "name")
1017 auto propValue = get_json_string(&it[1]);
1018 if(!propValue) break;
1020 TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
1021 propValue->c_str());
1023 DefaultSinkDevice = std::move(*propValue);
1025 DefaultSourceDevice = std::move(*propValue);
1030 if(spa_json_next(&it[1], &v) <= 0)
1038 bool EventManager::init()
1040 mLoop = ThreadMainloop::Create("PWEventThread");
1043 ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
1047 mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr));
1050 ERR("Failed to create PipeWire event context (errno: %d)\n", errno);
1054 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1057 ERR("Failed to connect PipeWire event context (errno: %d)\n", errno);
1061 mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
1064 ERR("Failed to get PipeWire event registry (errno: %d)\n", errno);
1068 static constexpr pw_core_events coreEvents{CreateCoreEvents()};
1069 static constexpr pw_registry_events registryEvents{CreateRegistryEvents()};
1071 ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this);
1072 ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, ®istryEvents, this);
1074 /* Set an initial sequence ID for initialization, to trigger after the
1075 * registry is first populated.
1077 mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0);
1079 if(int res{mLoop.start()})
1081 ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
1088 EventManager::~EventManager()
1090 if(mLoop) mLoop.stop();
1092 for(NodeProxy *node : mNodeList)
1093 al::destroy_at(node);
1094 if(mDefaultMetadata)
1095 al::destroy_at(mDefaultMetadata);
1098 void EventManager::kill()
1100 if(mLoop) mLoop.stop();
1102 for(NodeProxy *node : mNodeList)
1103 al::destroy_at(node);
1105 if(mDefaultMetadata)
1106 al::destroy_at(mDefaultMetadata);
1107 mDefaultMetadata = nullptr;
1109 mRegistry = nullptr;
1115 void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version,
1116 const spa_dict *props)
1118 /* We're only interested in interface nodes. */
1119 if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
1121 const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)};
1122 if(!media_class) return;
1124 /* Specifically, audio sinks and sources (and duplexes). */
1125 const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0
1126 || al::strcasecmp(media_class, AudioSourceClass) == 0
1127 || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0
1128 || al::strcasecmp(media_class, AudioDuplexClass) == 0};
1131 if(std::strstr(media_class, "/Video") == nullptr
1132 && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0)
1133 TRACE("Ignoring node class %s\n", media_class);
1137 /* Create the proxy object. */
1138 auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type,
1139 version, sizeof(NodeProxy)))};
1142 ERR("Failed to create node proxy object (errno: %d)\n", errno);
1146 /* Initialize the NodeProxy to hold the node object, add it to the
1147 * active node list, and update the sync point.
1149 auto *proxy = static_cast<NodeProxy*>(pw_proxy_get_user_data(as<pw_proxy*>(node.get())));
1150 mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node)));
1153 /* Signal any waiters that we have found a source or sink for audio
1156 if(!mHasAudio.exchange(true, std::memory_order_acq_rel))
1157 mLoop.signal(false);
1159 else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0)
1161 const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)};
1162 if(!data_class) return;
1164 if(std::strcmp(data_class, "default") != 0)
1166 TRACE("Ignoring metadata \"%s\"\n", data_class);
1170 if(mDefaultMetadata)
1172 ERR("Duplicate default metadata\n");
1176 auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id,
1177 type, version, sizeof(MetadataProxy)))};
1180 ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
1184 auto *proxy = static_cast<MetadataProxy*>(
1185 pw_proxy_get_user_data(as<pw_proxy*>(mdata.get())));
1186 mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata));
1191 void EventManager::removeCallback(uint32_t id)
1193 DeviceNode::Remove(id);
1195 auto clear_node = [id](NodeProxy *node) noexcept
1199 al::destroy_at(node);
1202 auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node);
1203 mNodeList.erase(node_end, mNodeList.end());
1205 if(mDefaultMetadata && mDefaultMetadata->mId == id)
1207 al::destroy_at(mDefaultMetadata);
1208 mDefaultMetadata = nullptr;
1212 void EventManager::coreCallback(uint32_t id, int seq)
1214 if(id == PW_ID_CORE && seq == mInitSeq)
1216 /* Initialization done. Remove this callback and signal anyone that may
1219 spa_hook_remove(&mCoreListener);
1221 mInitDone.store(true);
1222 mLoop.signal(false);
1227 enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
1228 spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p)
1230 spa_audio_info_raw info{};
1233 device->FmtType = DevFmtFloat;
1234 info.format = SPA_AUDIO_FORMAT_F32P;
1236 else switch(device->FmtType)
1238 case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break;
1239 case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break;
1240 case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break;
1241 case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break;
1242 case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break;
1243 case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break;
1244 case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
1247 info.rate = device->Frequency;
1249 al::span<const spa_audio_channel> map{};
1250 switch(device->FmtChans)
1252 case DevFmtMono: map = MonoMap; break;
1253 case DevFmtStereo: map = StereoMap; break;
1254 case DevFmtQuad: map = QuadMap; break;
1256 if(is51rear) map = X51RearMap;
1259 case DevFmtX61: map = X61Map; break;
1260 case DevFmtX71: map = X71Map; break;
1261 case DevFmtX714: map = X714Map; break;
1262 case DevFmtX3D71: map = X71Map; break;
1264 info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
1265 info.channels = device->channelsFromFmt();
1270 info.channels = static_cast<uint32_t>(map.size());
1271 std::copy(map.begin(), map.end(), info.position);
1277 class PipeWirePlayback final : public BackendBase {
1278 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
1279 static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
1281 { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); }
1283 void ioChangedCallback(uint32_t id, void *area, uint32_t size);
1284 static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size)
1285 { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); }
1287 void outputCallback();
1288 static void outputCallbackC(void *data)
1289 { static_cast<PipeWirePlayback*>(data)->outputCallback(); }
1291 void open(const char *name) override;
1292 bool reset() override;
1293 void start() override;
1294 void stop() override;
1295 ClockLatency getClockLatency() override;
1297 uint64_t mTargetId{PwIdAny};
1298 nanoseconds mTimeBase{0};
1299 ThreadMainloop mLoop;
1300 PwContextPtr mContext;
1302 PwStreamPtr mStream;
1303 spa_hook mStreamListener{};
1304 spa_io_rate_match *mRateMatch{};
1305 std::unique_ptr<float*[]> mChannelPtrs;
1306 uint mNumChannels{};
1308 static constexpr pw_stream_events CreateEvents()
1310 pw_stream_events ret{};
1311 ret.version = PW_VERSION_STREAM_EVENTS;
1312 ret.state_changed = &PipeWirePlayback::stateChangedCallbackC;
1313 ret.io_changed = &PipeWirePlayback::ioChangedCallbackC;
1314 ret.process = &PipeWirePlayback::outputCallbackC;
1319 PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
1322 /* Stop the mainloop so the stream can be properly destroyed. */
1323 if(mLoop) mLoop.stop();
1326 DEF_NEWDEL(PipeWirePlayback)
1330 void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
1331 { mLoop.signal(false); }
1333 void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size)
1337 case SPA_IO_RateMatch:
1338 if(size >= sizeof(spa_io_rate_match))
1339 mRateMatch = static_cast<spa_io_rate_match*>(area);
1344 void PipeWirePlayback::outputCallback()
1346 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1347 if(!pw_buf) UNLIKELY return;
1349 const al::span<spa_data> datas{pw_buf->buffer->datas,
1350 minu(mNumChannels, pw_buf->buffer->n_datas)};
1351 #if PW_CHECK_VERSION(0,3,49)
1352 /* In 0.3.49, pw_buffer::requested specifies the number of samples needed
1353 * by the resampler/graph for this audio update.
1355 uint length{static_cast<uint>(pw_buf->requested)};
1357 /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
1358 * of samples per update.
1360 uint length{mRateMatch ? mRateMatch->size : 0u};
1362 /* If no length is specified, use the device's update size as a fallback. */
1363 if(!length) UNLIKELY length = mDevice->UpdateSize;
1365 /* For planar formats, each datas[] seems to contain one channel, so store
1366 * the pointers in an array. Limit the render length in case the available
1367 * buffer length in any one channel is smaller than we wanted (shouldn't
1368 * be, but just in case).
1370 float **chanptr_end{mChannelPtrs.get()};
1371 for(const auto &data : datas)
1373 length = minu(length, data.maxsize/sizeof(float));
1374 *chanptr_end = static_cast<float*>(data.data);
1378 mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length);
1380 for(const auto &data : datas)
1382 data.chunk->offset = 0;
1383 data.chunk->stride = sizeof(float);
1384 data.chunk->size = length * sizeof(float);
1386 pw_buf->size = length;
1387 pw_stream_queue_buffer(mStream.get(), pw_buf);
1391 void PipeWirePlayback::open(const char *name)
1393 static std::atomic<uint> OpenCount{0};
1395 uint64_t targetid{PwIdAny};
1396 std::string devname{};
1397 gEventHandler.waitForInit();
1400 EventWatcherLockGuard _{gEventHandler};
1401 auto&& devlist = DeviceNode::GetList();
1403 auto match = devlist.cend();
1404 if(!DefaultSinkDevice.empty())
1406 auto match_default = [](const DeviceNode &n) -> bool
1407 { return n.mDevName == DefaultSinkDevice; };
1408 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1410 if(match == devlist.cend())
1412 auto match_playback = [](const DeviceNode &n) -> bool
1413 { return n.mType != NodeType::Source; };
1414 match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback);
1415 if(match == devlist.cend())
1416 throw al::backend_exception{al::backend_error::NoDevice,
1417 "No PipeWire playback device found"};
1420 targetid = match->mSerial;
1421 devname = match->mName;
1425 EventWatcherLockGuard _{gEventHandler};
1426 auto&& devlist = DeviceNode::GetList();
1428 auto match_name = [name](const DeviceNode &n) -> bool
1429 { return n.mType != NodeType::Source && n.mName == name; };
1430 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
1431 if(match == devlist.cend())
1432 throw al::backend_exception{al::backend_error::NoDevice,
1433 "Device name \"%s\" not found", name};
1435 targetid = match->mSerial;
1436 devname = match->mName;
1441 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
1442 const std::string thread_name{"ALSoftP" + std::to_string(count)};
1443 mLoop = ThreadMainloop::Create(thread_name.c_str());
1445 throw al::backend_exception{al::backend_error::DeviceError,
1446 "Failed to create PipeWire mainloop (errno: %d)", errno};
1447 if(int res{mLoop.start()})
1448 throw al::backend_exception{al::backend_error::DeviceError,
1449 "Failed to start PipeWire mainloop (res: %d)", res};
1451 MainloopUniqueLock mlock{mLoop};
1454 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
1455 mContext = mLoop.newContext(cprops);
1457 throw al::backend_exception{al::backend_error::DeviceError,
1458 "Failed to create PipeWire event context (errno: %d)\n", errno};
1462 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1464 throw al::backend_exception{al::backend_error::DeviceError,
1465 "Failed to connect PipeWire event context (errno: %d)\n", errno};
1469 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1471 mTargetId = targetid;
1472 if(!devname.empty())
1473 mDevice->DeviceName = std::move(devname);
1475 mDevice->DeviceName = pwireDevice;
1478 bool PipeWirePlayback::reset()
1482 MainloopLockGuard _{mLoop};
1485 mStreamListener = {};
1486 mRateMatch = nullptr;
1487 mTimeBase = GetDeviceClockTime(mDevice);
1489 /* If connecting to a specific device, update various device parameters to
1492 bool is51rear{false};
1493 mDevice->Flags.reset(DirectEar);
1494 if(mTargetId != PwIdAny)
1496 EventWatcherLockGuard _{gEventHandler};
1497 auto&& devlist = DeviceNode::GetList();
1499 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
1500 { return targetid == n.mSerial; };
1501 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
1502 if(match != devlist.cend())
1504 if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
1506 /* Scale the update size if the sample rate changes. */
1507 const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
1508 const double numbufs{static_cast<double>(mDevice->BufferSize)/mDevice->UpdateSize};
1509 mDevice->Frequency = match->mSampleRate;
1510 mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5,
1512 mDevice->BufferSize = static_cast<uint>(numbufs*mDevice->UpdateSize + 0.5);
1514 if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
1515 mDevice->FmtChans = match->mChannels;
1516 if(match->mChannels == DevFmtStereo && match->mIsHeadphones)
1517 mDevice->Flags.set(DirectEar);
1518 is51rear = match->mIs51Rear;
1521 /* Force planar 32-bit float output for playback. This is what PipeWire
1522 * handles internally, and it's easier for us too.
1524 spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)};
1526 /* TODO: How to tell what an appropriate size is? Examples just use this
1529 constexpr uint32_t pod_buffer_size{1024};
1530 auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
1531 spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
1533 const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
1535 throw al::backend_exception{al::backend_error::DeviceError,
1536 "Failed to set PipeWire audio format parameters"};
1538 /* TODO: Which properties are actually needed here? Any others that could
1541 auto&& binary = GetProcBinary();
1542 const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
1543 pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
1544 PW_KEY_NODE_DESCRIPTION, appname,
1545 PW_KEY_MEDIA_TYPE, "Audio",
1546 PW_KEY_MEDIA_CATEGORY, "Playback",
1547 PW_KEY_MEDIA_ROLE, "Game",
1548 PW_KEY_NODE_ALWAYS_PROCESS, "true",
1551 throw al::backend_exception{al::backend_error::DeviceError,
1552 "Failed to create PipeWire stream properties (errno: %d)", errno};
1554 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
1555 mDevice->Frequency);
1556 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
1557 #ifdef PW_KEY_TARGET_OBJECT
1558 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
1560 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
1563 MainloopUniqueLock plock{mLoop};
1564 /* The stream takes overship of 'props', even in the case of failure. */
1565 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
1567 throw al::backend_exception{al::backend_error::NoDevice,
1568 "Failed to create PipeWire stream (errno: %d)", errno};
1569 static constexpr pw_stream_events streamEvents{CreateEvents()};
1570 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
1572 pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
1573 | PW_STREAM_FLAG_MAP_BUFFERS};
1574 if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true))
1575 flags |= PW_STREAM_FLAG_RT_PROCESS;
1576 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)})
1577 throw al::backend_exception{al::backend_error::DeviceError,
1578 "Error connecting PipeWire stream (res: %d)", res};
1580 /* Wait for the stream to become paused (ready to start streaming). */
1581 plock.wait([stream=mStream.get()]()
1583 const char *error{};
1584 pw_stream_state state{pw_stream_get_state(stream, &error)};
1585 if(state == PW_STREAM_STATE_ERROR)
1586 throw al::backend_exception{al::backend_error::DeviceError,
1587 "Error connecting PipeWire stream: \"%s\"", error};
1588 return state == PW_STREAM_STATE_PAUSED;
1591 /* TODO: Update mDevice->UpdateSize with the stream's quantum, and
1592 * mDevice->BufferSize with the total known buffering delay from the head
1593 * of this playback stream to the tail of the device output.
1595 * This info is apparently not available until after the stream starts.
1599 mNumChannels = mDevice->channelsFromFmt();
1600 mChannelPtrs = std::make_unique<float*[]>(mNumChannels);
1602 setDefaultWFXChannelOrder();
1607 void PipeWirePlayback::start()
1609 MainloopUniqueLock plock{mLoop};
1610 if(int res{pw_stream_set_active(mStream.get(), true)})
1611 throw al::backend_exception{al::backend_error::DeviceError,
1612 "Failed to start PipeWire stream (res: %d)", res};
1614 /* Wait for the stream to start playing (would be nice to not, but we need
1615 * the actual update size which is only available after starting).
1617 plock.wait([stream=mStream.get()]()
1619 const char *error{};
1620 pw_stream_state state{pw_stream_get_state(stream, &error)};
1621 if(state == PW_STREAM_STATE_ERROR)
1622 throw al::backend_exception{al::backend_error::DeviceError,
1623 "PipeWire stream error: %s", error ? error : "(unknown)"};
1624 return state == PW_STREAM_STATE_STREAMING;
1627 /* HACK: Try to work out the update size and total buffering size. There's
1628 * no actual query for this, so we have to work it out from the stream time
1629 * info, and assume it stays accurate with future updates. The stream time
1630 * info may also not be available right away, so we have to wait until it
1631 * is (up to about 2 seconds).
1633 int wait_count{100};
1636 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1638 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1642 /* The rate match size is the update size for each buffer. */
1643 const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
1644 #if PW_CHECK_VERSION(0,3,50)
1645 /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
1648 if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
1650 const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
1652 /* Ensure the delay is in sample frames. */
1653 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1654 ptime.rate.num / ptime.rate.denom};
1656 mDevice->UpdateSize = updatesize;
1657 mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
1658 totalbuffers*updatesize);
1662 /* Prior to 0.3.50, we can only measure the delay with the update size,
1663 * assuming one buffer and no resample buffering.
1665 if(ptime.rate.denom > 0 && updatesize > 0)
1667 /* Ensure the delay is in sample frames. */
1668 const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
1669 ptime.rate.num / ptime.rate.denom};
1671 mDevice->UpdateSize = updatesize;
1672 mDevice->BufferSize = static_cast<uint>(delay + updatesize);
1680 std::this_thread::sleep_for(milliseconds{20});
1682 } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
1685 void PipeWirePlayback::stop()
1687 MainloopUniqueLock plock{mLoop};
1688 if(int res{pw_stream_set_active(mStream.get(), false)})
1689 throw al::backend_exception{al::backend_error::DeviceError,
1690 "Failed to stop PipeWire stream (res: %d)", res};
1692 /* Wait for the stream to stop playing. */
1693 plock.wait([stream=mStream.get()]()
1694 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
1697 ClockLatency PipeWirePlayback::getClockLatency()
1699 /* Given a real-time low-latency output, this is rather complicated to get
1700 * accurate timing. So, here we go.
1703 /* First, get the stream time info (tick delay, ticks played, and the
1704 * CLOCK_MONOTONIC time closest to when that last tick was played).
1709 MainloopLockGuard _{mLoop};
1710 if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
1711 ERR("Failed to get PipeWire stream time (res: %d)\n", res);
1714 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1715 * monotonic clock closest to 'now', and the last mixer time at 'now').
1717 nanoseconds mixtime{};
1721 refcount = mDevice->waitForMix();
1722 mixtime = GetDeviceClockTime(mDevice);
1723 clock_gettime(CLOCK_MONOTONIC, &tspec);
1724 std::atomic_thread_fence(std::memory_order_acquire);
1725 } while(refcount != ReadRef(mDevice->MixCount));
1727 /* Convert the monotonic clock, stream ticks, and stream delay to
1730 nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
1731 nanoseconds curtic{}, delay{};
1732 if(ptime.rate.denom < 1) UNLIKELY
1734 /* If there's no stream rate, the stream hasn't had a chance to get
1735 * going and return time info yet. Just use dummy values.
1737 ptime.now = monoclock.count();
1739 delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
1743 /* The stream gets recreated with each reset, so include the time that
1744 * had already passed with previous streams.
1747 /* More safely scale the ticks to avoid overflowing the pre-division
1748 * temporary as it gets larger.
1750 curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
1751 curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
1754 /* The delay should be small enough to not worry about overflow. */
1755 delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
1758 /* If the mixer time is ahead of the stream time, there's that much more
1759 * delay relative to the stream delay.
1761 if(mixtime > curtic)
1762 delay += mixtime - curtic;
1763 /* Reduce the delay according to how much time has passed since the known
1764 * stream time. This isn't 100% accurate since the system monotonic clock
1765 * doesn't tick at the exact same rate as the audio device, but it should
1766 * be good enough with ptime.now being constantly updated every few
1767 * milliseconds with ptime.ticks.
1769 delay -= monoclock - nanoseconds{ptime.now};
1771 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1772 * incase timer drift got that severe.
1775 ret.ClockTime = mixtime;
1776 ret.Latency = std::max(delay, nanoseconds{});
1782 class PipeWireCapture final : public BackendBase {
1783 void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
1784 static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
1786 { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); }
1788 void inputCallback();
1789 static void inputCallbackC(void *data)
1790 { static_cast<PipeWireCapture*>(data)->inputCallback(); }
1792 void open(const char *name) override;
1793 void start() override;
1794 void stop() override;
1795 void captureSamples(al::byte *buffer, uint samples) override;
1796 uint availableSamples() override;
1798 uint64_t mTargetId{PwIdAny};
1799 ThreadMainloop mLoop;
1800 PwContextPtr mContext;
1802 PwStreamPtr mStream;
1803 spa_hook mStreamListener{};
1805 RingBufferPtr mRing{};
1807 static constexpr pw_stream_events CreateEvents()
1809 pw_stream_events ret{};
1810 ret.version = PW_VERSION_STREAM_EVENTS;
1811 ret.state_changed = &PipeWireCapture::stateChangedCallbackC;
1812 ret.process = &PipeWireCapture::inputCallbackC;
1817 PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
1818 ~PipeWireCapture() { if(mLoop) mLoop.stop(); }
1820 DEF_NEWDEL(PipeWireCapture)
1824 void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
1825 { mLoop.signal(false); }
1827 void PipeWireCapture::inputCallback()
1829 pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
1830 if(!pw_buf) UNLIKELY return;
1832 spa_data *bufdata{pw_buf->buffer->datas};
1833 const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)};
1834 const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)};
1836 mRing->write(static_cast<char*>(bufdata->data) + offset, size / mRing->getElemSize());
1838 pw_stream_queue_buffer(mStream.get(), pw_buf);
1842 void PipeWireCapture::open(const char *name)
1844 static std::atomic<uint> OpenCount{0};
1846 uint64_t targetid{PwIdAny};
1847 std::string devname{};
1848 gEventHandler.waitForInit();
1851 EventWatcherLockGuard _{gEventHandler};
1852 auto&& devlist = DeviceNode::GetList();
1854 auto match = devlist.cend();
1855 if(!DefaultSourceDevice.empty())
1857 auto match_default = [](const DeviceNode &n) -> bool
1858 { return n.mDevName == DefaultSourceDevice; };
1859 match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
1861 if(match == devlist.cend())
1863 auto match_capture = [](const DeviceNode &n) -> bool
1864 { return n.mType != NodeType::Sink; };
1865 match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture);
1867 if(match == devlist.cend())
1869 match = devlist.cbegin();
1870 if(match == devlist.cend())
1871 throw al::backend_exception{al::backend_error::NoDevice,
1872 "No PipeWire capture device found"};
1875 targetid = match->mSerial;
1876 if(match->mType != NodeType::Sink) devname = match->mName;
1877 else devname = MonitorPrefix+match->mName;
1881 EventWatcherLockGuard _{gEventHandler};
1882 auto&& devlist = DeviceNode::GetList();
1884 auto match_name = [name](const DeviceNode &n) -> bool
1885 { return n.mType != NodeType::Sink && n.mName == name; };
1886 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
1887 if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0)
1889 const char *sinkname{name + MonitorPrefixLen};
1890 auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
1891 { return n.mType == NodeType::Sink && n.mName == sinkname; };
1892 match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
1894 if(match == devlist.cend())
1895 throw al::backend_exception{al::backend_error::NoDevice,
1896 "Device name \"%s\" not found", name};
1898 targetid = match->mSerial;
1904 const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
1905 const std::string thread_name{"ALSoftC" + std::to_string(count)};
1906 mLoop = ThreadMainloop::Create(thread_name.c_str());
1908 throw al::backend_exception{al::backend_error::DeviceError,
1909 "Failed to create PipeWire mainloop (errno: %d)", errno};
1910 if(int res{mLoop.start()})
1911 throw al::backend_exception{al::backend_error::DeviceError,
1912 "Failed to start PipeWire mainloop (res: %d)", res};
1914 MainloopUniqueLock mlock{mLoop};
1917 pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
1918 mContext = mLoop.newContext(cprops);
1920 throw al::backend_exception{al::backend_error::DeviceError,
1921 "Failed to create PipeWire event context (errno: %d)\n", errno};
1925 mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
1927 throw al::backend_exception{al::backend_error::DeviceError,
1928 "Failed to connect PipeWire event context (errno: %d)\n", errno};
1932 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1934 mTargetId = targetid;
1935 if(!devname.empty())
1936 mDevice->DeviceName = std::move(devname);
1938 mDevice->DeviceName = pwireInput;
1941 bool is51rear{false};
1942 if(mTargetId != PwIdAny)
1944 EventWatcherLockGuard _{gEventHandler};
1945 auto&& devlist = DeviceNode::GetList();
1947 auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
1948 { return targetid == n.mSerial; };
1949 auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
1950 if(match != devlist.cend())
1951 is51rear = match->mIs51Rear;
1953 spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)};
1955 constexpr uint32_t pod_buffer_size{1024};
1956 auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
1957 spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
1959 const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
1961 throw al::backend_exception{al::backend_error::DeviceError,
1962 "Failed to set PipeWire audio format parameters"};
1964 auto&& binary = GetProcBinary();
1965 const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
1966 pw_properties *props{pw_properties_new(
1967 PW_KEY_NODE_NAME, appname,
1968 PW_KEY_NODE_DESCRIPTION, appname,
1969 PW_KEY_MEDIA_TYPE, "Audio",
1970 PW_KEY_MEDIA_CATEGORY, "Capture",
1971 PW_KEY_MEDIA_ROLE, "Game",
1972 PW_KEY_NODE_ALWAYS_PROCESS, "true",
1975 throw al::backend_exception{al::backend_error::DeviceError,
1976 "Failed to create PipeWire stream properties (errno: %d)", errno};
1978 /* We don't actually care what the latency/update size is, as long as it's
1979 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
1980 * around 40ms, which isn't great. So request 20ms instead.
1982 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
1983 mDevice->Frequency);
1984 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
1985 #ifdef PW_KEY_TARGET_OBJECT
1986 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
1988 pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
1991 MainloopUniqueLock plock{mLoop};
1992 mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
1994 throw al::backend_exception{al::backend_error::NoDevice,
1995 "Failed to create PipeWire stream (errno: %d)", errno};
1996 static constexpr pw_stream_events streamEvents{CreateEvents()};
1997 pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
1999 constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
2000 | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
2001 if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)})
2002 throw al::backend_exception{al::backend_error::DeviceError,
2003 "Error connecting PipeWire stream (res: %d)", res};
2005 /* Wait for the stream to become paused (ready to start streaming). */
2006 plock.wait([stream=mStream.get()]()
2008 const char *error{};
2009 pw_stream_state state{pw_stream_get_state(stream, &error)};
2010 if(state == PW_STREAM_STATE_ERROR)
2011 throw al::backend_exception{al::backend_error::DeviceError,
2012 "Error connecting PipeWire stream: \"%s\"", error};
2013 return state == PW_STREAM_STATE_PAUSED;
2017 setDefaultWFXChannelOrder();
2019 /* Ensure at least a 100ms capture buffer. */
2020 mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize),
2021 mDevice->frameSizeFromFmt(), false);
2025 void PipeWireCapture::start()
2027 MainloopUniqueLock plock{mLoop};
2028 if(int res{pw_stream_set_active(mStream.get(), true)})
2029 throw al::backend_exception{al::backend_error::DeviceError,
2030 "Failed to start PipeWire stream (res: %d)", res};
2032 plock.wait([stream=mStream.get()]()
2034 const char *error{};
2035 pw_stream_state state{pw_stream_get_state(stream, &error)};
2036 if(state == PW_STREAM_STATE_ERROR)
2037 throw al::backend_exception{al::backend_error::DeviceError,
2038 "PipeWire stream error: %s", error ? error : "(unknown)"};
2039 return state == PW_STREAM_STATE_STREAMING;
2043 void PipeWireCapture::stop()
2045 MainloopUniqueLock plock{mLoop};
2046 if(int res{pw_stream_set_active(mStream.get(), false)})
2047 throw al::backend_exception{al::backend_error::DeviceError,
2048 "Failed to stop PipeWire stream (res: %d)", res};
2050 plock.wait([stream=mStream.get()]()
2051 { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
2054 uint PipeWireCapture::availableSamples()
2055 { return static_cast<uint>(mRing->readSpace()); }
2057 void PipeWireCapture::captureSamples(al::byte *buffer, uint samples)
2058 { mRing->read(buffer, samples); }
2063 bool PipeWireBackendFactory::init()
2068 const char *version{pw_get_library_version()};
2069 if(!check_version(version))
2071 WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
2072 pw_get_headers_version());
2075 TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
2077 pw_init(0, nullptr);
2078 if(!gEventHandler.init())
2081 if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
2082 && !gEventHandler.waitForAudio())
2084 gEventHandler.kill();
2085 /* TODO: Temporary warning, until PipeWire gets a proper way to report
2088 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
2094 bool PipeWireBackendFactory::querySupport(BackendType type)
2095 { return type == BackendType::Playback || type == BackendType::Capture; }
2097 std::string PipeWireBackendFactory::probe(BackendType type)
2099 std::string outnames;
2101 gEventHandler.waitForInit();
2102 EventWatcherLockGuard _{gEventHandler};
2103 auto&& devlist = DeviceNode::GetList();
2105 auto match_defsink = [](const DeviceNode &n) -> bool
2106 { return n.mDevName == DefaultSinkDevice; };
2107 auto match_defsource = [](const DeviceNode &n) -> bool
2108 { return n.mDevName == DefaultSourceDevice; };
2110 auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool
2111 { return lhs.mId < rhs.mId; };
2112 std::sort(devlist.begin(), devlist.end(), sort_devnode);
2114 auto defmatch = devlist.cbegin();
2117 case BackendType::Playback:
2118 defmatch = std::find_if(defmatch, devlist.cend(), match_defsink);
2119 if(defmatch != devlist.cend())
2121 /* Includes null char. */
2122 outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
2124 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2126 if(iter != defmatch && iter->mType != NodeType::Source)
2127 outnames.append(iter->mName.c_str(), iter->mName.length()+1);
2130 case BackendType::Capture:
2131 defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
2132 if(defmatch != devlist.cend())
2134 if(defmatch->mType == NodeType::Sink)
2135 outnames.append(MonitorPrefix);
2136 outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
2138 for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
2140 if(iter != defmatch)
2142 if(iter->mType == NodeType::Sink)
2143 outnames.append(MonitorPrefix);
2144 outnames.append(iter->mName.c_str(), iter->mName.length()+1);
2153 BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
2155 if(type == BackendType::Playback)
2156 return BackendPtr{new PipeWirePlayback{device}};
2157 if(type == BackendType::Capture)
2158 return BackendPtr{new PipeWireCapture{device}};
2162 BackendFactory &PipeWireBackendFactory::getFactory()
2164 static PipeWireBackendFactory factory{};