FreeRDP
Loading...
Searching...
No Matches
rdpsnd_pulse.c
1
22#include <freerdp/config.h>
23#include <freerdp/utils/helpers.h>
24
25#include <errno.h>
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <time.h>
31
32#include <winpr/crt.h>
33#include <winpr/cast.h>
34#include <winpr/assert.h>
35#include <winpr/stream.h>
36#include <winpr/cmdline.h>
37#include <winpr/sysinfo.h>
38
39#include <pulse/pulseaudio.h>
40
41#include <freerdp/types.h>
42#include <freerdp/codec/dsp.h>
43
44#include "rdpsnd_main.h"
45
46/* target latency when /sound:latency is not given */
47#define PULSE_DEFAULT_LATENCY_MS 100
48
49typedef struct
50{
51 rdpsndDevicePlugin device;
52
53 char* device_name;
54 char* client_name;
55 char* stream_name;
56 pa_threaded_mainloop* mainloop;
57 pa_context* context;
58 pa_sample_spec sample_spec;
59 pa_stream* stream;
60 UINT32 latency;
61 UINT32 volume;
62 time_t reconnect_delay_seconds;
63 time_t reconnect_time;
64 UINT64 play_end; /* tick count when the queued audio has been played */
65} rdpsndPulsePlugin;
66
67WINPR_ATTR_NODISCARD
68static BOOL rdpsnd_check_pulse(rdpsndPulsePlugin* pulse, BOOL haveStream)
69{
70 BOOL rc = TRUE;
71 WINPR_ASSERT(pulse);
72
73 if (!pulse->context)
74 {
75 WLog_WARN(TAG, "pulse->context=nullptr");
76 rc = FALSE;
77 }
78
79 if (haveStream)
80 {
81 if (!pulse->stream)
82 {
83 WLog_WARN(TAG, "pulse->stream=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->stream));
84 rc = FALSE;
85 }
86 }
87
88 if (!pulse->mainloop)
89 {
90 WLog_WARN(TAG, "pulse->mainloop=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->mainloop));
91 rc = FALSE;
92 }
93
94 return rc;
95}
96
97WINPR_ATTR_NODISCARD
98static BOOL rdpsnd_pulse_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
99 const AUDIO_FORMAT* format);
100
101static void rdpsnd_pulse_get_sink_info(WINPR_ATTR_UNUSED pa_context* c, const pa_sink_info* i,
102 WINPR_ATTR_UNUSED int eol, void* userdata)
103{
104 UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
105 UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
106 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
107
108 WINPR_ASSERT(c);
109 if (!rdpsnd_check_pulse(pulse, FALSE) || !i)
110 return;
111
112 for (uint8_t x = 0; x < i->volume.channels; x++)
113 {
114 pa_volume_t volume = i->volume.values[x];
115
116 if (volume >= PA_VOLUME_NORM)
117 volume = PA_VOLUME_NORM - 1;
118
119 switch (x)
120 {
121 case 0:
122 dwVolumeLeft = (UINT16)volume;
123 break;
124
125 case 1:
126 dwVolumeRight = (UINT16)volume;
127 break;
128
129 default:
130 break;
131 }
132 }
133
134 pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight;
135}
136
137static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata)
138{
139 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
140
141 WINPR_ASSERT(context);
142 WINPR_ASSERT(pulse);
143
144 pa_context_state_t state = pa_context_get_state(context);
145
146 switch (state)
147 {
148 case PA_CONTEXT_READY:
149 pa_threaded_mainloop_signal(pulse->mainloop, 0);
150 break;
151
152 case PA_CONTEXT_FAILED:
153 // Destroy context now, create new one for next connection attempt
154 pa_context_unref(pulse->context);
155 pulse->context = nullptr;
156 if (pulse->reconnect_delay_seconds >= 0)
157 pulse->reconnect_time = time(nullptr) + pulse->reconnect_delay_seconds;
158 pa_threaded_mainloop_signal(pulse->mainloop, 0);
159 break;
160
161 case PA_CONTEXT_TERMINATED:
162 pa_threaded_mainloop_signal(pulse->mainloop, 0);
163 break;
164
165 default:
166 break;
167 }
168}
169
170WINPR_ATTR_NODISCARD
171static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device)
172{
173 BOOL rc = 0;
174 pa_operation* o = nullptr;
175 pa_context_state_t state = PA_CONTEXT_FAILED;
176 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
177
178 if (!rdpsnd_check_pulse(pulse, FALSE))
179 return FALSE;
180
181 pa_threaded_mainloop_lock(pulse->mainloop);
182
183 if (pa_context_connect(pulse->context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
184 {
185 pa_threaded_mainloop_unlock(pulse->mainloop);
186 return FALSE;
187 }
188
189 /* the context state callback releases pulse->context on PA_CONTEXT_FAILED,
190 * so it must be re-checked after every wait */
191 while (pulse->context)
192 {
193 state = pa_context_get_state(pulse->context);
194
195 if (state == PA_CONTEXT_READY)
196 break;
197
198 if (!PA_CONTEXT_IS_GOOD(state))
199 {
200 break;
201 }
202
203 pa_threaded_mainloop_wait(pulse->mainloop);
204 }
205
206 if (pulse->context)
207 {
208 o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
209
210 if (o)
211 pa_operation_unref(o);
212 }
213
214 if (pulse->context && (state == PA_CONTEXT_READY))
215 {
216 rc = TRUE;
217 }
218 else
219 {
220 if (pulse->context)
221 pa_context_disconnect(pulse->context);
222 rc = FALSE;
223 }
224
225 pa_threaded_mainloop_unlock(pulse->mainloop);
226 return rc;
227}
228
229static void rdpsnd_pulse_stream_success_callback(WINPR_ATTR_UNUSED pa_stream* stream,
230 WINPR_ATTR_UNUSED int success, void* userdata)
231{
232 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
233
234 if (!rdpsnd_check_pulse(pulse, TRUE))
235 return;
236
237 pa_threaded_mainloop_signal(pulse->mainloop, 0);
238}
239
240static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation)
241{
242 if (!rdpsnd_check_pulse(pulse, TRUE))
243 return;
244
245 if (!operation)
246 return;
247
248 while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
249 {
250 pa_threaded_mainloop_wait(pulse->mainloop);
251 }
252
253 pa_operation_unref(operation);
254}
255
256static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata)
257{
258 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
259
260 WINPR_ASSERT(stream);
261 if (!rdpsnd_check_pulse(pulse, TRUE))
262 return;
263
264 pa_stream_state_t state = pa_stream_get_state(stream);
265
266 switch (state)
267 {
268 case PA_STREAM_READY:
269 pa_threaded_mainloop_signal(pulse->mainloop, 0);
270 break;
271
272 case PA_STREAM_FAILED:
273 case PA_STREAM_TERMINATED:
274 /* The stream is dead (e.g. its device vanished). Drop our
275 * reference; pa_stream dispatches state callbacks under its own
276 * ref/unref guard, so unref here is safe. */
277 pa_stream_unref(pulse->stream);
278 pulse->stream = nullptr;
279 pa_threaded_mainloop_signal(pulse->mainloop, 0);
280 break;
281
282 default:
283 break;
284 }
285}
286
287static void rdpsnd_pulse_stream_request_callback(WINPR_ATTR_UNUSED pa_stream* stream,
288 WINPR_ATTR_UNUSED size_t length, void* userdata)
289{
290 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
291
292 WINPR_ASSERT(stream);
293 if (!rdpsnd_check_pulse(pulse, TRUE))
294 return;
295
296 pa_threaded_mainloop_signal(pulse->mainloop, 0);
297}
298
299static void rdpsnd_pulse_close(rdpsndDevicePlugin* device)
300{
301 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
302
303 WINPR_ASSERT(pulse);
304
305 if (!rdpsnd_check_pulse(pulse, FALSE))
306 return;
307
308 pa_threaded_mainloop_lock(pulse->mainloop);
309 if (pulse->stream)
310 {
311 rdpsnd_pulse_wait_for_operation(
312 pulse, pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse));
313 /* The stream may have died while draining; the state callback then
314 * released it and cleared pulse->stream. */
315 if (pulse->stream)
316 {
317 pa_stream_disconnect(pulse->stream);
318 pa_stream_unref(pulse->stream);
319 pulse->stream = nullptr;
320 }
321 }
322 pa_threaded_mainloop_unlock(pulse->mainloop);
323}
324
325WINPR_ATTR_NODISCARD
326static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format)
327{
328 WINPR_ASSERT(format);
329
330 if (!rdpsnd_check_pulse(pulse, FALSE))
331 return FALSE;
332
333 if (!rdpsnd_pulse_format_supported(&pulse->device, format))
334 return FALSE;
335
336 pa_sample_format_t sformat = PA_SAMPLE_INVALID;
337 switch (format->wFormatTag)
338 {
339 case WAVE_FORMAT_PCM:
340 switch (format->wBitsPerSample)
341 {
342 case 8:
343 sformat = PA_SAMPLE_U8;
344 break;
345
346 case 16:
347 sformat = PA_SAMPLE_S16LE;
348 break;
349
350 default:
351 return FALSE;
352 }
353
354 break;
355
356 default:
357 return FALSE;
358 }
359
360 const pa_sample_spec sample_spec = { .format = sformat,
361 .rate = format->nSamplesPerSec,
362 .channels =
363 WINPR_ASSERTING_INT_CAST(uint8_t, format->nChannels) };
364 pulse->sample_spec = sample_spec;
365 return TRUE;
366}
367
368WINPR_ATTR_NODISCARD
369static BOOL rdpsnd_pulse_context_connect(rdpsndDevicePlugin* device)
370{
371 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
372 WINPR_ASSERT(pulse);
373
374 pulse->context =
375 pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), pulse->client_name);
376
377 if (!pulse->context)
378 return FALSE;
379
380 pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse);
381
382 return rdpsnd_pulse_connect(&pulse->device);
383}
384
385WINPR_ATTR_NODISCARD
386static BOOL rdpsnd_pulse_open_stream(rdpsndDevicePlugin* device)
387{
388 pa_stream_state_t state = PA_STREAM_FAILED;
389 int flags = PA_STREAM_NOFLAGS;
390 pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT;
391 char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = WINPR_C_ARRAY_INIT;
392 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
393 WINPR_ASSERT(pulse);
394
395 pulse->play_end = 0; /* a new stream starts empty */
396 if (pa_sample_spec_valid(&pulse->sample_spec) == 0)
397 {
398 pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec);
399 return FALSE;
400 }
401
402 pa_threaded_mainloop_lock(pulse->mainloop);
403 if (!pulse->context)
404 {
405 pa_threaded_mainloop_unlock(pulse->mainloop);
406 if (pulse->reconnect_delay_seconds >= 0 && time(nullptr) - pulse->reconnect_time >= 0)
407 {
408 if (!rdpsnd_pulse_context_connect(device))
409 return FALSE;
410 }
411 pa_threaded_mainloop_lock(pulse->mainloop);
412 }
413
414 if (!rdpsnd_check_pulse(pulse, FALSE))
415 {
416 pa_threaded_mainloop_unlock(pulse->mainloop);
417 return FALSE;
418 }
419
420 pulse->stream = pa_stream_new(pulse->context, pulse->stream_name, &pulse->sample_spec, nullptr);
421
422 if (!pulse->stream)
423 {
424 pa_threaded_mainloop_unlock(pulse->mainloop);
425 return FALSE;
426 }
427
428 /* register essential callbacks */
429 pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse);
430 pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse);
431 flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
432
433 /* Always ask for a target latency: without one the server picks its
434 * own buffering, and after a pause PulseAudio drops the start of the
435 * next sound (as much as its sink latency) while PipeWire runs dry
436 * shortly after the start */
437 const UINT32 ms = (pulse->latency > 0) ? pulse->latency : PULSE_DEFAULT_LATENCY_MS;
438 const size_t val = pa_usec_to_bytes(1000ULL * ms, &pulse->sample_spec);
439 buffer_attr.maxlength = UINT32_MAX;
440 buffer_attr.tlength = (val > UINT32_MAX) ? UINT32_MAX : (UINT32)val;
441 buffer_attr.prebuf = UINT32_MAX;
442 buffer_attr.minreq = UINT32_MAX;
443 buffer_attr.fragsize = UINT32_MAX;
444
445 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
446 pa_stream_flags_t eflags = (pa_stream_flags_t)flags;
447 if (pa_stream_connect_playback(pulse->stream, pulse->device_name, &buffer_attr, eflags, nullptr,
448 nullptr) < 0)
449 {
450 WLog_ERR(TAG, "error connecting playback stream");
451 pa_stream_unref(pulse->stream);
452 pulse->stream = nullptr;
453 pa_threaded_mainloop_unlock(pulse->mainloop);
454 return FALSE;
455 }
456
457 while (pulse->stream)
458 {
459 state = pa_stream_get_state(pulse->stream);
460
461 if (state == PA_STREAM_READY)
462 break;
463
464 if (!PA_STREAM_IS_GOOD(state))
465 {
466 break;
467 }
468
469 pa_threaded_mainloop_wait(pulse->mainloop);
470 }
471
472 pa_threaded_mainloop_unlock(pulse->mainloop);
473
474 if (state == PA_STREAM_READY)
475 return TRUE;
476
477 rdpsnd_pulse_close(device);
478 return FALSE;
479}
480
481WINPR_ATTR_NODISCARD
482static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
483 UINT32 latency)
484{
485 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
486
487 WINPR_ASSERT(format);
488
489 if (!rdpsnd_check_pulse(pulse, FALSE))
490 return TRUE;
491
492 if (!rdpsnd_pulse_set_format_spec(pulse, format))
493 return FALSE;
494
495 pulse->latency = latency;
496
497 return rdpsnd_pulse_open_stream(device);
498}
499
500static void rdpsnd_pulse_free(rdpsndDevicePlugin* device)
501{
502 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
503
504 if (!pulse)
505 return;
506
507 rdpsnd_pulse_close(device);
508
509 if (pulse->mainloop)
510 pa_threaded_mainloop_stop(pulse->mainloop);
511
512 if (pulse->context)
513 {
514 pa_context_disconnect(pulse->context);
515 pa_context_unref(pulse->context);
516 pulse->context = nullptr;
517 }
518
519 if (pulse->mainloop)
520 {
521 pa_threaded_mainloop_free(pulse->mainloop);
522 pulse->mainloop = nullptr;
523 }
524
525 free(pulse->device_name);
526 free(pulse->client_name);
527 free(pulse->stream_name);
528 free(pulse);
529}
530
531WINPR_ATTR_NODISCARD
532static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
533 AUDIO_FORMAT* defaultFormat)
534{
535 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
536
537 if (!pulse || !defaultFormat)
538 return FALSE;
539
540 *defaultFormat = *desired;
541 defaultFormat->data = nullptr;
542 defaultFormat->cbSize = 0;
543 defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
544 if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX))
545 defaultFormat->nChannels = 2;
546 if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX))
547 defaultFormat->nSamplesPerSec = 44100;
548 if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16))
549 defaultFormat->wBitsPerSample = 16;
550
551 defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
552 defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
553 return TRUE;
554}
555
556BOOL rdpsnd_pulse_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
557 const AUDIO_FORMAT* format)
558{
559 WINPR_ASSERT(device);
560 WINPR_ASSERT(format);
561
562 switch (format->wFormatTag)
563 {
564 case WAVE_FORMAT_PCM:
565 if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
566 (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
567 (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
568 {
569 return TRUE;
570 }
571
572 break;
573
574 default:
575 break;
576 }
577
578 return FALSE;
579}
580
581WINPR_ATTR_NODISCARD
582static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device)
583{
584 pa_operation* o = nullptr;
585 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
586
587 if (!pulse || !pulse->mainloop)
588 return 0;
589
590 pa_threaded_mainloop_lock(pulse->mainloop);
591 /* re-check under the lock, the context is released if the connection dies */
592 if (rdpsnd_check_pulse(pulse, FALSE))
593 {
594 o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
595 if (o)
596 pa_operation_unref(o);
597 }
598 pa_threaded_mainloop_unlock(pulse->mainloop);
599 return pulse->volume;
600}
601
602static void rdpsnd_set_volume_success_cb(WINPR_ATTR_UNUSED pa_context* c, int success,
603 void* userdata)
604{
605 rdpsndPulsePlugin* pulse = userdata;
606
607 if (!rdpsnd_check_pulse(pulse, TRUE))
608 return;
609 WINPR_ASSERT(c);
610
611 WLog_INFO(TAG, "%d", success);
612}
613
614WINPR_ATTR_NODISCARD
615static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value)
616{
617 pa_cvolume cv = WINPR_C_ARRAY_INIT;
618 pa_volume_t left = 0;
619 pa_volume_t right = 0;
620 pa_operation* operation = nullptr;
621 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
622
623 if (!pulse || !pulse->mainloop)
624 {
625 WLog_WARN(TAG, "called before pulse backend was initialized");
626 return FALSE;
627 }
628
629 left = (pa_volume_t)(value & 0xFFFF);
630 right = (pa_volume_t)((value >> 16) & 0xFFFF);
631 pa_cvolume_init(&cv);
632 cv.channels = 2;
633 cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
634 cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
635 pa_threaded_mainloop_lock(pulse->mainloop);
636
637 /* the stream state must be inspected under the mainloop lock - it may be
638 * cleared by the state callback when the stream dies */
639 if (!rdpsnd_check_pulse(pulse, TRUE))
640 {
641 pa_threaded_mainloop_unlock(pulse->mainloop);
642 WLog_WARN(TAG, "no pulse stream, not setting volume");
643 return FALSE;
644 }
645
646 operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream),
647 &cv, rdpsnd_set_volume_success_cb, pulse);
648
649 if (operation)
650 pa_operation_unref(operation);
651
652 pa_threaded_mainloop_unlock(pulse->mainloop);
653 return TRUE;
654}
655
656/* write size bytes of data, or of silence if data is nullptr; called with the mainloop locked */
657WINPR_ATTR_NODISCARD
658static BOOL rdpsnd_pulse_write(rdpsndPulsePlugin* pulse, const BYTE* data, size_t size)
659{
660 const size_t frame_size = pa_frame_size(&pulse->sample_spec);
661 if (frame_size == 0)
662 return FALSE;
663
664 /* pa_stream_write only accepts whole frames, drop an incomplete one at the end */
665 size -= size % frame_size;
666
667 while (size > 0)
668 {
669 size_t length = size;
670 void* pa_data = nullptr;
671
672 if (pa_stream_begin_write(pulse->stream, &pa_data, &length) < 0)
673 return FALSE;
674
675 /* The returned length is the size of the buffer handed out, which is
676 * not bound by what was asked for; never copy more than we have. */
677 if (length > size)
678 length = size;
679
680 /* A suspended or migrating stream (e.g. the default device just
681 * changed) can hand out an empty buffer; treating it as progress
682 * busy-loops forever with the mainloop lock held, which also stalls
683 * the channel thread calling Play. Drop the rest of the sample
684 * instead. */
685 if (!pa_data || (length == 0))
686 {
687 if (pa_data)
688 pa_stream_cancel_write(pulse->stream);
689 WLog_DBG(TAG, "dropping %" PRIuz " bytes, no buffer space", size);
690 return FALSE;
691 }
692
693 if (data)
694 memcpy(pa_data, data, length);
695 else
696 memset(pa_data, (pulse->sample_spec.format == PA_SAMPLE_U8) ? 0x80 : 0, length);
697
698 /* A buffer that is neither written nor cancelled stays attached to the
699 * stream and is handed out again, with its own size, by the next
700 * pa_stream_begin_write. */
701 if (pa_stream_write(pulse->stream, pa_data, length, nullptr, 0LL, PA_SEEK_RELATIVE) < 0)
702 {
703 pa_stream_cancel_write(pulse->stream);
704 return FALSE;
705 }
706
707 if (data)
708 data += length;
709 size -= length;
710 }
711 return TRUE;
712}
713
714/* whether the queued audio has run out: the tick count estimate runs ahead of the device whenever
715 * playback stalls (prebuffering after an underrun, bursts, clock drift); the stream's read and
716 * write positions say how much is really queued, the estimate is only trusted when they are not
717 * known */
718WINPR_ATTR_NODISCARD
719static BOOL rdpsnd_pulse_ran_dry(rdpsndPulsePlugin* pulse, UINT64 now)
720{
721 if (now < pulse->play_end)
722 return FALSE;
723
724 const pa_timing_info* ti = pa_stream_get_timing_info(pulse->stream);
725 if (!ti || ti->write_index_corrupt || ti->read_index_corrupt)
726 return TRUE;
727
728 int64_t queued = ti->write_index - ti->read_index;
729 if (ti->playing)
730 {
731 /* the positions are from the last timing update; take off what has been played since */
732 const pa_usec_t age = pa_timeval_age(&ti->timestamp);
733 queued -= (int64_t)pa_usec_to_bytes(age, &pulse->sample_spec);
734 }
735
736 /* less than 20 ms left: the device is about to run dry, as the estimate says */
737 if (queued <= (int64_t)pa_usec_to_bytes(20000ULL, &pulse->sample_spec))
738 return TRUE;
739
740 /* audio is still waiting: no silence, and the estimate follows the stream again */
741 pulse->play_end = now + pa_bytes_to_usec((uint64_t)queued, &pulse->sample_spec) / 1000ULL;
742 return FALSE;
743}
744
745WINPR_ATTR_NODISCARD
746static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
747{
748 pa_usec_t latency = 0;
749 int negative = 0;
750 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
751
752 if (!data)
753 return 0;
754
755 pa_threaded_mainloop_lock(pulse->mainloop);
756
757 if (!rdpsnd_check_pulse(pulse, TRUE))
758 {
759 pa_threaded_mainloop_unlock(pulse->mainloop);
760 // Discard this playback request and just attempt to reconnect the stream
761 WLog_DBG(TAG, "reconnecting playback stream");
762 if (!rdpsnd_pulse_open_stream(device))
763 WLog_WARN(TAG, "Failed to (re)open pulse stream, ignoring playback");
764 return 0;
765 }
766
767 /* When everything queued so far has been played, the stream starts
768 * again with this sample and nothing behind it: PipeWire plays it as
769 * soon as one quantum is there, so the next sample has to be on time,
770 * and PulseAudio loses some of it. Put some silence in front, so the
771 * following samples can arrive a little late. */
772 const UINT64 now = GetTickCount64();
773 if (rdpsnd_pulse_ran_dry(pulse, now))
774 {
775 const UINT32 ms = (pulse->latency > 0) ? pulse->latency : PULSE_DEFAULT_LATENCY_MS;
776 if (!rdpsnd_pulse_write(pulse, nullptr,
777 pa_usec_to_bytes(1000ULL * ms, &pulse->sample_spec)))
778 WLog_WARN(TAG, "rdpsnd_pulse_write failed");
779 pulse->play_end = now + ms;
780 }
781
782 if (rdpsnd_pulse_write(pulse, data, size))
783 pulse->play_end += pa_bytes_to_usec(size, &pulse->sample_spec) / 1000;
784
785 if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0)
786 latency = 0;
787
788 pa_threaded_mainloop_unlock(pulse->mainloop);
789
790 const pa_usec_t val = latency / 1000;
791 if (val > UINT32_MAX)
792 return UINT32_MAX;
793 return (UINT32)val;
794}
795
796WINPR_ATTR_NODISCARD
797static UINT rdpsnd_pulse_parse_addin_args(rdpsndPulsePlugin* pulse, const ADDIN_ARGV* args)
798{
799 COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = {
800 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
801 { "reconnect_delay_seconds", COMMAND_LINE_VALUE_REQUIRED, "<reconnect_delay_seconds>",
802 nullptr, nullptr, -1, nullptr, "reconnect_delay_seconds" },
803 { "client_name", COMMAND_LINE_VALUE_REQUIRED, "<client_name>", nullptr, nullptr, -1,
804 nullptr, "name of pulse client" },
805 { "stream_name", COMMAND_LINE_VALUE_REQUIRED, "<stream_name>", nullptr, nullptr, -1,
806 nullptr, "name of pulse stream" },
807 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
808 };
809 const DWORD flags =
810 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
811
812 WINPR_ASSERT(pulse);
813 WINPR_ASSERT(args);
814
815 const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_pulse_args, flags,
816 pulse, nullptr, nullptr);
817
818 if (status < 0)
819 return ERROR_INVALID_DATA;
820
821 const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_pulse_args;
822
823 const char* client_name = nullptr;
824 const char* stream_name = nullptr;
825 do
826 {
827 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
828 continue;
829
830 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
831 {
832 pulse->device_name = _strdup(arg->Value);
833
834 if (!pulse->device_name)
835 return ERROR_OUTOFMEMORY;
836 }
837 CommandLineSwitchCase(arg, "reconnect_delay_seconds")
838 {
839 unsigned long val = strtoul(arg->Value, nullptr, 0);
840
841 if ((errno != 0) || (val > INT32_MAX))
842 return ERROR_INVALID_DATA;
843
844 pulse->reconnect_delay_seconds = (time_t)val;
845 }
846 CommandLineSwitchCase(arg, "client_name")
847 {
848 client_name = arg->Value;
849 }
850 CommandLineSwitchCase(arg, "stream_name")
851 {
852 stream_name = arg->Value;
853 }
854 CommandLineSwitchEnd(arg)
855 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
856
857 if (!client_name)
858 client_name = freerdp_getApplicationDetailsString();
859 if (!stream_name)
860 stream_name = freerdp_getApplicationDetailsString();
861
862 pulse->client_name = _strdup(client_name);
863 pulse->stream_name = _strdup(stream_name);
864 if (!pulse->client_name || !pulse->stream_name)
865 return ERROR_OUTOFMEMORY;
866 return CHANNEL_RC_OK;
867}
868
869FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_rdpsnd_client_subsystem_entry(
871{
872 WINPR_ASSERT(pEntryPoints);
873
874 rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)calloc(1, sizeof(rdpsndPulsePlugin));
875
876 if (!pulse)
877 return CHANNEL_RC_NO_MEMORY;
878
879 pulse->device.Open = rdpsnd_pulse_open;
880 pulse->device.FormatSupported = rdpsnd_pulse_format_supported;
881 pulse->device.GetVolume = rdpsnd_pulse_get_volume;
882 pulse->device.SetVolume = rdpsnd_pulse_set_volume;
883 pulse->device.Play = rdpsnd_pulse_play;
884 pulse->device.Close = rdpsnd_pulse_close;
885 pulse->device.Free = rdpsnd_pulse_free;
886 pulse->device.DefaultFormat = rdpsnd_pulse_default_format;
887
888 const ADDIN_ARGV* args = pEntryPoints->args;
889 UINT ret = rdpsnd_pulse_parse_addin_args(pulse, args);
890
891 if (ret != CHANNEL_RC_OK)
892 {
893 WLog_ERR(TAG, "error parsing arguments");
894 goto error;
895 }
896
897 pulse->reconnect_delay_seconds = 5;
898 pulse->reconnect_time = time(nullptr);
899
900 ret = CHANNEL_RC_NO_MEMORY;
901 pulse->mainloop = pa_threaded_mainloop_new();
902
903 if (!pulse->mainloop)
904 goto error;
905
906 pa_threaded_mainloop_lock(pulse->mainloop);
907
908 if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
909 {
910 pa_threaded_mainloop_unlock(pulse->mainloop);
911 goto error;
912 }
913
914 pa_threaded_mainloop_unlock(pulse->mainloop);
915
916 if (!rdpsnd_pulse_context_connect((rdpsndDevicePlugin*)pulse))
917 goto error;
918
919 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse);
920 return CHANNEL_RC_OK;
921error:
922 rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse);
923 return ret;
924}