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