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 = nullptr;
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 =
88 snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, nullptr);
89 SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status);
90 /* Set number of channels */
91 status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels);
92 SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status);
93 /* Get maximum buffer size */
94 status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
95 SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status);
114 const size_t interrupts_per_sec_near = 50;
115 const size_t bytes_per_sec =
116 (1ull * alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels);
117 alsa->buffer_size = buffer_size_max;
118 alsa->period_size = (bytes_per_sec / interrupts_per_sec_near);
119
120 if (alsa->period_size > buffer_size_max)
121 {
122 WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n",
123 alsa->buffer_size, buffer_size_max);
124 alsa->period_size = (buffer_size_max / 8);
125 }
126
127 /* Set buffer size */
128 status =
129 snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size);
130 SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status);
131 /* Set period size */
132 status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size,
133 nullptr);
134 SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status);
135 status = snd_pcm_hw_params(alsa->pcm_handle, hw_params);
136 SND_PCM_CHECK("snd_pcm_hw_params", status);
137 snd_pcm_hw_params_free(hw_params);
138 return 0;
139}
140
141static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa)
142{
143 int status = 0;
144 snd_pcm_sw_params_t* sw_params = nullptr;
145 status = snd_pcm_sw_params_malloc(&sw_params);
146 SND_PCM_CHECK("snd_pcm_sw_params_malloc", status);
147 status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params);
148 SND_PCM_CHECK("snd_pcm_sw_params_current", status);
149 status = snd_pcm_sw_params_set_avail_min(
150 alsa->pcm_handle, sw_params, (1ULL * alsa->aformat.nChannels * alsa->actual_channels));
151 SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status);
152 status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params,
153 alsa->aformat.nBlockAlign);
154 SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status);
155 status = snd_pcm_sw_params(alsa->pcm_handle, sw_params);
156 SND_PCM_CHECK("snd_pcm_sw_params", status);
157 snd_pcm_sw_params_free(sw_params);
158 status = snd_pcm_prepare(alsa->pcm_handle);
159 SND_PCM_CHECK("snd_pcm_prepare", status);
160 return 0;
161}
162
163static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa)
164{
165 int status = 0;
166 snd_pcm_uframes_t buffer_size = 0;
167 snd_pcm_uframes_t period_size = 0;
168 status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size);
169 SND_PCM_CHECK("snd_pcm_get_params", status);
170 return 0;
171}
172
173static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa)
174{
175 snd_pcm_drop(alsa->pcm_handle);
176
177 if (rdpsnd_alsa_set_hw_params(alsa) < 0)
178 return -1;
179
180 if (rdpsnd_alsa_set_sw_params(alsa) < 0)
181 return -1;
182
183 return rdpsnd_alsa_validate_params(alsa);
184}
185
186static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
187 UINT32 latency)
188{
189 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
190
191 if (format)
192 {
193 alsa->aformat = *format;
194 alsa->actual_rate = format->nSamplesPerSec;
195 alsa->actual_channels = format->nChannels;
196
197 switch (format->wFormatTag)
198 {
199 case WAVE_FORMAT_PCM:
200 switch (format->wBitsPerSample)
201 {
202 case 8:
203 alsa->format = SND_PCM_FORMAT_S8;
204 break;
205
206 case 16:
207 alsa->format = SND_PCM_FORMAT_S16_LE;
208 break;
209
210 default:
211 return FALSE;
212 }
213
214 break;
215
216 default:
217 return FALSE;
218 }
219 }
220
221 alsa->latency = latency;
222 return (rdpsnd_alsa_set_params(alsa) == 0);
223}
224
225static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa)
226{
227 if (alsa && alsa->mixer_handle)
228 {
229 snd_mixer_close(alsa->mixer_handle);
230 alsa->mixer_handle = nullptr;
231 }
232}
233
234static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa)
235{
236 int status = 0;
237
238 if (alsa->mixer_handle)
239 return TRUE;
240
241 status = snd_mixer_open(&alsa->mixer_handle, 0);
242
243 if (status < 0)
244 {
245 WLog_ERR(TAG, "snd_mixer_open failed");
246 goto fail;
247 }
248
249 status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name);
250
251 if (status < 0)
252 {
253 WLog_ERR(TAG, "snd_mixer_attach failed");
254 goto fail;
255 }
256
257 status = snd_mixer_selem_register(alsa->mixer_handle, nullptr, nullptr);
258
259 if (status < 0)
260 {
261 WLog_ERR(TAG, "snd_mixer_selem_register failed");
262 goto fail;
263 }
264
265 status = snd_mixer_load(alsa->mixer_handle);
266
267 if (status < 0)
268 {
269 WLog_ERR(TAG, "snd_mixer_load failed");
270 goto fail;
271 }
272
273 return TRUE;
274fail:
275 rdpsnd_alsa_close_mixer(alsa);
276 return FALSE;
277}
278
279static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa)
280{
281 if (alsa && alsa->pcm_handle)
282 {
283 snd_pcm_drain(alsa->pcm_handle);
284 snd_pcm_close(alsa->pcm_handle);
285 alsa->pcm_handle = nullptr;
286 }
287}
288
289static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
290{
291 int mode = 0;
292 int status = 0;
293 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
294
295 if (alsa->pcm_handle)
296 return TRUE;
297
298 mode = 0;
299 /*mode |= SND_PCM_NONBLOCK;*/
300 status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode);
301
302 if (status < 0)
303 {
304 WLog_ERR(TAG, "snd_pcm_open failed");
305 return FALSE;
306 }
307
308 return rdpsnd_alsa_set_format(device, format, latency) && rdpsnd_alsa_open_mixer(alsa);
309}
310
311static void rdpsnd_alsa_close(rdpsndDevicePlugin* device)
312{
313 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
314
315 if (!alsa)
316 return;
317
318 rdpsnd_alsa_close_mixer(alsa);
319}
320
321static void rdpsnd_alsa_free(rdpsndDevicePlugin* device)
322{
323 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
324 rdpsnd_alsa_pcm_close(alsa);
325 rdpsnd_alsa_close_mixer(alsa);
326 free(alsa->device_name);
327 free(alsa);
328}
329
330static BOOL rdpsnd_alsa_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
331 const AUDIO_FORMAT* format)
332{
333 switch (format->wFormatTag)
334 {
335 case WAVE_FORMAT_PCM:
336 if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 &&
337 (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
338 (format->nChannels == 1 || format->nChannels == 2))
339 {
340 return TRUE;
341 }
342
343 break;
344 default:
345 break;
346 }
347
348 return FALSE;
349}
350
351static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device)
352{
353 long volume_min = 0;
354 long volume_max = 0;
355 long volume_left = 0;
356 long volume_right = 0;
357
358 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
359 UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
360 UINT32 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 return (dwVolumeLeft << 16) | dwVolumeRight;
382}
383
384static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value)
385{
386 long left = 0;
387 long right = 0;
388 long volume_min = 0;
389 long volume_max = 0;
390 long volume_left = 0;
391 long volume_right = 0;
392 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
393
394 if (!rdpsnd_alsa_open_mixer(alsa))
395 return FALSE;
396
397 left = (value & 0xFFFF);
398 right = ((value >> 16) & 0xFFFF);
399
400 for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
401 elem = snd_mixer_elem_next(elem))
402 {
403 if (snd_mixer_selem_has_playback_volume(elem))
404 {
405 snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
406 volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF;
407 volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF;
408
409 if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) <
410 0) ||
411 (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT,
412 volume_right) < 0))
413 {
414 WLog_ERR(TAG, "error setting the volume\n");
415 return FALSE;
416 }
417 }
418 }
419
420 return TRUE;
421}
422
423static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
424{
425 UINT latency = 0;
426 size_t offset = 0;
427 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
428 WINPR_ASSERT(alsa);
429 WINPR_ASSERT(data || (size == 0));
430 const size_t frame_size = 1ull * alsa->actual_channels * alsa->aformat.wBitsPerSample / 8;
431 if (frame_size == 0)
432 return 0;
433
434 while (offset < size)
435 {
436 snd_pcm_sframes_t status =
437 snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size);
438
439 if (status < 0)
440 status = snd_pcm_recover(alsa->pcm_handle, (int)status, 0);
441
442 if (status < 0)
443 {
444 WLog_ERR(TAG, "status: %ld\n", status);
445 rdpsnd_alsa_close(device);
446 rdpsnd_alsa_open(device, nullptr, alsa->latency);
447 break;
448 }
449
450 offset += WINPR_ASSERTING_INT_CAST(size_t, status) * frame_size;
451 }
452
453 {
454 snd_pcm_sframes_t available = 0;
455 snd_pcm_sframes_t delay = 0;
456 int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay);
457
458 if ((rc == 0) && (available == 0)) /* Get [ms] from number of samples */
459 latency = (UINT32)MIN(UINT32_MAX, delay * 1000 / alsa->actual_rate);
460 }
461
462 return latency + alsa->latency;
463}
464
470static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
471{
472 int status = 0;
473 DWORD flags = 0;
474 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
475 rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
476 COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = {
477 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
478 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
479 };
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,
483 nullptr, nullptr);
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)) != nullptr);
507
508 return CHANNEL_RC_OK;
509}
510
516FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_rdpsnd_client_subsystem_entry(
518{
519 const ADDIN_ARGV* args = nullptr;
520 rdpsndAlsaPlugin* alsa = nullptr;
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 = nullptr;
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;
566error_strdup:
567 free(alsa->device_name);
568error_parse_args:
569 free(alsa);
570 return error;
571}