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