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 "backends/pulseaudio.h"
40 #include <pulse/pulseaudio.h>
47 #define MAKE_FUNC(x) decltype(x) * p##x
48 MAKE_FUNC(pa_context_unref
);
49 MAKE_FUNC(pa_sample_spec_valid
);
50 MAKE_FUNC(pa_frame_size
);
51 MAKE_FUNC(pa_stream_drop
);
52 MAKE_FUNC(pa_strerror
);
53 MAKE_FUNC(pa_context_get_state
);
54 MAKE_FUNC(pa_stream_get_state
);
55 MAKE_FUNC(pa_threaded_mainloop_signal
);
56 MAKE_FUNC(pa_stream_peek
);
57 MAKE_FUNC(pa_threaded_mainloop_wait
);
58 MAKE_FUNC(pa_threaded_mainloop_unlock
);
59 MAKE_FUNC(pa_threaded_mainloop_in_thread
);
60 MAKE_FUNC(pa_context_new
);
61 MAKE_FUNC(pa_threaded_mainloop_stop
);
62 MAKE_FUNC(pa_context_disconnect
);
63 MAKE_FUNC(pa_threaded_mainloop_start
);
64 MAKE_FUNC(pa_threaded_mainloop_get_api
);
65 MAKE_FUNC(pa_context_set_state_callback
);
66 MAKE_FUNC(pa_stream_write
);
68 MAKE_FUNC(pa_stream_connect_record
);
69 MAKE_FUNC(pa_stream_connect_playback
);
70 MAKE_FUNC(pa_stream_readable_size
);
71 MAKE_FUNC(pa_stream_writable_size
);
72 MAKE_FUNC(pa_stream_is_corked
);
73 MAKE_FUNC(pa_stream_cork
);
74 MAKE_FUNC(pa_stream_is_suspended
);
75 MAKE_FUNC(pa_stream_get_device_name
);
76 MAKE_FUNC(pa_stream_get_latency
);
77 MAKE_FUNC(pa_path_get_filename
);
78 MAKE_FUNC(pa_get_binary_name
);
79 MAKE_FUNC(pa_threaded_mainloop_free
);
80 MAKE_FUNC(pa_context_errno
);
81 MAKE_FUNC(pa_xmalloc
);
82 MAKE_FUNC(pa_stream_unref
);
83 MAKE_FUNC(pa_threaded_mainloop_accept
);
84 MAKE_FUNC(pa_stream_set_write_callback
);
85 MAKE_FUNC(pa_threaded_mainloop_new
);
86 MAKE_FUNC(pa_context_connect
);
87 MAKE_FUNC(pa_stream_set_buffer_attr
);
88 MAKE_FUNC(pa_stream_get_buffer_attr
);
89 MAKE_FUNC(pa_stream_get_sample_spec
);
90 MAKE_FUNC(pa_stream_get_time
);
91 MAKE_FUNC(pa_stream_set_read_callback
);
92 MAKE_FUNC(pa_stream_set_state_callback
);
93 MAKE_FUNC(pa_stream_set_moved_callback
);
94 MAKE_FUNC(pa_stream_set_underflow_callback
);
95 MAKE_FUNC(pa_stream_new_with_proplist
);
96 MAKE_FUNC(pa_stream_disconnect
);
97 MAKE_FUNC(pa_threaded_mainloop_lock
);
98 MAKE_FUNC(pa_channel_map_init_auto
);
99 MAKE_FUNC(pa_channel_map_parse
);
100 MAKE_FUNC(pa_channel_map_snprint
);
101 MAKE_FUNC(pa_channel_map_equal
);
102 MAKE_FUNC(pa_context_get_server_info
);
103 MAKE_FUNC(pa_context_get_sink_info_by_name
);
104 MAKE_FUNC(pa_context_get_sink_info_list
);
105 MAKE_FUNC(pa_context_get_source_info_by_name
);
106 MAKE_FUNC(pa_context_get_source_info_list
);
107 MAKE_FUNC(pa_operation_get_state
);
108 MAKE_FUNC(pa_operation_unref
);
109 MAKE_FUNC(pa_proplist_new
);
110 MAKE_FUNC(pa_proplist_free
);
111 MAKE_FUNC(pa_proplist_set
);
112 MAKE_FUNC(pa_channel_map_superset
);
113 MAKE_FUNC(pa_stream_set_buffer_attr_callback
);
114 MAKE_FUNC(pa_stream_begin_write
);
117 #ifndef IN_IDE_PARSER
118 #define pa_context_unref ppa_context_unref
119 #define pa_sample_spec_valid ppa_sample_spec_valid
120 #define pa_frame_size ppa_frame_size
121 #define pa_stream_drop ppa_stream_drop
122 #define pa_strerror ppa_strerror
123 #define pa_context_get_state ppa_context_get_state
124 #define pa_stream_get_state ppa_stream_get_state
125 #define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal
126 #define pa_stream_peek ppa_stream_peek
127 #define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait
128 #define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock
129 #define pa_threaded_mainloop_in_thread ppa_threaded_mainloop_in_thread
130 #define pa_context_new ppa_context_new
131 #define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop
132 #define pa_context_disconnect ppa_context_disconnect
133 #define pa_threaded_mainloop_start ppa_threaded_mainloop_start
134 #define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api
135 #define pa_context_set_state_callback ppa_context_set_state_callback
136 #define pa_stream_write ppa_stream_write
137 #define pa_xfree ppa_xfree
138 #define pa_stream_connect_record ppa_stream_connect_record
139 #define pa_stream_connect_playback ppa_stream_connect_playback
140 #define pa_stream_readable_size ppa_stream_readable_size
141 #define pa_stream_writable_size ppa_stream_writable_size
142 #define pa_stream_is_corked ppa_stream_is_corked
143 #define pa_stream_cork ppa_stream_cork
144 #define pa_stream_is_suspended ppa_stream_is_suspended
145 #define pa_stream_get_device_name ppa_stream_get_device_name
146 #define pa_stream_get_latency ppa_stream_get_latency
147 #define pa_path_get_filename ppa_path_get_filename
148 #define pa_get_binary_name ppa_get_binary_name
149 #define pa_threaded_mainloop_free ppa_threaded_mainloop_free
150 #define pa_context_errno ppa_context_errno
151 #define pa_xmalloc ppa_xmalloc
152 #define pa_stream_unref ppa_stream_unref
153 #define pa_threaded_mainloop_accept ppa_threaded_mainloop_accept
154 #define pa_stream_set_write_callback ppa_stream_set_write_callback
155 #define pa_threaded_mainloop_new ppa_threaded_mainloop_new
156 #define pa_context_connect ppa_context_connect
157 #define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr
158 #define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr
159 #define pa_stream_get_sample_spec ppa_stream_get_sample_spec
160 #define pa_stream_get_time ppa_stream_get_time
161 #define pa_stream_set_read_callback ppa_stream_set_read_callback
162 #define pa_stream_set_state_callback ppa_stream_set_state_callback
163 #define pa_stream_set_moved_callback ppa_stream_set_moved_callback
164 #define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback
165 #define pa_stream_new_with_proplist ppa_stream_new_with_proplist
166 #define pa_stream_disconnect ppa_stream_disconnect
167 #define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock
168 #define pa_channel_map_init_auto ppa_channel_map_init_auto
169 #define pa_channel_map_parse ppa_channel_map_parse
170 #define pa_channel_map_snprint ppa_channel_map_snprint
171 #define pa_channel_map_equal ppa_channel_map_equal
172 #define pa_context_get_server_info ppa_context_get_server_info
173 #define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
174 #define pa_context_get_sink_info_list ppa_context_get_sink_info_list
175 #define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
176 #define pa_context_get_source_info_list ppa_context_get_source_info_list
177 #define pa_operation_get_state ppa_operation_get_state
178 #define pa_operation_unref ppa_operation_unref
179 #define pa_proplist_new ppa_proplist_new
180 #define pa_proplist_free ppa_proplist_free
181 #define pa_proplist_set ppa_proplist_set
182 #define pa_channel_map_superset ppa_channel_map_superset
183 #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback
184 #define pa_stream_begin_write ppa_stream_begin_write
185 #endif /* IN_IDE_PARSER */
189 ALCboolean
pulse_load()
191 ALCboolean ret
{ALC_TRUE
};
195 std::string missing_funcs
;
198 #define PALIB "libpulse-0.dll"
199 #elif defined(__APPLE__) && defined(__MACH__)
200 #define PALIB "libpulse.0.dylib"
202 #define PALIB "libpulse.so.0"
204 pa_handle
= LoadLib(PALIB
);
207 WARN("Failed to load %s\n", PALIB
);
211 #define LOAD_FUNC(x) do { \
212 p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pa_handle, #x)); \
215 missing_funcs += "\n" #x; \
218 LOAD_FUNC(pa_context_unref
);
219 LOAD_FUNC(pa_sample_spec_valid
);
220 LOAD_FUNC(pa_stream_drop
);
221 LOAD_FUNC(pa_frame_size
);
222 LOAD_FUNC(pa_strerror
);
223 LOAD_FUNC(pa_context_get_state
);
224 LOAD_FUNC(pa_stream_get_state
);
225 LOAD_FUNC(pa_threaded_mainloop_signal
);
226 LOAD_FUNC(pa_stream_peek
);
227 LOAD_FUNC(pa_threaded_mainloop_wait
);
228 LOAD_FUNC(pa_threaded_mainloop_unlock
);
229 LOAD_FUNC(pa_threaded_mainloop_in_thread
);
230 LOAD_FUNC(pa_context_new
);
231 LOAD_FUNC(pa_threaded_mainloop_stop
);
232 LOAD_FUNC(pa_context_disconnect
);
233 LOAD_FUNC(pa_threaded_mainloop_start
);
234 LOAD_FUNC(pa_threaded_mainloop_get_api
);
235 LOAD_FUNC(pa_context_set_state_callback
);
236 LOAD_FUNC(pa_stream_write
);
238 LOAD_FUNC(pa_stream_connect_record
);
239 LOAD_FUNC(pa_stream_connect_playback
);
240 LOAD_FUNC(pa_stream_readable_size
);
241 LOAD_FUNC(pa_stream_writable_size
);
242 LOAD_FUNC(pa_stream_is_corked
);
243 LOAD_FUNC(pa_stream_cork
);
244 LOAD_FUNC(pa_stream_is_suspended
);
245 LOAD_FUNC(pa_stream_get_device_name
);
246 LOAD_FUNC(pa_stream_get_latency
);
247 LOAD_FUNC(pa_path_get_filename
);
248 LOAD_FUNC(pa_get_binary_name
);
249 LOAD_FUNC(pa_threaded_mainloop_free
);
250 LOAD_FUNC(pa_context_errno
);
251 LOAD_FUNC(pa_xmalloc
);
252 LOAD_FUNC(pa_stream_unref
);
253 LOAD_FUNC(pa_threaded_mainloop_accept
);
254 LOAD_FUNC(pa_stream_set_write_callback
);
255 LOAD_FUNC(pa_threaded_mainloop_new
);
256 LOAD_FUNC(pa_context_connect
);
257 LOAD_FUNC(pa_stream_set_buffer_attr
);
258 LOAD_FUNC(pa_stream_get_buffer_attr
);
259 LOAD_FUNC(pa_stream_get_sample_spec
);
260 LOAD_FUNC(pa_stream_get_time
);
261 LOAD_FUNC(pa_stream_set_read_callback
);
262 LOAD_FUNC(pa_stream_set_state_callback
);
263 LOAD_FUNC(pa_stream_set_moved_callback
);
264 LOAD_FUNC(pa_stream_set_underflow_callback
);
265 LOAD_FUNC(pa_stream_new_with_proplist
);
266 LOAD_FUNC(pa_stream_disconnect
);
267 LOAD_FUNC(pa_threaded_mainloop_lock
);
268 LOAD_FUNC(pa_channel_map_init_auto
);
269 LOAD_FUNC(pa_channel_map_parse
);
270 LOAD_FUNC(pa_channel_map_snprint
);
271 LOAD_FUNC(pa_channel_map_equal
);
272 LOAD_FUNC(pa_context_get_server_info
);
273 LOAD_FUNC(pa_context_get_sink_info_by_name
);
274 LOAD_FUNC(pa_context_get_sink_info_list
);
275 LOAD_FUNC(pa_context_get_source_info_by_name
);
276 LOAD_FUNC(pa_context_get_source_info_list
);
277 LOAD_FUNC(pa_operation_get_state
);
278 LOAD_FUNC(pa_operation_unref
);
279 LOAD_FUNC(pa_proplist_new
);
280 LOAD_FUNC(pa_proplist_free
);
281 LOAD_FUNC(pa_proplist_set
);
282 LOAD_FUNC(pa_channel_map_superset
);
283 LOAD_FUNC(pa_stream_set_buffer_attr_callback
);
284 LOAD_FUNC(pa_stream_begin_write
);
289 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
294 #endif /* HAVE_DYNLOAD */
299 /* *grumble* Don't use enums for bitflags. */
300 inline pa_stream_flags_t
operator|(pa_stream_flags_t lhs
, pa_stream_flags_t rhs
)
301 { return pa_stream_flags_t(int(lhs
) | int(rhs
)); }
303 inline pa_stream_flags_t
operator|=(pa_stream_flags_t
&lhs
, pa_stream_flags_t rhs
)
305 lhs
= pa_stream_flags_t(int(lhs
) | int(rhs
));
309 inline pa_context_flags_t
operator|=(pa_context_flags_t
&lhs
, pa_context_flags_t rhs
)
311 lhs
= pa_context_flags_t(int(lhs
) | int(rhs
));
317 pa_threaded_mainloop
*mLoop
;
320 explicit palock_guard(pa_threaded_mainloop
*loop
) : mLoop(loop
)
321 { pa_threaded_mainloop_lock(mLoop
); }
322 ~palock_guard() { pa_threaded_mainloop_unlock(mLoop
); }
324 palock_guard(const palock_guard
&) = delete;
325 palock_guard
& operator=(const palock_guard
&) = delete;
328 class unique_palock
{
329 pa_threaded_mainloop
*mLoop
{nullptr};
333 unique_palock() noexcept
= default;
334 explicit unique_palock(pa_threaded_mainloop
*loop
) : mLoop(loop
)
336 pa_threaded_mainloop_lock(mLoop
);
339 unique_palock(unique_palock
&& rhs
) : mLoop(rhs
.mLoop
), mLocked(rhs
.mLocked
)
340 { rhs
.mLoop
= nullptr; rhs
.mLocked
= false; }
341 ~unique_palock() { if(mLocked
) pa_threaded_mainloop_unlock(mLoop
); }
343 unique_palock
& operator=(const unique_palock
&) = delete;
344 unique_palock
& operator=(unique_palock
&& rhs
)
347 pa_threaded_mainloop_unlock(mLoop
);
348 mLoop
= rhs
.mLoop
; rhs
.mLoop
= nullptr;
349 mLocked
= rhs
.mLocked
; rhs
.mLocked
= false;
355 pa_threaded_mainloop_lock(mLoop
);
361 pa_threaded_mainloop_unlock(mLoop
);
366 /* Global flags and properties */
367 pa_context_flags_t pulse_ctx_flags
;
368 pa_proplist
*prop_filter
;
371 /* PulseAudio Event Callbacks */
372 void context_state_callback(pa_context
*context
, void *pdata
)
374 auto loop
= static_cast<pa_threaded_mainloop
*>(pdata
);
375 pa_context_state_t state
{pa_context_get_state(context
)};
376 if(state
== PA_CONTEXT_READY
|| !PA_CONTEXT_IS_GOOD(state
))
377 pa_threaded_mainloop_signal(loop
, 0);
380 void stream_state_callback(pa_stream
*stream
, void *pdata
)
382 auto loop
= static_cast<pa_threaded_mainloop
*>(pdata
);
383 pa_stream_state_t state
{pa_stream_get_state(stream
)};
384 if(state
== PA_STREAM_READY
|| !PA_STREAM_IS_GOOD(state
))
385 pa_threaded_mainloop_signal(loop
, 0);
388 void stream_success_callback(pa_stream
*UNUSED(stream
), int UNUSED(success
), void *pdata
)
390 auto loop
= static_cast<pa_threaded_mainloop
*>(pdata
);
391 pa_threaded_mainloop_signal(loop
, 0);
394 void wait_for_operation(pa_operation
*op
, pa_threaded_mainloop
*loop
)
398 while(pa_operation_get_state(op
) == PA_OPERATION_RUNNING
)
399 pa_threaded_mainloop_wait(loop
);
400 pa_operation_unref(op
);
405 pa_context
*connect_context(pa_threaded_mainloop
*loop
, ALboolean silent
)
407 const char *name
{"OpenAL Soft"};
409 const PathNamePair
&binname
= GetProcBinary();
410 if(!binname
.fname
.empty())
411 name
= binname
.fname
.c_str();
413 pa_context
*context
{pa_context_new(pa_threaded_mainloop_get_api(loop
), name
)};
416 ERR("pa_context_new() failed\n");
420 pa_context_set_state_callback(context
, context_state_callback
, loop
);
423 if((err
=pa_context_connect(context
, nullptr, pulse_ctx_flags
, nullptr)) >= 0)
425 pa_context_state_t state
;
426 while((state
=pa_context_get_state(context
)) != PA_CONTEXT_READY
)
428 if(!PA_CONTEXT_IS_GOOD(state
))
430 err
= pa_context_errno(context
);
431 if(err
> 0) err
= -err
;
435 pa_threaded_mainloop_wait(loop
);
438 pa_context_set_state_callback(context
, nullptr, nullptr);
443 ERR("Context did not connect: %s\n", pa_strerror(err
));
444 pa_context_unref(context
);
452 using MainloopContextPair
= std::pair
<pa_threaded_mainloop
*,pa_context
*>;
453 MainloopContextPair
pulse_open(void(*state_cb
)(pa_context
*,void*), void *ptr
)
455 pa_threaded_mainloop
*loop
{pa_threaded_mainloop_new()};
458 ERR("pa_threaded_mainloop_new() failed!\n");
459 return {nullptr, nullptr};
461 if(UNLIKELY(pa_threaded_mainloop_start(loop
) < 0))
463 ERR("pa_threaded_mainloop_start() failed\n");
464 pa_threaded_mainloop_free(loop
);
465 return {nullptr, nullptr};
468 unique_palock palock
{loop
};
469 pa_context
*context
{connect_context(loop
, AL_FALSE
)};
470 if(UNLIKELY(!context
))
472 palock
= unique_palock
{};
473 pa_threaded_mainloop_stop(loop
);
474 pa_threaded_mainloop_free(loop
);
475 return {nullptr, nullptr};
477 pa_context_set_state_callback(context
, state_cb
, ptr
);
478 return {loop
, context
};
481 void pulse_close(pa_threaded_mainloop
*loop
, pa_context
*context
, pa_stream
*stream
)
483 { palock_guard _
{loop
};
486 pa_stream_set_state_callback(stream
, nullptr, nullptr);
487 pa_stream_set_moved_callback(stream
, nullptr, nullptr);
488 pa_stream_set_write_callback(stream
, nullptr, nullptr);
489 pa_stream_set_buffer_attr_callback(stream
, nullptr, nullptr);
490 pa_stream_disconnect(stream
);
491 pa_stream_unref(stream
);
494 pa_context_disconnect(context
);
495 pa_context_unref(context
);
498 pa_threaded_mainloop_stop(loop
);
499 pa_threaded_mainloop_free(loop
);
505 std::string device_name
;
507 template<typename StrT0
, typename StrT1
>
508 DevMap(StrT0
&& name_
, StrT1
&& devname_
)
509 : name
{std::forward
<StrT0
>(name_
)}, device_name
{std::forward
<StrT1
>(devname_
)}
513 bool checkName(const al::vector
<DevMap
> &list
, const std::string
&name
)
515 return std::find_if(list
.cbegin(), list
.cend(),
516 [&name
](const DevMap
&entry
) -> bool
517 { return entry
.name
== name
; }
521 al::vector
<DevMap
> PlaybackDevices
;
522 al::vector
<DevMap
> CaptureDevices
;
525 pa_stream
*pulse_connect_stream(const char *device_name
, pa_threaded_mainloop
*loop
,
526 pa_context
*context
, pa_stream_flags_t flags
, pa_buffer_attr
*attr
, pa_sample_spec
*spec
,
527 pa_channel_map
*chanmap
, BackendType type
)
529 pa_stream
*stream
{pa_stream_new_with_proplist(context
,
530 (type
==BackendType::Playback
) ? "Playback Stream" : "Capture Stream", spec
, chanmap
,
534 ERR("pa_stream_new_with_proplist() failed: %s\n", pa_strerror(pa_context_errno(context
)));
538 pa_stream_set_state_callback(stream
, stream_state_callback
, loop
);
540 int err
{(type
==BackendType::Playback
) ?
541 pa_stream_connect_playback(stream
, device_name
, attr
, flags
, nullptr, nullptr) :
542 pa_stream_connect_record(stream
, device_name
, attr
, flags
)};
545 ERR("Stream did not connect: %s\n", pa_strerror(err
));
546 pa_stream_unref(stream
);
550 pa_stream_state_t state
;
551 while((state
=pa_stream_get_state(stream
)) != PA_STREAM_READY
)
553 if(!PA_STREAM_IS_GOOD(state
))
555 ERR("Stream did not get ready: %s\n", pa_strerror(pa_context_errno(context
)));
556 pa_stream_unref(stream
);
560 pa_threaded_mainloop_wait(loop
);
562 pa_stream_set_state_callback(stream
, nullptr, nullptr);
568 void device_sink_callback(pa_context
*UNUSED(context
), const pa_sink_info
*info
, int eol
, void *pdata
)
570 auto loop
= static_cast<pa_threaded_mainloop
*>(pdata
);
574 pa_threaded_mainloop_signal(loop
, 0);
578 /* Skip this device is if it's already in the list. */
579 if(std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
580 [info
](const DevMap
&entry
) -> bool
581 { return entry
.device_name
== info
->name
; }
582 ) != PlaybackDevices
.cend())
585 /* Make sure the display name (description) is unique. Append a number
589 std::string newname
{info
->description
};
590 while(checkName(PlaybackDevices
, newname
))
592 newname
= info
->description
;
594 newname
+= std::to_string(++count
);
596 PlaybackDevices
.emplace_back(std::move(newname
), info
->name
);
597 DevMap
&newentry
= PlaybackDevices
.back();
599 TRACE("Got device \"%s\", \"%s\"\n", newentry
.name
.c_str(), newentry
.device_name
.c_str());
602 void probePlaybackDevices()
604 PlaybackDevices
.clear();
606 pa_threaded_mainloop
*loop
{pa_threaded_mainloop_new()};
607 if(loop
&& pa_threaded_mainloop_start(loop
) >= 0)
609 unique_palock palock
{loop
};
611 pa_context
*context
{connect_context(loop
, AL_FALSE
)};
614 pa_stream_flags_t flags
{PA_STREAM_FIX_FORMAT
| PA_STREAM_FIX_RATE
|
615 PA_STREAM_FIX_CHANNELS
| PA_STREAM_DONT_MOVE
};
618 spec
.format
= PA_SAMPLE_S16NE
;
622 pa_stream
*stream
{pulse_connect_stream(nullptr, loop
, context
, flags
, nullptr, &spec
,
623 nullptr, BackendType::Playback
)};
626 pa_operation
*op
{pa_context_get_sink_info_by_name(context
,
627 pa_stream_get_device_name(stream
), device_sink_callback
, loop
)};
628 wait_for_operation(op
, loop
);
630 pa_stream_disconnect(stream
);
631 pa_stream_unref(stream
);
635 pa_operation
*op
{pa_context_get_sink_info_list(context
,
636 device_sink_callback
, loop
)};
637 wait_for_operation(op
, loop
);
639 pa_context_disconnect(context
);
640 pa_context_unref(context
);
642 palock
= unique_palock
{};
643 pa_threaded_mainloop_stop(loop
);
646 pa_threaded_mainloop_free(loop
);
650 void device_source_callback(pa_context
*UNUSED(context
), const pa_source_info
*info
, int eol
, void *pdata
)
652 auto loop
= static_cast<pa_threaded_mainloop
*>(pdata
);
656 pa_threaded_mainloop_signal(loop
, 0);
660 /* Skip this device is if it's already in the list. */
661 if(std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
662 [info
](const DevMap
&entry
) -> bool
663 { return entry
.device_name
== info
->name
; }
664 ) != CaptureDevices
.cend())
667 /* Make sure the display name (description) is unique. Append a number
671 std::string newname
{info
->description
};
672 while(checkName(CaptureDevices
, newname
))
674 newname
= info
->description
;
676 newname
+= std::to_string(++count
);
678 CaptureDevices
.emplace_back(std::move(newname
), info
->name
);
679 DevMap
&newentry
= CaptureDevices
.back();
681 TRACE("Got device \"%s\", \"%s\"\n", newentry
.name
.c_str(), newentry
.device_name
.c_str());
684 void probeCaptureDevices()
686 CaptureDevices
.clear();
688 pa_threaded_mainloop
*loop
{pa_threaded_mainloop_new()};
689 if(loop
&& pa_threaded_mainloop_start(loop
) >= 0)
691 unique_palock palock
{loop
};
693 pa_context
*context
{connect_context(loop
, AL_FALSE
)};
696 pa_stream_flags_t flags
{PA_STREAM_FIX_FORMAT
| PA_STREAM_FIX_RATE
|
697 PA_STREAM_FIX_CHANNELS
| PA_STREAM_DONT_MOVE
};
700 spec
.format
= PA_SAMPLE_S16NE
;
704 pa_stream
*stream
{pulse_connect_stream(nullptr, loop
, context
, flags
, nullptr, &spec
,
705 nullptr, BackendType::Capture
)};
708 pa_operation
*op
{pa_context_get_source_info_by_name(context
,
709 pa_stream_get_device_name(stream
), device_source_callback
, loop
)};
710 wait_for_operation(op
, loop
);
712 pa_stream_disconnect(stream
);
713 pa_stream_unref(stream
);
717 pa_operation
*op
{pa_context_get_source_info_list(context
,
718 device_source_callback
, loop
)};
719 wait_for_operation(op
, loop
);
721 pa_context_disconnect(context
);
722 pa_context_unref(context
);
725 pa_threaded_mainloop_stop(loop
);
728 pa_threaded_mainloop_free(loop
);
732 struct PulsePlayback final
: public BackendBase
{
733 PulsePlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
734 ~PulsePlayback() override
;
736 static void bufferAttrCallbackC(pa_stream
*stream
, void *pdata
);
737 void bufferAttrCallback(pa_stream
*stream
);
739 static void contextStateCallbackC(pa_context
*context
, void *pdata
);
740 void contextStateCallback(pa_context
*context
);
742 static void streamStateCallbackC(pa_stream
*stream
, void *pdata
);
743 void streamStateCallback(pa_stream
*stream
);
745 static void streamWriteCallbackC(pa_stream
*stream
, size_t nbytes
, void *pdata
);
746 void streamWriteCallback(pa_stream
*stream
, size_t nbytes
);
748 static void sinkInfoCallbackC(pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
);
749 void sinkInfoCallback(pa_context
*context
, const pa_sink_info
*info
, int eol
);
751 static void sinkNameCallbackC(pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
);
752 void sinkNameCallback(pa_context
*context
, const pa_sink_info
*info
, int eol
);
754 static void streamMovedCallbackC(pa_stream
*stream
, void *pdata
);
755 void streamMovedCallback(pa_stream
*stream
);
757 ALCenum
open(const ALCchar
*name
) override
;
758 ALCboolean
reset() override
;
759 ALCboolean
start() override
;
760 void stop() override
;
761 ClockLatency
getClockLatency() override
;
762 void lock() override
;
763 void unlock() override
;
765 std::string mDeviceName
;
767 pa_buffer_attr mAttr
;
768 pa_sample_spec mSpec
;
770 pa_threaded_mainloop
*mLoop
{nullptr};
772 pa_stream
*mStream
{nullptr};
773 pa_context
*mContext
{nullptr};
775 ALuint mBufferSize
{0u};
776 ALuint mFrameSize
{0u};
778 static constexpr inline const char *CurrentPrefix() noexcept
{ return "PulsePlayback::"; }
779 DEF_NEWDEL(PulsePlayback
)
782 PulsePlayback::~PulsePlayback()
787 pulse_close(mLoop
, mContext
, mStream
);
794 void PulsePlayback::bufferAttrCallbackC(pa_stream
*stream
, void *pdata
)
795 { static_cast<PulsePlayback
*>(pdata
)->bufferAttrCallback(stream
); }
797 void PulsePlayback::bufferAttrCallback(pa_stream
*stream
)
799 /* FIXME: Update the device's UpdateSize (and/or NumUpdates) using the new
800 * buffer attributes? Changing UpdateSize will change the ALC_REFRESH
801 * property, which probably shouldn't change between device resets. But
802 * leaving it alone means ALC_REFRESH will be off.
804 mAttr
= *(pa_stream_get_buffer_attr(stream
));
805 TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr
.minreq
, mAttr
.tlength
, mAttr
.prebuf
);
807 const ALuint num_periods
{(mAttr
.tlength
+ mAttr
.minreq
/2u) / mAttr
.minreq
};
808 mBufferSize
= maxu(num_periods
, 2u) * mAttr
.minreq
;
811 void PulsePlayback::contextStateCallbackC(pa_context
*context
, void *pdata
)
812 { static_cast<PulsePlayback
*>(pdata
)->contextStateCallback(context
); }
814 void PulsePlayback::contextStateCallback(pa_context
*context
)
816 if(pa_context_get_state(context
) == PA_CONTEXT_FAILED
)
818 ERR("Received context failure!\n");
819 aluHandleDisconnect(mDevice
, "Playback state failure");
821 pa_threaded_mainloop_signal(mLoop
, 0);
824 void PulsePlayback::streamStateCallbackC(pa_stream
*stream
, void *pdata
)
825 { static_cast<PulsePlayback
*>(pdata
)->streamStateCallback(stream
); }
827 void PulsePlayback::streamStateCallback(pa_stream
*stream
)
829 if(pa_stream_get_state(stream
) == PA_STREAM_FAILED
)
831 ERR("Received stream failure!\n");
832 aluHandleDisconnect(mDevice
, "Playback stream failure");
834 pa_threaded_mainloop_signal(mLoop
, 0);
837 void PulsePlayback::streamWriteCallbackC(pa_stream
*stream
, size_t nbytes
, void *pdata
)
838 { static_cast<PulsePlayback
*>(pdata
)->streamWriteCallback(stream
, nbytes
); }
840 void PulsePlayback::streamWriteCallback(pa_stream
*stream
, size_t nbytes
)
842 /* Round down to the nearest period/minreq multiple if doing more than 1. */
843 if(nbytes
> mAttr
.minreq
)
844 nbytes
-= nbytes
%mAttr
.minreq
;
846 void *buf
{pa_xmalloc(nbytes
)};
847 aluMixData(mDevice
, buf
, nbytes
/mFrameSize
);
849 int ret
{pa_stream_write(stream
, buf
, nbytes
, pa_xfree
, 0, PA_SEEK_RELATIVE
)};
850 if(UNLIKELY(ret
!= PA_OK
))
851 ERR("Failed to write to stream: %d, %s\n", ret
, pa_strerror(ret
));
854 void PulsePlayback::sinkInfoCallbackC(pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
)
855 { static_cast<PulsePlayback
*>(pdata
)->sinkInfoCallback(context
, info
, eol
); }
857 void PulsePlayback::sinkInfoCallback(pa_context
* UNUSED(context
), const pa_sink_info
*info
, int eol
)
860 DevFmtChannels chans
;
863 static constexpr std::array
<ChannelMap
,7> chanmaps
{{
865 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
866 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
867 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
,
868 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
871 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
872 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
873 PA_CHANNEL_POSITION_REAR_CENTER
,
874 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
877 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
878 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
879 PA_CHANNEL_POSITION_SIDE_LEFT
, PA_CHANNEL_POSITION_SIDE_RIGHT
881 { DevFmtX51Rear
, { 6, {
882 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
883 PA_CHANNEL_POSITION_FRONT_CENTER
, PA_CHANNEL_POSITION_LFE
,
884 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
887 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
,
888 PA_CHANNEL_POSITION_REAR_LEFT
, PA_CHANNEL_POSITION_REAR_RIGHT
890 { DevFmtStereo
, { 2, {
891 PA_CHANNEL_POSITION_FRONT_LEFT
, PA_CHANNEL_POSITION_FRONT_RIGHT
893 { DevFmtMono
, { 1, {PA_CHANNEL_POSITION_MONO
} } }
898 pa_threaded_mainloop_signal(mLoop
, 0);
902 auto chanmap
= std::find_if(chanmaps
.cbegin(), chanmaps
.cend(),
903 [info
](const ChannelMap
&chanmap
) -> bool
904 { return pa_channel_map_superset(&info
->channel_map
, &chanmap
.map
); }
906 if(chanmap
!= chanmaps
.cend())
908 if(!(mDevice
->Flags
&DEVICE_CHANNELS_REQUEST
))
909 mDevice
->FmtChans
= chanmap
->chans
;
913 char chanmap_str
[PA_CHANNEL_MAP_SNPRINT_MAX
]{};
914 pa_channel_map_snprint(chanmap_str
, sizeof(chanmap_str
), &info
->channel_map
);
915 WARN("Failed to find format for channel map:\n %s\n", chanmap_str
);
918 if(info
->active_port
)
919 TRACE("Active port: %s (%s)\n", info
->active_port
->name
, info
->active_port
->description
);
920 mDevice
->IsHeadphones
= (mDevice
->FmtChans
== DevFmtStereo
&&
921 info
->active_port
&& strcmp(info
->active_port
->name
, "analog-output-headphones") == 0);
924 void PulsePlayback::sinkNameCallbackC(pa_context
*context
, const pa_sink_info
*info
, int eol
, void *pdata
)
925 { static_cast<PulsePlayback
*>(pdata
)->sinkNameCallback(context
, info
, eol
); }
927 void PulsePlayback::sinkNameCallback(pa_context
* UNUSED(context
), const pa_sink_info
*info
, int eol
)
931 pa_threaded_mainloop_signal(mLoop
, 0);
934 mDevice
->DeviceName
= info
->description
;
937 void PulsePlayback::streamMovedCallbackC(pa_stream
*stream
, void *pdata
)
938 { static_cast<PulsePlayback
*>(pdata
)->streamMovedCallback(stream
); }
940 void PulsePlayback::streamMovedCallback(pa_stream
*stream
)
942 mDeviceName
= pa_stream_get_device_name(stream
);
943 TRACE("Stream moved to %s\n", mDeviceName
.c_str());
947 ALCenum
PulsePlayback::open(const ALCchar
*name
)
949 const char *pulse_name
{nullptr};
950 const char *dev_name
{nullptr};
954 if(PlaybackDevices
.empty())
955 probePlaybackDevices();
957 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
958 [name
](const DevMap
&entry
) -> bool
959 { return entry
.name
== name
; }
961 if(iter
== PlaybackDevices
.cend())
962 return ALC_INVALID_VALUE
;
963 pulse_name
= iter
->device_name
.c_str();
964 dev_name
= iter
->name
.c_str();
967 std::tie(mLoop
, mContext
) = pulse_open(&PulsePlayback::contextStateCallbackC
, this);
968 if(!mLoop
) return ALC_INVALID_VALUE
;
970 unique_palock palock
{mLoop
};
972 pa_stream_flags_t flags
{PA_STREAM_FIX_FORMAT
| PA_STREAM_FIX_RATE
|
973 PA_STREAM_FIX_CHANNELS
};
974 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 0))
975 flags
|= PA_STREAM_DONT_MOVE
;
977 pa_sample_spec spec
{};
978 spec
.format
= PA_SAMPLE_S16NE
;
982 TRACE("Connecting to \"%s\"\n", pulse_name
? pulse_name
: "(default)");
985 pulse_name
= getenv("ALSOFT_PULSE_DEFAULT");
986 if(pulse_name
&& !pulse_name
[0]) pulse_name
= nullptr;
988 mStream
= pulse_connect_stream(pulse_name
, mLoop
, mContext
, flags
, nullptr, &spec
, nullptr,
989 BackendType::Playback
);
992 palock
= unique_palock
{};
993 pulse_close(mLoop
, mContext
, mStream
);
996 return ALC_INVALID_VALUE
;
998 pa_stream_set_moved_callback(mStream
, &PulsePlayback::streamMovedCallbackC
, this);
999 mFrameSize
= pa_frame_size(pa_stream_get_sample_spec(mStream
));
1001 mDeviceName
= pa_stream_get_device_name(mStream
);
1004 pa_operation
*op
{pa_context_get_sink_info_by_name(mContext
, mDeviceName
.c_str(),
1005 &PulsePlayback::sinkNameCallbackC
, this)};
1006 wait_for_operation(op
, mLoop
);
1009 mDevice
->DeviceName
= dev_name
;
1011 return ALC_NO_ERROR
;
1014 ALCboolean
PulsePlayback::reset()
1016 unique_palock palock
{mLoop
};
1020 pa_stream_set_state_callback(mStream
, nullptr, nullptr);
1021 pa_stream_set_moved_callback(mStream
, nullptr, nullptr);
1022 pa_stream_set_write_callback(mStream
, nullptr, nullptr);
1023 pa_stream_set_buffer_attr_callback(mStream
, nullptr, nullptr);
1024 pa_stream_disconnect(mStream
);
1025 pa_stream_unref(mStream
);
1029 pa_operation
*op
{pa_context_get_sink_info_by_name(mContext
, mDeviceName
.c_str(),
1030 &PulsePlayback::sinkInfoCallbackC
, this)};
1031 wait_for_operation(op
, mLoop
);
1033 pa_stream_flags_t flags
{PA_STREAM_START_CORKED
| PA_STREAM_INTERPOLATE_TIMING
|
1034 PA_STREAM_AUTO_TIMING_UPDATE
};
1035 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 0))
1036 flags
|= PA_STREAM_DONT_MOVE
;
1037 if(GetConfigValueBool(mDevice
->DeviceName
.c_str(), "pulse", "adjust-latency", 0))
1038 flags
|= PA_STREAM_ADJUST_LATENCY
;
1039 if(GetConfigValueBool(mDevice
->DeviceName
.c_str(), "pulse", "fix-rate", 0) ||
1040 !(mDevice
->Flags
&DEVICE_FREQUENCY_REQUEST
))
1041 flags
|= PA_STREAM_FIX_RATE
;
1043 switch(mDevice
->FmtType
)
1046 mDevice
->FmtType
= DevFmtUByte
;
1049 mSpec
.format
= PA_SAMPLE_U8
;
1052 mDevice
->FmtType
= DevFmtShort
;
1055 mSpec
.format
= PA_SAMPLE_S16NE
;
1058 mDevice
->FmtType
= DevFmtInt
;
1061 mSpec
.format
= PA_SAMPLE_S32NE
;
1064 mSpec
.format
= PA_SAMPLE_FLOAT32NE
;
1067 mSpec
.rate
= mDevice
->Frequency
;
1068 mSpec
.channels
= mDevice
->channelsFromFmt();
1070 if(pa_sample_spec_valid(&mSpec
) == 0)
1072 ERR("Invalid sample format\n");
1076 const char *mapname
{nullptr};
1077 pa_channel_map chanmap
;
1078 switch(mDevice
->FmtChans
)
1084 mDevice
->FmtChans
= DevFmtStereo
;
1087 mapname
= "front-left,front-right";
1090 mapname
= "front-left,front-right,rear-left,rear-right";
1093 mapname
= "front-left,front-right,front-center,lfe,side-left,side-right";
1096 mapname
= "front-left,front-right,front-center,lfe,rear-left,rear-right";
1099 mapname
= "front-left,front-right,front-center,lfe,rear-center,side-left,side-right";
1102 mapname
= "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right";
1105 if(!pa_channel_map_parse(&chanmap
, mapname
))
1107 ERR("Failed to build channel map for %s\n", DevFmtChannelsString(mDevice
->FmtChans
));
1110 SetDefaultWFXChannelOrder(mDevice
);
1112 size_t period_size
{mDevice
->UpdateSize
* pa_frame_size(&mSpec
)};
1113 mAttr
.maxlength
= -1;
1114 mAttr
.tlength
= period_size
* maxu(mDevice
->NumUpdates
, 2);
1116 mAttr
.minreq
= period_size
;
1117 mAttr
.fragsize
= -1;
1119 mStream
= pulse_connect_stream(mDeviceName
.c_str(), mLoop
, mContext
, flags
, &mAttr
, &mSpec
,
1120 &chanmap
, BackendType::Playback
);
1121 if(!mStream
) return ALC_FALSE
;
1123 pa_stream_set_state_callback(mStream
, &PulsePlayback::streamStateCallbackC
, this);
1124 pa_stream_set_moved_callback(mStream
, &PulsePlayback::streamMovedCallbackC
, this);
1126 mSpec
= *(pa_stream_get_sample_spec(mStream
));
1127 mFrameSize
= pa_frame_size(&mSpec
);
1129 if(mDevice
->Frequency
!= mSpec
.rate
)
1131 /* Server updated our playback rate, so modify the buffer attribs
1133 mDevice
->NumUpdates
= static_cast<ALuint
>(clampd(
1134 static_cast<ALdouble
>(mSpec
.rate
)/mDevice
->Frequency
*mDevice
->NumUpdates
+ 0.5, 2.0, 16.0));
1136 period_size
= mDevice
->UpdateSize
* mFrameSize
;
1137 mAttr
.maxlength
= -1;
1138 mAttr
.tlength
= period_size
* maxu(mDevice
->NumUpdates
, 2);
1140 mAttr
.minreq
= period_size
;
1142 op
= pa_stream_set_buffer_attr(mStream
, &mAttr
, stream_success_callback
, mLoop
);
1143 wait_for_operation(op
, mLoop
);
1145 mDevice
->Frequency
= mSpec
.rate
;
1148 pa_stream_set_buffer_attr_callback(mStream
, &PulsePlayback::bufferAttrCallbackC
, this);
1149 bufferAttrCallback(mStream
);
1151 mDevice
->NumUpdates
= clampu((mAttr
.tlength
+ mAttr
.minreq
/2u) / mAttr
.minreq
, 2u, 16u);
1152 mDevice
->UpdateSize
= mAttr
.minreq
/ mFrameSize
;
1154 /* HACK: prebuf should be 0 as that's what we set it to. However on some
1155 * systems it comes back as non-0, so we have to make sure the device will
1156 * write enough audio to start playback. The lack of manual start control
1157 * may have unintended consequences, but it's better than not starting at
1160 if(mAttr
.prebuf
!= 0)
1162 ALuint len
{mAttr
.prebuf
/ mFrameSize
};
1163 if(len
<= mDevice
->UpdateSize
*mDevice
->NumUpdates
)
1164 ERR("Non-0 prebuf, %u samples (%u bytes), device has %u samples\n",
1165 len
, mAttr
.prebuf
, mDevice
->UpdateSize
*mDevice
->NumUpdates
);
1168 ERR("Large prebuf, %u samples (%u bytes), increasing device from %u samples",
1169 len
, mAttr
.prebuf
, mDevice
->UpdateSize
*mDevice
->NumUpdates
);
1170 mDevice
->NumUpdates
= (len
+mDevice
->UpdateSize
-1) / mDevice
->UpdateSize
;
1177 ALCboolean
PulsePlayback::start()
1179 unique_palock palock
{mLoop
};
1181 pa_stream_set_write_callback(mStream
, &PulsePlayback::streamWriteCallbackC
, this);
1182 pa_operation
*op
{pa_stream_cork(mStream
, 0, stream_success_callback
, mLoop
)};
1183 wait_for_operation(op
, mLoop
);
1188 void PulsePlayback::stop()
1190 unique_palock palock
{mLoop
};
1192 pa_stream_set_write_callback(mStream
, nullptr, nullptr);
1193 pa_operation
*op
{pa_stream_cork(mStream
, 1, stream_success_callback
, mLoop
)};
1194 wait_for_operation(op
, mLoop
);
1198 ClockLatency
PulsePlayback::getClockLatency()
1204 { palock_guard _
{mLoop
};
1205 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1206 err
= pa_stream_get_latency(mStream
, &latency
, &neg
);
1209 if(UNLIKELY(err
!= 0))
1211 /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon
1212 * after starting the stream and no timing info has been received from
1213 * the server yet. Should we wait, possibly stalling the app, or give a
1214 * dummy value? Either way, it shouldn't be 0. */
1215 if(err
!= -PA_ERR_NODATA
)
1216 ERR("Failed to get stream latency: 0x%x\n", err
);
1220 else if(UNLIKELY(neg
))
1222 ret
.Latency
= std::chrono::microseconds
{latency
};
1228 void PulsePlayback::lock()
1229 { pa_threaded_mainloop_lock(mLoop
); }
1231 void PulsePlayback::unlock()
1232 { pa_threaded_mainloop_unlock(mLoop
); }
1235 struct PulseCapture final
: public BackendBase
{
1236 PulseCapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
1237 ~PulseCapture() override
;
1239 static void contextStateCallbackC(pa_context
*context
, void *pdata
);
1240 void contextStateCallback(pa_context
*context
);
1242 static void streamStateCallbackC(pa_stream
*stream
, void *pdata
);
1243 void streamStateCallback(pa_stream
*stream
);
1245 static void sourceNameCallbackC(pa_context
*context
, const pa_source_info
*info
, int eol
, void *pdata
);
1246 void sourceNameCallback(pa_context
*context
, const pa_source_info
*info
, int eol
);
1248 static void streamMovedCallbackC(pa_stream
*stream
, void *pdata
);
1249 void streamMovedCallback(pa_stream
*stream
);
1251 ALCenum
open(const ALCchar
*name
) override
;
1252 ALCboolean
start() override
;
1253 void stop() override
;
1254 ALCenum
captureSamples(ALCvoid
*buffer
, ALCuint samples
) override
;
1255 ALCuint
availableSamples() override
;
1256 ClockLatency
getClockLatency() override
;
1257 void lock() override
;
1258 void unlock() override
;
1260 std::string mDeviceName
;
1262 const void *mCapStore
{nullptr};
1264 size_t mCapRemain
{0u};
1266 ALCuint mLastReadable
{0u};
1268 pa_buffer_attr mAttr
{};
1269 pa_sample_spec mSpec
{};
1271 pa_threaded_mainloop
*mLoop
{nullptr};
1273 pa_stream
*mStream
{nullptr};
1274 pa_context
*mContext
{nullptr};
1276 static constexpr inline const char *CurrentPrefix() noexcept
{ return "PulseCapture::"; }
1277 DEF_NEWDEL(PulseCapture
)
1280 PulseCapture::~PulseCapture()
1284 pulse_close(mLoop
, mContext
, mStream
);
1290 void PulseCapture::contextStateCallbackC(pa_context
*context
, void *pdata
)
1291 { static_cast<PulseCapture
*>(pdata
)->contextStateCallback(context
); }
1293 void PulseCapture::contextStateCallback(pa_context
*context
)
1295 if(pa_context_get_state(context
) == PA_CONTEXT_FAILED
)
1297 ERR("Received context failure!\n");
1298 aluHandleDisconnect(mDevice
, "Capture state failure");
1300 pa_threaded_mainloop_signal(mLoop
, 0);
1303 void PulseCapture::streamStateCallbackC(pa_stream
*stream
, void *pdata
)
1304 { static_cast<PulseCapture
*>(pdata
)->streamStateCallback(stream
); }
1306 void PulseCapture::streamStateCallback(pa_stream
*stream
)
1308 if(pa_stream_get_state(stream
) == PA_STREAM_FAILED
)
1310 ERR("Received stream failure!\n");
1311 aluHandleDisconnect(mDevice
, "Capture stream failure");
1313 pa_threaded_mainloop_signal(mLoop
, 0);
1316 void PulseCapture::sourceNameCallbackC(pa_context
*context
, const pa_source_info
*info
, int eol
, void *pdata
)
1317 { static_cast<PulseCapture
*>(pdata
)->sourceNameCallback(context
, info
, eol
); }
1319 void PulseCapture::sourceNameCallback(pa_context
* UNUSED(context
), const pa_source_info
*info
, int eol
)
1323 pa_threaded_mainloop_signal(mLoop
, 0);
1326 mDevice
->DeviceName
= info
->description
;
1329 void PulseCapture::streamMovedCallbackC(pa_stream
*stream
, void *pdata
)
1330 { static_cast<PulseCapture
*>(pdata
)->streamMovedCallback(stream
); }
1332 void PulseCapture::streamMovedCallback(pa_stream
*stream
)
1334 mDeviceName
= pa_stream_get_device_name(stream
);
1335 TRACE("Stream moved to %s\n", mDeviceName
.c_str());
1339 ALCenum
PulseCapture::open(const ALCchar
*name
)
1341 const char *pulse_name
{nullptr};
1344 if(CaptureDevices
.empty())
1345 probeCaptureDevices();
1347 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1348 [name
](const DevMap
&entry
) -> bool
1349 { return entry
.name
== name
; }
1351 if(iter
== CaptureDevices
.cend())
1352 return ALC_INVALID_VALUE
;
1353 pulse_name
= iter
->device_name
.c_str();
1354 mDevice
->DeviceName
= iter
->name
;
1357 std::tie(mLoop
, mContext
) = pulse_open(&PulseCapture::contextStateCallbackC
, this);
1358 if(!mLoop
) return ALC_INVALID_VALUE
;
1360 unique_palock palock
{mLoop
};
1362 switch(mDevice
->FmtType
)
1365 mSpec
.format
= PA_SAMPLE_U8
;
1368 mSpec
.format
= PA_SAMPLE_S16NE
;
1371 mSpec
.format
= PA_SAMPLE_S32NE
;
1374 mSpec
.format
= PA_SAMPLE_FLOAT32NE
;
1379 ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice
->FmtType
));
1380 return ALC_INVALID_VALUE
;
1383 const char *mapname
{nullptr};
1384 pa_channel_map chanmap
;
1385 switch(mDevice
->FmtChans
)
1391 mapname
= "front-left,front-right";
1394 mapname
= "front-left,front-right,rear-left,rear-right";
1397 mapname
= "front-left,front-right,front-center,lfe,side-left,side-right";
1400 mapname
= "front-left,front-right,front-center,lfe,rear-left,rear-right";
1403 mapname
= "front-left,front-right,front-center,lfe,rear-center,side-left,side-right";
1406 mapname
= "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right";
1409 ERR("%s capture samples not supported\n", DevFmtChannelsString(mDevice
->FmtChans
));
1410 return ALC_INVALID_VALUE
;
1412 if(!pa_channel_map_parse(&chanmap
, mapname
))
1414 ERR("Failed to build channel map for %s\n", DevFmtChannelsString(mDevice
->FmtChans
));
1415 return ALC_INVALID_VALUE
;
1418 mSpec
.rate
= mDevice
->Frequency
;
1419 mSpec
.channels
= mDevice
->channelsFromFmt();
1421 if(pa_sample_spec_valid(&mSpec
) == 0)
1423 ERR("Invalid sample format\n");
1424 return ALC_INVALID_VALUE
;
1427 if(!pa_channel_map_init_auto(&chanmap
, mSpec
.channels
, PA_CHANNEL_MAP_WAVEEX
))
1429 ERR("Couldn't build map for channel count (%d)!\n", mSpec
.channels
);
1430 return ALC_INVALID_VALUE
;
1433 ALuint samples
{mDevice
->UpdateSize
* mDevice
->NumUpdates
};
1434 samples
= maxu(samples
, 100 * mDevice
->Frequency
/ 1000);
1438 mAttr
.maxlength
= samples
* pa_frame_size(&mSpec
);
1440 mAttr
.fragsize
= minu(samples
, 50*mDevice
->Frequency
/1000) * pa_frame_size(&mSpec
);
1442 pa_stream_flags_t flags
{PA_STREAM_START_CORKED
|PA_STREAM_ADJUST_LATENCY
};
1443 if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 0))
1444 flags
|= PA_STREAM_DONT_MOVE
;
1446 TRACE("Connecting to \"%s\"\n", pulse_name
? pulse_name
: "(default)");
1447 mStream
= pulse_connect_stream(pulse_name
, mLoop
, mContext
, flags
, &mAttr
, &mSpec
, &chanmap
,
1448 BackendType::Capture
);
1449 if(!mStream
) return ALC_INVALID_VALUE
;
1451 pa_stream_set_moved_callback(mStream
, &PulseCapture::streamMovedCallbackC
, this);
1452 pa_stream_set_state_callback(mStream
, &PulseCapture::streamStateCallbackC
, this);
1454 mDeviceName
= pa_stream_get_device_name(mStream
);
1455 if(mDevice
->DeviceName
.empty())
1457 pa_operation
*op
{pa_context_get_source_info_by_name(mContext
, mDeviceName
.c_str(),
1458 &PulseCapture::sourceNameCallbackC
, this)};
1459 wait_for_operation(op
, mLoop
);
1462 return ALC_NO_ERROR
;
1465 ALCboolean
PulseCapture::start()
1467 palock_guard _
{mLoop
};
1468 pa_operation
*op
{pa_stream_cork(mStream
, 0, stream_success_callback
, mLoop
)};
1469 wait_for_operation(op
, mLoop
);
1473 void PulseCapture::stop()
1475 palock_guard _
{mLoop
};
1476 pa_operation
*op
{pa_stream_cork(mStream
, 1, stream_success_callback
, mLoop
)};
1477 wait_for_operation(op
, mLoop
);
1480 ALCenum
PulseCapture::captureSamples(ALCvoid
*buffer
, ALCuint samples
)
1482 ALCuint todo
{samples
* static_cast<ALCuint
>(pa_frame_size(&mSpec
))};
1484 /* Capture is done in fragment-sized chunks, so we loop until we get all
1485 * that's available */
1486 mLastReadable
-= todo
;
1487 unique_palock palock
{mLoop
};
1494 pa_stream_state_t state
{pa_stream_get_state(mStream
)};
1495 if(!PA_STREAM_IS_GOOD(state
))
1497 aluHandleDisconnect(mDevice
, "Bad capture state: %u", state
);
1498 return ALC_INVALID_DEVICE
;
1500 if(pa_stream_peek(mStream
, &mCapStore
, &mCapLen
) < 0)
1502 ERR("pa_stream_peek() failed: %s\n",
1503 pa_strerror(pa_context_errno(mContext
)));
1504 aluHandleDisconnect(mDevice
, "Failed retrieving capture samples: %s",
1505 pa_strerror(pa_context_errno(mContext
)));
1506 return ALC_INVALID_DEVICE
;
1508 mCapRemain
= mCapLen
;
1510 rem
= minz(rem
, mCapRemain
);
1512 memcpy(buffer
, mCapStore
, rem
);
1514 buffer
= static_cast<ALbyte
*>(buffer
) + rem
;
1517 mCapStore
= reinterpret_cast<const ALbyte
*>(mCapStore
) + rem
;
1521 pa_stream_drop(mStream
);
1527 memset(buffer
, ((mDevice
->FmtType
==DevFmtUByte
) ? 0x80 : 0), todo
);
1529 return ALC_NO_ERROR
;
1532 ALCuint
PulseCapture::availableSamples()
1534 size_t readable
{mCapRemain
};
1536 if(mDevice
->Connected
.load(std::memory_order_acquire
))
1538 palock_guard _
{mLoop
};
1539 size_t got
{pa_stream_readable_size(mStream
)};
1540 if(static_cast<ssize_t
>(got
) < 0)
1542 ERR("pa_stream_readable_size() failed: %s\n", pa_strerror(got
));
1543 aluHandleDisconnect(mDevice
, "Failed getting readable size: %s", pa_strerror(got
));
1545 else if(got
> mCapLen
)
1546 readable
+= got
- mCapLen
;
1549 if(mLastReadable
< readable
)
1550 mLastReadable
= readable
;
1551 return mLastReadable
/ pa_frame_size(&mSpec
);
1555 ClockLatency
PulseCapture::getClockLatency()
1561 { palock_guard _
{mLoop
};
1562 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1563 err
= pa_stream_get_latency(mStream
, &latency
, &neg
);
1566 if(UNLIKELY(err
!= 0))
1568 ERR("Failed to get stream latency: 0x%x\n", err
);
1572 else if(UNLIKELY(neg
))
1574 ret
.Latency
= std::chrono::microseconds
{latency
};
1580 void PulseCapture::lock()
1581 { pa_threaded_mainloop_lock(mLoop
); }
1583 void PulseCapture::unlock()
1584 { pa_threaded_mainloop_unlock(mLoop
); }
1589 bool PulseBackendFactory::init()
1595 pulse_ctx_flags
= PA_CONTEXT_NOFLAGS
;
1596 if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1))
1597 pulse_ctx_flags
|= PA_CONTEXT_NOAUTOSPAWN
;
1599 pa_threaded_mainloop
*loop
{pa_threaded_mainloop_new()};
1600 if(loop
&& pa_threaded_mainloop_start(loop
) >= 0)
1602 unique_palock palock
{loop
};
1603 pa_context
*context
{connect_context(loop
, AL_TRUE
)};
1608 /* Some libraries (Phonon, Qt) set some pulseaudio properties
1609 * through environment variables, which causes all streams in
1610 * the process to inherit them. This attempts to filter those
1611 * properties out by setting them to 0-length data. */
1612 prop_filter
= pa_proplist_new();
1613 pa_proplist_set(prop_filter
, PA_PROP_MEDIA_ROLE
, nullptr, 0);
1614 pa_proplist_set(prop_filter
, "phonon.streamid", nullptr, 0);
1616 pa_context_disconnect(context
);
1617 pa_context_unref(context
);
1620 pa_threaded_mainloop_stop(loop
);
1623 pa_threaded_mainloop_free(loop
);
1629 void PulseBackendFactory::deinit()
1631 PlaybackDevices
.clear();
1632 CaptureDevices
.clear();
1635 pa_proplist_free(prop_filter
);
1636 prop_filter
= nullptr;
1638 /* PulseAudio doesn't like being CloseLib'd sometimes */
1641 bool PulseBackendFactory::querySupport(BackendType type
)
1642 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1644 void PulseBackendFactory::probe(DevProbe type
, std::string
*outnames
)
1646 auto add_device
= [outnames
](const DevMap
&entry
) -> void
1648 /* +1 to also append the null char (to ensure a null-separated list and
1649 * double-null terminated list).
1651 outnames
->append(entry
.name
.c_str(), entry
.name
.length()+1);
1655 case ALL_DEVICE_PROBE
:
1656 probePlaybackDevices();
1657 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
1660 case CAPTURE_DEVICE_PROBE
:
1661 probeCaptureDevices();
1662 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
1667 BackendPtr
PulseBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
1669 if(type
== BackendType::Playback
)
1670 return BackendPtr
{new PulsePlayback
{device
}};
1671 if(type
== BackendType::Capture
)
1672 return BackendPtr
{new PulseCapture
{device
}};
1676 BackendFactory
&PulseBackendFactory::getFactory()
1678 static PulseBackendFactory factory
{};