FreeRDP
rdpsnd_alsa.c
1 
23 #include <freerdp/config.h>
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include <winpr/crt.h>
30 #include <winpr/cmdline.h>
31 #include <winpr/sysinfo.h>
32 #include <winpr/collections.h>
33 
34 #include <alsa/asoundlib.h>
35 
36 #include <freerdp/types.h>
37 #include <freerdp/codec/dsp.h>
38 #include <freerdp/channels/log.h>
39 
40 #include "rdpsnd_main.h"
41 
42 typedef struct
43 {
44  rdpsndDevicePlugin device;
45 
46  UINT32 latency;
47  AUDIO_FORMAT aformat;
48  char* device_name;
49  snd_pcm_t* pcm_handle;
50  snd_mixer_t* mixer_handle;
51 
52  UINT32 actual_rate;
53  snd_pcm_format_t format;
54  UINT32 actual_channels;
55 
56  snd_pcm_uframes_t buffer_size;
57  snd_pcm_uframes_t period_size;
58 } rdpsndAlsaPlugin;
59 
60 #define SND_PCM_CHECK(_func, _status) \
61  do \
62  { \
63  if ((_status) < 0) \
64  { \
65  WLog_ERR(TAG, "%s: %d\n", (_func), (_status)); \
66  return -1; \
67  } \
68  } while (0)
69 
70 static int rdpsnd_alsa_set_hw_params(rdpsndAlsaPlugin* alsa)
71 {
72  int status = 0;
73  snd_pcm_hw_params_t* hw_params = NULL;
74  snd_pcm_uframes_t buffer_size_max = 0;
75  status = snd_pcm_hw_params_malloc(&hw_params);
76  SND_PCM_CHECK("snd_pcm_hw_params_malloc", status);
77  status = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params);
78  SND_PCM_CHECK("snd_pcm_hw_params_any", status);
79  /* Set interleaved read/write access */
80  status =
81  snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
82  SND_PCM_CHECK("snd_pcm_hw_params_set_access", status);
83  /* Set sample format */
84  status = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, alsa->format);
85  SND_PCM_CHECK("snd_pcm_hw_params_set_format", status);
86  /* Set sample rate */
87  status = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, NULL);
88  SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status);
89  /* Set number of channels */
90  status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels);
91  SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status);
92  /* Get maximum buffer size */
93  status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
94  SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status);
113  int interrupts_per_sec_near = 50;
114  const size_t bytes_per_sec =
115  (1ull * alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels);
116  alsa->buffer_size = buffer_size_max;
117  alsa->period_size = (bytes_per_sec / interrupts_per_sec_near);
118 
119  if (alsa->period_size > buffer_size_max)
120  {
121  WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n",
122  alsa->buffer_size, buffer_size_max);
123  alsa->period_size = (buffer_size_max / 8);
124  }
125 
126  /* Set buffer size */
127  status =
128  snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size);
129  SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status);
130  /* Set period size */
131  status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size,
132  NULL);
133  SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status);
134  status = snd_pcm_hw_params(alsa->pcm_handle, hw_params);
135  SND_PCM_CHECK("snd_pcm_hw_params", status);
136  snd_pcm_hw_params_free(hw_params);
137  return 0;
138 }
139 
140 static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa)
141 {
142  int status = 0;
143  snd_pcm_sw_params_t* sw_params = NULL;
144  status = snd_pcm_sw_params_malloc(&sw_params);
145  SND_PCM_CHECK("snd_pcm_sw_params_malloc", status);
146  status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params);
147  SND_PCM_CHECK("snd_pcm_sw_params_current", status);
148  status = snd_pcm_sw_params_set_avail_min(
149  alsa->pcm_handle, sw_params, (1ULL * alsa->aformat.nChannels * alsa->actual_channels));
150  SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status);
151  status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params,
152  alsa->aformat.nBlockAlign);
153  SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status);
154  status = snd_pcm_sw_params(alsa->pcm_handle, sw_params);
155  SND_PCM_CHECK("snd_pcm_sw_params", status);
156  snd_pcm_sw_params_free(sw_params);
157  status = snd_pcm_prepare(alsa->pcm_handle);
158  SND_PCM_CHECK("snd_pcm_prepare", status);
159  return 0;
160 }
161 
162 static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa)
163 {
164  int status = 0;
165  snd_pcm_uframes_t buffer_size = 0;
166  snd_pcm_uframes_t period_size = 0;
167  status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size);
168  SND_PCM_CHECK("snd_pcm_get_params", status);
169  return 0;
170 }
171 
172 static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa)
173 {
174  snd_pcm_drop(alsa->pcm_handle);
175 
176  if (rdpsnd_alsa_set_hw_params(alsa) < 0)
177  return -1;
178 
179  if (rdpsnd_alsa_set_sw_params(alsa) < 0)
180  return -1;
181 
182  return rdpsnd_alsa_validate_params(alsa);
183 }
184 
185 static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
186  UINT32 latency)
187 {
188  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
189 
190  if (format)
191  {
192  alsa->aformat = *format;
193  alsa->actual_rate = format->nSamplesPerSec;
194  alsa->actual_channels = format->nChannels;
195 
196  switch (format->wFormatTag)
197  {
198  case WAVE_FORMAT_PCM:
199  switch (format->wBitsPerSample)
200  {
201  case 8:
202  alsa->format = SND_PCM_FORMAT_S8;
203  break;
204 
205  case 16:
206  alsa->format = SND_PCM_FORMAT_S16_LE;
207  break;
208 
209  default:
210  return FALSE;
211  }
212 
213  break;
214 
215  default:
216  return FALSE;
217  }
218  }
219 
220  alsa->latency = latency;
221  return (rdpsnd_alsa_set_params(alsa) == 0);
222 }
223 
224 static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa)
225 {
226  if (alsa && alsa->mixer_handle)
227  {
228  snd_mixer_close(alsa->mixer_handle);
229  alsa->mixer_handle = NULL;
230  }
231 }
232 
233 static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa)
234 {
235  int status = 0;
236 
237  if (alsa->mixer_handle)
238  return TRUE;
239 
240  status = snd_mixer_open(&alsa->mixer_handle, 0);
241 
242  if (status < 0)
243  {
244  WLog_ERR(TAG, "snd_mixer_open failed");
245  goto fail;
246  }
247 
248  status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name);
249 
250  if (status < 0)
251  {
252  WLog_ERR(TAG, "snd_mixer_attach failed");
253  goto fail;
254  }
255 
256  status = snd_mixer_selem_register(alsa->mixer_handle, NULL, NULL);
257 
258  if (status < 0)
259  {
260  WLog_ERR(TAG, "snd_mixer_selem_register failed");
261  goto fail;
262  }
263 
264  status = snd_mixer_load(alsa->mixer_handle);
265 
266  if (status < 0)
267  {
268  WLog_ERR(TAG, "snd_mixer_load failed");
269  goto fail;
270  }
271 
272  return TRUE;
273 fail:
274  rdpsnd_alsa_close_mixer(alsa);
275  return FALSE;
276 }
277 
278 static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa)
279 {
280  if (alsa && alsa->pcm_handle)
281  {
282  snd_pcm_drain(alsa->pcm_handle);
283  snd_pcm_close(alsa->pcm_handle);
284  alsa->pcm_handle = 0;
285  }
286 }
287 
288 static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
289 {
290  int mode = 0;
291  int status = 0;
292  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
293 
294  if (alsa->pcm_handle)
295  return TRUE;
296 
297  mode = 0;
298  /*mode |= SND_PCM_NONBLOCK;*/
299  status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode);
300 
301  if (status < 0)
302  {
303  WLog_ERR(TAG, "snd_pcm_open failed");
304  return FALSE;
305  }
306 
307  return rdpsnd_alsa_set_format(device, format, latency) && rdpsnd_alsa_open_mixer(alsa);
308 }
309 
310 static void rdpsnd_alsa_close(rdpsndDevicePlugin* device)
311 {
312  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
313 
314  if (!alsa)
315  return;
316 
317  rdpsnd_alsa_close_mixer(alsa);
318 }
319 
320 static void rdpsnd_alsa_free(rdpsndDevicePlugin* device)
321 {
322  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
323  rdpsnd_alsa_pcm_close(alsa);
324  rdpsnd_alsa_close_mixer(alsa);
325  free(alsa->device_name);
326  free(alsa);
327 }
328 
329 static BOOL rdpsnd_alsa_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
330 {
331  switch (format->wFormatTag)
332  {
333  case WAVE_FORMAT_PCM:
334  if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 &&
335  (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
336  (format->nChannels == 1 || format->nChannels == 2))
337  {
338  return TRUE;
339  }
340 
341  break;
342  default:
343  break;
344  }
345 
346  return FALSE;
347 }
348 
349 static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device)
350 {
351  long volume_min = 0;
352  long volume_max = 0;
353  long volume_left = 0;
354  long volume_right = 0;
355  UINT32 dwVolume = 0;
356  UINT16 dwVolumeLeft = 0;
357  UINT16 dwVolumeRight = 0;
358  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
359  dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
360  dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
361 
362  if (!rdpsnd_alsa_open_mixer(alsa))
363  return 0;
364 
365  for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
366  elem = snd_mixer_elem_next(elem))
367  {
368  if (snd_mixer_selem_has_playback_volume(elem))
369  {
370  snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
371  snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left);
372  snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right);
373  dwVolumeLeft =
374  (UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min));
375  dwVolumeRight =
376  (UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min));
377  break;
378  }
379  }
380 
381  dwVolume = (dwVolumeLeft << 16) | dwVolumeRight;
382  return dwVolume;
383 }
384 
385 static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value)
386 {
387  long left = 0;
388  long right = 0;
389  long volume_min = 0;
390  long volume_max = 0;
391  long volume_left = 0;
392  long volume_right = 0;
393  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
394 
395  if (!rdpsnd_alsa_open_mixer(alsa))
396  return FALSE;
397 
398  left = (value & 0xFFFF);
399  right = ((value >> 16) & 0xFFFF);
400 
401  for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
402  elem = snd_mixer_elem_next(elem))
403  {
404  if (snd_mixer_selem_has_playback_volume(elem))
405  {
406  snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
407  volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF;
408  volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF;
409 
410  if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) <
411  0) ||
412  (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT,
413  volume_right) < 0))
414  {
415  WLog_ERR(TAG, "error setting the volume\n");
416  return FALSE;
417  }
418  }
419  }
420 
421  return TRUE;
422 }
423 
424 static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
425 {
426  UINT latency = 0;
427  size_t offset = 0;
428  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
429  WINPR_ASSERT(alsa);
430  WINPR_ASSERT(data || (size == 0));
431  const size_t frame_size = 1ull * alsa->actual_channels * alsa->aformat.wBitsPerSample / 8;
432  if (frame_size == 0)
433  return 0;
434 
435  while (offset < size)
436  {
437  snd_pcm_sframes_t status =
438  snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size);
439 
440  if (status < 0)
441  status = snd_pcm_recover(alsa->pcm_handle, (int)status, 0);
442 
443  if (status < 0)
444  {
445  WLog_ERR(TAG, "status: %d\n", status);
446  rdpsnd_alsa_close(device);
447  rdpsnd_alsa_open(device, NULL, alsa->latency);
448  break;
449  }
450 
451  offset += status * frame_size;
452  }
453 
454  {
455  snd_pcm_sframes_t available = 0;
456  snd_pcm_sframes_t delay = 0;
457  int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay);
458 
459  if ((rc == 0) && (available == 0)) /* Get [ms] from number of samples */
460  latency = (UINT32)MIN(UINT32_MAX, delay * 1000 / alsa->actual_rate);
461  }
462 
463  return latency + alsa->latency;
464 }
465 
471 static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
472 {
473  int status = 0;
474  DWORD flags = 0;
475  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
476  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
477  COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
478  NULL, NULL, -1, NULL, "device" },
479  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
480  flags =
481  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
482  status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_alsa_args, flags, alsa, NULL,
483  NULL);
484 
485  if (status < 0)
486  {
487  WLog_ERR(TAG, "CommandLineParseArgumentsA failed!");
488  return CHANNEL_RC_INITIALIZATION_ERROR;
489  }
490 
491  arg = rdpsnd_alsa_args;
492 
493  do
494  {
495  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
496  continue;
497 
498  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
499  {
500  alsa->device_name = _strdup(arg->Value);
501 
502  if (!alsa->device_name)
503  return CHANNEL_RC_NO_MEMORY;
504  }
505  CommandLineSwitchEnd(arg)
506  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
507 
508  return CHANNEL_RC_OK;
509 }
510 
516 FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_rdpsnd_client_subsystem_entry(
518 {
519  const ADDIN_ARGV* args = NULL;
520  rdpsndAlsaPlugin* alsa = NULL;
521  UINT error = 0;
522  alsa = (rdpsndAlsaPlugin*)calloc(1, sizeof(rdpsndAlsaPlugin));
523 
524  if (!alsa)
525  {
526  WLog_ERR(TAG, "calloc failed!");
527  return CHANNEL_RC_NO_MEMORY;
528  }
529 
530  alsa->device.Open = rdpsnd_alsa_open;
531  alsa->device.FormatSupported = rdpsnd_alsa_format_supported;
532  alsa->device.GetVolume = rdpsnd_alsa_get_volume;
533  alsa->device.SetVolume = rdpsnd_alsa_set_volume;
534  alsa->device.Play = rdpsnd_alsa_play;
535  alsa->device.Close = rdpsnd_alsa_close;
536  alsa->device.Free = rdpsnd_alsa_free;
537  args = pEntryPoints->args;
538 
539  if (args->argc > 1)
540  {
541  if ((error = rdpsnd_alsa_parse_addin_args(&alsa->device, args)))
542  {
543  WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %" PRIu32 "", error);
544  goto error_parse_args;
545  }
546  }
547 
548  if (!alsa->device_name)
549  {
550  alsa->device_name = _strdup("default");
551 
552  if (!alsa->device_name)
553  {
554  WLog_ERR(TAG, "_strdup failed!");
555  error = CHANNEL_RC_NO_MEMORY;
556  goto error_strdup;
557  }
558  }
559 
560  alsa->pcm_handle = 0;
561  alsa->actual_rate = 22050;
562  alsa->format = SND_PCM_FORMAT_S16_LE;
563  alsa->actual_channels = 2;
564  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa);
565  return CHANNEL_RC_OK;
566 error_strdup:
567  free(alsa->device_name);
568 error_parse_args:
569  free(alsa);
570  return error;
571 }
Definition: client/rdpsnd.h:76