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
35 #include <type_traits>
39 #include "alc/alconfig.h"
41 #include "alnumeric.h"
42 #include "aloptional.h"
45 #include "core/devformat.h"
46 #include "core/device.h"
47 #include "core/helpers.h"
48 #include "core/logging.h"
50 #include "opthelpers.h"
51 #include "ringbuffer.h"
53 /* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */
54 _Pragma("GCC diagnostic push")
55 _Pragma("GCC diagnostic ignored \"-Weverything\"")
56 #include "pipewire/pipewire.h"
57 #include "pipewire/extensions/metadata.h"
58 #include "spa/buffer/buffer.h"
59 #include "spa/param/audio/format-utils.h"
60 #include "spa/param/audio/raw.h"
61 #include "spa/param/param.h"
62 #include "spa/pod/builder.h"
63 #include "spa/utils/json.h"
66 /* Wrap some nasty macros here too... */
67 template<typename
...Args
>
68 auto ppw_core_add_listener(pw_core
*core
, Args
&& ...args
)
69 { return pw_core_add_listener(core
, std::forward
<Args
>(args
)...); }
70 template<typename
...Args
>
71 auto ppw_core_sync(pw_core
*core
, Args
&& ...args
)
72 { return pw_core_sync(core
, std::forward
<Args
>(args
)...); }
73 template<typename
...Args
>
74 auto ppw_registry_add_listener(pw_registry
*reg
, Args
&& ...args
)
75 { return pw_registry_add_listener(reg
, std::forward
<Args
>(args
)...); }
76 template<typename
...Args
>
77 auto ppw_node_add_listener(pw_node
*node
, Args
&& ...args
)
78 { return pw_node_add_listener(node
, std::forward
<Args
>(args
)...); }
79 template<typename
...Args
>
80 auto ppw_node_subscribe_params(pw_node
*node
, Args
&& ...args
)
81 { return pw_node_subscribe_params(node
, std::forward
<Args
>(args
)...); }
82 template<typename
...Args
>
83 auto ppw_metadata_add_listener(pw_metadata
*mdata
, Args
&& ...args
)
84 { return pw_metadata_add_listener(mdata
, std::forward
<Args
>(args
)...); }
87 constexpr auto get_pod_type(const spa_pod
*pod
) noexcept
88 { return SPA_POD_TYPE(pod
); }
91 constexpr auto get_pod_body(const spa_pod
*pod
, size_t count
) noexcept
92 { return al::span
<T
>{static_cast<T
*>(SPA_POD_BODY(pod
)), count
}; }
93 template<typename T
, size_t N
>
94 constexpr auto get_pod_body(const spa_pod
*pod
) noexcept
95 { return al::span
<T
,N
>{static_cast<T
*>(SPA_POD_BODY(pod
)), N
}; }
97 constexpr auto make_pod_builder(void *data
, uint32_t size
) noexcept
98 { return SPA_POD_BUILDER_INIT(data
, size
); }
100 constexpr auto get_array_value_type(const spa_pod
*pod
) noexcept
101 { return SPA_POD_ARRAY_VALUE_TYPE(pod
); }
103 constexpr auto PwIdAny
= PW_ID_ANY
;
106 _Pragma("GCC diagnostic pop")
110 using std::chrono::seconds
;
111 using std::chrono::nanoseconds
;
112 using uint
= unsigned int;
114 constexpr char pwireDevice
[] = "PipeWire Output";
115 constexpr char pwireInput
[] = "PipeWire Input";
119 #define PWIRE_FUNCS(MAGIC) \
120 MAGIC(pw_context_connect) \
121 MAGIC(pw_context_destroy) \
122 MAGIC(pw_context_new) \
123 MAGIC(pw_core_disconnect) \
125 MAGIC(pw_properties_free) \
126 MAGIC(pw_properties_new) \
127 MAGIC(pw_properties_set) \
128 MAGIC(pw_properties_setf) \
129 MAGIC(pw_proxy_add_object_listener) \
130 MAGIC(pw_proxy_destroy) \
131 MAGIC(pw_proxy_get_user_data) \
132 MAGIC(pw_stream_add_listener) \
133 MAGIC(pw_stream_connect) \
134 MAGIC(pw_stream_dequeue_buffer) \
135 MAGIC(pw_stream_destroy) \
136 MAGIC(pw_stream_get_state) \
137 MAGIC(pw_stream_get_time) \
138 MAGIC(pw_stream_new) \
139 MAGIC(pw_stream_queue_buffer) \
140 MAGIC(pw_stream_set_active) \
141 MAGIC(pw_thread_loop_new) \
142 MAGIC(pw_thread_loop_destroy) \
143 MAGIC(pw_thread_loop_get_loop) \
144 MAGIC(pw_thread_loop_start) \
145 MAGIC(pw_thread_loop_stop) \
146 MAGIC(pw_thread_loop_lock) \
147 MAGIC(pw_thread_loop_wait) \
148 MAGIC(pw_thread_loop_signal) \
149 MAGIC(pw_thread_loop_unlock) \
152 #define MAKE_FUNC(f) decltype(f) * p##f;
153 PWIRE_FUNCS(MAKE_FUNC
)
161 static constexpr char pwire_library
[] = "libpipewire-0.3.so.0";
162 std::string missing_funcs
;
164 pwire_handle
= LoadLib(pwire_library
);
167 WARN("Failed to load %s\n", pwire_library
);
171 #define LOAD_FUNC(f) do { \
172 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
173 if(p##f == nullptr) missing_funcs += "\n" #f; \
175 PWIRE_FUNCS(LOAD_FUNC
)
178 if(!missing_funcs
.empty())
180 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
181 CloseLib(pwire_handle
);
182 pwire_handle
= nullptr;
189 #ifndef IN_IDE_PARSER
190 #define pw_context_connect ppw_context_connect
191 #define pw_context_destroy ppw_context_destroy
192 #define pw_context_new ppw_context_new
193 #define pw_core_disconnect ppw_core_disconnect
194 #define pw_init ppw_init
195 #define pw_properties_free ppw_properties_free
196 #define pw_properties_new ppw_properties_new
197 #define pw_properties_set ppw_properties_set
198 #define pw_properties_setf ppw_properties_setf
199 #define pw_proxy_add_object_listener ppw_proxy_add_object_listener
200 #define pw_proxy_destroy ppw_proxy_destroy
201 #define pw_proxy_get_user_data ppw_proxy_get_user_data
202 #define pw_stream_add_listener ppw_stream_add_listener
203 #define pw_stream_connect ppw_stream_connect
204 #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
205 #define pw_stream_destroy ppw_stream_destroy
206 #define pw_stream_get_state ppw_stream_get_state
207 #define pw_stream_get_time ppw_stream_get_time
208 #define pw_stream_new ppw_stream_new
209 #define pw_stream_queue_buffer ppw_stream_queue_buffer
210 #define pw_stream_set_active ppw_stream_set_active
211 #define pw_thread_loop_destroy ppw_thread_loop_destroy
212 #define pw_thread_loop_get_loop ppw_thread_loop_get_loop
213 #define pw_thread_loop_lock ppw_thread_loop_lock
214 #define pw_thread_loop_new ppw_thread_loop_new
215 #define pw_thread_loop_signal ppw_thread_loop_signal
216 #define pw_thread_loop_start ppw_thread_loop_start
217 #define pw_thread_loop_stop ppw_thread_loop_stop
218 #define pw_thread_loop_unlock ppw_thread_loop_unlock
219 #define pw_thread_loop_wait ppw_thread_loop_wait
224 constexpr bool pwire_load() { return true; }
227 /* Helpers for retrieving values from params */
228 template<uint32_t T
> struct PodInfo
{ };
231 struct PodInfo
<SPA_TYPE_Int
> {
232 using Type
= int32_t;
233 static auto get_value(const spa_pod
*pod
, int32_t *val
)
234 { return spa_pod_get_int(pod
, val
); }
237 struct PodInfo
<SPA_TYPE_Id
> {
238 using Type
= uint32_t;
239 static auto get_value(const spa_pod
*pod
, uint32_t *val
)
240 { return spa_pod_get_id(pod
, val
); }
244 using Pod_t
= typename PodInfo
<T
>::Type
;
247 al::span
<const Pod_t
<T
>> get_array_span(const spa_pod
*pod
)
250 if(void *v
{spa_pod_get_array(pod
, &nvals
)})
252 if(get_array_value_type(pod
) == T
)
253 return {static_cast<const Pod_t
<T
>*>(v
), nvals
};
259 al::optional
<Pod_t
<T
>> get_value(const spa_pod
*value
)
262 if(PodInfo
<T
>::get_value(value
, &val
) == 0)
267 /* Internally, PipeWire types "inherit" from each other, but this is hidden
268 * from the API and the caller is expected to C-style cast to inherited types
269 * as needed. It's also not made very clear what types a given type can be
270 * casted to. To make it a bit safer, this as() method allows casting pw_*
271 * types to known inherited types, generating a compile-time error for
272 * unexpected/invalid casts.
274 template<typename To
, typename From
>
275 To
as(From
) noexcept
= delete;
283 pw_proxy
* as(pw_registry
*reg
) noexcept
{ return reinterpret_cast<pw_proxy
*>(reg
); }
285 pw_proxy
* as(pw_node
*node
) noexcept
{ return reinterpret_cast<pw_proxy
*>(node
); }
287 pw_proxy
* as(pw_metadata
*mdata
) noexcept
{ return reinterpret_cast<pw_proxy
*>(mdata
); }
290 struct PwContextDeleter
{
291 void operator()(pw_context
*context
) const { pw_context_destroy(context
); }
293 using PwContextPtr
= std::unique_ptr
<pw_context
,PwContextDeleter
>;
295 struct PwCoreDeleter
{
296 void operator()(pw_core
*core
) const { pw_core_disconnect(core
); }
298 using PwCorePtr
= std::unique_ptr
<pw_core
,PwCoreDeleter
>;
300 struct PwRegistryDeleter
{
301 void operator()(pw_registry
*reg
) const { pw_proxy_destroy(as
<pw_proxy
*>(reg
)); }
303 using PwRegistryPtr
= std::unique_ptr
<pw_registry
,PwRegistryDeleter
>;
305 struct PwNodeDeleter
{
306 void operator()(pw_node
*node
) const { pw_proxy_destroy(as
<pw_proxy
*>(node
)); }
308 using PwNodePtr
= std::unique_ptr
<pw_node
,PwNodeDeleter
>;
310 struct PwMetadataDeleter
{
311 void operator()(pw_metadata
*mdata
) const { pw_proxy_destroy(as
<pw_proxy
*>(mdata
)); }
313 using PwMetadataPtr
= std::unique_ptr
<pw_metadata
,PwMetadataDeleter
>;
315 struct PwStreamDeleter
{
316 void operator()(pw_stream
*stream
) const { pw_stream_destroy(stream
); }
318 using PwStreamPtr
= std::unique_ptr
<pw_stream
,PwStreamDeleter
>;
320 /* Enums for bitflags... again... *sigh* */
321 constexpr pw_stream_flags
operator|(pw_stream_flags lhs
, pw_stream_flags rhs
) noexcept
322 { return static_cast<pw_stream_flags
>(lhs
| std::underlying_type_t
<pw_stream_flags
>{rhs
}); }
324 class ThreadMainloop
{
325 pw_thread_loop
*mLoop
{};
328 ThreadMainloop() = default;
329 ThreadMainloop(const ThreadMainloop
&) = delete;
330 ThreadMainloop(ThreadMainloop
&& rhs
) noexcept
: mLoop
{rhs
.mLoop
} { rhs
.mLoop
= nullptr; }
331 explicit ThreadMainloop(pw_thread_loop
*loop
) noexcept
: mLoop
{loop
} { }
332 ~ThreadMainloop() { if(mLoop
) pw_thread_loop_destroy(mLoop
); }
334 ThreadMainloop
& operator=(const ThreadMainloop
&) = delete;
335 ThreadMainloop
& operator=(ThreadMainloop
&& rhs
) noexcept
336 { std::swap(mLoop
, rhs
.mLoop
); return *this; }
337 ThreadMainloop
& operator=(std::nullptr_t
) noexcept
340 pw_thread_loop_destroy(mLoop
);
345 explicit operator bool() const noexcept
{ return mLoop
!= nullptr; }
347 auto start() const { return pw_thread_loop_start(mLoop
); }
348 auto stop() const { return pw_thread_loop_stop(mLoop
); }
350 auto getLoop() const { return pw_thread_loop_get_loop(mLoop
); }
352 auto lock() const { return pw_thread_loop_lock(mLoop
); }
353 auto unlock() const { return pw_thread_loop_unlock(mLoop
); }
355 auto signal(bool wait
) const { return pw_thread_loop_signal(mLoop
, wait
); }
357 auto newContext(pw_properties
*props
=nullptr, size_t user_data_size
=0)
358 { return PwContextPtr
{pw_context_new(getLoop(), props
, user_data_size
)}; }
360 static auto Create(const char *name
, spa_dict
*props
=nullptr)
361 { return ThreadMainloop
{pw_thread_loop_new(name
, props
)}; }
363 friend struct MainloopUniqueLock
;
365 struct MainloopUniqueLock
: public std::unique_lock
<ThreadMainloop
> {
366 using std::unique_lock
<ThreadMainloop
>::unique_lock
;
367 MainloopUniqueLock
& operator=(MainloopUniqueLock
&&) = default;
369 auto wait() const -> void
370 { pw_thread_loop_wait(mutex()->mLoop
); }
372 template<typename Predicate
>
373 auto wait(Predicate done_waiting
) const -> void
374 { while(!done_waiting()) wait(); }
376 using MainloopLockGuard
= std::lock_guard
<ThreadMainloop
>;
379 /* There's quite a mess here, but the purpose is to track active devices and
380 * their default formats, so playback devices can be configured to match. The
381 * device list is updated asynchronously, so it will have the latest list of
382 * devices provided by the server.
386 struct MetadataProxy
;
388 /* The global thread watching for global events. This particular class responds
389 * to objects being added to or removed from the registry.
391 struct EventManager
{
392 ThreadMainloop mLoop
{};
393 PwContextPtr mContext
{};
395 PwRegistryPtr mRegistry
{};
396 spa_hook mRegistryListener
{};
397 spa_hook mCoreListener
{};
399 /* A list of proxy objects watching for events about changes to objects in
402 std::vector
<NodeProxy
*> mNodeList
;
403 MetadataProxy
*mDefaultMetadata
{nullptr};
405 /* Initialization handling. When init() is called, mInitSeq is set to a
406 * SequenceID that marks the end of populating the registry. As objects of
407 * interest are found, events to parse them are generated and mInitSeq is
408 * updated with a newer ID. When mInitSeq stops being updated and the event
409 * corresponding to it is reached, mInitDone will be set to true.
411 std::atomic
<bool> mInitDone
{false};
412 std::atomic
<bool> mHasAudio
{false};
420 auto lock() const { return mLoop
.lock(); }
421 auto unlock() const { return mLoop
.unlock(); }
424 * Waits for initialization to finish. The event manager must *NOT* be
425 * locked when calling this.
429 if(unlikely(!mInitDone
.load(std::memory_order_acquire
)))
431 MainloopUniqueLock plock
{mLoop
};
432 plock
.wait([this](){ return mInitDone
.load(std::memory_order_acquire
); });
437 * Waits for audio support to be detected, or initialization to finish,
438 * whichever is first. Returns true if audio support was detected. The
439 * event manager must *NOT* be locked when calling this.
443 MainloopUniqueLock plock
{mLoop
};
445 plock
.wait([this,&has_audio
]()
447 has_audio
= mHasAudio
.load(std::memory_order_acquire
);
448 return has_audio
|| mInitDone
.load(std::memory_order_acquire
);
455 /* If initialization isn't done, update the sequence ID so it won't
456 * complete until after currently scheduled events.
458 if(!mInitDone
.load(std::memory_order_relaxed
))
459 mInitSeq
= ppw_core_sync(mCore
.get(), PW_ID_CORE
, mInitSeq
);
462 void addCallback(uint32_t id
, uint32_t permissions
, const char *type
, uint32_t version
,
463 const spa_dict
*props
);
464 static void addCallbackC(void *object
, uint32_t id
, uint32_t permissions
, const char *type
,
465 uint32_t version
, const spa_dict
*props
)
466 { static_cast<EventManager
*>(object
)->addCallback(id
, permissions
, type
, version
, props
); }
468 void removeCallback(uint32_t id
);
469 static void removeCallbackC(void *object
, uint32_t id
)
470 { static_cast<EventManager
*>(object
)->removeCallback(id
); }
472 static constexpr pw_registry_events
CreateRegistryEvents()
474 pw_registry_events ret
{};
475 ret
.version
= PW_VERSION_REGISTRY_EVENTS
;
476 ret
.global
= &EventManager::addCallbackC
;
477 ret
.global_remove
= &EventManager::removeCallbackC
;
481 void coreCallback(uint32_t id
, int seq
);
482 static void coreCallbackC(void *object
, uint32_t id
, int seq
)
483 { static_cast<EventManager
*>(object
)->coreCallback(id
, seq
); }
485 static constexpr pw_core_events
CreateCoreEvents()
487 pw_core_events ret
{};
488 ret
.version
= PW_VERSION_CORE_EVENTS
;
489 ret
.done
= &EventManager::coreCallbackC
;
493 using EventWatcherUniqueLock
= std::unique_lock
<EventManager
>;
494 using EventWatcherLockGuard
= std::lock_guard
<EventManager
>;
496 EventManager gEventHandler
;
498 /* Enumerated devices. This is updated asynchronously as the app runs, and the
499 * gEventHandler thread loop must be locked when accessing the list.
501 enum class NodeType
: unsigned char {
504 constexpr auto InvalidChannelConfig
= DevFmtChannels(255);
507 std::string mDevName
;
511 bool mIsHeadphones
{};
515 DevFmtChannels mChannels
{InvalidChannelConfig
};
517 static std::vector
<DeviceNode
> sList
;
518 static DeviceNode
&Add(uint32_t id
);
519 static DeviceNode
*Find(uint32_t id
);
520 static void Remove(uint32_t id
);
521 static std::vector
<DeviceNode
> &GetList() noexcept
{ return sList
; }
523 void parseSampleRate(const spa_pod
*value
) noexcept
;
524 void parsePositions(const spa_pod
*value
) noexcept
;
525 void parseChannelCount(const spa_pod
*value
) noexcept
;
527 std::vector
<DeviceNode
> DeviceNode::sList
;
528 std::string DefaultSinkDevice
;
529 std::string DefaultSourceDevice
;
531 const char *AsString(NodeType type
) noexcept
535 case NodeType::Sink
: return "sink";
536 case NodeType::Source
: return "source";
537 case NodeType::Duplex
: return "duplex";
542 DeviceNode
&DeviceNode::Add(uint32_t id
)
544 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
545 { return n
.mId
== id
; };
547 /* If the node is already in the list, return the existing entry. */
548 auto match
= std::find_if(sList
.begin(), sList
.end(), match_id
);
549 if(match
!= sList
.end()) return *match
;
551 sList
.emplace_back();
552 auto &n
= sList
.back();
557 DeviceNode
*DeviceNode::Find(uint32_t id
)
559 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
560 { return n
.mId
== id
; };
562 auto match
= std::find_if(sList
.begin(), sList
.end(), match_id
);
563 if(match
!= sList
.end()) return std::addressof(*match
);
568 void DeviceNode::Remove(uint32_t id
)
570 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
574 TRACE("Removing device \"%s\"\n", n
.mDevName
.c_str());
578 auto end
= std::remove_if(sList
.begin(), sList
.end(), match_id
);
579 sList
.erase(end
, sList
.end());
583 const spa_audio_channel MonoMap
[]{
584 SPA_AUDIO_CHANNEL_MONO
586 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
588 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
590 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
591 SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
593 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
594 SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
596 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
597 SPA_AUDIO_CHANNEL_RC
, SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
599 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
600 SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
, SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
604 * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
605 * to or a superset of map1).
608 bool MatchChannelMap(const al::span
<const uint32_t> map0
, const spa_audio_channel (&map1
)[N
])
612 for(const spa_audio_channel chid
: map1
)
614 if(std::find(map0
.begin(), map0
.end(), chid
) == map0
.end())
620 void DeviceNode::parseSampleRate(const spa_pod
*value
) noexcept
622 /* TODO: Can this be anything else? Long, Float, Double? */
623 uint32_t nvals
{}, choiceType
{};
624 value
= spa_pod_get_values(value
, &nvals
, &choiceType
);
626 const uint podType
{get_pod_type(value
)};
627 if(podType
!= SPA_TYPE_Int
)
629 WARN("Unhandled sample rate POD type: %u\n", podType
);
633 if(choiceType
== SPA_CHOICE_Range
)
637 WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals
);
640 auto srates
= get_pod_body
<int32_t,3>(value
);
642 /* [0] is the default, [1] is the min, and [2] is the max. */
643 TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId
, srates
[0], srates
[1],
645 mSampleRate
= static_cast<uint
>(clampi(srates
[0], MIN_OUTPUT_RATE
, MAX_OUTPUT_RATE
));
649 if(choiceType
== SPA_CHOICE_Enum
)
653 WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals
);
656 auto srates
= get_pod_body
<int32_t>(value
, nvals
);
658 /* [0] is the default, [1...size()-1] are available selections. */
659 std::string others
{(srates
.size() > 1) ? std::to_string(srates
[1]) : std::string
{}};
660 for(size_t i
{2};i
< srates
.size();++i
)
663 others
+= std::to_string(srates
[i
]);
665 TRACE("Device ID %u sample rate: %d (%s)\n", mId
, srates
[0], others
.c_str());
666 /* Pick the first rate listed that's within the allowed range (default
669 for(const auto &rate
: srates
)
671 if(rate
>= MIN_OUTPUT_RATE
&& rate
<= MAX_OUTPUT_RATE
)
673 mSampleRate
= static_cast<uint
>(rate
);
680 if(choiceType
== SPA_CHOICE_None
)
684 WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals
);
687 auto srates
= get_pod_body
<int32_t,1>(value
);
689 TRACE("Device ID %u sample rate: %d\n", mId
, srates
[0]);
690 mSampleRate
= static_cast<uint
>(clampi(srates
[0], MIN_OUTPUT_RATE
, MAX_OUTPUT_RATE
));
694 WARN("Unhandled sample rate choice type: %u\n", choiceType
);
697 void DeviceNode::parsePositions(const spa_pod
*value
) noexcept
699 const auto chanmap
= get_array_span
<SPA_TYPE_Id
>(value
);
700 if(chanmap
.empty()) return;
704 if(MatchChannelMap(chanmap
, X71Map
))
705 mChannels
= DevFmtX71
;
706 else if(MatchChannelMap(chanmap
, X61Map
))
707 mChannels
= DevFmtX61
;
708 else if(MatchChannelMap(chanmap
, X51Map
))
709 mChannels
= DevFmtX51
;
710 else if(MatchChannelMap(chanmap
, X51RearMap
))
712 mChannels
= DevFmtX51
;
715 else if(MatchChannelMap(chanmap
, QuadMap
))
716 mChannels
= DevFmtQuad
;
717 else if(MatchChannelMap(chanmap
, StereoMap
))
718 mChannels
= DevFmtStereo
;
720 mChannels
= DevFmtMono
;
721 TRACE("Device ID %u got %zu position%s for %s%s\n", mId
, chanmap
.size(),
722 (chanmap
.size()==1)?"":"s", DevFmtChannelsString(mChannels
), mIs51Rear
?"(rear)":"");
725 void DeviceNode::parseChannelCount(const spa_pod
*value
) noexcept
727 /* As a fallback with just a channel count, just assume mono or stereo. */
728 const auto chancount
= get_value
<SPA_TYPE_Int
>(value
);
729 if(!chancount
) return;
734 mChannels
= DevFmtStereo
;
735 else if(*chancount
>= 1)
736 mChannels
= DevFmtMono
;
737 TRACE("Device ID %u got %d channel%s for %s\n", mId
, *chancount
, (*chancount
==1)?"":"s",
738 DevFmtChannelsString(mChannels
));
742 constexpr char MonitorPrefix
[]{"Monitor of "};
743 constexpr auto MonitorPrefixLen
= al::size(MonitorPrefix
) - 1;
744 constexpr char AudioSinkClass
[]{"Audio/Sink"};
745 constexpr char AudioSourceClass
[]{"Audio/Source"};
746 constexpr char AudioDuplexClass
[]{"Audio/Duplex"};
747 constexpr char StreamClass
[]{"Stream/"};
749 /* A generic PipeWire node proxy object used to track changes to sink and
753 static constexpr pw_node_events
CreateNodeEvents()
755 pw_node_events ret
{};
756 ret
.version
= PW_VERSION_NODE_EVENTS
;
757 ret
.info
= &NodeProxy::infoCallbackC
;
758 ret
.param
= &NodeProxy::paramCallbackC
;
765 spa_hook mListener
{};
767 NodeProxy(uint32_t id
, PwNodePtr node
)
768 : mId
{id
}, mNode
{std::move(node
)}
770 static constexpr pw_node_events nodeEvents
{CreateNodeEvents()};
771 ppw_node_add_listener(mNode
.get(), &mListener
, &nodeEvents
, this);
773 /* Track changes to the enumerable formats (indicates the default
774 * format, which is what we're interested in).
776 uint32_t fmtids
[]{SPA_PARAM_EnumFormat
};
777 ppw_node_subscribe_params(mNode
.get(), al::data(fmtids
), al::size(fmtids
));
780 { spa_hook_remove(&mListener
); }
783 void infoCallback(const pw_node_info
*info
);
784 static void infoCallbackC(void *object
, const pw_node_info
*info
)
785 { static_cast<NodeProxy
*>(object
)->infoCallback(info
); }
787 void paramCallback(int seq
, uint32_t id
, uint32_t index
, uint32_t next
, const spa_pod
*param
);
788 static void paramCallbackC(void *object
, int seq
, uint32_t id
, uint32_t index
, uint32_t next
,
789 const spa_pod
*param
)
790 { static_cast<NodeProxy
*>(object
)->paramCallback(seq
, id
, index
, next
, param
); }
793 void NodeProxy::infoCallback(const pw_node_info
*info
)
795 /* We only care about property changes here (media class, name/desc).
796 * Format changes will automatically invoke the param callback.
798 * TODO: Can the media class or name/desc change without being removed and
801 if((info
->change_mask
&PW_NODE_CHANGE_MASK_PROPS
))
803 /* Can this actually change? */
804 const char *media_class
{spa_dict_lookup(info
->props
, PW_KEY_MEDIA_CLASS
)};
805 if(unlikely(!media_class
)) return;
808 if(al::strcasecmp(media_class
, AudioSinkClass
) == 0)
809 ntype
= NodeType::Sink
;
810 else if(al::strcasecmp(media_class
, AudioSourceClass
) == 0)
811 ntype
= NodeType::Source
;
812 else if(al::strcasecmp(media_class
, AudioDuplexClass
) == 0)
813 ntype
= NodeType::Duplex
;
816 TRACE("Dropping device node %u which became type \"%s\"\n", info
->id
, media_class
);
817 DeviceNode::Remove(info
->id
);
821 const char *devName
{spa_dict_lookup(info
->props
, PW_KEY_NODE_NAME
)};
822 const char *nodeName
{spa_dict_lookup(info
->props
, PW_KEY_NODE_DESCRIPTION
)};
823 if(!nodeName
|| !*nodeName
) nodeName
= spa_dict_lookup(info
->props
, PW_KEY_NODE_NICK
);
824 if(!nodeName
|| !*nodeName
) nodeName
= devName
;
826 const char *form_factor
{spa_dict_lookup(info
->props
, PW_KEY_DEVICE_FORM_FACTOR
)};
827 TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype
), devName
? devName
: "(nil)",
828 form_factor
?" (":"", form_factor
?form_factor
:"", form_factor
?")":"");
829 TRACE(" \"%s\" = ID %u\n", nodeName
? nodeName
: "(nil)", info
->id
);
831 DeviceNode
&node
= DeviceNode::Add(info
->id
);
832 if(nodeName
&& *nodeName
) node
.mName
= nodeName
;
833 else node
.mName
= "PipeWire node #"+std::to_string(info
->id
);
834 node
.mDevName
= devName
? devName
: "";
836 node
.mIsHeadphones
= form_factor
&& (al::strcasecmp(form_factor
, "headphones") == 0
837 || al::strcasecmp(form_factor
, "headset") == 0);
841 void NodeProxy::paramCallback(int, uint32_t id
, uint32_t, uint32_t, const spa_pod
*param
)
843 if(id
== SPA_PARAM_EnumFormat
)
845 DeviceNode
*node
{DeviceNode::Find(mId
)};
846 if(unlikely(!node
)) return;
848 if(const spa_pod_prop
*prop
{spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_rate
)})
849 node
->parseSampleRate(&prop
->value
);
851 if(const spa_pod_prop
*prop
{spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_position
)})
852 node
->parsePositions(&prop
->value
);
853 else if((prop
=spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_channels
)) != nullptr)
854 node
->parseChannelCount(&prop
->value
);
859 /* A metadata proxy object used to query the default sink and source. */
860 struct MetadataProxy
{
861 static constexpr pw_metadata_events
CreateMetadataEvents()
863 pw_metadata_events ret
{};
864 ret
.version
= PW_VERSION_METADATA_EVENTS
;
865 ret
.property
= &MetadataProxy::propertyCallbackC
;
871 PwMetadataPtr mMetadata
{};
872 spa_hook mListener
{};
874 MetadataProxy(uint32_t id
, PwMetadataPtr mdata
)
875 : mId
{id
}, mMetadata
{std::move(mdata
)}
877 static constexpr pw_metadata_events metadataEvents
{CreateMetadataEvents()};
878 ppw_metadata_add_listener(mMetadata
.get(), &mListener
, &metadataEvents
, this);
881 { spa_hook_remove(&mListener
); }
884 int propertyCallback(uint32_t id
, const char *key
, const char *type
, const char *value
);
885 static int propertyCallbackC(void *object
, uint32_t id
, const char *key
, const char *type
,
887 { return static_cast<MetadataProxy
*>(object
)->propertyCallback(id
, key
, type
, value
); }
890 int MetadataProxy::propertyCallback(uint32_t id
, const char *key
, const char *type
,
897 if(std::strcmp(key
, "default.audio.sink") == 0)
899 else if(std::strcmp(key
, "default.audio.source") == 0)
906 TRACE("Default %s device cleared\n", isCapture
? "capture" : "playback");
907 if(!isCapture
) DefaultSinkDevice
.clear();
908 else DefaultSourceDevice
.clear();
911 if(std::strcmp(type
, "Spa:String:JSON") != 0)
913 ERR("Unexpected %s property type: %s\n", key
, type
);
918 spa_json_init(&it
[0], value
, strlen(value
));
919 if(spa_json_enter_object(&it
[0], &it
[1]) <= 0)
922 auto get_json_string
= [](spa_json
*iter
)
924 al::optional
<std::string
> str
;
927 int len
{spa_json_next(iter
, &val
)};
928 if(len
<= 0) return str
;
930 str
.emplace().resize(static_cast<uint
>(len
), '\0');
931 if(spa_json_parse_string(val
, len
, &str
->front()) <= 0)
933 else while(!str
->empty() && str
->back() == '\0')
937 while(auto propKey
= get_json_string(&it
[1]))
939 if(*propKey
== "name")
941 auto propValue
= get_json_string(&it
[1]);
942 if(!propValue
) break;
944 TRACE("Got default %s device \"%s\"\n", isCapture
? "capture" : "playback",
947 DefaultSinkDevice
= std::move(*propValue
);
949 DefaultSourceDevice
= std::move(*propValue
);
954 if(spa_json_next(&it
[1], &v
) <= 0)
962 bool EventManager::init()
964 mLoop
= ThreadMainloop::Create("PWEventThread");
967 ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno
);
971 mContext
= mLoop
.newContext(pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr));
974 ERR("Failed to create PipeWire event context (errno: %d)\n", errno
);
978 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
981 ERR("Failed to connect PipeWire event context (errno: %d)\n", errno
);
985 mRegistry
= PwRegistryPtr
{pw_core_get_registry(mCore
.get(), PW_VERSION_REGISTRY
, 0)};
988 ERR("Failed to get PipeWire event registry (errno: %d)\n", errno
);
992 static constexpr pw_core_events coreEvents
{CreateCoreEvents()};
993 static constexpr pw_registry_events registryEvents
{CreateRegistryEvents()};
995 ppw_core_add_listener(mCore
.get(), &mCoreListener
, &coreEvents
, this);
996 ppw_registry_add_listener(mRegistry
.get(), &mRegistryListener
, ®istryEvents
, this);
998 /* Set an initial sequence ID for initialization, to trigger after the
999 * registry is first populated.
1001 mInitSeq
= ppw_core_sync(mCore
.get(), PW_ID_CORE
, 0);
1003 if(int res
{mLoop
.start()})
1005 ERR("Failed to start PipeWire event thread loop (res: %d)\n", res
);
1012 EventManager::~EventManager()
1014 if(mLoop
) mLoop
.stop();
1016 for(NodeProxy
*node
: mNodeList
)
1017 al::destroy_at(node
);
1018 if(mDefaultMetadata
)
1019 al::destroy_at(mDefaultMetadata
);
1022 void EventManager::kill()
1024 if(mLoop
) mLoop
.stop();
1026 for(NodeProxy
*node
: mNodeList
)
1027 al::destroy_at(node
);
1029 if(mDefaultMetadata
)
1030 al::destroy_at(mDefaultMetadata
);
1031 mDefaultMetadata
= nullptr;
1033 mRegistry
= nullptr;
1039 void EventManager::addCallback(uint32_t id
, uint32_t, const char *type
, uint32_t version
,
1040 const spa_dict
*props
)
1042 /* We're only interested in interface nodes. */
1043 if(std::strcmp(type
, PW_TYPE_INTERFACE_Node
) == 0)
1045 const char *media_class
{spa_dict_lookup(props
, PW_KEY_MEDIA_CLASS
)};
1046 if(!media_class
) return;
1048 /* Specifically, audio sinks and sources (and duplexes). */
1049 const bool isGood
{al::strcasecmp(media_class
, AudioSinkClass
) == 0
1050 || al::strcasecmp(media_class
, AudioSourceClass
) == 0
1051 || al::strcasecmp(media_class
, AudioDuplexClass
) == 0};
1054 if(std::strstr(media_class
, "/Video") == nullptr
1055 && std::strncmp(media_class
, StreamClass
, sizeof(StreamClass
)-1) != 0)
1056 TRACE("Ignoring node class %s\n", media_class
);
1060 /* Create the proxy object. */
1061 auto node
= PwNodePtr
{static_cast<pw_node
*>(pw_registry_bind(mRegistry
.get(), id
, type
,
1062 version
, sizeof(NodeProxy
)))};
1065 ERR("Failed to create node proxy object (errno: %d)\n", errno
);
1069 /* Initialize the NodeProxy to hold the node object, add it to the
1070 * active node list, and update the sync point.
1072 auto *proxy
= static_cast<NodeProxy
*>(pw_proxy_get_user_data(as
<pw_proxy
*>(node
.get())));
1073 mNodeList
.emplace_back(al::construct_at(proxy
, id
, std::move(node
)));
1076 /* Signal any waiters that we have found a source or sink for audio
1079 if(!mHasAudio
.exchange(true, std::memory_order_acq_rel
))
1080 mLoop
.signal(false);
1082 else if(std::strcmp(type
, PW_TYPE_INTERFACE_Metadata
) == 0)
1084 const char *data_class
{spa_dict_lookup(props
, PW_KEY_METADATA_NAME
)};
1085 if(!data_class
) return;
1087 if(std::strcmp(data_class
, "default") != 0)
1089 TRACE("Ignoring metadata \"%s\"\n", data_class
);
1093 if(mDefaultMetadata
)
1095 ERR("Duplicate default metadata\n");
1099 auto mdata
= PwMetadataPtr
{static_cast<pw_metadata
*>(pw_registry_bind(mRegistry
.get(), id
,
1100 type
, version
, sizeof(MetadataProxy
)))};
1103 ERR("Failed to create metadata proxy object (errno: %d)\n", errno
);
1107 auto *proxy
= static_cast<MetadataProxy
*>(
1108 pw_proxy_get_user_data(as
<pw_proxy
*>(mdata
.get())));
1109 mDefaultMetadata
= al::construct_at(proxy
, id
, std::move(mdata
));
1114 void EventManager::removeCallback(uint32_t id
)
1116 DeviceNode::Remove(id
);
1118 auto clear_node
= [id
](NodeProxy
*node
) noexcept
1122 al::destroy_at(node
);
1125 auto node_end
= std::remove_if(mNodeList
.begin(), mNodeList
.end(), clear_node
);
1126 mNodeList
.erase(node_end
, mNodeList
.end());
1128 if(mDefaultMetadata
&& mDefaultMetadata
->mId
== id
)
1130 al::destroy_at(mDefaultMetadata
);
1131 mDefaultMetadata
= nullptr;
1135 void EventManager::coreCallback(uint32_t id
, int seq
)
1137 if(id
== PW_ID_CORE
&& seq
== mInitSeq
)
1139 /* Initialization done. Remove this callback and signal anyone that may
1142 spa_hook_remove(&mCoreListener
);
1144 mInitDone
.store(true);
1145 mLoop
.signal(false);
1150 enum use_f32p_e
: bool { UseDevType
=false, ForceF32Planar
=true };
1151 spa_audio_info_raw
make_spa_info(DeviceBase
*device
, bool is51rear
, use_f32p_e use_f32p
)
1153 spa_audio_info_raw info
{};
1156 device
->FmtType
= DevFmtFloat
;
1157 info
.format
= SPA_AUDIO_FORMAT_F32P
;
1159 else switch(device
->FmtType
)
1161 case DevFmtByte
: info
.format
= SPA_AUDIO_FORMAT_S8
; break;
1162 case DevFmtUByte
: info
.format
= SPA_AUDIO_FORMAT_U8
; break;
1163 case DevFmtShort
: info
.format
= SPA_AUDIO_FORMAT_S16
; break;
1164 case DevFmtUShort
: info
.format
= SPA_AUDIO_FORMAT_U16
; break;
1165 case DevFmtInt
: info
.format
= SPA_AUDIO_FORMAT_S32
; break;
1166 case DevFmtUInt
: info
.format
= SPA_AUDIO_FORMAT_U32
; break;
1167 case DevFmtFloat
: info
.format
= SPA_AUDIO_FORMAT_F32
; break;
1170 info
.rate
= device
->Frequency
;
1172 al::span
<const spa_audio_channel
> map
{};
1173 switch(device
->FmtChans
)
1175 case DevFmtMono
: map
= MonoMap
; break;
1176 case DevFmtStereo
: map
= StereoMap
; break;
1177 case DevFmtQuad
: map
= QuadMap
; break;
1179 if(is51rear
) map
= X51RearMap
;
1182 case DevFmtX61
: map
= X61Map
; break;
1183 case DevFmtX71
: map
= X71Map
; break;
1185 info
.flags
|= SPA_AUDIO_FLAG_UNPOSITIONED
;
1186 info
.channels
= device
->channelsFromFmt();
1191 info
.channels
= static_cast<uint32_t>(map
.size());
1192 std::copy(map
.begin(), map
.end(), info
.position
);
1198 class PipeWirePlayback final
: public BackendBase
{
1199 void stateChangedCallback(pw_stream_state old
, pw_stream_state state
, const char *error
);
1200 static void stateChangedCallbackC(void *data
, pw_stream_state old
, pw_stream_state state
,
1202 { static_cast<PipeWirePlayback
*>(data
)->stateChangedCallback(old
, state
, error
); }
1204 void ioChangedCallback(uint32_t id
, void *area
, uint32_t size
);
1205 static void ioChangedCallbackC(void *data
, uint32_t id
, void *area
, uint32_t size
)
1206 { static_cast<PipeWirePlayback
*>(data
)->ioChangedCallback(id
, area
, size
); }
1208 void outputCallback();
1209 static void outputCallbackC(void *data
)
1210 { static_cast<PipeWirePlayback
*>(data
)->outputCallback(); }
1212 void open(const char *name
) override
;
1213 bool reset() override
;
1214 void start() override
;
1215 void stop() override
;
1216 ClockLatency
getClockLatency() override
;
1218 uint32_t mTargetId
{PwIdAny
};
1219 nanoseconds mTimeBase
{0};
1220 ThreadMainloop mLoop
;
1221 PwContextPtr mContext
;
1223 PwStreamPtr mStream
;
1224 spa_hook mStreamListener
{};
1225 spa_io_rate_match
*mRateMatch
{};
1226 std::unique_ptr
<float*[]> mChannelPtrs
;
1227 uint mNumChannels
{};
1229 static constexpr pw_stream_events
CreateEvents()
1231 pw_stream_events ret
{};
1232 ret
.version
= PW_VERSION_STREAM_EVENTS
;
1233 ret
.state_changed
= &PipeWirePlayback::stateChangedCallbackC
;
1234 ret
.io_changed
= &PipeWirePlayback::ioChangedCallbackC
;
1235 ret
.process
= &PipeWirePlayback::outputCallbackC
;
1240 PipeWirePlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1243 /* Stop the mainloop so the stream can be properly destroyed. */
1244 if(mLoop
) mLoop
.stop();
1247 DEF_NEWDEL(PipeWirePlayback
)
1251 void PipeWirePlayback::stateChangedCallback(pw_stream_state
, pw_stream_state
, const char*)
1252 { mLoop
.signal(false); }
1254 void PipeWirePlayback::ioChangedCallback(uint32_t id
, void *area
, uint32_t size
)
1258 case SPA_IO_RateMatch
:
1259 if(size
>= sizeof(spa_io_rate_match
))
1260 mRateMatch
= static_cast<spa_io_rate_match
*>(area
);
1265 void PipeWirePlayback::outputCallback()
1267 pw_buffer
*pw_buf
{pw_stream_dequeue_buffer(mStream
.get())};
1268 if(unlikely(!pw_buf
)) return;
1270 /* For planar formats, each datas[] seems to contain one channel, so store
1271 * the pointers in an array. Limit the render length in case the available
1272 * buffer length in any one channel is smaller than we wanted (shouldn't
1273 * be, but just in case).
1275 spa_data
*datas
{pw_buf
->buffer
->datas
};
1276 const size_t chancount
{minu(mNumChannels
, pw_buf
->buffer
->n_datas
)};
1277 /* TODO: How many samples should actually be written? 'maxsize' can be 16k
1278 * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains
1279 * a 'size' field that apparently indicates how many samples should be
1280 * written per update, but it's not obviously right.
1282 uint length
{mRateMatch
? mRateMatch
->size
: mDevice
->UpdateSize
};
1283 for(size_t i
{0};i
< chancount
;++i
)
1285 length
= minu(length
, datas
[i
].maxsize
/sizeof(float));
1286 mChannelPtrs
[i
] = static_cast<float*>(datas
[i
].data
);
1289 mDevice
->renderSamples({mChannelPtrs
.get(), chancount
}, length
);
1291 for(size_t i
{0};i
< chancount
;++i
)
1293 datas
[i
].chunk
->offset
= 0;
1294 datas
[i
].chunk
->stride
= sizeof(float);
1295 datas
[i
].chunk
->size
= length
* sizeof(float);
1297 pw_buf
->size
= length
;
1298 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1302 void PipeWirePlayback::open(const char *name
)
1304 static std::atomic
<uint
> OpenCount
{0};
1306 uint32_t targetid
{PwIdAny
};
1307 std::string devname
{};
1308 gEventHandler
.waitForInit();
1311 EventWatcherLockGuard _
{gEventHandler
};
1312 auto&& devlist
= DeviceNode::GetList();
1314 auto match
= devlist
.cend();
1315 if(!DefaultSinkDevice
.empty())
1317 auto match_default
= [](const DeviceNode
&n
) -> bool
1318 { return n
.mDevName
== DefaultSinkDevice
; };
1319 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1321 if(match
== devlist
.cend())
1323 auto match_playback
= [](const DeviceNode
&n
) -> bool
1324 { return n
.mType
!= NodeType::Source
; };
1325 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_playback
);
1326 if(match
== devlist
.cend())
1327 throw al::backend_exception
{al::backend_error::NoDevice
,
1328 "No PipeWire playback device found"};
1331 targetid
= match
->mId
;
1332 devname
= match
->mName
;
1336 EventWatcherLockGuard _
{gEventHandler
};
1337 auto&& devlist
= DeviceNode::GetList();
1339 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1340 { return n
.mType
!= NodeType::Source
&& n
.mName
== name
; };
1341 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1342 if(match
== devlist
.cend())
1343 throw al::backend_exception
{al::backend_error::NoDevice
,
1344 "Device name \"%s\" not found", name
};
1346 targetid
= match
->mId
;
1347 devname
= match
->mName
;
1352 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1353 const std::string thread_name
{"ALSoftP" + std::to_string(count
)};
1354 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1356 throw al::backend_exception
{al::backend_error::DeviceError
,
1357 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1358 if(int res
{mLoop
.start()})
1359 throw al::backend_exception
{al::backend_error::DeviceError
,
1360 "Failed to start PipeWire mainloop (res: %d)", res
};
1362 MainloopUniqueLock mlock
{mLoop
};
1365 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1366 mContext
= mLoop
.newContext(cprops
);
1368 throw al::backend_exception
{al::backend_error::DeviceError
,
1369 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1373 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1375 throw al::backend_exception
{al::backend_error::DeviceError
,
1376 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1380 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1382 mTargetId
= targetid
;
1383 if(!devname
.empty())
1384 mDevice
->DeviceName
= std::move(devname
);
1386 mDevice
->DeviceName
= pwireDevice
;
1389 bool PipeWirePlayback::reset()
1393 MainloopLockGuard _
{mLoop
};
1396 mStreamListener
= {};
1397 mRateMatch
= nullptr;
1398 mTimeBase
= GetDeviceClockTime(mDevice
);
1400 /* If connecting to a specific device, update various device parameters to
1403 bool is51rear
{false};
1404 mDevice
->Flags
.reset(DirectEar
);
1405 if(mTargetId
!= PwIdAny
)
1407 EventWatcherLockGuard _
{gEventHandler
};
1408 auto&& devlist
= DeviceNode::GetList();
1410 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1411 { return targetid
== n
.mId
; };
1412 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1413 if(match
!= devlist
.cend())
1415 if(!mDevice
->Flags
.test(FrequencyRequest
) && match
->mSampleRate
> 0)
1417 /* Scale the update size if the sample rate changes. */
1418 const double scale
{static_cast<double>(match
->mSampleRate
) / mDevice
->Frequency
};
1419 mDevice
->Frequency
= match
->mSampleRate
;
1420 mDevice
->UpdateSize
= static_cast<uint
>(clampd(mDevice
->UpdateSize
*scale
+ 0.5,
1422 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1424 if(!mDevice
->Flags
.test(ChannelsRequest
) && match
->mChannels
!= InvalidChannelConfig
)
1425 mDevice
->FmtChans
= match
->mChannels
;
1426 if(match
->mChannels
== DevFmtStereo
&& match
->mIsHeadphones
)
1427 mDevice
->Flags
.set(DirectEar
);
1428 is51rear
= match
->mIs51Rear
;
1431 /* Force planar 32-bit float output for playback. This is what PipeWire
1432 * handles internally, and it's easier for us too.
1434 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, ForceF32Planar
)};
1436 /* TODO: How to tell what an appropriate size is? Examples just use this
1439 constexpr uint32_t pod_buffer_size
{1024};
1440 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1441 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1443 const spa_pod
*params
{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1445 throw al::backend_exception
{al::backend_error::DeviceError
,
1446 "Failed to set PipeWire audio format parameters"};
1448 pw_properties
*props
{pw_properties_new(
1449 PW_KEY_MEDIA_TYPE
, "Audio",
1450 PW_KEY_MEDIA_CATEGORY
, "Playback",
1451 PW_KEY_MEDIA_ROLE
, "Game",
1452 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1455 throw al::backend_exception
{al::backend_error::DeviceError
,
1456 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1458 auto&& binary
= GetProcBinary();
1459 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1460 /* TODO: Which properties are actually needed here? Any others that could
1463 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1464 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1465 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", mDevice
->UpdateSize
,
1466 mDevice
->Frequency
);
1467 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1469 MainloopUniqueLock plock
{mLoop
};
1470 /* The stream takes overship of 'props', even in the case of failure. */
1471 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Playback Stream", props
)};
1473 throw al::backend_exception
{al::backend_error::NoDevice
,
1474 "Failed to create PipeWire stream (errno: %d)", errno
};
1475 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1476 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1478 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1479 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1480 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_OUTPUT
, mTargetId
, Flags
, ¶ms
, 1)})
1481 throw al::backend_exception
{al::backend_error::DeviceError
,
1482 "Error connecting PipeWire stream (res: %d)", res
};
1484 /* Wait for the stream to become paused (ready to start streaming). */
1485 pw_stream_state state
{};
1486 const char *error
{};
1487 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1489 state
= pw_stream_get_state(stream
, &error
);
1490 if(state
== PW_STREAM_STATE_ERROR
)
1491 throw al::backend_exception
{al::backend_error::DeviceError
,
1492 "Error connecting PipeWire stream: \"%s\"", error
};
1493 return state
== PW_STREAM_STATE_PAUSED
;
1496 /* TODO: Update mDevice->BufferSize with the total known buffering delay
1497 * from the head of this playback stream to the tail of the device output.
1499 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1502 mNumChannels
= mDevice
->channelsFromFmt();
1503 mChannelPtrs
= std::make_unique
<float*[]>(mNumChannels
);
1505 setDefaultWFXChannelOrder();
1510 void PipeWirePlayback::start()
1512 MainloopUniqueLock plock
{mLoop
};
1513 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1514 throw al::backend_exception
{al::backend_error::DeviceError
,
1515 "Failed to start PipeWire stream (res: %d)", res
};
1517 /* Wait for the stream to start playing (would be nice to not, but we need
1518 * the actual update size which is only available after starting).
1520 pw_stream_state state
{};
1521 const char *error
{};
1522 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1524 state
= pw_stream_get_state(stream
, &error
);
1525 return state
!= PW_STREAM_STATE_PAUSED
;
1528 if(state
== PW_STREAM_STATE_ERROR
)
1529 throw al::backend_exception
{al::backend_error::DeviceError
,
1530 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1531 if(state
== PW_STREAM_STATE_STREAMING
&& mRateMatch
&& mRateMatch
->size
)
1533 mDevice
->UpdateSize
= mRateMatch
->size
;
1534 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1538 void PipeWirePlayback::stop()
1540 MainloopUniqueLock plock
{mLoop
};
1541 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1542 throw al::backend_exception
{al::backend_error::DeviceError
,
1543 "Failed to stop PipeWire stream (res: %d)", res
};
1545 /* Wait for the stream to stop playing. */
1546 plock
.wait([stream
=mStream
.get()]()
1547 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1550 ClockLatency
PipeWirePlayback::getClockLatency()
1552 /* Given a real-time low-latency output, this is rather complicated to get
1553 * accurate timing. So, here we go.
1556 /* First, get the stream time info (tick delay, ticks played, and the
1557 * CLOCK_MONOTONIC time closest to when that last tick was played).
1562 MainloopLockGuard _
{mLoop
};
1563 if(int res
{pw_stream_get_time(mStream
.get(), &ptime
)})
1564 ERR("Failed to get PipeWire stream time (res: %d)\n", res
);
1567 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1568 * monotonic clock closest to 'now', and the last mixer time at 'now').
1570 nanoseconds mixtime
{};
1574 refcount
= mDevice
->waitForMix();
1575 mixtime
= GetDeviceClockTime(mDevice
);
1576 clock_gettime(CLOCK_MONOTONIC
, &tspec
);
1577 std::atomic_thread_fence(std::memory_order_acquire
);
1578 } while(refcount
!= ReadRef(mDevice
->MixCount
));
1580 /* Convert the monotonic clock, stream ticks, and stream delay to
1583 nanoseconds monoclock
{seconds
{tspec
.tv_sec
} + nanoseconds
{tspec
.tv_nsec
}};
1584 nanoseconds curtic
{}, delay
{};
1585 if(unlikely(ptime
.rate
.denom
< 1))
1587 /* If there's no stream rate, the stream hasn't had a chance to get
1588 * going and return time info yet. Just use dummy values.
1590 ptime
.now
= monoclock
.count();
1592 delay
= nanoseconds
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
;
1596 /* The stream gets recreated with each reset, so include the time that
1597 * had already passed with previous streams.
1600 /* More safely scale the ticks to avoid overflowing the pre-division
1601 * temporary as it gets larger.
1603 curtic
+= seconds
{ptime
.ticks
/ ptime
.rate
.denom
} * ptime
.rate
.num
;
1604 curtic
+= nanoseconds
{seconds
{ptime
.ticks
%ptime
.rate
.denom
} * ptime
.rate
.num
} /
1607 /* The delay should be small enough to not worry about overflow. */
1608 delay
= nanoseconds
{seconds
{ptime
.delay
} * ptime
.rate
.num
} / ptime
.rate
.denom
;
1611 /* If the mixer time is ahead of the stream time, there's that much more
1612 * delay relative to the stream delay.
1614 if(mixtime
> curtic
)
1615 delay
+= mixtime
- curtic
;
1616 /* Reduce the delay according to how much time has passed since the known
1617 * stream time. This isn't 100% accurate since the system monotonic clock
1618 * doesn't tick at the exact same rate as the audio device, but it should
1619 * be good enough with ptime.now being constantly updated every few
1620 * milliseconds with ptime.ticks.
1622 delay
-= monoclock
- nanoseconds
{ptime
.now
};
1624 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1625 * incase timer drift got that severe.
1628 ret
.ClockTime
= mixtime
;
1629 ret
.Latency
= std::max(delay
, nanoseconds
{});
1635 class PipeWireCapture final
: public BackendBase
{
1636 void stateChangedCallback(pw_stream_state old
, pw_stream_state state
, const char *error
);
1637 static void stateChangedCallbackC(void *data
, pw_stream_state old
, pw_stream_state state
,
1639 { static_cast<PipeWireCapture
*>(data
)->stateChangedCallback(old
, state
, error
); }
1641 void inputCallback();
1642 static void inputCallbackC(void *data
)
1643 { static_cast<PipeWireCapture
*>(data
)->inputCallback(); }
1645 void open(const char *name
) override
;
1646 void start() override
;
1647 void stop() override
;
1648 void captureSamples(al::byte
*buffer
, uint samples
) override
;
1649 uint
availableSamples() override
;
1651 uint32_t mTargetId
{PwIdAny
};
1652 ThreadMainloop mLoop
;
1653 PwContextPtr mContext
;
1655 PwStreamPtr mStream
;
1656 spa_hook mStreamListener
{};
1658 RingBufferPtr mRing
{};
1660 static constexpr pw_stream_events
CreateEvents()
1662 pw_stream_events ret
{};
1663 ret
.version
= PW_VERSION_STREAM_EVENTS
;
1664 ret
.state_changed
= &PipeWireCapture::stateChangedCallbackC
;
1665 ret
.process
= &PipeWireCapture::inputCallbackC
;
1670 PipeWireCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1671 ~PipeWireCapture() { if(mLoop
) mLoop
.stop(); }
1673 DEF_NEWDEL(PipeWireCapture
)
1677 void PipeWireCapture::stateChangedCallback(pw_stream_state
, pw_stream_state
, const char*)
1678 { mLoop
.signal(false); }
1680 void PipeWireCapture::inputCallback()
1682 pw_buffer
*pw_buf
{pw_stream_dequeue_buffer(mStream
.get())};
1683 if(unlikely(!pw_buf
)) return;
1685 spa_data
*bufdata
{pw_buf
->buffer
->datas
};
1686 const uint offset
{minu(bufdata
->chunk
->offset
, bufdata
->maxsize
)};
1687 const uint size
{minu(bufdata
->chunk
->size
, bufdata
->maxsize
- offset
)};
1689 mRing
->write(static_cast<char*>(bufdata
->data
) + offset
, size
/ mRing
->getElemSize());
1691 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1695 void PipeWireCapture::open(const char *name
)
1697 static std::atomic
<uint
> OpenCount
{0};
1699 uint32_t targetid
{PwIdAny
};
1700 std::string devname
{};
1701 gEventHandler
.waitForInit();
1704 EventWatcherLockGuard _
{gEventHandler
};
1705 auto&& devlist
= DeviceNode::GetList();
1707 auto match
= devlist
.cend();
1708 if(!DefaultSourceDevice
.empty())
1710 auto match_default
= [](const DeviceNode
&n
) -> bool
1711 { return n
.mDevName
== DefaultSourceDevice
; };
1712 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1714 if(match
== devlist
.cend())
1716 auto match_capture
= [](const DeviceNode
&n
) -> bool
1717 { return n
.mType
!= NodeType::Sink
; };
1718 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_capture
);
1720 if(match
== devlist
.cend())
1722 match
= devlist
.cbegin();
1723 if(match
== devlist
.cend())
1724 throw al::backend_exception
{al::backend_error::NoDevice
,
1725 "No PipeWire capture device found"};
1728 targetid
= match
->mId
;
1729 if(match
->mType
!= NodeType::Sink
) devname
= match
->mName
;
1730 else devname
= MonitorPrefix
+match
->mName
;
1734 EventWatcherLockGuard _
{gEventHandler
};
1735 auto&& devlist
= DeviceNode::GetList();
1737 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1738 { return n
.mType
!= NodeType::Sink
&& n
.mName
== name
; };
1739 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1740 if(match
== devlist
.cend() && std::strncmp(name
, MonitorPrefix
, MonitorPrefixLen
) == 0)
1742 const char *sinkname
{name
+ MonitorPrefixLen
};
1743 auto match_sinkname
= [sinkname
](const DeviceNode
&n
) -> bool
1744 { return n
.mType
== NodeType::Sink
&& n
.mName
== sinkname
; };
1745 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_sinkname
);
1747 if(match
== devlist
.cend())
1748 throw al::backend_exception
{al::backend_error::NoDevice
,
1749 "Device name \"%s\" not found", name
};
1751 targetid
= match
->mId
;
1757 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1758 const std::string thread_name
{"ALSoftC" + std::to_string(count
)};
1759 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1761 throw al::backend_exception
{al::backend_error::DeviceError
,
1762 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1763 if(int res
{mLoop
.start()})
1764 throw al::backend_exception
{al::backend_error::DeviceError
,
1765 "Failed to start PipeWire mainloop (res: %d)", res
};
1767 MainloopUniqueLock mlock
{mLoop
};
1770 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1771 mContext
= mLoop
.newContext(cprops
);
1773 throw al::backend_exception
{al::backend_error::DeviceError
,
1774 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1778 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1780 throw al::backend_exception
{al::backend_error::DeviceError
,
1781 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1785 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1787 mTargetId
= targetid
;
1788 if(!devname
.empty())
1789 mDevice
->DeviceName
= std::move(devname
);
1791 mDevice
->DeviceName
= pwireInput
;
1794 bool is51rear
{false};
1795 if(mTargetId
!= PwIdAny
)
1797 EventWatcherLockGuard _
{gEventHandler
};
1798 auto&& devlist
= DeviceNode::GetList();
1800 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1801 { return targetid
== n
.mId
; };
1802 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1803 if(match
!= devlist
.cend())
1804 is51rear
= match
->mIs51Rear
;
1806 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, UseDevType
)};
1808 constexpr uint32_t pod_buffer_size
{1024};
1809 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1810 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1812 const spa_pod
*params
[]{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1814 throw al::backend_exception
{al::backend_error::DeviceError
,
1815 "Failed to set PipeWire audio format parameters"};
1817 pw_properties
*props
{pw_properties_new(
1818 PW_KEY_MEDIA_TYPE
, "Audio",
1819 PW_KEY_MEDIA_CATEGORY
, "Capture",
1820 PW_KEY_MEDIA_ROLE
, "Game",
1821 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1824 throw al::backend_exception
{al::backend_error::DeviceError
,
1825 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1827 auto&& binary
= GetProcBinary();
1828 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1829 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1830 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1831 /* We don't actually care what the latency/update size is, as long as it's
1832 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
1833 * around 40ms, which isn't great. So request 20ms instead.
1835 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", (mDevice
->Frequency
+25) / 50,
1836 mDevice
->Frequency
);
1837 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1839 MainloopUniqueLock plock
{mLoop
};
1840 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Capture Stream", props
)};
1842 throw al::backend_exception
{al::backend_error::NoDevice
,
1843 "Failed to create PipeWire stream (errno: %d)", errno
};
1844 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1845 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1847 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1848 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1849 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_INPUT
, mTargetId
, Flags
, params
, 1)})
1850 throw al::backend_exception
{al::backend_error::DeviceError
,
1851 "Error connecting PipeWire stream (res: %d)", res
};
1853 /* Wait for the stream to become paused (ready to start streaming). */
1854 pw_stream_state state
{};
1855 const char *error
{};
1856 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1858 state
= pw_stream_get_state(stream
, &error
);
1859 if(state
== PW_STREAM_STATE_ERROR
)
1860 throw al::backend_exception
{al::backend_error::DeviceError
,
1861 "Error connecting PipeWire stream: \"%s\"", error
};
1862 return state
== PW_STREAM_STATE_PAUSED
;
1866 setDefaultWFXChannelOrder();
1868 /* Ensure at least a 100ms capture buffer. */
1869 mRing
= RingBuffer::Create(maxu(mDevice
->Frequency
/10, mDevice
->BufferSize
),
1870 mDevice
->frameSizeFromFmt(), false);
1874 void PipeWireCapture::start()
1876 MainloopUniqueLock plock
{mLoop
};
1877 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1878 throw al::backend_exception
{al::backend_error::DeviceError
,
1879 "Failed to start PipeWire stream (res: %d)", res
};
1881 pw_stream_state state
{};
1882 const char *error
{};
1883 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1885 state
= pw_stream_get_state(stream
, &error
);
1886 return state
!= PW_STREAM_STATE_PAUSED
;
1889 if(state
== PW_STREAM_STATE_ERROR
)
1890 throw al::backend_exception
{al::backend_error::DeviceError
,
1891 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1894 void PipeWireCapture::stop()
1896 MainloopUniqueLock plock
{mLoop
};
1897 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1898 throw al::backend_exception
{al::backend_error::DeviceError
,
1899 "Failed to stop PipeWire stream (res: %d)", res
};
1901 plock
.wait([stream
=mStream
.get()]()
1902 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1905 uint
PipeWireCapture::availableSamples()
1906 { return static_cast<uint
>(mRing
->readSpace()); }
1908 void PipeWireCapture::captureSamples(al::byte
*buffer
, uint samples
)
1909 { mRing
->read(buffer
, samples
); }
1914 bool PipeWireBackendFactory::init()
1919 pw_init(0, nullptr);
1920 if(!gEventHandler
.init())
1923 if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
1924 && !gEventHandler
.waitForAudio())
1926 gEventHandler
.kill();
1927 /* TODO: Temporary warning, until PipeWire gets a proper way to report
1930 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
1936 bool PipeWireBackendFactory::querySupport(BackendType type
)
1937 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1939 std::string
PipeWireBackendFactory::probe(BackendType type
)
1941 std::string outnames
;
1943 gEventHandler
.waitForInit();
1944 EventWatcherLockGuard _
{gEventHandler
};
1945 auto&& devlist
= DeviceNode::GetList();
1947 auto match_defsink
= [](const DeviceNode
&n
) -> bool
1948 { return n
.mDevName
== DefaultSinkDevice
; };
1949 auto match_defsource
= [](const DeviceNode
&n
) -> bool
1950 { return n
.mDevName
== DefaultSourceDevice
; };
1952 auto sort_devnode
= [](DeviceNode
&lhs
, DeviceNode
&rhs
) noexcept
-> bool
1953 { return lhs
.mId
< rhs
.mId
; };
1954 std::sort(devlist
.begin(), devlist
.end(), sort_devnode
);
1956 auto defmatch
= devlist
.cbegin();
1959 case BackendType::Playback
:
1960 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsink
);
1961 if(defmatch
!= devlist
.cend())
1963 /* Includes null char. */
1964 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
1966 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
1968 if(iter
!= defmatch
&& iter
->mType
!= NodeType::Source
)
1969 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
1972 case BackendType::Capture
:
1973 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsource
);
1974 if(defmatch
!= devlist
.cend())
1976 if(defmatch
->mType
== NodeType::Sink
)
1977 outnames
.append(MonitorPrefix
);
1978 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
1980 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
1982 if(iter
!= defmatch
)
1984 if(iter
->mType
== NodeType::Sink
)
1985 outnames
.append(MonitorPrefix
);
1986 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
1995 BackendPtr
PipeWireBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
1997 if(type
== BackendType::Playback
)
1998 return BackendPtr
{new PipeWirePlayback
{device
}};
1999 if(type
== BackendType::Capture
)
2000 return BackendPtr
{new PipeWireCapture
{device
}};
2004 BackendFactory
&PipeWireBackendFactory::getFactory()
2006 static PipeWireBackendFactory factory
{};