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). GCC
54 * doesn't support ignoring -Weverything, so we have the list the individual
55 * warnings to ignore (and ignoring -Winline doesn't seem to work).
57 _Pragma("GCC diagnostic push")
58 _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
59 _Pragma("GCC diagnostic ignored \"-Wconversion\"")
60 _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
61 _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
62 _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
63 _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
64 _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
65 _Pragma("GCC diagnostic ignored \"-Winline\"")
66 _Pragma("GCC diagnostic ignored \"-Wpragmas\"")
67 _Pragma("GCC diagnostic ignored \"-Weverything\"")
68 #include "pipewire/pipewire.h"
69 #include "pipewire/extensions/metadata.h"
70 #include "spa/buffer/buffer.h"
71 #include "spa/param/audio/format-utils.h"
72 #include "spa/param/audio/raw.h"
73 #include "spa/param/param.h"
74 #include "spa/pod/builder.h"
75 #include "spa/utils/json.h"
78 /* Wrap some nasty macros here too... */
79 template<typename
...Args
>
80 auto ppw_core_add_listener(pw_core
*core
, Args
&& ...args
)
81 { return pw_core_add_listener(core
, std::forward
<Args
>(args
)...); }
82 template<typename
...Args
>
83 auto ppw_core_sync(pw_core
*core
, Args
&& ...args
)
84 { return pw_core_sync(core
, std::forward
<Args
>(args
)...); }
85 template<typename
...Args
>
86 auto ppw_registry_add_listener(pw_registry
*reg
, Args
&& ...args
)
87 { return pw_registry_add_listener(reg
, std::forward
<Args
>(args
)...); }
88 template<typename
...Args
>
89 auto ppw_node_add_listener(pw_node
*node
, Args
&& ...args
)
90 { return pw_node_add_listener(node
, std::forward
<Args
>(args
)...); }
91 template<typename
...Args
>
92 auto ppw_node_subscribe_params(pw_node
*node
, Args
&& ...args
)
93 { return pw_node_subscribe_params(node
, std::forward
<Args
>(args
)...); }
94 template<typename
...Args
>
95 auto ppw_metadata_add_listener(pw_metadata
*mdata
, Args
&& ...args
)
96 { return pw_metadata_add_listener(mdata
, std::forward
<Args
>(args
)...); }
99 constexpr auto get_pod_type(const spa_pod
*pod
) noexcept
100 { return SPA_POD_TYPE(pod
); }
103 constexpr auto get_pod_body(const spa_pod
*pod
, size_t count
) noexcept
104 { return al::span
<T
>{static_cast<T
*>(SPA_POD_BODY(pod
)), count
}; }
105 template<typename T
, size_t N
>
106 constexpr auto get_pod_body(const spa_pod
*pod
) noexcept
107 { return al::span
<T
,N
>{static_cast<T
*>(SPA_POD_BODY(pod
)), N
}; }
109 constexpr auto make_pod_builder(void *data
, uint32_t size
) noexcept
110 { return SPA_POD_BUILDER_INIT(data
, size
); }
112 constexpr auto get_array_value_type(const spa_pod
*pod
) noexcept
113 { return SPA_POD_ARRAY_VALUE_TYPE(pod
); }
115 constexpr auto PwIdAny
= PW_ID_ANY
;
118 _Pragma("GCC diagnostic pop")
122 using std::chrono::seconds
;
123 using std::chrono::nanoseconds
;
124 using uint
= unsigned int;
126 constexpr char pwireDevice
[] = "PipeWire Output";
127 constexpr char pwireInput
[] = "PipeWire Input";
130 bool check_version(const char *version
)
132 /* There doesn't seem to be a function to get the version as an integer, so
133 * instead we have to parse the string, which hopefully won't break in the
136 int major
{0}, minor
{0}, revision
{0};
137 int ret
{sscanf(version
, "%d.%d.%d", &major
, &minor
, &revision
)};
138 if(ret
== 3 && PW_CHECK_VERSION(major
, minor
, revision
))
144 #define PWIRE_FUNCS(MAGIC) \
145 MAGIC(pw_context_connect) \
146 MAGIC(pw_context_destroy) \
147 MAGIC(pw_context_new) \
148 MAGIC(pw_core_disconnect) \
149 MAGIC(pw_get_library_version) \
151 MAGIC(pw_properties_free) \
152 MAGIC(pw_properties_new) \
153 MAGIC(pw_properties_set) \
154 MAGIC(pw_properties_setf) \
155 MAGIC(pw_proxy_add_object_listener) \
156 MAGIC(pw_proxy_destroy) \
157 MAGIC(pw_proxy_get_user_data) \
158 MAGIC(pw_stream_add_listener) \
159 MAGIC(pw_stream_connect) \
160 MAGIC(pw_stream_dequeue_buffer) \
161 MAGIC(pw_stream_destroy) \
162 MAGIC(pw_stream_get_state) \
163 MAGIC(pw_stream_new) \
164 MAGIC(pw_stream_queue_buffer) \
165 MAGIC(pw_stream_set_active) \
166 MAGIC(pw_thread_loop_new) \
167 MAGIC(pw_thread_loop_destroy) \
168 MAGIC(pw_thread_loop_get_loop) \
169 MAGIC(pw_thread_loop_start) \
170 MAGIC(pw_thread_loop_stop) \
171 MAGIC(pw_thread_loop_lock) \
172 MAGIC(pw_thread_loop_wait) \
173 MAGIC(pw_thread_loop_signal) \
174 MAGIC(pw_thread_loop_unlock)
175 #if PW_CHECK_VERSION(0,3,50)
176 #define PWIRE_FUNCS2(MAGIC) \
177 MAGIC(pw_stream_get_time_n)
179 #define PWIRE_FUNCS2(MAGIC) \
180 MAGIC(pw_stream_get_time)
184 #define MAKE_FUNC(f) decltype(f) * p##f;
185 PWIRE_FUNCS(MAKE_FUNC
)
186 PWIRE_FUNCS2(MAKE_FUNC
)
194 static constexpr char pwire_library
[] = "libpipewire-0.3.so.0";
195 std::string missing_funcs
;
197 pwire_handle
= LoadLib(pwire_library
);
200 WARN("Failed to load %s\n", pwire_library
);
204 #define LOAD_FUNC(f) do { \
205 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
206 if(p##f == nullptr) missing_funcs += "\n" #f; \
208 PWIRE_FUNCS(LOAD_FUNC
)
209 PWIRE_FUNCS2(LOAD_FUNC
)
212 if(!missing_funcs
.empty())
214 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
215 CloseLib(pwire_handle
);
216 pwire_handle
= nullptr;
223 #ifndef IN_IDE_PARSER
224 #define pw_context_connect ppw_context_connect
225 #define pw_context_destroy ppw_context_destroy
226 #define pw_context_new ppw_context_new
227 #define pw_core_disconnect ppw_core_disconnect
228 #define pw_get_library_version ppw_get_library_version
229 #define pw_init ppw_init
230 #define pw_properties_free ppw_properties_free
231 #define pw_properties_new ppw_properties_new
232 #define pw_properties_set ppw_properties_set
233 #define pw_properties_setf ppw_properties_setf
234 #define pw_proxy_add_object_listener ppw_proxy_add_object_listener
235 #define pw_proxy_destroy ppw_proxy_destroy
236 #define pw_proxy_get_user_data ppw_proxy_get_user_data
237 #define pw_stream_add_listener ppw_stream_add_listener
238 #define pw_stream_connect ppw_stream_connect
239 #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
240 #define pw_stream_destroy ppw_stream_destroy
241 #define pw_stream_get_state ppw_stream_get_state
242 #define pw_stream_new ppw_stream_new
243 #define pw_stream_queue_buffer ppw_stream_queue_buffer
244 #define pw_stream_set_active ppw_stream_set_active
245 #define pw_thread_loop_destroy ppw_thread_loop_destroy
246 #define pw_thread_loop_get_loop ppw_thread_loop_get_loop
247 #define pw_thread_loop_lock ppw_thread_loop_lock
248 #define pw_thread_loop_new ppw_thread_loop_new
249 #define pw_thread_loop_signal ppw_thread_loop_signal
250 #define pw_thread_loop_start ppw_thread_loop_start
251 #define pw_thread_loop_stop ppw_thread_loop_stop
252 #define pw_thread_loop_unlock ppw_thread_loop_unlock
253 #define pw_thread_loop_wait ppw_thread_loop_wait
254 #if PW_CHECK_VERSION(0,3,50)
255 #define pw_stream_get_time_n ppw_stream_get_time_n
257 inline auto pw_stream_get_time_n(pw_stream
*stream
, pw_time
*ptime
, size_t /*size*/)
258 { return ppw_stream_get_time(stream
, ptime
); }
264 constexpr bool pwire_load() { return true; }
267 /* Helpers for retrieving values from params */
268 template<uint32_t T
> struct PodInfo
{ };
271 struct PodInfo
<SPA_TYPE_Int
> {
272 using Type
= int32_t;
273 static auto get_value(const spa_pod
*pod
, int32_t *val
)
274 { return spa_pod_get_int(pod
, val
); }
277 struct PodInfo
<SPA_TYPE_Id
> {
278 using Type
= uint32_t;
279 static auto get_value(const spa_pod
*pod
, uint32_t *val
)
280 { return spa_pod_get_id(pod
, val
); }
284 using Pod_t
= typename PodInfo
<T
>::Type
;
287 al::span
<const Pod_t
<T
>> get_array_span(const spa_pod
*pod
)
290 if(void *v
{spa_pod_get_array(pod
, &nvals
)})
292 if(get_array_value_type(pod
) == T
)
293 return {static_cast<const Pod_t
<T
>*>(v
), nvals
};
299 al::optional
<Pod_t
<T
>> get_value(const spa_pod
*value
)
302 if(PodInfo
<T
>::get_value(value
, &val
) == 0)
307 /* Internally, PipeWire types "inherit" from each other, but this is hidden
308 * from the API and the caller is expected to C-style cast to inherited types
309 * as needed. It's also not made very clear what types a given type can be
310 * casted to. To make it a bit safer, this as() method allows casting pw_*
311 * types to known inherited types, generating a compile-time error for
312 * unexpected/invalid casts.
314 template<typename To
, typename From
>
315 To
as(From
) noexcept
= delete;
323 pw_proxy
* as(pw_registry
*reg
) noexcept
{ return reinterpret_cast<pw_proxy
*>(reg
); }
325 pw_proxy
* as(pw_node
*node
) noexcept
{ return reinterpret_cast<pw_proxy
*>(node
); }
327 pw_proxy
* as(pw_metadata
*mdata
) noexcept
{ return reinterpret_cast<pw_proxy
*>(mdata
); }
330 struct PwContextDeleter
{
331 void operator()(pw_context
*context
) const { pw_context_destroy(context
); }
333 using PwContextPtr
= std::unique_ptr
<pw_context
,PwContextDeleter
>;
335 struct PwCoreDeleter
{
336 void operator()(pw_core
*core
) const { pw_core_disconnect(core
); }
338 using PwCorePtr
= std::unique_ptr
<pw_core
,PwCoreDeleter
>;
340 struct PwRegistryDeleter
{
341 void operator()(pw_registry
*reg
) const { pw_proxy_destroy(as
<pw_proxy
*>(reg
)); }
343 using PwRegistryPtr
= std::unique_ptr
<pw_registry
,PwRegistryDeleter
>;
345 struct PwNodeDeleter
{
346 void operator()(pw_node
*node
) const { pw_proxy_destroy(as
<pw_proxy
*>(node
)); }
348 using PwNodePtr
= std::unique_ptr
<pw_node
,PwNodeDeleter
>;
350 struct PwMetadataDeleter
{
351 void operator()(pw_metadata
*mdata
) const { pw_proxy_destroy(as
<pw_proxy
*>(mdata
)); }
353 using PwMetadataPtr
= std::unique_ptr
<pw_metadata
,PwMetadataDeleter
>;
355 struct PwStreamDeleter
{
356 void operator()(pw_stream
*stream
) const { pw_stream_destroy(stream
); }
358 using PwStreamPtr
= std::unique_ptr
<pw_stream
,PwStreamDeleter
>;
360 /* Enums for bitflags... again... *sigh* */
361 constexpr pw_stream_flags
operator|(pw_stream_flags lhs
, pw_stream_flags rhs
) noexcept
362 { return static_cast<pw_stream_flags
>(lhs
| std::underlying_type_t
<pw_stream_flags
>{rhs
}); }
364 class ThreadMainloop
{
365 pw_thread_loop
*mLoop
{};
368 ThreadMainloop() = default;
369 ThreadMainloop(const ThreadMainloop
&) = delete;
370 ThreadMainloop(ThreadMainloop
&& rhs
) noexcept
: mLoop
{rhs
.mLoop
} { rhs
.mLoop
= nullptr; }
371 explicit ThreadMainloop(pw_thread_loop
*loop
) noexcept
: mLoop
{loop
} { }
372 ~ThreadMainloop() { if(mLoop
) pw_thread_loop_destroy(mLoop
); }
374 ThreadMainloop
& operator=(const ThreadMainloop
&) = delete;
375 ThreadMainloop
& operator=(ThreadMainloop
&& rhs
) noexcept
376 { std::swap(mLoop
, rhs
.mLoop
); return *this; }
377 ThreadMainloop
& operator=(std::nullptr_t
) noexcept
380 pw_thread_loop_destroy(mLoop
);
385 explicit operator bool() const noexcept
{ return mLoop
!= nullptr; }
387 auto start() const { return pw_thread_loop_start(mLoop
); }
388 auto stop() const { return pw_thread_loop_stop(mLoop
); }
390 auto getLoop() const { return pw_thread_loop_get_loop(mLoop
); }
392 auto lock() const { return pw_thread_loop_lock(mLoop
); }
393 auto unlock() const { return pw_thread_loop_unlock(mLoop
); }
395 auto signal(bool wait
) const { return pw_thread_loop_signal(mLoop
, wait
); }
397 auto newContext(pw_properties
*props
=nullptr, size_t user_data_size
=0)
398 { return PwContextPtr
{pw_context_new(getLoop(), props
, user_data_size
)}; }
400 static auto Create(const char *name
, spa_dict
*props
=nullptr)
401 { return ThreadMainloop
{pw_thread_loop_new(name
, props
)}; }
403 friend struct MainloopUniqueLock
;
405 struct MainloopUniqueLock
: public std::unique_lock
<ThreadMainloop
> {
406 using std::unique_lock
<ThreadMainloop
>::unique_lock
;
407 MainloopUniqueLock
& operator=(MainloopUniqueLock
&&) = default;
409 auto wait() const -> void
410 { pw_thread_loop_wait(mutex()->mLoop
); }
412 template<typename Predicate
>
413 auto wait(Predicate done_waiting
) const -> void
414 { while(!done_waiting()) wait(); }
416 using MainloopLockGuard
= std::lock_guard
<ThreadMainloop
>;
419 /* There's quite a mess here, but the purpose is to track active devices and
420 * their default formats, so playback devices can be configured to match. The
421 * device list is updated asynchronously, so it will have the latest list of
422 * devices provided by the server.
426 struct MetadataProxy
;
428 /* The global thread watching for global events. This particular class responds
429 * to objects being added to or removed from the registry.
431 struct EventManager
{
432 ThreadMainloop mLoop
{};
433 PwContextPtr mContext
{};
435 PwRegistryPtr mRegistry
{};
436 spa_hook mRegistryListener
{};
437 spa_hook mCoreListener
{};
439 /* A list of proxy objects watching for events about changes to objects in
442 std::vector
<NodeProxy
*> mNodeList
;
443 MetadataProxy
*mDefaultMetadata
{nullptr};
445 /* Initialization handling. When init() is called, mInitSeq is set to a
446 * SequenceID that marks the end of populating the registry. As objects of
447 * interest are found, events to parse them are generated and mInitSeq is
448 * updated with a newer ID. When mInitSeq stops being updated and the event
449 * corresponding to it is reached, mInitDone will be set to true.
451 std::atomic
<bool> mInitDone
{false};
452 std::atomic
<bool> mHasAudio
{false};
460 auto lock() const { return mLoop
.lock(); }
461 auto unlock() const { return mLoop
.unlock(); }
464 * Waits for initialization to finish. The event manager must *NOT* be
465 * locked when calling this.
469 if(unlikely(!mInitDone
.load(std::memory_order_acquire
)))
471 MainloopUniqueLock plock
{mLoop
};
472 plock
.wait([this](){ return mInitDone
.load(std::memory_order_acquire
); });
477 * Waits for audio support to be detected, or initialization to finish,
478 * whichever is first. Returns true if audio support was detected. The
479 * event manager must *NOT* be locked when calling this.
483 MainloopUniqueLock plock
{mLoop
};
485 plock
.wait([this,&has_audio
]()
487 has_audio
= mHasAudio
.load(std::memory_order_acquire
);
488 return has_audio
|| mInitDone
.load(std::memory_order_acquire
);
495 /* If initialization isn't done, update the sequence ID so it won't
496 * complete until after currently scheduled events.
498 if(!mInitDone
.load(std::memory_order_relaxed
))
499 mInitSeq
= ppw_core_sync(mCore
.get(), PW_ID_CORE
, mInitSeq
);
502 void addCallback(uint32_t id
, uint32_t permissions
, const char *type
, uint32_t version
,
503 const spa_dict
*props
);
504 static void addCallbackC(void *object
, uint32_t id
, uint32_t permissions
, const char *type
,
505 uint32_t version
, const spa_dict
*props
)
506 { static_cast<EventManager
*>(object
)->addCallback(id
, permissions
, type
, version
, props
); }
508 void removeCallback(uint32_t id
);
509 static void removeCallbackC(void *object
, uint32_t id
)
510 { static_cast<EventManager
*>(object
)->removeCallback(id
); }
512 static constexpr pw_registry_events
CreateRegistryEvents()
514 pw_registry_events ret
{};
515 ret
.version
= PW_VERSION_REGISTRY_EVENTS
;
516 ret
.global
= &EventManager::addCallbackC
;
517 ret
.global_remove
= &EventManager::removeCallbackC
;
521 void coreCallback(uint32_t id
, int seq
);
522 static void coreCallbackC(void *object
, uint32_t id
, int seq
)
523 { static_cast<EventManager
*>(object
)->coreCallback(id
, seq
); }
525 static constexpr pw_core_events
CreateCoreEvents()
527 pw_core_events ret
{};
528 ret
.version
= PW_VERSION_CORE_EVENTS
;
529 ret
.done
= &EventManager::coreCallbackC
;
533 using EventWatcherUniqueLock
= std::unique_lock
<EventManager
>;
534 using EventWatcherLockGuard
= std::lock_guard
<EventManager
>;
536 EventManager gEventHandler
;
538 /* Enumerated devices. This is updated asynchronously as the app runs, and the
539 * gEventHandler thread loop must be locked when accessing the list.
541 enum class NodeType
: unsigned char {
544 constexpr auto InvalidChannelConfig
= DevFmtChannels(255);
547 std::string mDevName
;
551 bool mIsHeadphones
{};
555 DevFmtChannels mChannels
{InvalidChannelConfig
};
557 static std::vector
<DeviceNode
> sList
;
558 static DeviceNode
&Add(uint32_t id
);
559 static DeviceNode
*Find(uint32_t id
);
560 static void Remove(uint32_t id
);
561 static std::vector
<DeviceNode
> &GetList() noexcept
{ return sList
; }
563 void parseSampleRate(const spa_pod
*value
) noexcept
;
564 void parsePositions(const spa_pod
*value
) noexcept
;
565 void parseChannelCount(const spa_pod
*value
) noexcept
;
567 std::vector
<DeviceNode
> DeviceNode::sList
;
568 std::string DefaultSinkDevice
;
569 std::string DefaultSourceDevice
;
571 const char *AsString(NodeType type
) noexcept
575 case NodeType::Sink
: return "sink";
576 case NodeType::Source
: return "source";
577 case NodeType::Duplex
: return "duplex";
582 DeviceNode
&DeviceNode::Add(uint32_t id
)
584 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
585 { return n
.mId
== id
; };
587 /* If the node is already in the list, return the existing entry. */
588 auto match
= std::find_if(sList
.begin(), sList
.end(), match_id
);
589 if(match
!= sList
.end()) return *match
;
591 sList
.emplace_back();
592 auto &n
= sList
.back();
597 DeviceNode
*DeviceNode::Find(uint32_t id
)
599 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
600 { return n
.mId
== id
; };
602 auto match
= std::find_if(sList
.begin(), sList
.end(), match_id
);
603 if(match
!= sList
.end()) return std::addressof(*match
);
608 void DeviceNode::Remove(uint32_t id
)
610 auto match_id
= [id
](DeviceNode
&n
) noexcept
-> bool
614 TRACE("Removing device \"%s\"\n", n
.mDevName
.c_str());
618 auto end
= std::remove_if(sList
.begin(), sList
.end(), match_id
);
619 sList
.erase(end
, sList
.end());
623 const spa_audio_channel MonoMap
[]{
624 SPA_AUDIO_CHANNEL_MONO
626 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
628 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
630 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
631 SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
633 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
634 SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
636 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
637 SPA_AUDIO_CHANNEL_RC
, SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
639 SPA_AUDIO_CHANNEL_FL
, SPA_AUDIO_CHANNEL_FR
, SPA_AUDIO_CHANNEL_FC
, SPA_AUDIO_CHANNEL_LFE
,
640 SPA_AUDIO_CHANNEL_RL
, SPA_AUDIO_CHANNEL_RR
, SPA_AUDIO_CHANNEL_SL
, SPA_AUDIO_CHANNEL_SR
644 * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
645 * to or a superset of map1).
648 bool MatchChannelMap(const al::span
<const uint32_t> map0
, const spa_audio_channel (&map1
)[N
])
652 for(const spa_audio_channel chid
: map1
)
654 if(std::find(map0
.begin(), map0
.end(), chid
) == map0
.end())
660 void DeviceNode::parseSampleRate(const spa_pod
*value
) noexcept
662 /* TODO: Can this be anything else? Long, Float, Double? */
663 uint32_t nvals
{}, choiceType
{};
664 value
= spa_pod_get_values(value
, &nvals
, &choiceType
);
666 const uint podType
{get_pod_type(value
)};
667 if(podType
!= SPA_TYPE_Int
)
669 WARN("Unhandled sample rate POD type: %u\n", podType
);
673 if(choiceType
== SPA_CHOICE_Range
)
677 WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals
);
680 auto srates
= get_pod_body
<int32_t,3>(value
);
682 /* [0] is the default, [1] is the min, and [2] is the max. */
683 TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId
, srates
[0], srates
[1],
685 mSampleRate
= static_cast<uint
>(clampi(srates
[0], MIN_OUTPUT_RATE
, MAX_OUTPUT_RATE
));
689 if(choiceType
== SPA_CHOICE_Enum
)
693 WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals
);
696 auto srates
= get_pod_body
<int32_t>(value
, nvals
);
698 /* [0] is the default, [1...size()-1] are available selections. */
699 std::string others
{(srates
.size() > 1) ? std::to_string(srates
[1]) : std::string
{}};
700 for(size_t i
{2};i
< srates
.size();++i
)
703 others
+= std::to_string(srates
[i
]);
705 TRACE("Device ID %u sample rate: %d (%s)\n", mId
, srates
[0], others
.c_str());
706 /* Pick the first rate listed that's within the allowed range (default
709 for(const auto &rate
: srates
)
711 if(rate
>= MIN_OUTPUT_RATE
&& rate
<= MAX_OUTPUT_RATE
)
713 mSampleRate
= static_cast<uint
>(rate
);
720 if(choiceType
== SPA_CHOICE_None
)
724 WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals
);
727 auto srates
= get_pod_body
<int32_t,1>(value
);
729 TRACE("Device ID %u sample rate: %d\n", mId
, srates
[0]);
730 mSampleRate
= static_cast<uint
>(clampi(srates
[0], MIN_OUTPUT_RATE
, MAX_OUTPUT_RATE
));
734 WARN("Unhandled sample rate choice type: %u\n", choiceType
);
737 void DeviceNode::parsePositions(const spa_pod
*value
) noexcept
739 const auto chanmap
= get_array_span
<SPA_TYPE_Id
>(value
);
740 if(chanmap
.empty()) return;
744 if(MatchChannelMap(chanmap
, X71Map
))
745 mChannels
= DevFmtX71
;
746 else if(MatchChannelMap(chanmap
, X61Map
))
747 mChannels
= DevFmtX61
;
748 else if(MatchChannelMap(chanmap
, X51Map
))
749 mChannels
= DevFmtX51
;
750 else if(MatchChannelMap(chanmap
, X51RearMap
))
752 mChannels
= DevFmtX51
;
755 else if(MatchChannelMap(chanmap
, QuadMap
))
756 mChannels
= DevFmtQuad
;
757 else if(MatchChannelMap(chanmap
, StereoMap
))
758 mChannels
= DevFmtStereo
;
760 mChannels
= DevFmtMono
;
761 TRACE("Device ID %u got %zu position%s for %s%s\n", mId
, chanmap
.size(),
762 (chanmap
.size()==1)?"":"s", DevFmtChannelsString(mChannels
), mIs51Rear
?"(rear)":"");
765 void DeviceNode::parseChannelCount(const spa_pod
*value
) noexcept
767 /* As a fallback with just a channel count, just assume mono or stereo. */
768 const auto chancount
= get_value
<SPA_TYPE_Int
>(value
);
769 if(!chancount
) return;
774 mChannels
= DevFmtStereo
;
775 else if(*chancount
>= 1)
776 mChannels
= DevFmtMono
;
777 TRACE("Device ID %u got %d channel%s for %s\n", mId
, *chancount
, (*chancount
==1)?"":"s",
778 DevFmtChannelsString(mChannels
));
782 constexpr char MonitorPrefix
[]{"Monitor of "};
783 constexpr auto MonitorPrefixLen
= al::size(MonitorPrefix
) - 1;
784 constexpr char AudioSinkClass
[]{"Audio/Sink"};
785 constexpr char AudioSourceClass
[]{"Audio/Source"};
786 constexpr char AudioDuplexClass
[]{"Audio/Duplex"};
787 constexpr char StreamClass
[]{"Stream/"};
789 /* A generic PipeWire node proxy object used to track changes to sink and
793 static constexpr pw_node_events
CreateNodeEvents()
795 pw_node_events ret
{};
796 ret
.version
= PW_VERSION_NODE_EVENTS
;
797 ret
.info
= &NodeProxy::infoCallbackC
;
798 ret
.param
= &NodeProxy::paramCallbackC
;
805 spa_hook mListener
{};
807 NodeProxy(uint32_t id
, PwNodePtr node
)
808 : mId
{id
}, mNode
{std::move(node
)}
810 static constexpr pw_node_events nodeEvents
{CreateNodeEvents()};
811 ppw_node_add_listener(mNode
.get(), &mListener
, &nodeEvents
, this);
813 /* Track changes to the enumerable formats (indicates the default
814 * format, which is what we're interested in).
816 uint32_t fmtids
[]{SPA_PARAM_EnumFormat
};
817 ppw_node_subscribe_params(mNode
.get(), al::data(fmtids
), al::size(fmtids
));
820 { spa_hook_remove(&mListener
); }
823 void infoCallback(const pw_node_info
*info
);
824 static void infoCallbackC(void *object
, const pw_node_info
*info
)
825 { static_cast<NodeProxy
*>(object
)->infoCallback(info
); }
827 void paramCallback(int seq
, uint32_t id
, uint32_t index
, uint32_t next
, const spa_pod
*param
);
828 static void paramCallbackC(void *object
, int seq
, uint32_t id
, uint32_t index
, uint32_t next
,
829 const spa_pod
*param
)
830 { static_cast<NodeProxy
*>(object
)->paramCallback(seq
, id
, index
, next
, param
); }
833 void NodeProxy::infoCallback(const pw_node_info
*info
)
835 /* We only care about property changes here (media class, name/desc).
836 * Format changes will automatically invoke the param callback.
838 * TODO: Can the media class or name/desc change without being removed and
841 if((info
->change_mask
&PW_NODE_CHANGE_MASK_PROPS
))
843 /* Can this actually change? */
844 const char *media_class
{spa_dict_lookup(info
->props
, PW_KEY_MEDIA_CLASS
)};
845 if(unlikely(!media_class
)) return;
848 if(al::strcasecmp(media_class
, AudioSinkClass
) == 0)
849 ntype
= NodeType::Sink
;
850 else if(al::strcasecmp(media_class
, AudioSourceClass
) == 0)
851 ntype
= NodeType::Source
;
852 else if(al::strcasecmp(media_class
, AudioDuplexClass
) == 0)
853 ntype
= NodeType::Duplex
;
856 TRACE("Dropping device node %u which became type \"%s\"\n", info
->id
, media_class
);
857 DeviceNode::Remove(info
->id
);
861 const char *devName
{spa_dict_lookup(info
->props
, PW_KEY_NODE_NAME
)};
862 const char *nodeName
{spa_dict_lookup(info
->props
, PW_KEY_NODE_DESCRIPTION
)};
863 if(!nodeName
|| !*nodeName
) nodeName
= spa_dict_lookup(info
->props
, PW_KEY_NODE_NICK
);
864 if(!nodeName
|| !*nodeName
) nodeName
= devName
;
866 const char *form_factor
{spa_dict_lookup(info
->props
, PW_KEY_DEVICE_FORM_FACTOR
)};
867 TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype
), devName
? devName
: "(nil)",
868 form_factor
?" (":"", form_factor
?form_factor
:"", form_factor
?")":"");
869 TRACE(" \"%s\" = ID %u\n", nodeName
? nodeName
: "(nil)", info
->id
);
871 DeviceNode
&node
= DeviceNode::Add(info
->id
);
872 if(nodeName
&& *nodeName
) node
.mName
= nodeName
;
873 else node
.mName
= "PipeWire node #"+std::to_string(info
->id
);
874 node
.mDevName
= devName
? devName
: "";
876 node
.mIsHeadphones
= form_factor
&& (al::strcasecmp(form_factor
, "headphones") == 0
877 || al::strcasecmp(form_factor
, "headset") == 0);
881 void NodeProxy::paramCallback(int, uint32_t id
, uint32_t, uint32_t, const spa_pod
*param
)
883 if(id
== SPA_PARAM_EnumFormat
)
885 DeviceNode
*node
{DeviceNode::Find(mId
)};
886 if(unlikely(!node
)) return;
888 if(const spa_pod_prop
*prop
{spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_rate
)})
889 node
->parseSampleRate(&prop
->value
);
891 if(const spa_pod_prop
*prop
{spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_position
)})
892 node
->parsePositions(&prop
->value
);
893 else if((prop
=spa_pod_find_prop(param
, nullptr, SPA_FORMAT_AUDIO_channels
)) != nullptr)
894 node
->parseChannelCount(&prop
->value
);
899 /* A metadata proxy object used to query the default sink and source. */
900 struct MetadataProxy
{
901 static constexpr pw_metadata_events
CreateMetadataEvents()
903 pw_metadata_events ret
{};
904 ret
.version
= PW_VERSION_METADATA_EVENTS
;
905 ret
.property
= &MetadataProxy::propertyCallbackC
;
911 PwMetadataPtr mMetadata
{};
912 spa_hook mListener
{};
914 MetadataProxy(uint32_t id
, PwMetadataPtr mdata
)
915 : mId
{id
}, mMetadata
{std::move(mdata
)}
917 static constexpr pw_metadata_events metadataEvents
{CreateMetadataEvents()};
918 ppw_metadata_add_listener(mMetadata
.get(), &mListener
, &metadataEvents
, this);
921 { spa_hook_remove(&mListener
); }
924 int propertyCallback(uint32_t id
, const char *key
, const char *type
, const char *value
);
925 static int propertyCallbackC(void *object
, uint32_t id
, const char *key
, const char *type
,
927 { return static_cast<MetadataProxy
*>(object
)->propertyCallback(id
, key
, type
, value
); }
930 int MetadataProxy::propertyCallback(uint32_t id
, const char *key
, const char *type
,
937 if(std::strcmp(key
, "default.audio.sink") == 0)
939 else if(std::strcmp(key
, "default.audio.source") == 0)
946 TRACE("Default %s device cleared\n", isCapture
? "capture" : "playback");
947 if(!isCapture
) DefaultSinkDevice
.clear();
948 else DefaultSourceDevice
.clear();
951 if(std::strcmp(type
, "Spa:String:JSON") != 0)
953 ERR("Unexpected %s property type: %s\n", key
, type
);
958 spa_json_init(&it
[0], value
, strlen(value
));
959 if(spa_json_enter_object(&it
[0], &it
[1]) <= 0)
962 auto get_json_string
= [](spa_json
*iter
)
964 al::optional
<std::string
> str
;
967 int len
{spa_json_next(iter
, &val
)};
968 if(len
<= 0) return str
;
970 str
.emplace().resize(static_cast<uint
>(len
), '\0');
971 if(spa_json_parse_string(val
, len
, &str
->front()) <= 0)
973 else while(!str
->empty() && str
->back() == '\0')
977 while(auto propKey
= get_json_string(&it
[1]))
979 if(*propKey
== "name")
981 auto propValue
= get_json_string(&it
[1]);
982 if(!propValue
) break;
984 TRACE("Got default %s device \"%s\"\n", isCapture
? "capture" : "playback",
987 DefaultSinkDevice
= std::move(*propValue
);
989 DefaultSourceDevice
= std::move(*propValue
);
994 if(spa_json_next(&it
[1], &v
) <= 0)
1002 bool EventManager::init()
1004 mLoop
= ThreadMainloop::Create("PWEventThread");
1007 ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno
);
1011 mContext
= mLoop
.newContext(pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr));
1014 ERR("Failed to create PipeWire event context (errno: %d)\n", errno
);
1018 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1021 ERR("Failed to connect PipeWire event context (errno: %d)\n", errno
);
1025 mRegistry
= PwRegistryPtr
{pw_core_get_registry(mCore
.get(), PW_VERSION_REGISTRY
, 0)};
1028 ERR("Failed to get PipeWire event registry (errno: %d)\n", errno
);
1032 static constexpr pw_core_events coreEvents
{CreateCoreEvents()};
1033 static constexpr pw_registry_events registryEvents
{CreateRegistryEvents()};
1035 ppw_core_add_listener(mCore
.get(), &mCoreListener
, &coreEvents
, this);
1036 ppw_registry_add_listener(mRegistry
.get(), &mRegistryListener
, ®istryEvents
, this);
1038 /* Set an initial sequence ID for initialization, to trigger after the
1039 * registry is first populated.
1041 mInitSeq
= ppw_core_sync(mCore
.get(), PW_ID_CORE
, 0);
1043 if(int res
{mLoop
.start()})
1045 ERR("Failed to start PipeWire event thread loop (res: %d)\n", res
);
1052 EventManager::~EventManager()
1054 if(mLoop
) mLoop
.stop();
1056 for(NodeProxy
*node
: mNodeList
)
1057 al::destroy_at(node
);
1058 if(mDefaultMetadata
)
1059 al::destroy_at(mDefaultMetadata
);
1062 void EventManager::kill()
1064 if(mLoop
) mLoop
.stop();
1066 for(NodeProxy
*node
: mNodeList
)
1067 al::destroy_at(node
);
1069 if(mDefaultMetadata
)
1070 al::destroy_at(mDefaultMetadata
);
1071 mDefaultMetadata
= nullptr;
1073 mRegistry
= nullptr;
1079 void EventManager::addCallback(uint32_t id
, uint32_t, const char *type
, uint32_t version
,
1080 const spa_dict
*props
)
1082 /* We're only interested in interface nodes. */
1083 if(std::strcmp(type
, PW_TYPE_INTERFACE_Node
) == 0)
1085 const char *media_class
{spa_dict_lookup(props
, PW_KEY_MEDIA_CLASS
)};
1086 if(!media_class
) return;
1088 /* Specifically, audio sinks and sources (and duplexes). */
1089 const bool isGood
{al::strcasecmp(media_class
, AudioSinkClass
) == 0
1090 || al::strcasecmp(media_class
, AudioSourceClass
) == 0
1091 || al::strcasecmp(media_class
, AudioDuplexClass
) == 0};
1094 if(std::strstr(media_class
, "/Video") == nullptr
1095 && std::strncmp(media_class
, StreamClass
, sizeof(StreamClass
)-1) != 0)
1096 TRACE("Ignoring node class %s\n", media_class
);
1100 /* Create the proxy object. */
1101 auto node
= PwNodePtr
{static_cast<pw_node
*>(pw_registry_bind(mRegistry
.get(), id
, type
,
1102 version
, sizeof(NodeProxy
)))};
1105 ERR("Failed to create node proxy object (errno: %d)\n", errno
);
1109 /* Initialize the NodeProxy to hold the node object, add it to the
1110 * active node list, and update the sync point.
1112 auto *proxy
= static_cast<NodeProxy
*>(pw_proxy_get_user_data(as
<pw_proxy
*>(node
.get())));
1113 mNodeList
.emplace_back(al::construct_at(proxy
, id
, std::move(node
)));
1116 /* Signal any waiters that we have found a source or sink for audio
1119 if(!mHasAudio
.exchange(true, std::memory_order_acq_rel
))
1120 mLoop
.signal(false);
1122 else if(std::strcmp(type
, PW_TYPE_INTERFACE_Metadata
) == 0)
1124 const char *data_class
{spa_dict_lookup(props
, PW_KEY_METADATA_NAME
)};
1125 if(!data_class
) return;
1127 if(std::strcmp(data_class
, "default") != 0)
1129 TRACE("Ignoring metadata \"%s\"\n", data_class
);
1133 if(mDefaultMetadata
)
1135 ERR("Duplicate default metadata\n");
1139 auto mdata
= PwMetadataPtr
{static_cast<pw_metadata
*>(pw_registry_bind(mRegistry
.get(), id
,
1140 type
, version
, sizeof(MetadataProxy
)))};
1143 ERR("Failed to create metadata proxy object (errno: %d)\n", errno
);
1147 auto *proxy
= static_cast<MetadataProxy
*>(
1148 pw_proxy_get_user_data(as
<pw_proxy
*>(mdata
.get())));
1149 mDefaultMetadata
= al::construct_at(proxy
, id
, std::move(mdata
));
1154 void EventManager::removeCallback(uint32_t id
)
1156 DeviceNode::Remove(id
);
1158 auto clear_node
= [id
](NodeProxy
*node
) noexcept
1162 al::destroy_at(node
);
1165 auto node_end
= std::remove_if(mNodeList
.begin(), mNodeList
.end(), clear_node
);
1166 mNodeList
.erase(node_end
, mNodeList
.end());
1168 if(mDefaultMetadata
&& mDefaultMetadata
->mId
== id
)
1170 al::destroy_at(mDefaultMetadata
);
1171 mDefaultMetadata
= nullptr;
1175 void EventManager::coreCallback(uint32_t id
, int seq
)
1177 if(id
== PW_ID_CORE
&& seq
== mInitSeq
)
1179 /* Initialization done. Remove this callback and signal anyone that may
1182 spa_hook_remove(&mCoreListener
);
1184 mInitDone
.store(true);
1185 mLoop
.signal(false);
1190 enum use_f32p_e
: bool { UseDevType
=false, ForceF32Planar
=true };
1191 spa_audio_info_raw
make_spa_info(DeviceBase
*device
, bool is51rear
, use_f32p_e use_f32p
)
1193 spa_audio_info_raw info
{};
1196 device
->FmtType
= DevFmtFloat
;
1197 info
.format
= SPA_AUDIO_FORMAT_F32P
;
1199 else switch(device
->FmtType
)
1201 case DevFmtByte
: info
.format
= SPA_AUDIO_FORMAT_S8
; break;
1202 case DevFmtUByte
: info
.format
= SPA_AUDIO_FORMAT_U8
; break;
1203 case DevFmtShort
: info
.format
= SPA_AUDIO_FORMAT_S16
; break;
1204 case DevFmtUShort
: info
.format
= SPA_AUDIO_FORMAT_U16
; break;
1205 case DevFmtInt
: info
.format
= SPA_AUDIO_FORMAT_S32
; break;
1206 case DevFmtUInt
: info
.format
= SPA_AUDIO_FORMAT_U32
; break;
1207 case DevFmtFloat
: info
.format
= SPA_AUDIO_FORMAT_F32
; break;
1210 info
.rate
= device
->Frequency
;
1212 al::span
<const spa_audio_channel
> map
{};
1213 switch(device
->FmtChans
)
1215 case DevFmtMono
: map
= MonoMap
; break;
1216 case DevFmtStereo
: map
= StereoMap
; break;
1217 case DevFmtQuad
: map
= QuadMap
; break;
1219 if(is51rear
) map
= X51RearMap
;
1222 case DevFmtX61
: map
= X61Map
; break;
1223 case DevFmtX71
: map
= X71Map
; break;
1224 case DevFmtX3D71
: map
= X71Map
; break;
1226 info
.flags
|= SPA_AUDIO_FLAG_UNPOSITIONED
;
1227 info
.channels
= device
->channelsFromFmt();
1232 info
.channels
= static_cast<uint32_t>(map
.size());
1233 std::copy(map
.begin(), map
.end(), info
.position
);
1239 class PipeWirePlayback final
: public BackendBase
{
1240 void stateChangedCallback(pw_stream_state old
, pw_stream_state state
, const char *error
);
1241 static void stateChangedCallbackC(void *data
, pw_stream_state old
, pw_stream_state state
,
1243 { static_cast<PipeWirePlayback
*>(data
)->stateChangedCallback(old
, state
, error
); }
1245 void ioChangedCallback(uint32_t id
, void *area
, uint32_t size
);
1246 static void ioChangedCallbackC(void *data
, uint32_t id
, void *area
, uint32_t size
)
1247 { static_cast<PipeWirePlayback
*>(data
)->ioChangedCallback(id
, area
, size
); }
1249 void outputCallback();
1250 static void outputCallbackC(void *data
)
1251 { static_cast<PipeWirePlayback
*>(data
)->outputCallback(); }
1253 void open(const char *name
) override
;
1254 bool reset() override
;
1255 void start() override
;
1256 void stop() override
;
1257 ClockLatency
getClockLatency() override
;
1259 uint32_t mTargetId
{PwIdAny
};
1260 nanoseconds mTimeBase
{0};
1261 ThreadMainloop mLoop
;
1262 PwContextPtr mContext
;
1264 PwStreamPtr mStream
;
1265 spa_hook mStreamListener
{};
1266 spa_io_rate_match
*mRateMatch
{};
1267 std::unique_ptr
<float*[]> mChannelPtrs
;
1268 uint mNumChannels
{};
1270 static constexpr pw_stream_events
CreateEvents()
1272 pw_stream_events ret
{};
1273 ret
.version
= PW_VERSION_STREAM_EVENTS
;
1274 ret
.state_changed
= &PipeWirePlayback::stateChangedCallbackC
;
1275 ret
.io_changed
= &PipeWirePlayback::ioChangedCallbackC
;
1276 ret
.process
= &PipeWirePlayback::outputCallbackC
;
1281 PipeWirePlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1284 /* Stop the mainloop so the stream can be properly destroyed. */
1285 if(mLoop
) mLoop
.stop();
1288 DEF_NEWDEL(PipeWirePlayback
)
1292 void PipeWirePlayback::stateChangedCallback(pw_stream_state
, pw_stream_state
, const char*)
1293 { mLoop
.signal(false); }
1295 void PipeWirePlayback::ioChangedCallback(uint32_t id
, void *area
, uint32_t size
)
1299 case SPA_IO_RateMatch
:
1300 if(size
>= sizeof(spa_io_rate_match
))
1301 mRateMatch
= static_cast<spa_io_rate_match
*>(area
);
1306 void PipeWirePlayback::outputCallback()
1308 pw_buffer
*pw_buf
{pw_stream_dequeue_buffer(mStream
.get())};
1309 if(unlikely(!pw_buf
)) return;
1311 /* For planar formats, each datas[] seems to contain one channel, so store
1312 * the pointers in an array. Limit the render length in case the available
1313 * buffer length in any one channel is smaller than we wanted (shouldn't
1314 * be, but just in case).
1316 spa_data
*datas
{pw_buf
->buffer
->datas
};
1317 const size_t chancount
{minu(mNumChannels
, pw_buf
->buffer
->n_datas
)};
1318 /* TODO: How many samples should actually be written? 'maxsize' can be 16k
1319 * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains
1320 * a 'size' field that apparently indicates how many samples should be
1321 * written per update, but it's not obviously right.
1323 uint length
{mRateMatch
? mRateMatch
->size
: mDevice
->UpdateSize
};
1324 for(size_t i
{0};i
< chancount
;++i
)
1326 length
= minu(length
, datas
[i
].maxsize
/sizeof(float));
1327 mChannelPtrs
[i
] = static_cast<float*>(datas
[i
].data
);
1330 mDevice
->renderSamples({mChannelPtrs
.get(), chancount
}, length
);
1332 for(size_t i
{0};i
< chancount
;++i
)
1334 datas
[i
].chunk
->offset
= 0;
1335 datas
[i
].chunk
->stride
= sizeof(float);
1336 datas
[i
].chunk
->size
= length
* sizeof(float);
1338 pw_buf
->size
= length
;
1339 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1343 void PipeWirePlayback::open(const char *name
)
1345 static std::atomic
<uint
> OpenCount
{0};
1347 uint32_t targetid
{PwIdAny
};
1348 std::string devname
{};
1349 gEventHandler
.waitForInit();
1352 EventWatcherLockGuard _
{gEventHandler
};
1353 auto&& devlist
= DeviceNode::GetList();
1355 auto match
= devlist
.cend();
1356 if(!DefaultSinkDevice
.empty())
1358 auto match_default
= [](const DeviceNode
&n
) -> bool
1359 { return n
.mDevName
== DefaultSinkDevice
; };
1360 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1362 if(match
== devlist
.cend())
1364 auto match_playback
= [](const DeviceNode
&n
) -> bool
1365 { return n
.mType
!= NodeType::Source
; };
1366 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_playback
);
1367 if(match
== devlist
.cend())
1368 throw al::backend_exception
{al::backend_error::NoDevice
,
1369 "No PipeWire playback device found"};
1372 targetid
= match
->mId
;
1373 devname
= match
->mName
;
1377 EventWatcherLockGuard _
{gEventHandler
};
1378 auto&& devlist
= DeviceNode::GetList();
1380 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1381 { return n
.mType
!= NodeType::Source
&& n
.mName
== name
; };
1382 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1383 if(match
== devlist
.cend())
1384 throw al::backend_exception
{al::backend_error::NoDevice
,
1385 "Device name \"%s\" not found", name
};
1387 targetid
= match
->mId
;
1388 devname
= match
->mName
;
1393 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1394 const std::string thread_name
{"ALSoftP" + std::to_string(count
)};
1395 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1397 throw al::backend_exception
{al::backend_error::DeviceError
,
1398 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1399 if(int res
{mLoop
.start()})
1400 throw al::backend_exception
{al::backend_error::DeviceError
,
1401 "Failed to start PipeWire mainloop (res: %d)", res
};
1403 MainloopUniqueLock mlock
{mLoop
};
1406 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1407 mContext
= mLoop
.newContext(cprops
);
1409 throw al::backend_exception
{al::backend_error::DeviceError
,
1410 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1414 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1416 throw al::backend_exception
{al::backend_error::DeviceError
,
1417 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1421 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1423 mTargetId
= targetid
;
1424 if(!devname
.empty())
1425 mDevice
->DeviceName
= std::move(devname
);
1427 mDevice
->DeviceName
= pwireDevice
;
1430 bool PipeWirePlayback::reset()
1434 MainloopLockGuard _
{mLoop
};
1437 mStreamListener
= {};
1438 mRateMatch
= nullptr;
1439 mTimeBase
= GetDeviceClockTime(mDevice
);
1441 /* If connecting to a specific device, update various device parameters to
1444 bool is51rear
{false};
1445 mDevice
->Flags
.reset(DirectEar
);
1446 if(mTargetId
!= PwIdAny
)
1448 EventWatcherLockGuard _
{gEventHandler
};
1449 auto&& devlist
= DeviceNode::GetList();
1451 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1452 { return targetid
== n
.mId
; };
1453 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1454 if(match
!= devlist
.cend())
1456 if(!mDevice
->Flags
.test(FrequencyRequest
) && match
->mSampleRate
> 0)
1458 /* Scale the update size if the sample rate changes. */
1459 const double scale
{static_cast<double>(match
->mSampleRate
) / mDevice
->Frequency
};
1460 mDevice
->Frequency
= match
->mSampleRate
;
1461 mDevice
->UpdateSize
= static_cast<uint
>(clampd(mDevice
->UpdateSize
*scale
+ 0.5,
1463 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1465 if(!mDevice
->Flags
.test(ChannelsRequest
) && match
->mChannels
!= InvalidChannelConfig
)
1466 mDevice
->FmtChans
= match
->mChannels
;
1467 if(match
->mChannels
== DevFmtStereo
&& match
->mIsHeadphones
)
1468 mDevice
->Flags
.set(DirectEar
);
1469 is51rear
= match
->mIs51Rear
;
1472 /* Force planar 32-bit float output for playback. This is what PipeWire
1473 * handles internally, and it's easier for us too.
1475 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, ForceF32Planar
)};
1477 /* TODO: How to tell what an appropriate size is? Examples just use this
1480 constexpr uint32_t pod_buffer_size
{1024};
1481 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1482 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1484 const spa_pod
*params
{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1486 throw al::backend_exception
{al::backend_error::DeviceError
,
1487 "Failed to set PipeWire audio format parameters"};
1489 pw_properties
*props
{pw_properties_new(
1490 PW_KEY_MEDIA_TYPE
, "Audio",
1491 PW_KEY_MEDIA_CATEGORY
, "Playback",
1492 PW_KEY_MEDIA_ROLE
, "Game",
1493 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1496 throw al::backend_exception
{al::backend_error::DeviceError
,
1497 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1499 auto&& binary
= GetProcBinary();
1500 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1501 /* TODO: Which properties are actually needed here? Any others that could
1504 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1505 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1506 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", mDevice
->UpdateSize
,
1507 mDevice
->Frequency
);
1508 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1510 MainloopUniqueLock plock
{mLoop
};
1511 /* The stream takes overship of 'props', even in the case of failure. */
1512 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Playback Stream", props
)};
1514 throw al::backend_exception
{al::backend_error::NoDevice
,
1515 "Failed to create PipeWire stream (errno: %d)", errno
};
1516 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1517 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1519 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1520 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1521 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_OUTPUT
, mTargetId
, Flags
, ¶ms
, 1)})
1522 throw al::backend_exception
{al::backend_error::DeviceError
,
1523 "Error connecting PipeWire stream (res: %d)", res
};
1525 /* Wait for the stream to become paused (ready to start streaming). */
1526 pw_stream_state state
{};
1527 const char *error
{};
1528 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1530 state
= pw_stream_get_state(stream
, &error
);
1531 if(state
== PW_STREAM_STATE_ERROR
)
1532 throw al::backend_exception
{al::backend_error::DeviceError
,
1533 "Error connecting PipeWire stream: \"%s\"", error
};
1534 return state
== PW_STREAM_STATE_PAUSED
;
1537 /* TODO: Update mDevice->BufferSize with the total known buffering delay
1538 * from the head of this playback stream to the tail of the device output.
1540 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1543 mNumChannels
= mDevice
->channelsFromFmt();
1544 mChannelPtrs
= std::make_unique
<float*[]>(mNumChannels
);
1546 setDefaultWFXChannelOrder();
1551 void PipeWirePlayback::start()
1553 MainloopUniqueLock plock
{mLoop
};
1554 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1555 throw al::backend_exception
{al::backend_error::DeviceError
,
1556 "Failed to start PipeWire stream (res: %d)", res
};
1558 /* Wait for the stream to start playing (would be nice to not, but we need
1559 * the actual update size which is only available after starting).
1561 pw_stream_state state
{};
1562 const char *error
{};
1563 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1565 state
= pw_stream_get_state(stream
, &error
);
1566 return state
!= PW_STREAM_STATE_PAUSED
;
1569 if(state
== PW_STREAM_STATE_ERROR
)
1570 throw al::backend_exception
{al::backend_error::DeviceError
,
1571 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1572 if(state
== PW_STREAM_STATE_STREAMING
&& mRateMatch
&& mRateMatch
->size
)
1574 mDevice
->UpdateSize
= mRateMatch
->size
;
1575 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1579 void PipeWirePlayback::stop()
1581 MainloopUniqueLock plock
{mLoop
};
1582 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1583 throw al::backend_exception
{al::backend_error::DeviceError
,
1584 "Failed to stop PipeWire stream (res: %d)", res
};
1586 /* Wait for the stream to stop playing. */
1587 plock
.wait([stream
=mStream
.get()]()
1588 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1591 ClockLatency
PipeWirePlayback::getClockLatency()
1593 /* Given a real-time low-latency output, this is rather complicated to get
1594 * accurate timing. So, here we go.
1597 /* First, get the stream time info (tick delay, ticks played, and the
1598 * CLOCK_MONOTONIC time closest to when that last tick was played).
1603 MainloopLockGuard _
{mLoop
};
1604 if(int res
{pw_stream_get_time_n(mStream
.get(), &ptime
, sizeof(ptime
))})
1605 ERR("Failed to get PipeWire stream time (res: %d)\n", res
);
1608 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1609 * monotonic clock closest to 'now', and the last mixer time at 'now').
1611 nanoseconds mixtime
{};
1615 refcount
= mDevice
->waitForMix();
1616 mixtime
= GetDeviceClockTime(mDevice
);
1617 clock_gettime(CLOCK_MONOTONIC
, &tspec
);
1618 std::atomic_thread_fence(std::memory_order_acquire
);
1619 } while(refcount
!= ReadRef(mDevice
->MixCount
));
1621 /* Convert the monotonic clock, stream ticks, and stream delay to
1624 nanoseconds monoclock
{seconds
{tspec
.tv_sec
} + nanoseconds
{tspec
.tv_nsec
}};
1625 nanoseconds curtic
{}, delay
{};
1626 if(unlikely(ptime
.rate
.denom
< 1))
1628 /* If there's no stream rate, the stream hasn't had a chance to get
1629 * going and return time info yet. Just use dummy values.
1631 ptime
.now
= monoclock
.count();
1633 delay
= nanoseconds
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
;
1637 /* The stream gets recreated with each reset, so include the time that
1638 * had already passed with previous streams.
1641 /* More safely scale the ticks to avoid overflowing the pre-division
1642 * temporary as it gets larger.
1644 curtic
+= seconds
{ptime
.ticks
/ ptime
.rate
.denom
} * ptime
.rate
.num
;
1645 curtic
+= nanoseconds
{seconds
{ptime
.ticks
%ptime
.rate
.denom
} * ptime
.rate
.num
} /
1648 /* The delay should be small enough to not worry about overflow. */
1649 delay
= nanoseconds
{seconds
{ptime
.delay
} * ptime
.rate
.num
} / ptime
.rate
.denom
;
1652 /* If the mixer time is ahead of the stream time, there's that much more
1653 * delay relative to the stream delay.
1655 if(mixtime
> curtic
)
1656 delay
+= mixtime
- curtic
;
1657 /* Reduce the delay according to how much time has passed since the known
1658 * stream time. This isn't 100% accurate since the system monotonic clock
1659 * doesn't tick at the exact same rate as the audio device, but it should
1660 * be good enough with ptime.now being constantly updated every few
1661 * milliseconds with ptime.ticks.
1663 delay
-= monoclock
- nanoseconds
{ptime
.now
};
1665 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1666 * incase timer drift got that severe.
1669 ret
.ClockTime
= mixtime
;
1670 ret
.Latency
= std::max(delay
, nanoseconds
{});
1676 class PipeWireCapture final
: public BackendBase
{
1677 void stateChangedCallback(pw_stream_state old
, pw_stream_state state
, const char *error
);
1678 static void stateChangedCallbackC(void *data
, pw_stream_state old
, pw_stream_state state
,
1680 { static_cast<PipeWireCapture
*>(data
)->stateChangedCallback(old
, state
, error
); }
1682 void inputCallback();
1683 static void inputCallbackC(void *data
)
1684 { static_cast<PipeWireCapture
*>(data
)->inputCallback(); }
1686 void open(const char *name
) override
;
1687 void start() override
;
1688 void stop() override
;
1689 void captureSamples(al::byte
*buffer
, uint samples
) override
;
1690 uint
availableSamples() override
;
1692 uint32_t mTargetId
{PwIdAny
};
1693 ThreadMainloop mLoop
;
1694 PwContextPtr mContext
;
1696 PwStreamPtr mStream
;
1697 spa_hook mStreamListener
{};
1699 RingBufferPtr mRing
{};
1701 static constexpr pw_stream_events
CreateEvents()
1703 pw_stream_events ret
{};
1704 ret
.version
= PW_VERSION_STREAM_EVENTS
;
1705 ret
.state_changed
= &PipeWireCapture::stateChangedCallbackC
;
1706 ret
.process
= &PipeWireCapture::inputCallbackC
;
1711 PipeWireCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1712 ~PipeWireCapture() { if(mLoop
) mLoop
.stop(); }
1714 DEF_NEWDEL(PipeWireCapture
)
1718 void PipeWireCapture::stateChangedCallback(pw_stream_state
, pw_stream_state
, const char*)
1719 { mLoop
.signal(false); }
1721 void PipeWireCapture::inputCallback()
1723 pw_buffer
*pw_buf
{pw_stream_dequeue_buffer(mStream
.get())};
1724 if(unlikely(!pw_buf
)) return;
1726 spa_data
*bufdata
{pw_buf
->buffer
->datas
};
1727 const uint offset
{minu(bufdata
->chunk
->offset
, bufdata
->maxsize
)};
1728 const uint size
{minu(bufdata
->chunk
->size
, bufdata
->maxsize
- offset
)};
1730 mRing
->write(static_cast<char*>(bufdata
->data
) + offset
, size
/ mRing
->getElemSize());
1732 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1736 void PipeWireCapture::open(const char *name
)
1738 static std::atomic
<uint
> OpenCount
{0};
1740 uint32_t targetid
{PwIdAny
};
1741 std::string devname
{};
1742 gEventHandler
.waitForInit();
1745 EventWatcherLockGuard _
{gEventHandler
};
1746 auto&& devlist
= DeviceNode::GetList();
1748 auto match
= devlist
.cend();
1749 if(!DefaultSourceDevice
.empty())
1751 auto match_default
= [](const DeviceNode
&n
) -> bool
1752 { return n
.mDevName
== DefaultSourceDevice
; };
1753 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1755 if(match
== devlist
.cend())
1757 auto match_capture
= [](const DeviceNode
&n
) -> bool
1758 { return n
.mType
!= NodeType::Sink
; };
1759 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_capture
);
1761 if(match
== devlist
.cend())
1763 match
= devlist
.cbegin();
1764 if(match
== devlist
.cend())
1765 throw al::backend_exception
{al::backend_error::NoDevice
,
1766 "No PipeWire capture device found"};
1769 targetid
= match
->mId
;
1770 if(match
->mType
!= NodeType::Sink
) devname
= match
->mName
;
1771 else devname
= MonitorPrefix
+match
->mName
;
1775 EventWatcherLockGuard _
{gEventHandler
};
1776 auto&& devlist
= DeviceNode::GetList();
1778 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1779 { return n
.mType
!= NodeType::Sink
&& n
.mName
== name
; };
1780 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1781 if(match
== devlist
.cend() && std::strncmp(name
, MonitorPrefix
, MonitorPrefixLen
) == 0)
1783 const char *sinkname
{name
+ MonitorPrefixLen
};
1784 auto match_sinkname
= [sinkname
](const DeviceNode
&n
) -> bool
1785 { return n
.mType
== NodeType::Sink
&& n
.mName
== sinkname
; };
1786 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_sinkname
);
1788 if(match
== devlist
.cend())
1789 throw al::backend_exception
{al::backend_error::NoDevice
,
1790 "Device name \"%s\" not found", name
};
1792 targetid
= match
->mId
;
1798 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1799 const std::string thread_name
{"ALSoftC" + std::to_string(count
)};
1800 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1802 throw al::backend_exception
{al::backend_error::DeviceError
,
1803 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1804 if(int res
{mLoop
.start()})
1805 throw al::backend_exception
{al::backend_error::DeviceError
,
1806 "Failed to start PipeWire mainloop (res: %d)", res
};
1808 MainloopUniqueLock mlock
{mLoop
};
1811 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1812 mContext
= mLoop
.newContext(cprops
);
1814 throw al::backend_exception
{al::backend_error::DeviceError
,
1815 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1819 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1821 throw al::backend_exception
{al::backend_error::DeviceError
,
1822 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1826 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1828 mTargetId
= targetid
;
1829 if(!devname
.empty())
1830 mDevice
->DeviceName
= std::move(devname
);
1832 mDevice
->DeviceName
= pwireInput
;
1835 bool is51rear
{false};
1836 if(mTargetId
!= PwIdAny
)
1838 EventWatcherLockGuard _
{gEventHandler
};
1839 auto&& devlist
= DeviceNode::GetList();
1841 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1842 { return targetid
== n
.mId
; };
1843 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1844 if(match
!= devlist
.cend())
1845 is51rear
= match
->mIs51Rear
;
1847 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, UseDevType
)};
1849 constexpr uint32_t pod_buffer_size
{1024};
1850 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1851 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1853 const spa_pod
*params
[]{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1855 throw al::backend_exception
{al::backend_error::DeviceError
,
1856 "Failed to set PipeWire audio format parameters"};
1858 pw_properties
*props
{pw_properties_new(
1859 PW_KEY_MEDIA_TYPE
, "Audio",
1860 PW_KEY_MEDIA_CATEGORY
, "Capture",
1861 PW_KEY_MEDIA_ROLE
, "Game",
1862 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1865 throw al::backend_exception
{al::backend_error::DeviceError
,
1866 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1868 auto&& binary
= GetProcBinary();
1869 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1870 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1871 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1872 /* We don't actually care what the latency/update size is, as long as it's
1873 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
1874 * around 40ms, which isn't great. So request 20ms instead.
1876 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", (mDevice
->Frequency
+25) / 50,
1877 mDevice
->Frequency
);
1878 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1880 MainloopUniqueLock plock
{mLoop
};
1881 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Capture Stream", props
)};
1883 throw al::backend_exception
{al::backend_error::NoDevice
,
1884 "Failed to create PipeWire stream (errno: %d)", errno
};
1885 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1886 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1888 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1889 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1890 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_INPUT
, mTargetId
, Flags
, params
, 1)})
1891 throw al::backend_exception
{al::backend_error::DeviceError
,
1892 "Error connecting PipeWire stream (res: %d)", res
};
1894 /* Wait for the stream to become paused (ready to start streaming). */
1895 pw_stream_state state
{};
1896 const char *error
{};
1897 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1899 state
= pw_stream_get_state(stream
, &error
);
1900 if(state
== PW_STREAM_STATE_ERROR
)
1901 throw al::backend_exception
{al::backend_error::DeviceError
,
1902 "Error connecting PipeWire stream: \"%s\"", error
};
1903 return state
== PW_STREAM_STATE_PAUSED
;
1907 setDefaultWFXChannelOrder();
1909 /* Ensure at least a 100ms capture buffer. */
1910 mRing
= RingBuffer::Create(maxu(mDevice
->Frequency
/10, mDevice
->BufferSize
),
1911 mDevice
->frameSizeFromFmt(), false);
1915 void PipeWireCapture::start()
1917 MainloopUniqueLock plock
{mLoop
};
1918 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1919 throw al::backend_exception
{al::backend_error::DeviceError
,
1920 "Failed to start PipeWire stream (res: %d)", res
};
1922 pw_stream_state state
{};
1923 const char *error
{};
1924 plock
.wait([stream
=mStream
.get(),&state
,&error
]()
1926 state
= pw_stream_get_state(stream
, &error
);
1927 return state
!= PW_STREAM_STATE_PAUSED
;
1930 if(state
== PW_STREAM_STATE_ERROR
)
1931 throw al::backend_exception
{al::backend_error::DeviceError
,
1932 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1935 void PipeWireCapture::stop()
1937 MainloopUniqueLock plock
{mLoop
};
1938 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1939 throw al::backend_exception
{al::backend_error::DeviceError
,
1940 "Failed to stop PipeWire stream (res: %d)", res
};
1942 plock
.wait([stream
=mStream
.get()]()
1943 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1946 uint
PipeWireCapture::availableSamples()
1947 { return static_cast<uint
>(mRing
->readSpace()); }
1949 void PipeWireCapture::captureSamples(al::byte
*buffer
, uint samples
)
1950 { mRing
->read(buffer
, samples
); }
1955 bool PipeWireBackendFactory::init()
1960 const char *version
{pw_get_library_version()};
1961 if(!check_version(version
))
1963 WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version
,
1964 pw_get_headers_version());
1967 TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version
, pw_get_headers_version());
1969 pw_init(0, nullptr);
1970 if(!gEventHandler
.init())
1973 if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
1974 && !gEventHandler
.waitForAudio())
1976 gEventHandler
.kill();
1977 /* TODO: Temporary warning, until PipeWire gets a proper way to report
1980 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
1986 bool PipeWireBackendFactory::querySupport(BackendType type
)
1987 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1989 std::string
PipeWireBackendFactory::probe(BackendType type
)
1991 std::string outnames
;
1993 gEventHandler
.waitForInit();
1994 EventWatcherLockGuard _
{gEventHandler
};
1995 auto&& devlist
= DeviceNode::GetList();
1997 auto match_defsink
= [](const DeviceNode
&n
) -> bool
1998 { return n
.mDevName
== DefaultSinkDevice
; };
1999 auto match_defsource
= [](const DeviceNode
&n
) -> bool
2000 { return n
.mDevName
== DefaultSourceDevice
; };
2002 auto sort_devnode
= [](DeviceNode
&lhs
, DeviceNode
&rhs
) noexcept
-> bool
2003 { return lhs
.mId
< rhs
.mId
; };
2004 std::sort(devlist
.begin(), devlist
.end(), sort_devnode
);
2006 auto defmatch
= devlist
.cbegin();
2009 case BackendType::Playback
:
2010 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsink
);
2011 if(defmatch
!= devlist
.cend())
2013 /* Includes null char. */
2014 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
2016 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
2018 if(iter
!= defmatch
&& iter
->mType
!= NodeType::Source
)
2019 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
2022 case BackendType::Capture
:
2023 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsource
);
2024 if(defmatch
!= devlist
.cend())
2026 if(defmatch
->mType
== NodeType::Sink
)
2027 outnames
.append(MonitorPrefix
);
2028 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
2030 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
2032 if(iter
!= defmatch
)
2034 if(iter
->mType
== NodeType::Sink
)
2035 outnames
.append(MonitorPrefix
);
2036 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
2045 BackendPtr
PipeWireBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
2047 if(type
== BackendType::Playback
)
2048 return BackendPtr
{new PipeWirePlayback
{device
}};
2049 if(type
== BackendType::Capture
)
2050 return BackendPtr
{new PipeWireCapture
{device
}};
2054 BackendFactory
&PipeWireBackendFactory::getFactory()
2056 static PipeWireBackendFactory factory
{};