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  const size_t 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 
356  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
357  UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
358  UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
359 
360  if (!rdpsnd_alsa_open_mixer(alsa))
361  return 0;
362 
363  for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
364  elem = snd_mixer_elem_next(elem))
365  {
366  if (snd_mixer_selem_has_playback_volume(elem))
367  {
368  snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
369  snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left);
370  snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right);
371  dwVolumeLeft =
372  (UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min));
373  dwVolumeRight =
374  (UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min));
375  break;
376  }
377  }
378 
379  return (dwVolumeLeft << 16) | dwVolumeRight;
380 }
381 
382 static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value)
383 {
384  long left = 0;
385  long right = 0;
386  long volume_min = 0;
387  long volume_max = 0;
388  long volume_left = 0;
389  long volume_right = 0;
390  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
391 
392  if (!rdpsnd_alsa_open_mixer(alsa))
393  return FALSE;
394 
395  left = (value & 0xFFFF);
396  right = ((value >> 16) & 0xFFFF);
397 
398  for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
399  elem = snd_mixer_elem_next(elem))
400  {
401  if (snd_mixer_selem_has_playback_volume(elem))
402  {
403  snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
404  volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF;
405  volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF;
406 
407  if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) <
408  0) ||
409  (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT,
410  volume_right) < 0))
411  {
412  WLog_ERR(TAG, "error setting the volume\n");
413  return FALSE;
414  }
415  }
416  }
417 
418  return TRUE;
419 }
420 
421 static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
422 {
423  UINT latency = 0;
424  size_t offset = 0;
425  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
426  WINPR_ASSERT(alsa);
427  WINPR_ASSERT(data || (size == 0));
428  const size_t frame_size = 1ull * alsa->actual_channels * alsa->aformat.wBitsPerSample / 8;
429  if (frame_size == 0)
430  return 0;
431 
432  while (offset < size)
433  {
434  snd_pcm_sframes_t status =
435  snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size);
436 
437  if (status < 0)
438  status = snd_pcm_recover(alsa->pcm_handle, (int)status, 0);
439 
440  if (status < 0)
441  {
442  WLog_ERR(TAG, "status: %d\n", status);
443  rdpsnd_alsa_close(device);
444  rdpsnd_alsa_open(device, NULL, alsa->latency);
445  break;
446  }
447 
448  offset += WINPR_ASSERTING_INT_CAST(size_t, status) * frame_size;
449  }
450 
451  {
452  snd_pcm_sframes_t available = 0;
453  snd_pcm_sframes_t delay = 0;
454  int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay);
455 
456  if ((rc == 0) && (available == 0)) /* Get [ms] from number of samples */
457  latency = (UINT32)MIN(UINT32_MAX, delay * 1000 / alsa->actual_rate);
458  }
459 
460  return latency + alsa->latency;
461 }
462 
468 static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
469 {
470  int status = 0;
471  DWORD flags = 0;
472  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
473  rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
474  COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
475  NULL, NULL, -1, NULL, "device" },
476  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
477  flags =
478  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
479  status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_alsa_args, flags, alsa, NULL,
480  NULL);
481 
482  if (status < 0)
483  {
484  WLog_ERR(TAG, "CommandLineParseArgumentsA failed!");
485  return CHANNEL_RC_INITIALIZATION_ERROR;
486  }
487 
488  arg = rdpsnd_alsa_args;
489 
490  do
491  {
492  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
493  continue;
494 
495  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
496  {
497  alsa->device_name = _strdup(arg->Value);
498 
499  if (!alsa->device_name)
500  return CHANNEL_RC_NO_MEMORY;
501  }
502  CommandLineSwitchEnd(arg)
503  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
504 
505  return CHANNEL_RC_OK;
506 }
507 
513 FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_rdpsnd_client_subsystem_entry(
515 {
516  const ADDIN_ARGV* args = NULL;
517  rdpsndAlsaPlugin* alsa = NULL;
518  UINT error = 0;
519  alsa = (rdpsndAlsaPlugin*)calloc(1, sizeof(rdpsndAlsaPlugin));
520 
521  if (!alsa)
522  {
523  WLog_ERR(TAG, "calloc failed!");
524  return CHANNEL_RC_NO_MEMORY;
525  }
526 
527  alsa->device.Open = rdpsnd_alsa_open;
528  alsa->device.FormatSupported = rdpsnd_alsa_format_supported;
529  alsa->device.GetVolume = rdpsnd_alsa_get_volume;
530  alsa->device.SetVolume = rdpsnd_alsa_set_volume;
531  alsa->device.Play = rdpsnd_alsa_play;
532  alsa->device.Close = rdpsnd_alsa_close;
533  alsa->device.Free = rdpsnd_alsa_free;
534  args = pEntryPoints->args;
535 
536  if (args->argc > 1)
537  {
538  if ((error = rdpsnd_alsa_parse_addin_args(&alsa->device, args)))
539  {
540  WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %" PRIu32 "", error);
541  goto error_parse_args;
542  }
543  }
544 
545  if (!alsa->device_name)
546  {
547  alsa->device_name = _strdup("default");
548 
549  if (!alsa->device_name)
550  {
551  WLog_ERR(TAG, "_strdup failed!");
552  error = CHANNEL_RC_NO_MEMORY;
553  goto error_strdup;
554  }
555  }
556 
557  alsa->pcm_handle = 0;
558  alsa->actual_rate = 22050;
559  alsa->format = SND_PCM_FORMAT_S16_LE;
560  alsa->actual_channels = 2;
561  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa);
562  return CHANNEL_RC_OK;
563 error_strdup:
564  free(alsa->device_name);
565 error_parse_args:
566  free(alsa);
567  return error;
568 }
Definition: client/rdpsnd.h:76