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