FreeRDP
rdpsnd_pulse.c
1 
22 #include <freerdp/config.h>
23 
24 #include <errno.h>
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 
31 #include <winpr/crt.h>
32 #include <winpr/assert.h>
33 #include <winpr/stream.h>
34 #include <winpr/cmdline.h>
35 
36 #include <pulse/pulseaudio.h>
37 
38 #include <freerdp/types.h>
39 #include <freerdp/codec/dsp.h>
40 
41 #include "rdpsnd_main.h"
42 
43 typedef struct
44 {
45  rdpsndDevicePlugin device;
46 
47  char* device_name;
48  pa_threaded_mainloop* mainloop;
49  pa_context* context;
50  pa_sample_spec sample_spec;
51  pa_stream* stream;
52  UINT32 latency;
53  UINT32 volume;
54  time_t reconnect_delay_seconds;
55  time_t reconnect_time;
56 } rdpsndPulsePlugin;
57 
58 static BOOL rdpsnd_check_pulse(rdpsndPulsePlugin* pulse, BOOL haveStream)
59 {
60  BOOL rc = TRUE;
61  WINPR_ASSERT(pulse);
62 
63  if (!pulse->context)
64  {
65  WLog_WARN(TAG, "pulse->context=%p", pulse->context);
66  rc = FALSE;
67  }
68 
69  if (haveStream)
70  {
71  if (!pulse->stream)
72  {
73  WLog_WARN(TAG, "pulse->stream=%p", pulse->stream);
74  rc = FALSE;
75  }
76  }
77 
78  if (!pulse->mainloop)
79  {
80  WLog_WARN(TAG, "pulse->mainloop=%p", pulse->mainloop);
81  rc = FALSE;
82  }
83 
84  return rc;
85 }
86 
87 static BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format);
88 
89 static void rdpsnd_pulse_get_sink_info(pa_context* c, const pa_sink_info* i, int eol,
90  void* userdata)
91 {
92  UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
93  UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
94  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
95 
96  WINPR_ASSERT(c);
97  if (!rdpsnd_check_pulse(pulse, FALSE) || !i)
98  return;
99 
100  for (uint8_t x = 0; x < i->volume.channels; x++)
101  {
102  pa_volume_t volume = i->volume.values[x];
103 
104  if (volume >= PA_VOLUME_NORM)
105  volume = PA_VOLUME_NORM - 1;
106 
107  switch (x)
108  {
109  case 0:
110  dwVolumeLeft = (UINT16)volume;
111  break;
112 
113  case 1:
114  dwVolumeRight = (UINT16)volume;
115  break;
116 
117  default:
118  break;
119  }
120  }
121 
122  pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight;
123 }
124 
125 static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata)
126 {
127  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
128 
129  WINPR_ASSERT(context);
130  WINPR_ASSERT(pulse);
131 
132  pa_context_state_t state = pa_context_get_state(context);
133 
134  switch (state)
135  {
136  case PA_CONTEXT_READY:
137  pa_threaded_mainloop_signal(pulse->mainloop, 0);
138  break;
139 
140  case PA_CONTEXT_FAILED:
141  // Destroy context now, create new one for next connection attempt
142  pa_context_unref(pulse->context);
143  pulse->context = NULL;
144  if (pulse->reconnect_delay_seconds >= 0)
145  pulse->reconnect_time = time(NULL) + pulse->reconnect_delay_seconds;
146  pa_threaded_mainloop_signal(pulse->mainloop, 0);
147  break;
148 
149  case PA_CONTEXT_TERMINATED:
150  pa_threaded_mainloop_signal(pulse->mainloop, 0);
151  break;
152 
153  default:
154  break;
155  }
156 }
157 
158 static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device)
159 {
160  BOOL rc = 0;
161  pa_operation* o = NULL;
162  pa_context_state_t state = PA_CONTEXT_FAILED;
163  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
164 
165  if (!rdpsnd_check_pulse(pulse, FALSE))
166  return FALSE;
167 
168  pa_threaded_mainloop_lock(pulse->mainloop);
169 
170  if (pa_context_connect(pulse->context, NULL, 0, NULL) < 0)
171  {
172  pa_threaded_mainloop_unlock(pulse->mainloop);
173  return FALSE;
174  }
175 
176  for (;;)
177  {
178  state = pa_context_get_state(pulse->context);
179 
180  if (state == PA_CONTEXT_READY)
181  break;
182 
183  if (!PA_CONTEXT_IS_GOOD(state))
184  {
185  break;
186  }
187 
188  pa_threaded_mainloop_wait(pulse->mainloop);
189  }
190 
191  o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
192 
193  if (o)
194  pa_operation_unref(o);
195 
196  if (state == PA_CONTEXT_READY)
197  {
198  rc = TRUE;
199  }
200  else
201  {
202  pa_context_disconnect(pulse->context);
203  rc = FALSE;
204  }
205 
206  pa_threaded_mainloop_unlock(pulse->mainloop);
207  return rc;
208 }
209 
210 static void rdpsnd_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata)
211 {
212  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
213 
214  if (!rdpsnd_check_pulse(pulse, TRUE))
215  return;
216 
217  pa_threaded_mainloop_signal(pulse->mainloop, 0);
218 }
219 
220 static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation)
221 {
222  if (!rdpsnd_check_pulse(pulse, TRUE))
223  return;
224 
225  if (!operation)
226  return;
227 
228  while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
229  {
230  pa_threaded_mainloop_wait(pulse->mainloop);
231  }
232 
233  pa_operation_unref(operation);
234 }
235 
236 static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata)
237 {
238  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
239 
240  WINPR_ASSERT(stream);
241  if (!rdpsnd_check_pulse(pulse, TRUE))
242  return;
243 
244  pa_stream_state_t state = pa_stream_get_state(stream);
245 
246  switch (state)
247  {
248  case PA_STREAM_READY:
249  pa_threaded_mainloop_signal(pulse->mainloop, 0);
250  break;
251 
252  case PA_STREAM_FAILED:
253  case PA_STREAM_TERMINATED:
254  // Stream object is about to be destroyed, clean up our pointer
255  pulse->stream = NULL;
256  pa_threaded_mainloop_signal(pulse->mainloop, 0);
257  break;
258 
259  default:
260  break;
261  }
262 }
263 
264 static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
265 {
266  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
267 
268  WINPR_ASSERT(stream);
269  if (!rdpsnd_check_pulse(pulse, TRUE))
270  return;
271 
272  pa_threaded_mainloop_signal(pulse->mainloop, 0);
273 }
274 
275 static void rdpsnd_pulse_close(rdpsndDevicePlugin* device)
276 {
277  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
278 
279  WINPR_ASSERT(pulse);
280 
281  if (!rdpsnd_check_pulse(pulse, FALSE))
282  return;
283 
284  pa_threaded_mainloop_lock(pulse->mainloop);
285  if (pulse->stream)
286  {
287  rdpsnd_pulse_wait_for_operation(
288  pulse, pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse));
289  pa_stream_disconnect(pulse->stream);
290  pa_stream_unref(pulse->stream);
291  pulse->stream = NULL;
292  }
293  pa_threaded_mainloop_unlock(pulse->mainloop);
294 }
295 
296 static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format)
297 {
298  pa_sample_spec sample_spec = { 0 };
299 
300  WINPR_ASSERT(format);
301 
302  if (!rdpsnd_check_pulse(pulse, FALSE))
303  return FALSE;
304 
305  if (!rdpsnd_pulse_format_supported(&pulse->device, format))
306  return FALSE;
307 
308  sample_spec.rate = format->nSamplesPerSec;
309  sample_spec.channels = format->nChannels;
310 
311  switch (format->wFormatTag)
312  {
313  case WAVE_FORMAT_PCM:
314  switch (format->wBitsPerSample)
315  {
316  case 8:
317  sample_spec.format = PA_SAMPLE_U8;
318  break;
319 
320  case 16:
321  sample_spec.format = PA_SAMPLE_S16LE;
322  break;
323 
324  default:
325  return FALSE;
326  }
327 
328  break;
329 
330  default:
331  return FALSE;
332  }
333 
334  pulse->sample_spec = sample_spec;
335  return TRUE;
336 }
337 
338 static BOOL rdpsnd_pulse_context_connect(rdpsndDevicePlugin* device)
339 {
340  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
341 
342  pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
343 
344  if (!pulse->context)
345  return FALSE;
346 
347  pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse);
348 
349  if (!rdpsnd_pulse_connect((rdpsndDevicePlugin*)pulse))
350  return FALSE;
351 
352  return TRUE;
353 }
354 
355 static BOOL rdpsnd_pulse_open_stream(rdpsndDevicePlugin* device)
356 {
357  pa_stream_state_t state = PA_STREAM_FAILED;
358  int flags = PA_STREAM_NOFLAGS;
359  pa_buffer_attr buffer_attr = { 0 };
360  char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = { 0 };
361  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
362 
363  if (pa_sample_spec_valid(&pulse->sample_spec) == 0)
364  {
365  pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec);
366  return FALSE;
367  }
368 
369  pa_threaded_mainloop_lock(pulse->mainloop);
370  if (!pulse->context)
371  {
372  pa_threaded_mainloop_unlock(pulse->mainloop);
373  if (pulse->reconnect_delay_seconds >= 0 && time(NULL) - pulse->reconnect_time >= 0)
374  rdpsnd_pulse_context_connect(device);
375  pa_threaded_mainloop_lock(pulse->mainloop);
376  }
377 
378  if (!rdpsnd_check_pulse(pulse, FALSE))
379  {
380  pa_threaded_mainloop_unlock(pulse->mainloop);
381  return FALSE;
382  }
383 
384  pulse->stream = pa_stream_new(pulse->context, "freerdp", &pulse->sample_spec, NULL);
385 
386  if (!pulse->stream)
387  {
388  pa_threaded_mainloop_unlock(pulse->mainloop);
389  return FALSE;
390  }
391 
392  /* register essential callbacks */
393  pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse);
394  pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse);
395  flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
396 
397  if (pulse->latency > 0)
398  {
399  const size_t val = pa_usec_to_bytes(1000ULL * pulse->latency, &pulse->sample_spec);
400  buffer_attr.maxlength = UINT32_MAX;
401  buffer_attr.tlength = (val > UINT32_MAX) ? UINT32_MAX : (UINT32)val;
402  buffer_attr.prebuf = UINT32_MAX;
403  buffer_attr.minreq = UINT32_MAX;
404  buffer_attr.fragsize = UINT32_MAX;
405  flags |= PA_STREAM_ADJUST_LATENCY;
406  }
407 
408  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
409  pa_stream_flags_t eflags = (pa_stream_flags_t)flags;
410  if (pa_stream_connect_playback(pulse->stream, pulse->device_name,
411  pulse->latency > 0 ? &buffer_attr : NULL, eflags, NULL,
412  NULL) < 0)
413  {
414  WLog_ERR(TAG, "error connecting playback stream");
415  pa_stream_unref(pulse->stream);
416  pulse->stream = NULL;
417  pa_threaded_mainloop_unlock(pulse->mainloop);
418  return FALSE;
419  }
420 
421  while (pulse->stream)
422  {
423  state = pa_stream_get_state(pulse->stream);
424 
425  if (state == PA_STREAM_READY)
426  break;
427 
428  if (!PA_STREAM_IS_GOOD(state))
429  {
430  break;
431  }
432 
433  pa_threaded_mainloop_wait(pulse->mainloop);
434  }
435 
436  pa_threaded_mainloop_unlock(pulse->mainloop);
437 
438  if (state == PA_STREAM_READY)
439  return TRUE;
440 
441  rdpsnd_pulse_close(device);
442  return FALSE;
443 }
444 
445 static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
446  UINT32 latency)
447 {
448  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
449 
450  WINPR_ASSERT(format);
451 
452  if (!rdpsnd_check_pulse(pulse, FALSE))
453  return TRUE;
454 
455  if (!rdpsnd_pulse_set_format_spec(pulse, format))
456  return FALSE;
457 
458  pulse->latency = latency;
459 
460  return rdpsnd_pulse_open_stream(device);
461 }
462 
463 static void rdpsnd_pulse_free(rdpsndDevicePlugin* device)
464 {
465  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
466 
467  if (!pulse)
468  return;
469 
470  rdpsnd_pulse_close(device);
471 
472  if (pulse->mainloop)
473  pa_threaded_mainloop_stop(pulse->mainloop);
474 
475  if (pulse->context)
476  {
477  pa_context_disconnect(pulse->context);
478  pa_context_unref(pulse->context);
479  pulse->context = NULL;
480  }
481 
482  if (pulse->mainloop)
483  {
484  pa_threaded_mainloop_free(pulse->mainloop);
485  pulse->mainloop = NULL;
486  }
487 
488  free(pulse->device_name);
489  free(pulse);
490 }
491 
492 static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
493  AUDIO_FORMAT* defaultFormat)
494 {
495  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
496 
497  if (!pulse || !defaultFormat)
498  return FALSE;
499 
500  *defaultFormat = *desired;
501  defaultFormat->data = NULL;
502  defaultFormat->cbSize = 0;
503  defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
504  if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX))
505  defaultFormat->nChannels = 2;
506  if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX))
507  defaultFormat->nSamplesPerSec = 44100;
508  if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16))
509  defaultFormat->wBitsPerSample = 16;
510 
511  defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
512  defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
513  return TRUE;
514 }
515 
516 BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
517 {
518  WINPR_ASSERT(device);
519  WINPR_ASSERT(format);
520 
521  switch (format->wFormatTag)
522  {
523  case WAVE_FORMAT_PCM:
524  if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
525  (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
526  (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
527  {
528  return TRUE;
529  }
530 
531  break;
532 
533  default:
534  break;
535  }
536 
537  return FALSE;
538 }
539 
540 static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device)
541 {
542  pa_operation* o = NULL;
543  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
544 
545  if (!rdpsnd_check_pulse(pulse, FALSE))
546  return 0;
547 
548  pa_threaded_mainloop_lock(pulse->mainloop);
549  o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
550  if (o)
551  pa_operation_unref(o);
552  pa_threaded_mainloop_unlock(pulse->mainloop);
553  return pulse->volume;
554 }
555 
556 static void rdpsnd_set_volume_success_cb(pa_context* c, int success, void* userdata)
557 {
558  rdpsndPulsePlugin* pulse = userdata;
559 
560  if (!rdpsnd_check_pulse(pulse, TRUE))
561  return;
562  WINPR_ASSERT(c);
563 
564  WLog_INFO(TAG, "%d", success);
565 }
566 
567 static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value)
568 {
569  pa_cvolume cv = { 0 };
570  pa_volume_t left = 0;
571  pa_volume_t right = 0;
572  pa_operation* operation = NULL;
573  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
574 
575  if (!rdpsnd_check_pulse(pulse, TRUE))
576  {
577  WLog_WARN(TAG, "%s called before pulse backend was initialized");
578  return FALSE;
579  }
580 
581  left = (pa_volume_t)(value & 0xFFFF);
582  right = (pa_volume_t)((value >> 16) & 0xFFFF);
583  pa_cvolume_init(&cv);
584  cv.channels = 2;
585  cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
586  cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
587  pa_threaded_mainloop_lock(pulse->mainloop);
588  operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream),
589  &cv, rdpsnd_set_volume_success_cb, pulse);
590 
591  if (operation)
592  pa_operation_unref(operation);
593 
594  pa_threaded_mainloop_unlock(pulse->mainloop);
595  return TRUE;
596 }
597 
598 static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
599 {
600  size_t length = 0;
601  void* pa_data = NULL;
602  int status = 0;
603  pa_usec_t latency = 0;
604  int negative = 0;
605  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
606 
607  if (!data)
608  return 0;
609 
610  pa_threaded_mainloop_lock(pulse->mainloop);
611 
612  if (!rdpsnd_check_pulse(pulse, TRUE))
613  {
614  pa_threaded_mainloop_unlock(pulse->mainloop);
615  // Discard this playback request and just attempt to reconnect the stream
616  WLog_DBG(TAG, "reconnecting playback stream");
617  rdpsnd_pulse_open_stream(device);
618  return 0;
619  }
620 
621  while (size > 0)
622  {
623  length = size;
624 
625  status = pa_stream_begin_write(pulse->stream, &pa_data, &length);
626 
627  if (status < 0)
628  break;
629 
630  memcpy(pa_data, data, length);
631 
632  status = pa_stream_write(pulse->stream, pa_data, length, NULL, 0LL, PA_SEEK_RELATIVE);
633 
634  if (status < 0)
635  {
636  break;
637  }
638 
639  data += length;
640  size -= length;
641  }
642 
643  if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0)
644  latency = 0;
645 
646  pa_threaded_mainloop_unlock(pulse->mainloop);
647 
648  const pa_usec_t val = latency / 1000;
649  if (val > UINT32_MAX)
650  return UINT32_MAX;
651  return (UINT32)val;
652 }
653 
654 static UINT rdpsnd_pulse_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
655 {
656  int status = 0;
657  DWORD flags = 0;
658  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
659  rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
660  COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = {
661  { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
662  { "reconnect_delay_seconds", COMMAND_LINE_VALUE_REQUIRED, "<reconnect_delay_seconds>", NULL,
663  NULL, -1, NULL, "reconnect_delay_seconds" },
664  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
665  };
666  flags =
667  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
668 
669  WINPR_ASSERT(pulse);
670  WINPR_ASSERT(args);
671 
672  status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_pulse_args, flags, pulse,
673  NULL, NULL);
674 
675  if (status < 0)
676  return ERROR_INVALID_DATA;
677 
678  arg = rdpsnd_pulse_args;
679 
680  do
681  {
682  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
683  continue;
684 
685  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
686  {
687  pulse->device_name = _strdup(arg->Value);
688 
689  if (!pulse->device_name)
690  return ERROR_OUTOFMEMORY;
691  }
692  CommandLineSwitchCase(arg, "reconnect_delay_seconds")
693  {
694  unsigned long val = strtoul(arg->Value, NULL, 0);
695 
696  if ((errno != 0) || (val > INT32_MAX))
697  return ERROR_INVALID_DATA;
698 
699  pulse->reconnect_delay_seconds = (time_t)val;
700  }
701  CommandLineSwitchEnd(arg)
702  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
703 
704  return CHANNEL_RC_OK;
705 }
706 
707 FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_rdpsnd_client_subsystem_entry(
709 {
710  const ADDIN_ARGV* args = NULL;
711  rdpsndPulsePlugin* pulse = NULL;
712  UINT ret = 0;
713 
714  WINPR_ASSERT(pEntryPoints);
715 
716  pulse = (rdpsndPulsePlugin*)calloc(1, sizeof(rdpsndPulsePlugin));
717 
718  if (!pulse)
719  return CHANNEL_RC_NO_MEMORY;
720 
721  pulse->device.Open = rdpsnd_pulse_open;
722  pulse->device.FormatSupported = rdpsnd_pulse_format_supported;
723  pulse->device.GetVolume = rdpsnd_pulse_get_volume;
724  pulse->device.SetVolume = rdpsnd_pulse_set_volume;
725  pulse->device.Play = rdpsnd_pulse_play;
726  pulse->device.Close = rdpsnd_pulse_close;
727  pulse->device.Free = rdpsnd_pulse_free;
728  pulse->device.DefaultFormat = rdpsnd_pulse_default_format;
729  args = pEntryPoints->args;
730 
731  if (args->argc > 1)
732  {
733  ret = rdpsnd_pulse_parse_addin_args(&pulse->device, args);
734 
735  if (ret != CHANNEL_RC_OK)
736  {
737  WLog_ERR(TAG, "error parsing arguments");
738  goto error;
739  }
740  }
741  pulse->reconnect_delay_seconds = 5;
742  pulse->reconnect_time = time(NULL);
743 
744  ret = CHANNEL_RC_NO_MEMORY;
745  pulse->mainloop = pa_threaded_mainloop_new();
746 
747  if (!pulse->mainloop)
748  goto error;
749 
750  pa_threaded_mainloop_lock(pulse->mainloop);
751 
752  if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
753  {
754  pa_threaded_mainloop_unlock(pulse->mainloop);
755  goto error;
756  }
757 
758  pa_threaded_mainloop_unlock(pulse->mainloop);
759 
760  if (!rdpsnd_pulse_context_connect((rdpsndDevicePlugin*)pulse))
761  goto error;
762 
763  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse);
764  return CHANNEL_RC_OK;
765 error:
766  rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse);
767  return ret;
768 }
Definition: client/rdpsnd.h:76