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 const al::span
<spa_data
> datas
{pw_buf
->buffer
->datas
,
1312 minu(mNumChannels
, pw_buf
->buffer
->n_datas
)};
1313 #if PW_CHECK_VERSION(0,3,49)
1314 /* In 0.3.49, pw_buffer::requested specifies the number of samples needed
1315 * by the resampler/graph for this audio update.
1317 uint length
{static_cast<uint
>(pw_buf
->requested
)};
1319 /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
1320 * of samples per update.
1322 uint length
{mRateMatch
? mRateMatch
->size
: 0u};
1324 /* If no length is specified, use the device's update size as a fallback. */
1325 if(unlikely(!length
)) length
= mDevice
->UpdateSize
;
1327 /* For planar formats, each datas[] seems to contain one channel, so store
1328 * the pointers in an array. Limit the render length in case the available
1329 * buffer length in any one channel is smaller than we wanted (shouldn't
1330 * be, but just in case).
1332 float **chanptr_end
{mChannelPtrs
.get()};
1333 for(const auto &data
: datas
)
1335 length
= minu(length
, data
.maxsize
/sizeof(float));
1336 *chanptr_end
= static_cast<float*>(data
.data
);
1340 mDevice
->renderSamples({mChannelPtrs
.get(), chanptr_end
}, length
);
1342 for(const auto &data
: datas
)
1344 data
.chunk
->offset
= 0;
1345 data
.chunk
->stride
= sizeof(float);
1346 data
.chunk
->size
= length
* sizeof(float);
1348 pw_buf
->size
= length
;
1349 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1353 void PipeWirePlayback::open(const char *name
)
1355 static std::atomic
<uint
> OpenCount
{0};
1357 uint32_t targetid
{PwIdAny
};
1358 std::string devname
{};
1359 gEventHandler
.waitForInit();
1362 EventWatcherLockGuard _
{gEventHandler
};
1363 auto&& devlist
= DeviceNode::GetList();
1365 auto match
= devlist
.cend();
1366 if(!DefaultSinkDevice
.empty())
1368 auto match_default
= [](const DeviceNode
&n
) -> bool
1369 { return n
.mDevName
== DefaultSinkDevice
; };
1370 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1372 if(match
== devlist
.cend())
1374 auto match_playback
= [](const DeviceNode
&n
) -> bool
1375 { return n
.mType
!= NodeType::Source
; };
1376 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_playback
);
1377 if(match
== devlist
.cend())
1378 throw al::backend_exception
{al::backend_error::NoDevice
,
1379 "No PipeWire playback device found"};
1382 targetid
= match
->mId
;
1383 devname
= match
->mName
;
1387 EventWatcherLockGuard _
{gEventHandler
};
1388 auto&& devlist
= DeviceNode::GetList();
1390 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1391 { return n
.mType
!= NodeType::Source
&& n
.mName
== name
; };
1392 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1393 if(match
== devlist
.cend())
1394 throw al::backend_exception
{al::backend_error::NoDevice
,
1395 "Device name \"%s\" not found", name
};
1397 targetid
= match
->mId
;
1398 devname
= match
->mName
;
1403 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1404 const std::string thread_name
{"ALSoftP" + std::to_string(count
)};
1405 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1407 throw al::backend_exception
{al::backend_error::DeviceError
,
1408 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1409 if(int res
{mLoop
.start()})
1410 throw al::backend_exception
{al::backend_error::DeviceError
,
1411 "Failed to start PipeWire mainloop (res: %d)", res
};
1413 MainloopUniqueLock mlock
{mLoop
};
1416 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1417 mContext
= mLoop
.newContext(cprops
);
1419 throw al::backend_exception
{al::backend_error::DeviceError
,
1420 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1424 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1426 throw al::backend_exception
{al::backend_error::DeviceError
,
1427 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1431 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1433 mTargetId
= targetid
;
1434 if(!devname
.empty())
1435 mDevice
->DeviceName
= std::move(devname
);
1437 mDevice
->DeviceName
= pwireDevice
;
1440 bool PipeWirePlayback::reset()
1444 MainloopLockGuard _
{mLoop
};
1447 mStreamListener
= {};
1448 mRateMatch
= nullptr;
1449 mTimeBase
= GetDeviceClockTime(mDevice
);
1451 /* If connecting to a specific device, update various device parameters to
1454 bool is51rear
{false};
1455 mDevice
->Flags
.reset(DirectEar
);
1456 if(mTargetId
!= PwIdAny
)
1458 EventWatcherLockGuard _
{gEventHandler
};
1459 auto&& devlist
= DeviceNode::GetList();
1461 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1462 { return targetid
== n
.mId
; };
1463 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1464 if(match
!= devlist
.cend())
1466 if(!mDevice
->Flags
.test(FrequencyRequest
) && match
->mSampleRate
> 0)
1468 /* Scale the update size if the sample rate changes. */
1469 const double scale
{static_cast<double>(match
->mSampleRate
) / mDevice
->Frequency
};
1470 mDevice
->Frequency
= match
->mSampleRate
;
1471 mDevice
->UpdateSize
= static_cast<uint
>(clampd(mDevice
->UpdateSize
*scale
+ 0.5,
1473 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1475 if(!mDevice
->Flags
.test(ChannelsRequest
) && match
->mChannels
!= InvalidChannelConfig
)
1476 mDevice
->FmtChans
= match
->mChannels
;
1477 if(match
->mChannels
== DevFmtStereo
&& match
->mIsHeadphones
)
1478 mDevice
->Flags
.set(DirectEar
);
1479 is51rear
= match
->mIs51Rear
;
1482 /* Force planar 32-bit float output for playback. This is what PipeWire
1483 * handles internally, and it's easier for us too.
1485 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, ForceF32Planar
)};
1487 /* TODO: How to tell what an appropriate size is? Examples just use this
1490 constexpr uint32_t pod_buffer_size
{1024};
1491 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1492 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1494 const spa_pod
*params
{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1496 throw al::backend_exception
{al::backend_error::DeviceError
,
1497 "Failed to set PipeWire audio format parameters"};
1499 pw_properties
*props
{pw_properties_new(
1500 PW_KEY_MEDIA_TYPE
, "Audio",
1501 PW_KEY_MEDIA_CATEGORY
, "Playback",
1502 PW_KEY_MEDIA_ROLE
, "Game",
1503 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1506 throw al::backend_exception
{al::backend_error::DeviceError
,
1507 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1509 auto&& binary
= GetProcBinary();
1510 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1511 /* TODO: Which properties are actually needed here? Any others that could
1514 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1515 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1516 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", mDevice
->UpdateSize
,
1517 mDevice
->Frequency
);
1518 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1520 MainloopUniqueLock plock
{mLoop
};
1521 /* The stream takes overship of 'props', even in the case of failure. */
1522 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Playback Stream", props
)};
1524 throw al::backend_exception
{al::backend_error::NoDevice
,
1525 "Failed to create PipeWire stream (errno: %d)", errno
};
1526 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1527 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1529 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1530 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1531 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_OUTPUT
, mTargetId
, Flags
, ¶ms
, 1)})
1532 throw al::backend_exception
{al::backend_error::DeviceError
,
1533 "Error connecting PipeWire stream (res: %d)", res
};
1535 /* Wait for the stream to become paused (ready to start streaming). */
1536 plock
.wait([stream
=mStream
.get()]()
1538 const char *error
{};
1539 pw_stream_state state
{pw_stream_get_state(stream
, &error
)};
1540 if(state
== PW_STREAM_STATE_ERROR
)
1541 throw al::backend_exception
{al::backend_error::DeviceError
,
1542 "Error connecting PipeWire stream: \"%s\"", error
};
1543 return state
== PW_STREAM_STATE_PAUSED
;
1546 /* TODO: Update mDevice->BufferSize with the total known buffering delay
1547 * from the head of this playback stream to the tail of the device output.
1549 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1552 mNumChannels
= mDevice
->channelsFromFmt();
1553 mChannelPtrs
= std::make_unique
<float*[]>(mNumChannels
);
1555 setDefaultWFXChannelOrder();
1560 void PipeWirePlayback::start()
1562 MainloopUniqueLock plock
{mLoop
};
1563 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1564 throw al::backend_exception
{al::backend_error::DeviceError
,
1565 "Failed to start PipeWire stream (res: %d)", res
};
1567 /* Wait for the stream to start playing (would be nice to not, but we need
1568 * the actual update size which is only available after starting).
1570 plock
.wait([stream
=mStream
.get()]()
1572 const char *error
{};
1573 pw_stream_state state
{pw_stream_get_state(stream
, &error
)};
1574 if(state
== PW_STREAM_STATE_ERROR
)
1575 throw al::backend_exception
{al::backend_error::DeviceError
,
1576 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1577 return state
== PW_STREAM_STATE_STREAMING
;
1580 if(mRateMatch
&& mRateMatch
->size
)
1582 mDevice
->UpdateSize
= mRateMatch
->size
;
1583 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
1587 void PipeWirePlayback::stop()
1589 MainloopUniqueLock plock
{mLoop
};
1590 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1591 throw al::backend_exception
{al::backend_error::DeviceError
,
1592 "Failed to stop PipeWire stream (res: %d)", res
};
1594 /* Wait for the stream to stop playing. */
1595 plock
.wait([stream
=mStream
.get()]()
1596 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1599 ClockLatency
PipeWirePlayback::getClockLatency()
1601 /* Given a real-time low-latency output, this is rather complicated to get
1602 * accurate timing. So, here we go.
1605 /* First, get the stream time info (tick delay, ticks played, and the
1606 * CLOCK_MONOTONIC time closest to when that last tick was played).
1611 MainloopLockGuard _
{mLoop
};
1612 if(int res
{pw_stream_get_time_n(mStream
.get(), &ptime
, sizeof(ptime
))})
1613 ERR("Failed to get PipeWire stream time (res: %d)\n", res
);
1616 /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
1617 * monotonic clock closest to 'now', and the last mixer time at 'now').
1619 nanoseconds mixtime
{};
1623 refcount
= mDevice
->waitForMix();
1624 mixtime
= GetDeviceClockTime(mDevice
);
1625 clock_gettime(CLOCK_MONOTONIC
, &tspec
);
1626 std::atomic_thread_fence(std::memory_order_acquire
);
1627 } while(refcount
!= ReadRef(mDevice
->MixCount
));
1629 /* Convert the monotonic clock, stream ticks, and stream delay to
1632 nanoseconds monoclock
{seconds
{tspec
.tv_sec
} + nanoseconds
{tspec
.tv_nsec
}};
1633 nanoseconds curtic
{}, delay
{};
1634 if(unlikely(ptime
.rate
.denom
< 1))
1636 /* If there's no stream rate, the stream hasn't had a chance to get
1637 * going and return time info yet. Just use dummy values.
1639 ptime
.now
= monoclock
.count();
1641 delay
= nanoseconds
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
;
1645 /* The stream gets recreated with each reset, so include the time that
1646 * had already passed with previous streams.
1649 /* More safely scale the ticks to avoid overflowing the pre-division
1650 * temporary as it gets larger.
1652 curtic
+= seconds
{ptime
.ticks
/ ptime
.rate
.denom
} * ptime
.rate
.num
;
1653 curtic
+= nanoseconds
{seconds
{ptime
.ticks
%ptime
.rate
.denom
} * ptime
.rate
.num
} /
1656 /* The delay should be small enough to not worry about overflow. */
1657 delay
= nanoseconds
{seconds
{ptime
.delay
} * ptime
.rate
.num
} / ptime
.rate
.denom
;
1660 /* If the mixer time is ahead of the stream time, there's that much more
1661 * delay relative to the stream delay.
1663 if(mixtime
> curtic
)
1664 delay
+= mixtime
- curtic
;
1665 /* Reduce the delay according to how much time has passed since the known
1666 * stream time. This isn't 100% accurate since the system monotonic clock
1667 * doesn't tick at the exact same rate as the audio device, but it should
1668 * be good enough with ptime.now being constantly updated every few
1669 * milliseconds with ptime.ticks.
1671 delay
-= monoclock
- nanoseconds
{ptime
.now
};
1673 /* Return the mixer time and delay. Clamp the delay to no less than 0,
1674 * incase timer drift got that severe.
1677 ret
.ClockTime
= mixtime
;
1678 ret
.Latency
= std::max(delay
, nanoseconds
{});
1684 class PipeWireCapture final
: public BackendBase
{
1685 void stateChangedCallback(pw_stream_state old
, pw_stream_state state
, const char *error
);
1686 static void stateChangedCallbackC(void *data
, pw_stream_state old
, pw_stream_state state
,
1688 { static_cast<PipeWireCapture
*>(data
)->stateChangedCallback(old
, state
, error
); }
1690 void inputCallback();
1691 static void inputCallbackC(void *data
)
1692 { static_cast<PipeWireCapture
*>(data
)->inputCallback(); }
1694 void open(const char *name
) override
;
1695 void start() override
;
1696 void stop() override
;
1697 void captureSamples(al::byte
*buffer
, uint samples
) override
;
1698 uint
availableSamples() override
;
1700 uint32_t mTargetId
{PwIdAny
};
1701 ThreadMainloop mLoop
;
1702 PwContextPtr mContext
;
1704 PwStreamPtr mStream
;
1705 spa_hook mStreamListener
{};
1707 RingBufferPtr mRing
{};
1709 static constexpr pw_stream_events
CreateEvents()
1711 pw_stream_events ret
{};
1712 ret
.version
= PW_VERSION_STREAM_EVENTS
;
1713 ret
.state_changed
= &PipeWireCapture::stateChangedCallbackC
;
1714 ret
.process
= &PipeWireCapture::inputCallbackC
;
1719 PipeWireCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1720 ~PipeWireCapture() { if(mLoop
) mLoop
.stop(); }
1722 DEF_NEWDEL(PipeWireCapture
)
1726 void PipeWireCapture::stateChangedCallback(pw_stream_state
, pw_stream_state
, const char*)
1727 { mLoop
.signal(false); }
1729 void PipeWireCapture::inputCallback()
1731 pw_buffer
*pw_buf
{pw_stream_dequeue_buffer(mStream
.get())};
1732 if(unlikely(!pw_buf
)) return;
1734 spa_data
*bufdata
{pw_buf
->buffer
->datas
};
1735 const uint offset
{minu(bufdata
->chunk
->offset
, bufdata
->maxsize
)};
1736 const uint size
{minu(bufdata
->chunk
->size
, bufdata
->maxsize
- offset
)};
1738 mRing
->write(static_cast<char*>(bufdata
->data
) + offset
, size
/ mRing
->getElemSize());
1740 pw_stream_queue_buffer(mStream
.get(), pw_buf
);
1744 void PipeWireCapture::open(const char *name
)
1746 static std::atomic
<uint
> OpenCount
{0};
1748 uint32_t targetid
{PwIdAny
};
1749 std::string devname
{};
1750 gEventHandler
.waitForInit();
1753 EventWatcherLockGuard _
{gEventHandler
};
1754 auto&& devlist
= DeviceNode::GetList();
1756 auto match
= devlist
.cend();
1757 if(!DefaultSourceDevice
.empty())
1759 auto match_default
= [](const DeviceNode
&n
) -> bool
1760 { return n
.mDevName
== DefaultSourceDevice
; };
1761 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_default
);
1763 if(match
== devlist
.cend())
1765 auto match_capture
= [](const DeviceNode
&n
) -> bool
1766 { return n
.mType
!= NodeType::Sink
; };
1767 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_capture
);
1769 if(match
== devlist
.cend())
1771 match
= devlist
.cbegin();
1772 if(match
== devlist
.cend())
1773 throw al::backend_exception
{al::backend_error::NoDevice
,
1774 "No PipeWire capture device found"};
1777 targetid
= match
->mId
;
1778 if(match
->mType
!= NodeType::Sink
) devname
= match
->mName
;
1779 else devname
= MonitorPrefix
+match
->mName
;
1783 EventWatcherLockGuard _
{gEventHandler
};
1784 auto&& devlist
= DeviceNode::GetList();
1786 auto match_name
= [name
](const DeviceNode
&n
) -> bool
1787 { return n
.mType
!= NodeType::Sink
&& n
.mName
== name
; };
1788 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_name
);
1789 if(match
== devlist
.cend() && std::strncmp(name
, MonitorPrefix
, MonitorPrefixLen
) == 0)
1791 const char *sinkname
{name
+ MonitorPrefixLen
};
1792 auto match_sinkname
= [sinkname
](const DeviceNode
&n
) -> bool
1793 { return n
.mType
== NodeType::Sink
&& n
.mName
== sinkname
; };
1794 match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_sinkname
);
1796 if(match
== devlist
.cend())
1797 throw al::backend_exception
{al::backend_error::NoDevice
,
1798 "Device name \"%s\" not found", name
};
1800 targetid
= match
->mId
;
1806 const uint count
{OpenCount
.fetch_add(1, std::memory_order_relaxed
)};
1807 const std::string thread_name
{"ALSoftC" + std::to_string(count
)};
1808 mLoop
= ThreadMainloop::Create(thread_name
.c_str());
1810 throw al::backend_exception
{al::backend_error::DeviceError
,
1811 "Failed to create PipeWire mainloop (errno: %d)", errno
};
1812 if(int res
{mLoop
.start()})
1813 throw al::backend_exception
{al::backend_error::DeviceError
,
1814 "Failed to start PipeWire mainloop (res: %d)", res
};
1816 MainloopUniqueLock mlock
{mLoop
};
1819 pw_properties
*cprops
{pw_properties_new(PW_KEY_CONFIG_NAME
, "client-rt.conf", nullptr)};
1820 mContext
= mLoop
.newContext(cprops
);
1822 throw al::backend_exception
{al::backend_error::DeviceError
,
1823 "Failed to create PipeWire event context (errno: %d)\n", errno
};
1827 mCore
= PwCorePtr
{pw_context_connect(mContext
.get(), nullptr, 0)};
1829 throw al::backend_exception
{al::backend_error::DeviceError
,
1830 "Failed to connect PipeWire event context (errno: %d)\n", errno
};
1834 /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
1836 mTargetId
= targetid
;
1837 if(!devname
.empty())
1838 mDevice
->DeviceName
= std::move(devname
);
1840 mDevice
->DeviceName
= pwireInput
;
1843 bool is51rear
{false};
1844 if(mTargetId
!= PwIdAny
)
1846 EventWatcherLockGuard _
{gEventHandler
};
1847 auto&& devlist
= DeviceNode::GetList();
1849 auto match_id
= [targetid
=mTargetId
](const DeviceNode
&n
) -> bool
1850 { return targetid
== n
.mId
; };
1851 auto match
= std::find_if(devlist
.cbegin(), devlist
.cend(), match_id
);
1852 if(match
!= devlist
.cend())
1853 is51rear
= match
->mIs51Rear
;
1855 spa_audio_info_raw info
{make_spa_info(mDevice
, is51rear
, UseDevType
)};
1857 constexpr uint32_t pod_buffer_size
{1024};
1858 auto pod_buffer
= std::make_unique
<al::byte
[]>(pod_buffer_size
);
1859 spa_pod_builder b
{make_pod_builder(pod_buffer
.get(), pod_buffer_size
)};
1861 const spa_pod
*params
[]{spa_format_audio_raw_build(&b
, SPA_PARAM_EnumFormat
, &info
)};
1863 throw al::backend_exception
{al::backend_error::DeviceError
,
1864 "Failed to set PipeWire audio format parameters"};
1866 pw_properties
*props
{pw_properties_new(
1867 PW_KEY_MEDIA_TYPE
, "Audio",
1868 PW_KEY_MEDIA_CATEGORY
, "Capture",
1869 PW_KEY_MEDIA_ROLE
, "Game",
1870 PW_KEY_NODE_ALWAYS_PROCESS
, "true",
1873 throw al::backend_exception
{al::backend_error::DeviceError
,
1874 "Failed to create PipeWire stream properties (errno: %d)", errno
};
1876 auto&& binary
= GetProcBinary();
1877 const char *appname
{binary
.fname
.length() ? binary
.fname
.c_str() : "OpenAL Soft"};
1878 pw_properties_set(props
, PW_KEY_NODE_NAME
, appname
);
1879 pw_properties_set(props
, PW_KEY_NODE_DESCRIPTION
, appname
);
1880 /* We don't actually care what the latency/update size is, as long as it's
1881 * reasonable. Unfortunately, when unspecified PipeWire seems to default to
1882 * around 40ms, which isn't great. So request 20ms instead.
1884 pw_properties_setf(props
, PW_KEY_NODE_LATENCY
, "%u/%u", (mDevice
->Frequency
+25) / 50,
1885 mDevice
->Frequency
);
1886 pw_properties_setf(props
, PW_KEY_NODE_RATE
, "1/%u", mDevice
->Frequency
);
1888 MainloopUniqueLock plock
{mLoop
};
1889 mStream
= PwStreamPtr
{pw_stream_new(mCore
.get(), "Capture Stream", props
)};
1891 throw al::backend_exception
{al::backend_error::NoDevice
,
1892 "Failed to create PipeWire stream (errno: %d)", errno
};
1893 static constexpr pw_stream_events streamEvents
{CreateEvents()};
1894 pw_stream_add_listener(mStream
.get(), &mStreamListener
, &streamEvents
, this);
1896 constexpr pw_stream_flags Flags
{PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
1897 | PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS
};
1898 if(int res
{pw_stream_connect(mStream
.get(), PW_DIRECTION_INPUT
, mTargetId
, Flags
, params
, 1)})
1899 throw al::backend_exception
{al::backend_error::DeviceError
,
1900 "Error connecting PipeWire stream (res: %d)", res
};
1902 /* Wait for the stream to become paused (ready to start streaming). */
1903 plock
.wait([stream
=mStream
.get()]()
1905 const char *error
{};
1906 pw_stream_state state
{pw_stream_get_state(stream
, &error
)};
1907 if(state
== PW_STREAM_STATE_ERROR
)
1908 throw al::backend_exception
{al::backend_error::DeviceError
,
1909 "Error connecting PipeWire stream: \"%s\"", error
};
1910 return state
== PW_STREAM_STATE_PAUSED
;
1914 setDefaultWFXChannelOrder();
1916 /* Ensure at least a 100ms capture buffer. */
1917 mRing
= RingBuffer::Create(maxu(mDevice
->Frequency
/10, mDevice
->BufferSize
),
1918 mDevice
->frameSizeFromFmt(), false);
1922 void PipeWireCapture::start()
1924 MainloopUniqueLock plock
{mLoop
};
1925 if(int res
{pw_stream_set_active(mStream
.get(), true)})
1926 throw al::backend_exception
{al::backend_error::DeviceError
,
1927 "Failed to start PipeWire stream (res: %d)", res
};
1929 plock
.wait([stream
=mStream
.get()]()
1931 const char *error
{};
1932 pw_stream_state state
{pw_stream_get_state(stream
, &error
)};
1933 if(state
== PW_STREAM_STATE_ERROR
)
1934 throw al::backend_exception
{al::backend_error::DeviceError
,
1935 "PipeWire stream error: %s", error
? error
: "(unknown)"};
1936 return state
== PW_STREAM_STATE_STREAMING
;
1940 void PipeWireCapture::stop()
1942 MainloopUniqueLock plock
{mLoop
};
1943 if(int res
{pw_stream_set_active(mStream
.get(), false)})
1944 throw al::backend_exception
{al::backend_error::DeviceError
,
1945 "Failed to stop PipeWire stream (res: %d)", res
};
1947 plock
.wait([stream
=mStream
.get()]()
1948 { return pw_stream_get_state(stream
, nullptr) != PW_STREAM_STATE_STREAMING
; });
1951 uint
PipeWireCapture::availableSamples()
1952 { return static_cast<uint
>(mRing
->readSpace()); }
1954 void PipeWireCapture::captureSamples(al::byte
*buffer
, uint samples
)
1955 { mRing
->read(buffer
, samples
); }
1960 bool PipeWireBackendFactory::init()
1965 const char *version
{pw_get_library_version()};
1966 if(!check_version(version
))
1968 WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version
,
1969 pw_get_headers_version());
1972 TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version
, pw_get_headers_version());
1974 pw_init(0, nullptr);
1975 if(!gEventHandler
.init())
1978 if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
1979 && !gEventHandler
.waitForAudio())
1981 gEventHandler
.kill();
1982 /* TODO: Temporary warning, until PipeWire gets a proper way to report
1985 WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
1991 bool PipeWireBackendFactory::querySupport(BackendType type
)
1992 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1994 std::string
PipeWireBackendFactory::probe(BackendType type
)
1996 std::string outnames
;
1998 gEventHandler
.waitForInit();
1999 EventWatcherLockGuard _
{gEventHandler
};
2000 auto&& devlist
= DeviceNode::GetList();
2002 auto match_defsink
= [](const DeviceNode
&n
) -> bool
2003 { return n
.mDevName
== DefaultSinkDevice
; };
2004 auto match_defsource
= [](const DeviceNode
&n
) -> bool
2005 { return n
.mDevName
== DefaultSourceDevice
; };
2007 auto sort_devnode
= [](DeviceNode
&lhs
, DeviceNode
&rhs
) noexcept
-> bool
2008 { return lhs
.mId
< rhs
.mId
; };
2009 std::sort(devlist
.begin(), devlist
.end(), sort_devnode
);
2011 auto defmatch
= devlist
.cbegin();
2014 case BackendType::Playback
:
2015 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsink
);
2016 if(defmatch
!= devlist
.cend())
2018 /* Includes null char. */
2019 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
2021 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
2023 if(iter
!= defmatch
&& iter
->mType
!= NodeType::Source
)
2024 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
2027 case BackendType::Capture
:
2028 defmatch
= std::find_if(defmatch
, devlist
.cend(), match_defsource
);
2029 if(defmatch
!= devlist
.cend())
2031 if(defmatch
->mType
== NodeType::Sink
)
2032 outnames
.append(MonitorPrefix
);
2033 outnames
.append(defmatch
->mName
.c_str(), defmatch
->mName
.length()+1);
2035 for(auto iter
= devlist
.cbegin();iter
!= devlist
.cend();++iter
)
2037 if(iter
!= defmatch
)
2039 if(iter
->mType
== NodeType::Sink
)
2040 outnames
.append(MonitorPrefix
);
2041 outnames
.append(iter
->mName
.c_str(), iter
->mName
.length()+1);
2050 BackendPtr
PipeWireBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
2052 if(type
== BackendType::Playback
)
2053 return BackendPtr
{new PipeWirePlayback
{device
}};
2054 if(type
== BackendType::Capture
)
2055 return BackendPtr
{new PipeWireCapture
{device
}};
2059 BackendFactory
&PipeWireBackendFactory::getFactory()
2061 static PipeWireBackendFactory factory
{};