20#include <freerdp/config.h>
25#include <winpr/assert.h>
26#include <winpr/cast.h>
28#include <winpr/cmdline.h>
29#include <winpr/synch.h>
31#include <freerdp/types.h>
32#include <freerdp/channels/log.h>
36#include "rdpsnd_main.h"
38#define RDPSND_SDL_DEFAULT_LATENCY_MS 100
39#define RDPSND_SDL_MAX_LATENCY_MS 1000
41#define RDPSND_SDL_OVERRUN_SLACK_MS 200
43#define RDPSND_SDL_MIN_RATE 8000
44#define RDPSND_SDL_MAX_RATE 192000
48 rdpsndDevicePlugin device;
51 SDL_AudioStream* stream;
62#define RDPSND_SDL_LOCK_PROPERTY "FreeRDP.SDL.audio.lock"
64static void SDLCALL rdpsnd_sdl_destroy_lock(WINPR_ATTR_UNUSED
void* userdata,
void* value)
66 SDL_DestroyMutex(value);
70static SDL_Mutex* rdpsnd_sdl_audio_lock(
void)
72 const SDL_PropertiesID props = SDL_GetGlobalProperties();
73 if (!props || !SDL_LockProperties(props))
76 SDL_Mutex* lock = SDL_GetPointerProperty(props, RDPSND_SDL_LOCK_PROPERTY,
nullptr);
79 lock = SDL_CreateMutex();
81 if (lock && !SDL_SetPointerPropertyWithCleanup(props, RDPSND_SDL_LOCK_PROPERTY, lock,
82 rdpsnd_sdl_destroy_lock,
nullptr))
85 SDL_UnlockProperties(props);
90static BOOL rdpsnd_sdl_init_audio(
void)
92 SDL_Mutex* lock = rdpsnd_sdl_audio_lock();
97 const BOOL rc = SDL_InitSubSystem(SDL_INIT_AUDIO);
98 SDL_UnlockMutex(lock);
102static void rdpsnd_sdl_quit_audio(
void)
104 SDL_Mutex* lock = rdpsnd_sdl_audio_lock();
106 WLog_WARN(TAG,
"quitting SDL audio without the lock: %s", SDL_GetError());
109 SDL_QuitSubSystem(SDL_INIT_AUDIO);
110 SDL_UnlockMutex(lock);
114static BOOL rdpsnd_sdl_format_to_spec(
const AUDIO_FORMAT* format, SDL_AudioSpec* spec)
116 WINPR_ASSERT(format);
119 if ((format->wFormatTag != WAVE_FORMAT_PCM) || (format->nChannels < 1) ||
120 (format->nChannels > 8) || (format->nSamplesPerSec < RDPSND_SDL_MIN_RATE) ||
121 (format->nSamplesPerSec > RDPSND_SDL_MAX_RATE))
124 switch (format->wBitsPerSample)
127 spec->format = SDL_AUDIO_U8;
130 spec->format = SDL_AUDIO_S16LE;
136 spec->channels = format->nChannels;
137 spec->freq = WINPR_ASSERTING_INT_CAST(
int, format->nSamplesPerSec);
142static SDL_AudioDeviceID rdpsnd_sdl_find_device(
const char* name)
144 SDL_AudioDeviceID
id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
149 SDL_AudioDeviceID* devices = SDL_GetAudioPlaybackDevices(&count);
152 WLog_WARN(TAG,
"SDL_GetAudioPlaybackDevices: %s", SDL_GetError());
157 for (
int x = 0; x < count; x++)
159 const char* cur = SDL_GetAudioDeviceName(devices[x]);
160 if (cur && (strcmp(cur, name) == 0))
170 WLog_WARN(TAG,
"playback device '%s' not found, using the default device", name);
174static void rdpsnd_sdl_apply_volume(rdpsndSdlPlugin*
sdl)
182 const UINT32 left =
sdl->volume & 0xFFFFU;
183 const UINT32 right = (
sdl->volume >> 16) & 0xFFFFU;
184 const float gain = (float)MAX(left, right) / (float)0xFFFFU;
185 if (!SDL_SetAudioStreamGain(
sdl->stream, gain))
186 WLog_WARN(TAG,
"SDL_SetAudioStreamGain: %s", SDL_GetError());
190static BOOL rdpsnd_sdl_format_supported(rdpsndDevicePlugin* device,
const AUDIO_FORMAT* format)
192 WINPR_ASSERT(device);
193 WINPR_ASSERT(format);
195 SDL_AudioSpec spec = WINPR_C_ARRAY_INIT;
196 return rdpsnd_sdl_format_to_spec(format, &spec);
200static BOOL rdpsnd_sdl_default_format(rdpsndDevicePlugin* device,
const AUDIO_FORMAT* desired,
203 WINPR_ASSERT(device);
204 WINPR_ASSERT(defaultFormat);
207 *defaultFormat = *desired;
209 defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
210 defaultFormat->wBitsPerSample = 16;
211 defaultFormat->cbSize = 0;
212 defaultFormat->data =
nullptr;
214 if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > 8))
215 defaultFormat->nChannels = 2;
216 if ((defaultFormat->nSamplesPerSec < RDPSND_SDL_MIN_RATE) ||
217 (defaultFormat->nSamplesPerSec > RDPSND_SDL_MAX_RATE))
218 defaultFormat->nSamplesPerSec = 48000;
220 defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
221 defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
225static void rdpsnd_sdl_close(rdpsndDevicePlugin* device)
227 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
230 EnterCriticalSection(&
sdl->lock);
231 SDL_DestroyAudioStream(
sdl->stream);
232 sdl->stream =
nullptr;
233 LeaveCriticalSection(&
sdl->lock);
237static BOOL rdpsnd_sdl_open(rdpsndDevicePlugin* device,
const AUDIO_FORMAT* format, UINT32 latency)
239 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
241 WINPR_ASSERT(format);
243 SDL_AudioSpec spec = WINPR_C_ARRAY_INIT;
244 if (!rdpsnd_sdl_format_to_spec(format, &spec))
246 WLog_ERR(TAG,
"unsupported format %s", audio_format_get_tag_string(format->wFormatTag));
250 EnterCriticalSection(&
sdl->lock);
251 SDL_DestroyAudioStream(
sdl->stream);
253 (latency > 0) ? MIN(latency, RDPSND_SDL_MAX_LATENCY_MS) : RDPSND_SDL_DEFAULT_LATENCY_MS;
255 sdl->frame_size = WINPR_ASSERTING_INT_CAST(
size_t, SDL_AUDIO_FRAMESIZE(spec));
256 sdl->stream = SDL_OpenAudioDeviceStream(rdpsnd_sdl_find_device(
sdl->device_name), &spec,
261 WLog_ERR(TAG,
"SDL_OpenAudioDeviceStream: %s", SDL_GetError());
262 else if (!SDL_ResumeAudioStreamDevice(
sdl->stream))
263 WLog_ERR(TAG,
"SDL_ResumeAudioStreamDevice: %s", SDL_GetError());
266 rdpsnd_sdl_apply_volume(
sdl);
272 SDL_DestroyAudioStream(
sdl->stream);
273 sdl->stream =
nullptr;
275 LeaveCriticalSection(&
sdl->lock);
279static void rdpsnd_sdl_free(rdpsndDevicePlugin* device)
281 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
285 rdpsnd_sdl_close(device);
286 rdpsnd_sdl_quit_audio();
287 DeleteCriticalSection(&
sdl->lock);
288 free(
sdl->device_name);
293static UINT32 rdpsnd_sdl_get_volume(rdpsndDevicePlugin* device)
295 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
302static BOOL rdpsnd_sdl_set_volume(rdpsndDevicePlugin* device, UINT32 value)
304 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
307 EnterCriticalSection(&
sdl->lock);
309 rdpsnd_sdl_apply_volume(
sdl);
310 LeaveCriticalSection(&
sdl->lock);
316static BOOL rdpsnd_sdl_put_silence(rdpsndSdlPlugin*
sdl)
319 WINPR_ASSERT(
sdl->stream);
322 const size_t frameSize =
sdl->frame_size;
326 const size_t chunkSize =
sizeof(chunk) / frameSize * frameSize;
327 memset(chunk, SDL_GetSilenceValueForFormat(
sdl->spec.format),
sizeof(chunk));
330 1ULL *
sdl->latency * WINPR_ASSERTING_INT_CAST(
size_t,
sdl->spec.freq) / 1000 * frameSize;
333 const size_t len = MIN(left, chunkSize);
334 if (!SDL_PutAudioStreamData(
sdl->stream, chunk, WINPR_ASSERTING_INT_CAST(
int, len)))
336 WLog_WARN(TAG,
"SDL_PutAudioStreamData: %s", SDL_GetError());
345static UINT rdpsnd_sdl_bytes_to_ms(
const rdpsndSdlPlugin*
sdl,
size_t bytes)
349 const UINT64 ms = 1000ULL * bytes /
sdl->frame_size / (UINT64)
sdl->spec.freq;
350 return WINPR_ASSERTING_INT_CAST(UINT, ms);
355static UINT rdpsnd_sdl_queue(rdpsndSdlPlugin*
sdl,
const BYTE* data,
size_t size)
359 const size_t frameSize =
sdl->frame_size;
360 if (!
sdl->stream || (frameSize == 0) || (
sdl->spec.freq <= 0))
364 size -= size % frameSize;
365 if ((size == 0) || (size > INT32_MAX))
369 const int before = SDL_GetAudioStreamQueued(
sdl->stream);
370 const size_t drained = frameSize * WINPR_ASSERTING_INT_CAST(
size_t,
sdl->spec.freq) / 100;
371 if ((before >= 0) && ((
size_t)before < drained) && !rdpsnd_sdl_put_silence(
sdl))
375 const UINT queuedMs = (before > 0) ? rdpsnd_sdl_bytes_to_ms(
sdl, (
size_t)before) : 0;
376 const UINT sizeMs = rdpsnd_sdl_bytes_to_ms(
sdl, size);
377 if (queuedMs > sizeMs +
sdl->latency + RDPSND_SDL_OVERRUN_SLACK_MS)
379 WLog_DBG(TAG,
"%" PRIu32
" ms queued, dropping %" PRIu32
" ms", queuedMs, sizeMs);
383 if (!SDL_PutAudioStreamData(
sdl->stream, data, WINPR_ASSERTING_INT_CAST(
int, size)))
385 WLog_ERR(TAG,
"SDL_PutAudioStreamData: %s", SDL_GetError());
389 const int queued = SDL_GetAudioStreamQueued(
sdl->stream);
393 return rdpsnd_sdl_bytes_to_ms(
sdl, (
size_t)queued);
397static UINT rdpsnd_sdl_play(rdpsndDevicePlugin* device,
const BYTE* data,
size_t size)
399 rdpsndSdlPlugin*
sdl = (rdpsndSdlPlugin*)device;
403 EnterCriticalSection(&
sdl->lock);
404 const UINT latency = rdpsnd_sdl_queue(
sdl, data, size);
405 LeaveCriticalSection(&
sdl->lock);
410static UINT rdpsnd_sdl_parse_addin_args(rdpsndSdlPlugin*
sdl,
const ADDIN_ARGV* args)
413 {
"dev", COMMAND_LINE_VALUE_REQUIRED,
"<device>",
nullptr,
nullptr, -1,
nullptr,
"device" },
414 {
nullptr, 0,
nullptr,
nullptr,
nullptr, -1,
nullptr,
nullptr }
417 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
422 const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_sdl_args, flags,
423 sdl,
nullptr,
nullptr);
425 return ERROR_INVALID_DATA;
430 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
433 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg,
"dev")
435 free(
sdl->device_name);
436 sdl->device_name = _strdup(arg->Value);
437 if (!
sdl->device_name)
438 return ERROR_OUTOFMEMORY;
440 CommandLineSwitchEnd(arg)
441 }
while ((arg = CommandLineFindNextArgumentA(arg)) !=
nullptr);
443 return CHANNEL_RC_OK;
446FREERDP_ENTRY_POINT(UINT VCAPITYPE sdl_freerdp_rdpsnd_client_subsystem_entry(
449 WINPR_ASSERT(pEntryPoints);
450 WINPR_ASSERT(pEntryPoints->pRegisterRdpsndDevice);
452 rdpsndSdlPlugin*
sdl = calloc(1,
sizeof(rdpsndSdlPlugin));
454 return CHANNEL_RC_NO_MEMORY;
456 if (!rdpsnd_sdl_init_audio())
458 WLog_ERR(TAG,
"SDL_InitSubSystem(SDL_INIT_AUDIO): %s", SDL_GetError());
460 return ERROR_INTERNAL_ERROR;
463 InitializeCriticalSection(&
sdl->lock);
464 sdl->device.Open = rdpsnd_sdl_open;
465 sdl->device.FormatSupported = rdpsnd_sdl_format_supported;
466 sdl->device.GetVolume = rdpsnd_sdl_get_volume;
467 sdl->device.SetVolume = rdpsnd_sdl_set_volume;
468 sdl->device.Play = rdpsnd_sdl_play;
469 sdl->device.Close = rdpsnd_sdl_close;
470 sdl->device.Free = rdpsnd_sdl_free;
471 sdl->device.DefaultFormat = rdpsnd_sdl_default_format;
472 sdl->volume = 0xFFFFFFFF;
474 if (pEntryPoints->args)
476 const UINT rc = rdpsnd_sdl_parse_addin_args(
sdl, pEntryPoints->args);
477 if (rc != CHANNEL_RC_OK)
479 WLog_ERR(TAG,
"error parsing arguments");
480 rdpsnd_sdl_free(&
sdl->device);
485 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &
sdl->device);
486 return CHANNEL_RC_OK;