2 * OpenAL cross platform audio library
3 * Copyright (C) 2009 by Konstantinos Natsakis <konstantinos.natsakis@gmail.com>
4 * Copyright (C) 2010 by Chris Robinson <chris.kcat@gmail.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 * Or go to http://www.gnu.org/copyleft/lgpl.html
24 #include "pulseaudio.h"
37 #include <sys/types.h>
41 #include "alc/alconfig.h"
43 #include "alnumeric.h"
44 #include "aloptional.h"
46 #include "core/devformat.h"
47 #include "core/device.h"
48 #include "core/logging.h"
50 #include "opthelpers.h"
54 #include <pulse/pulseaudio.h>
59 using uint
= unsigned int;
62 #define PULSE_FUNCS(MAGIC) \
63 MAGIC(pa_context_new); \
64 MAGIC(pa_context_unref); \
65 MAGIC(pa_context_get_state); \
66 MAGIC(pa_context_disconnect); \
67 MAGIC(pa_context_set_state_callback); \
68 MAGIC(pa_context_errno); \
69 MAGIC(pa_context_connect); \
70 MAGIC(pa_context_get_server_info); \
71 MAGIC(pa_context_get_sink_info_by_name); \
72 MAGIC(pa_context_get_sink_info_list); \
73 MAGIC(pa_context_get_source_info_by_name); \
74 MAGIC(pa_context_get_source_info_list); \
75 MAGIC(pa_stream_new); \
76 MAGIC(pa_stream_unref); \
77 MAGIC(pa_stream_drop); \
78 MAGIC(pa_stream_get_state); \
79 MAGIC(pa_stream_peek); \
80 MAGIC(pa_stream_write); \
81 MAGIC(pa_stream_connect_record); \
82 MAGIC(pa_stream_connect_playback); \
83 MAGIC(pa_stream_readable_size); \
84 MAGIC(pa_stream_writable_size); \
85 MAGIC(pa_stream_is_corked); \
86 MAGIC(pa_stream_cork); \
87 MAGIC(pa_stream_is_suspended); \
88 MAGIC(pa_stream_get_device_name); \
89 MAGIC(pa_stream_get_latency); \
90 MAGIC(pa_stream_set_write_callback); \
91 MAGIC(pa_stream_set_buffer_attr); \
92 MAGIC(pa_stream_get_buffer_attr); \
93 MAGIC(pa_stream_get_sample_spec); \
94 MAGIC(pa_stream_get_time); \
95 MAGIC(pa_stream_set_read_callback); \
96 MAGIC(pa_stream_set_state_callback); \
97 MAGIC(pa_stream_set_moved_callback); \
98 MAGIC(pa_stream_set_underflow_callback); \
99 MAGIC(pa_stream_new_with_proplist); \
100 MAGIC(pa_stream_disconnect); \
101 MAGIC(pa_stream_set_buffer_attr_callback); \
102 MAGIC(pa_stream_begin_write); \
103 MAGIC(pa_threaded_mainloop_free); \
104 MAGIC(pa_threaded_mainloop_get_api); \
105 MAGIC(pa_threaded_mainloop_lock); \
106 MAGIC(pa_threaded_mainloop_new); \
107 MAGIC(pa_threaded_mainloop_signal); \
108 MAGIC(pa_threaded_mainloop_start); \
109 MAGIC(pa_threaded_mainloop_stop); \
110 MAGIC(pa_threaded_mainloop_unlock); \
111 MAGIC(pa_threaded_mainloop_wait); \
112 MAGIC(pa_channel_map_init_auto); \
113 MAGIC(pa_channel_map_parse); \
114 MAGIC(pa_channel_map_snprint); \
115 MAGIC(pa_channel_map_equal); \
116 MAGIC(pa_channel_map_superset); \
117 MAGIC(pa_channel_position_to_string); \
118 MAGIC(pa_operation_get_state); \
119 MAGIC(pa_operation_unref); \
120 MAGIC(pa_sample_spec_valid); \
121 MAGIC(pa_frame_size); \
122 MAGIC(pa_strerror); \
123 MAGIC(pa_path_get_filename); \
124 MAGIC(pa_get_binary_name); \
129 #define MAKE_FUNC(x) decltype(x) * p##x
130 PULSE_FUNCS(MAKE_FUNC
)
133 #ifndef IN_IDE_PARSER
134 #define pa_context_new ppa_context_new
135 #define pa_context_unref ppa_context_unref
136 #define pa_context_get_state ppa_context_get_state
137 #define pa_context_disconnect ppa_context_disconnect
138 #define pa_context_set_state_callback ppa_context_set_state_callback
139 #define pa_context_errno ppa_context_errno
140 #define pa_context_connect ppa_context_connect
141 #define pa_context_get_server_info ppa_context_get_server_info
142 #define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
143 #define pa_context_get_sink_info_list ppa_context_get_sink_info_list
144 #define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
145 #define pa_context_get_source_info_list ppa_context_get_source_info_list
146 #define pa_stream_new ppa_stream_new
147 #define pa_stream_unref ppa_stream_unref
148 #define pa_stream_disconnect ppa_stream_disconnect
149 #define pa_stream_drop ppa_stream_drop
150 #define pa_stream_set_write_callback ppa_stream_set_write_callback
151 #define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr
152 #define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr
153 #define pa_stream_get_sample_spec ppa_stream_get_sample_spec
154 #define pa_stream_get_time ppa_stream_get_time
155 #define pa_stream_set_read_callback ppa_stream_set_read_callback
156 #define pa_stream_set_state_callback ppa_stream_set_state_callback
157 #define pa_stream_set_moved_callback ppa_stream_set_moved_callback
158 #define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback
159 #define pa_stream_connect_record ppa_stream_connect_record
160 #define pa_stream_connect_playback ppa_stream_connect_playback
161 #define pa_stream_readable_size ppa_stream_readable_size
162 #define pa_stream_writable_size ppa_stream_writable_size
163 #define pa_stream_is_corked ppa_stream_is_corked
164 #define pa_stream_cork ppa_stream_cork
165 #define pa_stream_is_suspended ppa_stream_is_suspended
166 #define pa_stream_get_device_name ppa_stream_get_device_name
167 #define pa_stream_get_latency ppa_stream_get_latency
168 #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback
169 #define pa_stream_begin_write ppa_stream_begin_write
170 #define pa_threaded_mainloop_free ppa_threaded_mainloop_free
171 #define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api
172 #define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock
173 #define pa_threaded_mainloop_new ppa_threaded_mainloop_new
174 #define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal
175 #define pa_threaded_mainloop_start ppa_threaded_mainloop_start
176 #define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop
177 #define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock
178 #define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait
179 #define pa_channel_map_init_auto ppa_channel_map_init_auto
180 #define pa_channel_map_parse ppa_channel_map_parse
181 #define pa_channel_map_snprint ppa_channel_map_snprint
182 #define pa_channel_map_equal ppa_channel_map_equal
183 #define pa_channel_map_superset ppa_channel_map_superset
184 #define pa_channel_position_to_string ppa_channel_position_to_string
185 #define pa_operation_get_state ppa_operation_get_state
186 #define pa_operation_unref ppa_operation_unref
187 #define pa_sample_spec_valid ppa_sample_spec_valid
188 #define pa_frame_size ppa_frame_size
189 #define pa_strerror ppa_strerror
190 #define pa_stream_get_state ppa_stream_get_state
191 #define pa_stream_peek ppa_stream_peek
192 #define pa_stream_write ppa_stream_write
193 #define pa_xfree ppa_xfree
194 #define pa_path_get_filename ppa_path_get_filename
195 #define pa_get_binary_name ppa_get_binary_name
196 #define pa_xmalloc ppa_xmalloc
197 #endif /* IN_IDE_PARSER */
202 constexpr pa_channel_map MonoChanMap
{
203 1, {PA_CHANNEL_POSITION_MONO
}
205 2, {PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
}
208 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
209 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
213 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
214 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
215 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
219 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
220 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
221 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
225 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
226 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
227 PA_CHANNEL_POSITION_REAR_CENTER
,
228 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
232 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
233 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
234 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
,
235 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
239 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
240 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
241 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
,
242 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
,
243 PA_CHANNEL_POSITION_TOP_FRONT_LEFT
, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT
,
244 PA_CHANNEL_POSITION_TOP_REAR_LEFT
, PA_CHANNEL_POSITION_TOP_REAR_RIGHT
249 /* *grumble* Don't use enums for bitflags. */
250 constexpr pa_stream_flags_t
operator|(pa_stream_flags_t lhs
, pa_stream_flags_t rhs
)
251 { return pa_stream_flags_t(lhs
| al::to_underlying(rhs
)); }
252 constexpr pa_stream_flags_t
& operator|=(pa_stream_flags_t
&lhs
, pa_stream_flags_t rhs
)
257 constexpr pa_stream_flags_t
operator~(pa_stream_flags_t flag
)
258 { return pa_stream_flags_t(~al::to_underlying(flag
)); }
259 constexpr pa_stream_flags_t
& operator&=(pa_stream_flags_t
&lhs
, pa_stream_flags_t rhs
)
261 lhs
= pa_stream_flags_t(al::to_underlying(lhs
) & rhs
);
265 constexpr pa_context_flags_t
operator|(pa_context_flags_t lhs
, pa_context_flags_t rhs
)
266 { return pa_context_flags_t(lhs
| al::to_underlying(rhs
)); }
267 constexpr pa_context_flags_t
& operator|=(pa_context_flags_t
&lhs
, pa_context_flags_t rhs
)
276 std::string device_name
;
279 bool checkName(const al::span
<const DevMap
> list
, const std::string
&name
)
281 auto match_name
= [&name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; };
282 return std::find_if(list
.cbegin(), list
.cend(), match_name
) != list
.cend();
285 al::vector
<DevMap
> PlaybackDevices
;
286 al::vector
<DevMap
> CaptureDevices
;
289 /* Global flags and properties */
290 pa_context_flags_t pulse_ctx_flags
;
292 class PulseMainloop
{
293 pa_threaded_mainloop
*mLoop
{};
296 PulseMainloop() = default;
297 PulseMainloop(const PulseMainloop
&) = delete;
298 PulseMainloop(PulseMainloop
&& rhs
) noexcept
: mLoop
{rhs
.mLoop
} { rhs
.mLoop
= nullptr; }
299 explicit PulseMainloop(pa_threaded_mainloop
*loop
) noexcept
: mLoop
{loop
} { }
300 ~PulseMainloop() { if(mLoop
) pa_threaded_mainloop_free(mLoop
); }
302 PulseMainloop
& operator=(const PulseMainloop
&) = delete;
303 PulseMainloop
& operator=(PulseMainloop
&& rhs
) noexcept
304 { std::swap(mLoop
, rhs
.mLoop
); return *this; }
305 PulseMainloop
& operator=(std::nullptr_t
) noexcept
308 pa_threaded_mainloop_free(mLoop
);
313 explicit operator bool() const noexcept
{ return mLoop
!= nullptr; }
315 auto start() const { return pa_threaded_mainloop_start(mLoop
); }
316 auto stop() const { return pa_threaded_mainloop_stop(mLoop
); }
318 auto getApi() const { return pa_threaded_mainloop_get_api(mLoop
); }
320 auto lock() const { return pa_threaded_mainloop_lock(mLoop
); }
321 auto unlock() const { return pa_threaded_mainloop_unlock(mLoop
); }
323 auto signal(bool wait
=false) const { return pa_threaded_mainloop_signal(mLoop
, wait
); }
325 static auto Create() { return PulseMainloop
{pa_threaded_mainloop_new()}; }
328 void streamSuccessCallback(pa_stream
*, int) noexcept
{ signal(); }
329 static void streamSuccessCallbackC(pa_stream
*stream
, int success
, void *pdata
) noexcept
330 { static_cast<PulseMainloop
*>(pdata
)->streamSuccessCallback(stream
, success
); }
332 void close(pa_context
*context
, pa_stream
*stream
=nullptr);
335 void deviceSinkCallback(pa_context
*, const pa_sink_info
*info
, int eol
) noexcept
343 /* Skip this device is if it's already in the list. */
344 auto match_devname
= [info
](const DevMap
&entry
) -> bool
345 { return entry
.device_name
== info
->name
; };
346 if(std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), match_devname
) != PlaybackDevices
.cend())
349 /* Make sure the display name (description) is unique. Append a number
353 std::string newname
{info
->description
};
354 while(checkName(PlaybackDevices
, newname
))
356 newname
= info
->description
;
358 newname
+= std::to_string(++count
);
360 PlaybackDevices
.emplace_back(DevMap
{std::move(newname
), info
->name
});
361 DevMap
&newentry
= PlaybackDevices
.back();
363 TRACE("Got device \"%s\", \"%s\"\n", newentry
.name
.c_str(), newentry
.device_name
.c_str());
366 void deviceSourceCallback(pa_context
*, const pa_source_info
*info
, int eol
) noexcept
374 /* Skip this device is if it's already in the list. */
375 auto match_devname
= [info
](const DevMap
&entry
) -> bool
376 { return entry
.device_name
== info
->name
; };
377 if(std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(), match_devname
) != CaptureDevices
.cend())
380 /* Make sure the display name (description) is unique. Append a number
384 std::string newname
{info
->description
};
385 while(checkName(CaptureDevices
, newname
))
387 newname
= info
->description
;
389 newname
+= std::to_string(++count
);
391 CaptureDevices
.emplace_back(DevMap
{std::move(newname
), info
->name
});
392 DevMap
&newentry
= CaptureDevices
.back();
394 TRACE("Got device \"%s\", \"%s\"\n", newentry
.name
.c_str(), newentry
.device_name
.c_str());
397 void probePlaybackDevices();
398 void probeCaptureDevices();
400 friend struct MainloopUniqueLock
;
402 struct MainloopUniqueLock
: public std::unique_lock
<PulseMainloop
> {
403 using std::unique_lock
<PulseMainloop
>::unique_lock
;
404 MainloopUniqueLock
& operator=(MainloopUniqueLock
&&) = default;
406 auto wait() const -> void
407 { pa_threaded_mainloop_wait(mutex()->mLoop
); }
409 template<typename Predicate
>
410 auto wait(Predicate done_waiting
) const -> void
411 { while(!done_waiting()) wait(); }
413 void waitForOperation(pa_operation
*op
)
417 wait([op
]{ return pa_operation_get_state(op
) != PA_OPERATION_RUNNING
; });
418 pa_operation_unref(op
);
423 void contextStateCallback(pa_context
*context
) noexcept
425 pa_context_state_t state
{pa_context_get_state(context
)};
426 if(state
== PA_CONTEXT_READY
|| !PA_CONTEXT_IS_GOOD(state
))
430 void streamStateCallback(pa_stream
*stream
) noexcept
432 pa_stream_state_t state
{pa_stream_get_state(stream
)};
433 if(state
== PA_STREAM_READY
|| !PA_STREAM_IS_GOOD(state
))
437 pa_context
*connectContext();
438 pa_stream
*connectStream(const char *device_name
, pa_context
*context
, pa_stream_flags_t flags
,
439 pa_buffer_attr
*attr
, pa_sample_spec
*spec
, pa_channel_map
*chanmap
, BackendType type
);
441 using MainloopLockGuard
= std::lock_guard
<PulseMainloop
>;
444 pa_context
*MainloopUniqueLock::connectContext()
446 pa_context
*context
{pa_context_new(mutex()->getApi(), nullptr)};
447 if(!context
) throw al::backend_exception
{al::backend_error::OutOfMemory
,
448 "pa_context_new() failed"};
450 pa_context_set_state_callback(context
, [](pa_context
*ctx
, void *pdata
) noexcept
451 { return static_cast<MainloopUniqueLock
*>(pdata
)->contextStateCallback(ctx
); }, this);
454 if((err
=pa_context_connect(context
, nullptr, pulse_ctx_flags
, nullptr)) >= 0)
456 pa_context_state_t state
;
457 while((state
=pa_context_get_state(context
)) != PA_CONTEXT_READY
)
459 if(!PA_CONTEXT_IS_GOOD(state
))
461 err
= pa_context_errno(context
);
462 if(err
> 0) err
= -err
;
469 pa_context_set_state_callback(context
, nullptr, nullptr);
473 pa_context_unref(context
);
474 throw al::backend_exception
{al::backend_error::DeviceError
, "Context did not connect (%s)",
481 pa_stream
*MainloopUniqueLock::connectStream(const char *device_name
, pa_context
*context
,
482 pa_stream_flags_t flags
, pa_buffer_attr
*attr
, pa_sample_spec
*spec
, pa_channel_map
*chanmap
,
485 const char *stream_id
{(type
==BackendType::Playback
) ? "Playback Stream" : "Capture Stream"};
486 pa_stream
*stream
{pa_stream_new(context
, stream_id
, spec
, chanmap
)};
488 throw al::backend_exception
{al::backend_error::OutOfMemory
, "pa_stream_new() failed (%s)",
489 pa_strerror(pa_context_errno(context
))};
491 pa_stream_set_state_callback(stream
, [](pa_stream
*strm
, void *pdata
) noexcept
492 { return static_cast<MainloopUniqueLock
*>(pdata
)->streamStateCallback(strm
); }, this);
494 int err
{(type
==BackendType::Playback
) ?
495 pa_stream_connect_playback(stream
, device_name
, attr
, flags
, nullptr, nullptr) :
496 pa_stream_connect_record(stream
, device_name
, attr
, flags
)};
499 pa_stream_unref(stream
);
500 throw al::backend_exception
{al::backend_error::DeviceError
, "%s did not connect (%s)",
501 stream_id
, pa_strerror(err
)};
504 pa_stream_state_t state
;
505 while((state
=pa_stream_get_state(stream
)) != PA_STREAM_READY
)
507 if(!PA_STREAM_IS_GOOD(state
))
509 err
= pa_context_errno(context
);
510 pa_stream_unref(stream
);
511 throw al::backend_exception
{al::backend_error::DeviceError
,
512 "%s did not get ready (%s)", stream_id
, pa_strerror(err
)};
517 pa_stream_set_state_callback(stream
, nullptr, nullptr);
522 void PulseMainloop::close(pa_context
*context
, pa_stream
*stream
)
524 MainloopUniqueLock _
{*this};
527 pa_stream_set_state_callback(stream
, nullptr, nullptr);
528 pa_stream_set_moved_callback(stream
, nullptr, nullptr);
529 pa_stream_set_write_callback(stream
, nullptr, nullptr);
530 pa_stream_set_buffer_attr_callback(stream
, nullptr, nullptr);
531 pa_stream_disconnect(stream
);
532 pa_stream_unref(stream
);
535 pa_context_disconnect(context
);
536 pa_context_unref(context
);
540 void PulseMainloop::probePlaybackDevices()
542 pa_context
*context
{};
544 PlaybackDevices
.clear();
546 MainloopUniqueLock plock
{*this};
547 auto sink_callback
= [](pa_context
*ctx
, const pa_sink_info
*info
, int eol
, void *pdata
) noexcept
548 { return static_cast<PulseMainloop
*>(pdata
)->deviceSinkCallback(ctx
, info
, eol
); };
550 context
= plock
.connectContext();
551 pa_operation
*op
{pa_context_get_sink_info_by_name(context
, nullptr, sink_callback
, this)};
552 plock
.waitForOperation(op
);
554 op
= pa_context_get_sink_info_list(context
, sink_callback
, this);
555 plock
.waitForOperation(op
);
557 pa_context_disconnect(context
);
558 pa_context_unref(context
);
561 catch(std::exception
&e
) {
562 ERR("Error enumerating devices: %s\n", e
.what());
563 if(context
) close(context
);
567 void PulseMainloop::probeCaptureDevices()
569 pa_context
*context
{};
571 CaptureDevices
.clear();
573 MainloopUniqueLock plock
{*this};
574 auto src_callback
= [](pa_context
*ctx
, const pa_source_info
*info
, int eol
, void *pdata
) noexcept
575 { return static_cast<PulseMainloop
*>(pdata
)->deviceSourceCallback(ctx
, info
, eol
); };
577 context
= plock
.connectContext();
578 pa_operation
*op
{pa_context_get_source_info_by_name(context
, nullptr, src_callback
, this)};
579 plock
.waitForOperation(op
);
581 op
= pa_context_get_source_info_list(context
, src_callback
, this);
582 plock
.waitForOperation(op
);
584 pa_context_disconnect(context
);
585 pa_context_unref(context
);
588 catch(std::exception
&e
) {
589 ERR("Error enumerating devices: %s\n", e
.what());
590 if(context
) close(context
);
595 /* Used for initial connection test and enumeration. */
596 PulseMainloop gGlobalMainloop
;
599 struct PulsePlayback final
: public BackendBase
{
600 PulsePlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
601 ~PulsePlayback() override
;
603 void bufferAttrCallback(pa_stream
*stream
) noexcept
;
604 void streamStateCallback(pa_stream
*stream
) noexcept
;
605 void streamWriteCallback(pa_stream
*stream
, size_t nbytes
) noexcept
;
606 void sinkInfoCallback(pa_context
*context
, const pa_sink_info
*info
, int eol
) noexcept
;
607 void sinkNameCallback(pa_context
*context
, const pa_sink_info
*info
, int eol
) noexcept
;
608 void streamMovedCallback(pa_stream
*stream
) noexcept
;
610 void open(const char *name
) override
;
611 bool reset() override
;
612 void start() override
;
613 void stop() override
;
614 ClockLatency
getClockLatency() override
;
616 PulseMainloop mMainloop
;
618 al::optional
<std::string
> mDeviceName
{al::nullopt
};
620 bool mIs51Rear
{false};
621 pa_buffer_attr mAttr
;
622 pa_sample_spec mSpec
;
624 pa_stream
*mStream
{nullptr};
625 pa_context
*mContext
{nullptr};
629 DEF_NEWDEL(PulsePlayback
)
632 PulsePlayback::~PulsePlayback()
637 mMainloop
.close(mContext
, mStream
);
643 void PulsePlayback::bufferAttrCallback(pa_stream
*stream
) noexcept
645 /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new
646 * buffer attributes? Changing UpdateSize will change the ALC_REFRESH
647 * property, which probably shouldn't change between device resets. But
648 * leaving it alone means ALC_REFRESH will be off.
650 mAttr
= *(pa_stream_get_buffer_attr(stream
));
651 TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr
.minreq
, mAttr
.tlength
, mAttr
.prebuf
);
654 void PulsePlayback::streamStateCallback(pa_stream
*stream
) noexcept
656 if(pa_stream_get_state(stream
) == PA_STREAM_FAILED
)
658 ERR("Received stream failure!\n");
659 mDevice
->handleDisconnect("Playback stream failure");
664 void PulsePlayback::streamWriteCallback(pa_stream
*stream
, size_t nbytes
) noexcept
667 pa_free_cb_t free_func
{nullptr};
668 auto buflen
= static_cast<size_t>(-1);
670 if(pa_stream_begin_write(stream
, &buf
, &buflen
) || !buf
) [[unlikely
]]
673 buf
= pa_xmalloc(buflen
);
674 free_func
= pa_xfree
;
677 buflen
= minz(buflen
, nbytes
);
680 mDevice
->renderSamples(buf
, static_cast<uint
>(buflen
/mFrameSize
), mSpec
.channels
);
682 int ret
{pa_stream_write(stream
, buf
, buflen
, free_func
, 0, PA_SEEK_RELATIVE
)};
683 if(ret
!= PA_OK
) [[unlikely
]]
684 ERR("Failed to write to stream: %d, %s\n", ret
, pa_strerror(ret
));
688 void PulsePlayback::sinkInfoCallback(pa_context
*, const pa_sink_info
*info
, int eol
) noexcept
695 static constexpr std::array
<ChannelMap
,8> chanmaps
{{
696 { DevFmtX714
, X714ChanMap
, false },
697 { DevFmtX71
, X71ChanMap
, false },
698 { DevFmtX61
, X61ChanMap
, false },
699 { DevFmtX51
, X51ChanMap
, false },
700 { DevFmtX51
, X51RearChanMap
, true },
701 { DevFmtQuad
, QuadChanMap
, false },
702 { DevFmtStereo
, StereoChanMap
, false },
703 { DevFmtMono
, MonoChanMap
, false }
712 auto chaniter
= std::find_if(chanmaps
.cbegin(), chanmaps
.cend(),
713 [info
](const ChannelMap
&chanmap
) -> bool
714 { return pa_channel_map_superset(&info
->channel_map
, &chanmap
.map
); }
716 if(chaniter
!= chanmaps
.cend())
718 if(!mDevice
->Flags
.test(ChannelsRequest
))
719 mDevice
->FmtChans
= chaniter
->fmt
;
720 mIs51Rear
= chaniter
->is_51rear
;
725 char chanmap_str
[PA_CHANNEL_MAP_SNPRINT_MAX
]{};
726 pa_channel_map_snprint(chanmap_str
, sizeof(chanmap_str
), &info
->channel_map
);
727 WARN("Failed to find format for channel map:\n %s\n", chanmap_str
);
730 if(info
->active_port
)
731 TRACE("Active port: %s (%s)\n", info
->active_port
->name
, info
->active_port
->description
);
732 mDevice
->Flags
.set(DirectEar
, (info
->active_port
733 && strcmp(info
->active_port
->name
, "analog-output-headphones") == 0));
736 void PulsePlayback::sinkNameCallback(pa_context
*, const pa_sink_info
*info
, int eol
) noexcept
743 mDevice
->DeviceName
= info
->description
;
746 void PulsePlayback::streamMovedCallback(pa_stream
*stream
) noexcept
748 mDeviceName
= pa_stream_get_device_name(stream
);
749 TRACE("Stream moved to %s\n", mDeviceName
->c_str());
753 void PulsePlayback::open(const char *name
)
755 mMainloop
= PulseMainloop::Create();
758 const char *pulse_name
{nullptr};
759 const char *dev_name
{nullptr};
762 if(PlaybackDevices
.empty())
763 mMainloop
.probePlaybackDevices();
765 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
766 [name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; });
767 if(iter
== PlaybackDevices
.cend())
768 throw al::backend_exception
{al::backend_error::NoDevice
,
769 "Device name \"%s\" not found", name
};
770 pulse_name
= iter
->device_name
.c_str();
771 dev_name
= iter
->name
.c_str();
774 MainloopUniqueLock plock
{mMainloop
};
775 mContext
= plock
.connectContext();
777 pa_stream_flags_t flags
{PA_STREAM_START_CORKED
| PA_STREAM_FIX_FORMAT
| PA_STREAM_FIX_RATE
|
778 PA_STREAM_FIX_CHANNELS
};
779 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
780 flags
|= PA_STREAM_DONT_MOVE
;
782 pa_sample_spec spec
{};
783 spec
.format
= PA_SAMPLE_S16NE
;
789 static const auto defname
= al::getenv("ALSOFT_PULSE_DEFAULT");
790 if(defname
) pulse_name
= defname
->c_str();
792 TRACE("Connecting to \"%s\"\n", pulse_name
? pulse_name
: "(default)");
793 mStream
= plock
.connectStream(pulse_name
, mContext
, flags
, nullptr, &spec
, nullptr,
794 BackendType::Playback
);
796 pa_stream_set_moved_callback(mStream
, [](pa_stream
*stream
, void *pdata
) noexcept
797 { return static_cast<PulsePlayback
*>(pdata
)->streamMovedCallback(stream
); }, this);
798 mFrameSize
= static_cast<uint
>(pa_frame_size(pa_stream_get_sample_spec(mStream
)));
800 if(pulse_name
) mDeviceName
.emplace(pulse_name
);
801 else mDeviceName
.reset();
804 auto name_callback
= [](pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
) noexcept
805 { return static_cast<PulsePlayback
*>(pdata
)->sinkNameCallback(context
, info
, eol
); };
806 pa_operation
*op
{pa_context_get_sink_info_by_name(mContext
,
807 pa_stream_get_device_name(mStream
), name_callback
, this)};
808 plock
.waitForOperation(op
);
811 mDevice
->DeviceName
= dev_name
;
814 bool PulsePlayback::reset()
816 MainloopUniqueLock plock
{mMainloop
};
817 const auto deviceName
= mDeviceName
? mDeviceName
->c_str() : nullptr;
821 pa_stream_set_state_callback(mStream
, nullptr, nullptr);
822 pa_stream_set_moved_callback(mStream
, nullptr, nullptr);
823 pa_stream_set_write_callback(mStream
, nullptr, nullptr);
824 pa_stream_set_buffer_attr_callback(mStream
, nullptr, nullptr);
825 pa_stream_disconnect(mStream
);
826 pa_stream_unref(mStream
);
830 auto info_callback
= [](pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
) noexcept
831 { return static_cast<PulsePlayback
*>(pdata
)->sinkInfoCallback(context
, info
, eol
); };
832 pa_operation
*op
{pa_context_get_sink_info_by_name(mContext
, deviceName
, info_callback
, this)};
833 plock
.waitForOperation(op
);
835 pa_stream_flags_t flags
{PA_STREAM_START_CORKED
| PA_STREAM_INTERPOLATE_TIMING
|
836 PA_STREAM_AUTO_TIMING_UPDATE
| PA_STREAM_EARLY_REQUESTS
};
837 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
838 flags
|= PA_STREAM_DONT_MOVE
;
839 if(GetConfigValueBool(mDevice
->DeviceName
.c_str(), "pulse", "adjust-latency", false))
841 /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some
842 * reason. So if the user wants to adjust the overall device latency,
843 * we can't ask to get write signals as soon as minreq is reached.
845 flags
&= ~PA_STREAM_EARLY_REQUESTS
;
846 flags
|= PA_STREAM_ADJUST_LATENCY
;
848 if(GetConfigValueBool(mDevice
->DeviceName
.c_str(), "pulse", "fix-rate", false)
849 || !mDevice
->Flags
.test(FrequencyRequest
))
850 flags
|= PA_STREAM_FIX_RATE
;
852 pa_channel_map chanmap
{};
853 switch(mDevice
->FmtChans
)
856 chanmap
= MonoChanMap
;
859 mDevice
->FmtChans
= DevFmtStereo
;
862 chanmap
= StereoChanMap
;
865 chanmap
= QuadChanMap
;
868 chanmap
= (mIs51Rear
? X51RearChanMap
: X51ChanMap
);
871 chanmap
= X61ChanMap
;
875 chanmap
= X71ChanMap
;
878 chanmap
= X714ChanMap
;
881 setDefaultWFXChannelOrder();
883 switch(mDevice
->FmtType
)
886 mDevice
->FmtType
= DevFmtUByte
;
889 mSpec
.format
= PA_SAMPLE_U8
;
892 mDevice
->FmtType
= DevFmtShort
;
895 mSpec
.format
= PA_SAMPLE_S16NE
;
898 mDevice
->FmtType
= DevFmtInt
;
901 mSpec
.format
= PA_SAMPLE_S32NE
;
904 mSpec
.format
= PA_SAMPLE_FLOAT32NE
;
907 mSpec
.rate
= mDevice
->Frequency
;
908 mSpec
.channels
= static_cast<uint8_t>(mDevice
->channelsFromFmt());
909 if(pa_sample_spec_valid(&mSpec
) == 0)
910 throw al::backend_exception
{al::backend_error::DeviceError
, "Invalid sample spec"};
912 const auto frame_size
= static_cast<uint
>(pa_frame_size(&mSpec
));
913 mAttr
.maxlength
= ~0u;
914 mAttr
.tlength
= mDevice
->BufferSize
* frame_size
;
916 mAttr
.minreq
= mDevice
->UpdateSize
* frame_size
;
917 mAttr
.fragsize
= ~0u;
919 mStream
= plock
.connectStream(deviceName
, mContext
, flags
, &mAttr
, &mSpec
, &chanmap
,
920 BackendType::Playback
);
922 pa_stream_set_state_callback(mStream
, [](pa_stream
*stream
, void *pdata
) noexcept
923 { return static_cast<PulsePlayback
*>(pdata
)->streamStateCallback(stream
); }, this);
924 pa_stream_set_moved_callback(mStream
, [](pa_stream
*stream
, void *pdata
) noexcept
925 { return static_cast<PulsePlayback
*>(pdata
)->streamMovedCallback(stream
); }, this);
927 mSpec
= *(pa_stream_get_sample_spec(mStream
));
928 mFrameSize
= static_cast<uint
>(pa_frame_size(&mSpec
));
930 if(mDevice
->Frequency
!= mSpec
.rate
)
932 /* Server updated our playback rate, so modify the buffer attribs
935 const auto scale
= static_cast<double>(mSpec
.rate
) / mDevice
->Frequency
;
936 const auto perlen
= static_cast<uint
>(clampd(scale
*mDevice
->UpdateSize
+ 0.5, 64.0,
938 const auto buflen
= static_cast<uint
>(clampd(scale
*mDevice
->BufferSize
+ 0.5, perlen
*2,
939 std::numeric_limits
<int>::max()/mFrameSize
));
941 mAttr
.maxlength
= ~0u;
942 mAttr
.tlength
= buflen
* mFrameSize
;
944 mAttr
.minreq
= perlen
* mFrameSize
;
946 op
= pa_stream_set_buffer_attr(mStream
, &mAttr
, &PulseMainloop::streamSuccessCallbackC
,
948 plock
.waitForOperation(op
);
950 mDevice
->Frequency
= mSpec
.rate
;
953 auto attr_callback
= [](pa_stream
*stream
, void *pdata
) noexcept
954 { return static_cast<PulsePlayback
*>(pdata
)->bufferAttrCallback(stream
); };
955 pa_stream_set_buffer_attr_callback(mStream
, attr_callback
, this);
956 bufferAttrCallback(mStream
);
958 mDevice
->BufferSize
= mAttr
.tlength
/ mFrameSize
;
959 mDevice
->UpdateSize
= mAttr
.minreq
/ mFrameSize
;
964 void PulsePlayback::start()
966 MainloopUniqueLock plock
{mMainloop
};
968 /* Write some samples to fill the buffer before we start feeding it newly
971 if(size_t todo
{pa_stream_writable_size(mStream
)})
973 void *buf
{pa_xmalloc(todo
)};
974 mDevice
->renderSamples(buf
, static_cast<uint
>(todo
/mFrameSize
), mSpec
.channels
);
975 pa_stream_write(mStream
, buf
, todo
, pa_xfree
, 0, PA_SEEK_RELATIVE
);
978 pa_stream_set_write_callback(mStream
, [](pa_stream
*stream
, size_t nbytes
, void *pdata
)noexcept
979 { return static_cast<PulsePlayback
*>(pdata
)->streamWriteCallback(stream
, nbytes
); }, this);
980 pa_operation
*op
{pa_stream_cork(mStream
, 0, &PulseMainloop::streamSuccessCallbackC
,
983 plock
.waitForOperation(op
);
986 void PulsePlayback::stop()
988 MainloopUniqueLock plock
{mMainloop
};
990 pa_operation
*op
{pa_stream_cork(mStream
, 1, &PulseMainloop::streamSuccessCallbackC
,
992 plock
.waitForOperation(op
);
993 pa_stream_set_write_callback(mStream
, nullptr, nullptr);
997 ClockLatency
PulsePlayback::getClockLatency()
1004 MainloopUniqueLock plock
{mMainloop
};
1005 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1006 err
= pa_stream_get_latency(mStream
, &latency
, &neg
);
1009 if(err
!= 0) [[unlikely
]]
1011 /* If err = -PA_ERR_NODATA, it means we were called too soon after
1012 * starting the stream and no timing info has been received from the
1013 * server yet. Give a generic value since nothing better is available.
1015 if(err
!= -PA_ERR_NODATA
)
1016 ERR("Failed to get stream latency: 0x%x\n", err
);
1017 latency
= mDevice
->BufferSize
- mDevice
->UpdateSize
;
1020 else if(neg
) [[unlikely
]]
1022 ret
.Latency
= std::chrono::microseconds
{latency
};
1028 struct PulseCapture final
: public BackendBase
{
1029 PulseCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1030 ~PulseCapture() override
;
1032 void streamStateCallback(pa_stream
*stream
) noexcept
;
1033 void sourceNameCallback(pa_context
*context
, const pa_source_info
*info
, int eol
) noexcept
;
1034 void streamMovedCallback(pa_stream
*stream
) noexcept
;
1036 void open(const char *name
) override
;
1037 void start() override
;
1038 void stop() override
;
1039 void captureSamples(al::byte
*buffer
, uint samples
) override
;
1040 uint
availableSamples() override
;
1041 ClockLatency
getClockLatency() override
;
1043 PulseMainloop mMainloop
;
1045 al::optional
<std::string
> mDeviceName
{al::nullopt
};
1047 al::span
<const al::byte
> mCapBuffer
;
1048 size_t mHoleLength
{0};
1049 size_t mPacketLength
{0};
1051 uint mLastReadable
{0u};
1052 al::byte mSilentVal
{};
1054 pa_buffer_attr mAttr
{};
1055 pa_sample_spec mSpec
{};
1057 pa_stream
*mStream
{nullptr};
1058 pa_context
*mContext
{nullptr};
1060 DEF_NEWDEL(PulseCapture
)
1063 PulseCapture::~PulseCapture()
1068 mMainloop
.close(mContext
, mStream
);
1074 void PulseCapture::streamStateCallback(pa_stream
*stream
) noexcept
1076 if(pa_stream_get_state(stream
) == PA_STREAM_FAILED
)
1078 ERR("Received stream failure!\n");
1079 mDevice
->handleDisconnect("Capture stream failure");
1084 void PulseCapture::sourceNameCallback(pa_context
*, const pa_source_info
*info
, int eol
) noexcept
1091 mDevice
->DeviceName
= info
->description
;
1094 void PulseCapture::streamMovedCallback(pa_stream
*stream
) noexcept
1096 mDeviceName
= pa_stream_get_device_name(stream
);
1097 TRACE("Stream moved to %s\n", mDeviceName
->c_str());
1101 void PulseCapture::open(const char *name
)
1105 mMainloop
= PulseMainloop::Create();
1109 const char *pulse_name
{nullptr};
1112 if(CaptureDevices
.empty())
1113 mMainloop
.probeCaptureDevices();
1115 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1116 [name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; });
1117 if(iter
== CaptureDevices
.cend())
1118 throw al::backend_exception
{al::backend_error::NoDevice
,
1119 "Device name \"%s\" not found", name
};
1120 pulse_name
= iter
->device_name
.c_str();
1121 mDevice
->DeviceName
= iter
->name
;
1124 MainloopUniqueLock plock
{mMainloop
};
1125 mContext
= plock
.connectContext();
1127 pa_channel_map chanmap
{};
1128 switch(mDevice
->FmtChans
)
1131 chanmap
= MonoChanMap
;
1134 chanmap
= StereoChanMap
;
1137 chanmap
= QuadChanMap
;
1140 chanmap
= X51ChanMap
;
1143 chanmap
= X61ChanMap
;
1146 chanmap
= X71ChanMap
;
1149 chanmap
= X714ChanMap
;
1153 throw al::backend_exception
{al::backend_error::DeviceError
, "%s capture not supported",
1154 DevFmtChannelsString(mDevice
->FmtChans
)};
1156 setDefaultWFXChannelOrder();
1158 switch(mDevice
->FmtType
)
1161 mSilentVal
= al::byte(0x80);
1162 mSpec
.format
= PA_SAMPLE_U8
;
1165 mSpec
.format
= PA_SAMPLE_S16NE
;
1168 mSpec
.format
= PA_SAMPLE_S32NE
;
1171 mSpec
.format
= PA_SAMPLE_FLOAT32NE
;
1176 throw al::backend_exception
{al::backend_error::DeviceError
,
1177 "%s capture samples not supported", DevFmtTypeString(mDevice
->FmtType
)};
1179 mSpec
.rate
= mDevice
->Frequency
;
1180 mSpec
.channels
= static_cast<uint8_t>(mDevice
->channelsFromFmt());
1181 if(pa_sample_spec_valid(&mSpec
) == 0)
1182 throw al::backend_exception
{al::backend_error::DeviceError
, "Invalid sample format"};
1184 const auto frame_size
= static_cast<uint
>(pa_frame_size(&mSpec
));
1185 const uint samples
{maxu(mDevice
->BufferSize
, 100 * mDevice
->Frequency
/ 1000)};
1188 mAttr
.maxlength
= samples
* frame_size
;
1189 mAttr
.tlength
= ~0u;
1190 mAttr
.fragsize
= minu(samples
, 50*mDevice
->Frequency
/1000) * frame_size
;
1192 pa_stream_flags_t flags
{PA_STREAM_START_CORKED
| PA_STREAM_ADJUST_LATENCY
};
1193 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
1194 flags
|= PA_STREAM_DONT_MOVE
;
1196 TRACE("Connecting to \"%s\"\n", pulse_name
? pulse_name
: "(default)");
1197 mStream
= plock
.connectStream(pulse_name
, mContext
, flags
, &mAttr
, &mSpec
, &chanmap
,
1198 BackendType::Capture
);
1200 pa_stream_set_moved_callback(mStream
, [](pa_stream
*stream
, void *pdata
) noexcept
1201 { return static_cast<PulseCapture
*>(pdata
)->streamMovedCallback(stream
); }, this);
1202 pa_stream_set_state_callback(mStream
, [](pa_stream
*stream
, void *pdata
) noexcept
1203 { return static_cast<PulseCapture
*>(pdata
)->streamStateCallback(stream
); }, this);
1205 if(pulse_name
) mDeviceName
.emplace(pulse_name
);
1206 else mDeviceName
.reset();
1207 if(mDevice
->DeviceName
.empty())
1209 auto name_callback
= [](pa_context
*context
, const pa_source_info
*info
, int eol
, void *pdata
) noexcept
1210 { return static_cast<PulseCapture
*>(pdata
)->sourceNameCallback(context
, info
, eol
); };
1211 pa_operation
*op
{pa_context_get_source_info_by_name(mContext
,
1212 pa_stream_get_device_name(mStream
), name_callback
, this)};
1213 plock
.waitForOperation(op
);
1217 void PulseCapture::start()
1219 MainloopUniqueLock plock
{mMainloop
};
1220 pa_operation
*op
{pa_stream_cork(mStream
, 0, &PulseMainloop::streamSuccessCallbackC
,
1222 plock
.waitForOperation(op
);
1225 void PulseCapture::stop()
1227 MainloopUniqueLock plock
{mMainloop
};
1228 pa_operation
*op
{pa_stream_cork(mStream
, 1, &PulseMainloop::streamSuccessCallbackC
,
1230 plock
.waitForOperation(op
);
1233 void PulseCapture::captureSamples(al::byte
*buffer
, uint samples
)
1235 al::span
<al::byte
> dstbuf
{buffer
, samples
* pa_frame_size(&mSpec
)};
1237 /* Capture is done in fragment-sized chunks, so we loop until we get all
1240 mLastReadable
-= static_cast<uint
>(dstbuf
.size());
1241 while(!dstbuf
.empty())
1243 if(mHoleLength
> 0) [[unlikely
]]
1245 const size_t rem
{minz(dstbuf
.size(), mHoleLength
)};
1246 std::fill_n(dstbuf
.begin(), rem
, mSilentVal
);
1247 dstbuf
= dstbuf
.subspan(rem
);
1252 if(!mCapBuffer
.empty())
1254 const size_t rem
{minz(dstbuf
.size(), mCapBuffer
.size())};
1255 std::copy_n(mCapBuffer
.begin(), rem
, dstbuf
.begin());
1256 dstbuf
= dstbuf
.subspan(rem
);
1257 mCapBuffer
= mCapBuffer
.subspan(rem
);
1262 if(!mDevice
->Connected
.load(std::memory_order_acquire
)) [[unlikely
]]
1265 MainloopUniqueLock plock
{mMainloop
};
1266 if(mPacketLength
> 0)
1268 pa_stream_drop(mStream
);
1272 const pa_stream_state_t state
{pa_stream_get_state(mStream
)};
1273 if(!PA_STREAM_IS_GOOD(state
)) [[unlikely
]]
1275 mDevice
->handleDisconnect("Bad capture state: %u", state
);
1281 if(pa_stream_peek(mStream
, &capbuf
, &caplen
) < 0) [[unlikely
]]
1283 mDevice
->handleDisconnect("Failed retrieving capture samples: %s",
1284 pa_strerror(pa_context_errno(mContext
)));
1289 if(caplen
== 0) break;
1290 if(!capbuf
) [[unlikely
]]
1291 mHoleLength
= caplen
;
1293 mCapBuffer
= {static_cast<const al::byte
*>(capbuf
), caplen
};
1294 mPacketLength
= caplen
;
1297 std::fill(dstbuf
.begin(), dstbuf
.end(), mSilentVal
);
1300 uint
PulseCapture::availableSamples()
1302 size_t readable
{maxz(mCapBuffer
.size(), mHoleLength
)};
1304 if(mDevice
->Connected
.load(std::memory_order_acquire
))
1306 MainloopUniqueLock plock
{mMainloop
};
1307 size_t got
{pa_stream_readable_size(mStream
)};
1308 if(static_cast<ssize_t
>(got
) < 0) [[unlikely
]]
1310 const char *err
{pa_strerror(static_cast<int>(got
))};
1311 ERR("pa_stream_readable_size() failed: %s\n", err
);
1312 mDevice
->handleDisconnect("Failed getting readable size: %s", err
);
1316 /* "readable" is the number of bytes from the last packet that have
1317 * not yet been read by the caller. So add the stream's readable
1318 * size excluding the last packet (the stream size includes the
1319 * last packet until it's dropped).
1321 if(got
> mPacketLength
)
1322 readable
+= got
- mPacketLength
;
1326 /* Avoid uint overflow, and avoid decreasing the readable count. */
1327 readable
= std::min
<size_t>(readable
, std::numeric_limits
<uint
>::max());
1328 mLastReadable
= std::max(mLastReadable
, static_cast<uint
>(readable
));
1329 return mLastReadable
/ static_cast<uint
>(pa_frame_size(&mSpec
));
1333 ClockLatency
PulseCapture::getClockLatency()
1340 MainloopUniqueLock plock
{mMainloop
};
1341 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1342 err
= pa_stream_get_latency(mStream
, &latency
, &neg
);
1345 if(err
!= 0) [[unlikely
]]
1347 ERR("Failed to get stream latency: 0x%x\n", err
);
1351 else if(neg
) [[unlikely
]]
1353 ret
.Latency
= std::chrono::microseconds
{latency
};
1361 bool PulseBackendFactory::init()
1367 std::string missing_funcs
;
1370 #define PALIB "libpulse-0.dll"
1371 #elif defined(__APPLE__) && defined(__MACH__)
1372 #define PALIB "libpulse.0.dylib"
1374 #define PALIB "libpulse.so.0"
1376 pulse_handle
= LoadLib(PALIB
);
1379 WARN("Failed to load %s\n", PALIB
);
1383 #define LOAD_FUNC(x) do { \
1384 p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x)); \
1387 missing_funcs += "\n" #x; \
1390 PULSE_FUNCS(LOAD_FUNC
)
1395 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
1396 CloseLib(pulse_handle
);
1397 pulse_handle
= nullptr;
1401 #endif /* HAVE_DYNLOAD */
1403 pulse_ctx_flags
= PA_CONTEXT_NOFLAGS
;
1404 if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false))
1405 pulse_ctx_flags
|= PA_CONTEXT_NOAUTOSPAWN
;
1408 if(!gGlobalMainloop
)
1410 gGlobalMainloop
= PulseMainloop::Create();
1411 gGlobalMainloop
.start();
1414 MainloopUniqueLock plock
{gGlobalMainloop
};
1415 pa_context
*context
{plock
.connectContext()};
1416 pa_context_disconnect(context
);
1417 pa_context_unref(context
);
1425 bool PulseBackendFactory::querySupport(BackendType type
)
1426 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1428 std::string
PulseBackendFactory::probe(BackendType type
)
1430 std::string outnames
;
1432 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
1434 /* +1 to also append the null char (to ensure a null-separated list and
1435 * double-null terminated list).
1437 outnames
.append(entry
.name
.c_str(), entry
.name
.length()+1);
1442 case BackendType::Playback
:
1443 gGlobalMainloop
.probePlaybackDevices();
1444 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
1447 case BackendType::Capture
:
1448 gGlobalMainloop
.probeCaptureDevices();
1449 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
1456 BackendPtr
PulseBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
1458 if(type
== BackendType::Playback
)
1459 return BackendPtr
{new PulsePlayback
{device
}};
1460 if(type
== BackendType::Capture
)
1461 return BackendPtr
{new PulseCapture
{device
}};
1465 BackendFactory
&PulseBackendFactory::getFactory()
1467 static PulseBackendFactory factory
{};