FreeRDP
audin_pulse.c
1 
22 #include <freerdp/config.h>
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <winpr/crt.h>
29 #include <winpr/cmdline.h>
30 #include <winpr/wlog.h>
31 
32 #include <pulse/pulseaudio.h>
33 
34 #include <freerdp/types.h>
35 #include <freerdp/addin.h>
36 #include <freerdp/freerdp.h>
37 #include <freerdp/codec/audio.h>
38 #include <freerdp/client/audin.h>
39 
40 #include "audin_main.h"
41 
42 typedef struct
43 {
44  IAudinDevice iface;
45 
46  char* device_name;
47  UINT32 frames_per_packet;
48  pa_threaded_mainloop* mainloop;
49  pa_context* context;
50  pa_sample_spec sample_spec;
51  pa_stream* stream;
52  AUDIO_FORMAT format;
53 
54  size_t bytes_per_frame;
55  size_t buffer_frames;
56 
57  AudinReceive receive;
58  void* user_data;
59 
60  rdpContext* rdpcontext;
61  wLog* log;
62 } AudinPulseDevice;
63 
64 static const char* pulse_context_state_string(pa_context_state_t state)
65 {
66  switch (state)
67  {
68  case PA_CONTEXT_UNCONNECTED:
69  return "PA_CONTEXT_UNCONNECTED";
70  case PA_CONTEXT_CONNECTING:
71  return "PA_CONTEXT_CONNECTING";
72  case PA_CONTEXT_AUTHORIZING:
73  return "PA_CONTEXT_AUTHORIZING";
74  case PA_CONTEXT_SETTING_NAME:
75  return "PA_CONTEXT_SETTING_NAME";
76  case PA_CONTEXT_READY:
77  return "PA_CONTEXT_READY";
78  case PA_CONTEXT_FAILED:
79  return "PA_CONTEXT_FAILED";
80  case PA_CONTEXT_TERMINATED:
81  return "PA_CONTEXT_TERMINATED";
82  default:
83  return "UNKNOWN";
84  }
85 }
86 
87 static const char* pulse_stream_state_string(pa_stream_state_t state)
88 {
89  switch (state)
90  {
91  case PA_STREAM_UNCONNECTED:
92  return "PA_STREAM_UNCONNECTED";
93  case PA_STREAM_CREATING:
94  return "PA_STREAM_CREATING";
95  case PA_STREAM_READY:
96  return "PA_STREAM_READY";
97  case PA_STREAM_FAILED:
98  return "PA_STREAM_FAILED";
99  case PA_STREAM_TERMINATED:
100  return "PA_STREAM_TERMINATED";
101  default:
102  return "UNKNOWN";
103  }
104 }
105 
106 static void audin_pulse_context_state_callback(pa_context* context, void* userdata)
107 {
108  AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
109  pa_context_state_t state = pa_context_get_state(context);
110 
111  WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state));
112  switch (state)
113  {
114  case PA_CONTEXT_READY:
115  case PA_CONTEXT_FAILED:
116  case PA_CONTEXT_TERMINATED:
117  pa_threaded_mainloop_signal(pulse->mainloop, 0);
118  break;
119 
120  default:
121  break;
122  }
123 }
124 
130 static UINT audin_pulse_connect(IAudinDevice* device)
131 {
132  pa_context_state_t state = PA_CONTEXT_FAILED;
133  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
134 
135  if (!pulse->context)
136  return ERROR_INVALID_PARAMETER;
137 
138  if (pa_context_connect(pulse->context, NULL, 0, NULL))
139  {
140  WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)",
141  pa_context_errno(pulse->context));
142  return ERROR_INTERNAL_ERROR;
143  }
144 
145  pa_threaded_mainloop_lock(pulse->mainloop);
146 
147  if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
148  {
149  pa_threaded_mainloop_unlock(pulse->mainloop);
150  WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)",
151  pa_context_errno(pulse->context));
152  return ERROR_INTERNAL_ERROR;
153  }
154 
155  for (;;)
156  {
157  state = pa_context_get_state(pulse->context);
158 
159  if (state == PA_CONTEXT_READY)
160  break;
161 
162  if (!PA_CONTEXT_IS_GOOD(state))
163  {
164  WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%s: %d)",
165  pulse_context_state_string(state), pa_context_errno(pulse->context));
166  pa_context_disconnect(pulse->context);
167  return ERROR_INVALID_STATE;
168  }
169 
170  pa_threaded_mainloop_wait(pulse->mainloop);
171  }
172 
173  pa_threaded_mainloop_unlock(pulse->mainloop);
174  WLog_Print(pulse->log, WLOG_DEBUG, "connected");
175  return CHANNEL_RC_OK;
176 }
177 
183 static UINT audin_pulse_free(IAudinDevice* device)
184 {
185  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
186 
187  if (!pulse)
188  return ERROR_INVALID_PARAMETER;
189 
190  if (pulse->mainloop)
191  {
192  pa_threaded_mainloop_stop(pulse->mainloop);
193  }
194 
195  if (pulse->context)
196  {
197  pa_context_disconnect(pulse->context);
198  pa_context_unref(pulse->context);
199  pulse->context = NULL;
200  }
201 
202  if (pulse->mainloop)
203  {
204  pa_threaded_mainloop_free(pulse->mainloop);
205  pulse->mainloop = NULL;
206  }
207 
208  free(pulse);
209  return CHANNEL_RC_OK;
210 }
211 
212 static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
213 {
214  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
215 
216  if (!pulse || !format)
217  return FALSE;
218 
219  if (!pulse->context)
220  return 0;
221 
222  switch (format->wFormatTag)
223  {
224  case WAVE_FORMAT_PCM:
225  if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
226  (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
227  (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
228  {
229  return TRUE;
230  }
231 
232  break;
233 
234  default:
235  return FALSE;
236  }
237 
238  return FALSE;
239 }
240 
246 static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
247  UINT32 FramesPerPacket)
248 {
249  pa_sample_spec sample_spec = { 0 };
250  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
251 
252  if (!pulse || !format)
253  return ERROR_INVALID_PARAMETER;
254 
255  if (!pulse->context)
256  return ERROR_INVALID_PARAMETER;
257 
258  if (FramesPerPacket > 0)
259  pulse->frames_per_packet = FramesPerPacket;
260 
261  sample_spec.rate = format->nSamplesPerSec;
262  sample_spec.channels = format->nChannels;
263 
264  switch (format->wFormatTag)
265  {
266  case WAVE_FORMAT_PCM: /* PCM */
267  switch (format->wBitsPerSample)
268  {
269  case 8:
270  sample_spec.format = PA_SAMPLE_U8;
271  break;
272 
273  case 16:
274  sample_spec.format = PA_SAMPLE_S16LE;
275  break;
276 
277  default:
278  return ERROR_INTERNAL_ERROR;
279  }
280 
281  break;
282 
283  default:
284  return ERROR_INTERNAL_ERROR;
285  }
286 
287  pulse->sample_spec = sample_spec;
288  pulse->format = *format;
289  return CHANNEL_RC_OK;
290 }
291 
292 static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata)
293 {
294  AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
295  WINPR_ASSERT(pulse);
296 
297  pa_stream_state_t state = pa_stream_get_state(stream);
298 
299  WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state));
300  switch (state)
301  {
302  case PA_STREAM_READY:
303  case PA_STREAM_FAILED:
304  case PA_STREAM_TERMINATED:
305  pa_threaded_mainloop_signal(pulse->mainloop, 0);
306  break;
307 
308  case PA_STREAM_UNCONNECTED:
309  case PA_STREAM_CREATING:
310  default:
311  break;
312  }
313 }
314 
315 static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
316 {
317  const void* data = NULL;
318  AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
319  UINT error = CHANNEL_RC_OK;
320  pa_stream_peek(stream, &data, &length);
321  error =
322  IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data);
323  pa_stream_drop(stream);
324 
325  if (error && pulse->rdpcontext)
326  setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error");
327 }
328 
334 static UINT audin_pulse_close(IAudinDevice* device)
335 {
336  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
337 
338  if (!pulse)
339  return ERROR_INVALID_PARAMETER;
340 
341  if (pulse->stream)
342  {
343  pa_threaded_mainloop_lock(pulse->mainloop);
344  pa_stream_disconnect(pulse->stream);
345  pa_stream_unref(pulse->stream);
346  pulse->stream = NULL;
347  pa_threaded_mainloop_unlock(pulse->mainloop);
348  }
349 
350  pulse->receive = NULL;
351  pulse->user_data = NULL;
352  return CHANNEL_RC_OK;
353 }
354 
360 static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
361 {
362  pa_stream_state_t state = PA_STREAM_FAILED;
363  pa_buffer_attr buffer_attr = { 0 };
364  AudinPulseDevice* pulse = (AudinPulseDevice*)device;
365 
366  if (!pulse || !receive || !user_data)
367  return ERROR_INVALID_PARAMETER;
368 
369  if (!pulse->context)
370  return ERROR_INVALID_PARAMETER;
371 
372  if (!pulse->sample_spec.rate || pulse->stream)
373  return ERROR_INVALID_PARAMETER;
374 
375  pulse->receive = receive;
376  pulse->user_data = user_data;
377  pa_threaded_mainloop_lock(pulse->mainloop);
378  pulse->stream = pa_stream_new(pulse->context, "freerdp_audin", &pulse->sample_spec, NULL);
379 
380  if (!pulse->stream)
381  {
382  pa_threaded_mainloop_unlock(pulse->mainloop);
383  WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)",
384  pa_context_errno(pulse->context));
385  return pa_context_errno(pulse->context);
386  }
387 
388  pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec);
389  pa_stream_set_state_callback(pulse->stream, audin_pulse_stream_state_callback, pulse);
390  pa_stream_set_read_callback(pulse->stream, audin_pulse_stream_request_callback, pulse);
391  buffer_attr.maxlength = (UINT32)-1;
392  buffer_attr.tlength = (UINT32)-1;
393  buffer_attr.prebuf = (UINT32)-1;
394  buffer_attr.minreq = (UINT32)-1;
395  /* 500ms latency */
396  const size_t frag = pulse->bytes_per_frame * pulse->frames_per_packet;
397  WINPR_ASSERT(frag <= UINT32_MAX);
398  buffer_attr.fragsize = (uint32_t)frag;
399 
400  if (buffer_attr.fragsize % pulse->format.nBlockAlign)
401  buffer_attr.fragsize +=
402  pulse->format.nBlockAlign - buffer_attr.fragsize % pulse->format.nBlockAlign;
403 
404  if (pa_stream_connect_record(pulse->stream, pulse->device_name, &buffer_attr,
405  PA_STREAM_ADJUST_LATENCY) < 0)
406  {
407  pa_threaded_mainloop_unlock(pulse->mainloop);
408  WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)",
409  pa_context_errno(pulse->context));
410  return pa_context_errno(pulse->context);
411  }
412 
413  while (pulse->stream)
414  {
415  state = pa_stream_get_state(pulse->stream);
416 
417  if (state == PA_STREAM_READY)
418  break;
419 
420  if (!PA_STREAM_IS_GOOD(state))
421  {
422  audin_pulse_close(device);
423  WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%s: %d)",
424  pulse_stream_state_string(state), pa_context_errno(pulse->context));
425  pa_threaded_mainloop_unlock(pulse->mainloop);
426  return pa_context_errno(pulse->context);
427  }
428 
429  pa_threaded_mainloop_wait(pulse->mainloop);
430  }
431 
432  pa_threaded_mainloop_unlock(pulse->mainloop);
433  pulse->buffer_frames = 0;
434  WLog_Print(pulse->log, WLOG_DEBUG, "connected");
435  return CHANNEL_RC_OK;
436 }
437 
443 static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, const ADDIN_ARGV* args)
444 {
445  int status = 0;
446  DWORD flags = 0;
447  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
448  AudinPulseDevice* pulse = device;
449  COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
450  NULL, NULL, -1, NULL, "audio device name" },
451  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
452 
453  flags =
454  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
455  status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags, pulse,
456  NULL, NULL);
457 
458  if (status < 0)
459  return ERROR_INVALID_PARAMETER;
460 
461  arg = audin_pulse_args;
462 
463  do
464  {
465  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
466  continue;
467 
468  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
469  {
470  pulse->device_name = _strdup(arg->Value);
471 
472  if (!pulse->device_name)
473  {
474  WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!");
475  return CHANNEL_RC_NO_MEMORY;
476  }
477  }
478  CommandLineSwitchEnd(arg)
479  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
480 
481  return CHANNEL_RC_OK;
482 }
483 
489 FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_audin_client_subsystem_entry(
491 {
492  const ADDIN_ARGV* args = NULL;
493  AudinPulseDevice* pulse = NULL;
494  UINT error = 0;
495  pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice));
496 
497  if (!pulse)
498  {
499  WLog_ERR(TAG, "calloc failed!");
500  return CHANNEL_RC_NO_MEMORY;
501  }
502 
503  pulse->log = WLog_Get(TAG);
504  pulse->iface.Open = audin_pulse_open;
505  pulse->iface.FormatSupported = audin_pulse_format_supported;
506  pulse->iface.SetFormat = audin_pulse_set_format;
507  pulse->iface.Close = audin_pulse_close;
508  pulse->iface.Free = audin_pulse_free;
509  pulse->rdpcontext = pEntryPoints->rdpcontext;
510  args = pEntryPoints->args;
511 
512  if ((error = audin_pulse_parse_addin_args(pulse, args)))
513  {
514  WLog_Print(pulse->log, WLOG_ERROR,
515  "audin_pulse_parse_addin_args failed with error %" PRIu32 "!", error);
516  goto error_out;
517  }
518 
519  pulse->mainloop = pa_threaded_mainloop_new();
520 
521  if (!pulse->mainloop)
522  {
523  WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed");
524  error = CHANNEL_RC_NO_MEMORY;
525  goto error_out;
526  }
527 
528  pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
529 
530  if (!pulse->context)
531  {
532  WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed");
533  error = CHANNEL_RC_NO_MEMORY;
534  goto error_out;
535  }
536 
537  pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse);
538 
539  if ((error = audin_pulse_connect(&pulse->iface)))
540  {
541  WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed");
542  goto error_out;
543  }
544 
545  if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface)))
546  {
547  WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
548  error);
549  goto error_out;
550  }
551 
552  return CHANNEL_RC_OK;
553 error_out:
554  audin_pulse_free(&pulse->iface);
555  return error;
556 }
Definition: client/audin.h:59