FreeRDP
Loading...
Searching...
No Matches
rdpsnd_winmm.c
1
24#include <freerdp/config.h>
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include <windows.h>
31#include <mmsystem.h>
32
33#include <winpr/crt.h>
34#include <winpr/wtsapi.h>
35#include <winpr/cmdline.h>
36#include <winpr/sysinfo.h>
37
38#include <freerdp/types.h>
39#include <freerdp/channels/log.h>
40
41#include "rdpsnd_main.h"
42
43/* Silence queued in front of the audio whenever playback starts from an
44 * empty device, so audio arriving a little late does not leave the device
45 * without data (an audible gap). /sound:latency:<ms> overrides it. */
46#define WINMM_DEFAULT_PREBUFFER_MS 100
47
48typedef struct
49{
50 rdpsndDevicePlugin device;
51
52 HWAVEOUT hWaveOut;
53 WAVEFORMATEX format;
54 UINT32 volume;
55 wLog* log;
56 UINT32 latency;
57 HANDLE hThread;
58 DWORD threadId;
60 size_t pending; /* buffers written and not played yet, guarded by cs */
61} rdpsndWinmmPlugin;
62
63static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out)
64{
65 if (!in || !out)
66 return FALSE;
67
68 ZeroMemory(out, sizeof(WAVEFORMATEX));
69 out->wFormatTag = WAVE_FORMAT_PCM;
70 out->nChannels = in->nChannels;
71 out->nSamplesPerSec = in->nSamplesPerSec;
72
73 switch (in->wFormatTag)
74 {
75 case WAVE_FORMAT_PCM:
76 out->wBitsPerSample = in->wBitsPerSample;
77 break;
78
79 default:
80 return FALSE;
81 }
82
83 out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8;
84 out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign;
85 return TRUE;
86}
87
88static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
89 UINT32 latency)
90{
91 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
92
93 winmm->latency = latency;
94 if (!rdpsnd_winmm_convert_format(format, &winmm->format))
95 return FALSE;
96
97 return TRUE;
98}
99
100static DWORD WINAPI waveOutProc(LPVOID lpParameter)
101{
102 MSG msg;
103 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter;
104 while (GetMessage(&msg, nullptr, 0, 0))
105 {
106 if (msg.message == MM_WOM_CLOSE)
107 {
108 /* device was closed - exit thread */
109 break;
110 }
111 else if (msg.message == MM_WOM_DONE)
112 {
113 /* free buffer */
114 LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam;
115 EnterCriticalSection(&winmm->cs);
116 waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR));
117 if (winmm->pending > 0)
118 winmm->pending--;
119 LeaveCriticalSection(&winmm->cs);
120 free(waveHdr->lpData);
121 free(waveHdr);
122 }
123 }
124
125 return 0;
126}
127
128static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
129 UINT32 latency)
130{
131 MMRESULT mmResult;
132 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
133
134 if (winmm->hWaveOut)
135 return TRUE;
136
137 if (!rdpsnd_winmm_set_format(device, format, latency))
138 return FALSE;
139
140 winmm->pending = 0;
141 winmm->hThread = CreateThread(nullptr, 0, waveOutProc, winmm, 0, &winmm->threadId);
142 if (!winmm->hThread)
143 {
144 WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError());
145 return FALSE;
146 }
147
148 mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format,
149 (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD);
150
151 if (mmResult != MMSYSERR_NOERROR)
152 {
153 WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult);
154 return FALSE;
155 }
156
157 mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume);
158
159 if (mmResult != MMSYSERR_NOERROR)
160 {
161 WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult);
162 return FALSE;
163 }
164
165 return TRUE;
166}
167
168static void rdpsnd_winmm_close(rdpsndDevicePlugin* device)
169{
170 MMRESULT mmResult;
171 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
172
173 if (winmm->hWaveOut)
174 {
175 EnterCriticalSection(&winmm->cs);
176
177 mmResult = waveOutReset(winmm->hWaveOut);
178 if (mmResult != MMSYSERR_NOERROR)
179 WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult);
180
181 mmResult = waveOutClose(winmm->hWaveOut);
182 if (mmResult != MMSYSERR_NOERROR)
183 WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult);
184
185 LeaveCriticalSection(&winmm->cs);
186
187 winmm->hWaveOut = nullptr;
188 }
189
190 if (winmm->hThread)
191 {
192 (void)WaitForSingleObject(winmm->hThread, INFINITE);
193 (void)CloseHandle(winmm->hThread);
194 winmm->hThread = nullptr;
195 }
196}
197
198static void rdpsnd_winmm_free(rdpsndDevicePlugin* device)
199{
200 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
201
202 if (winmm)
203 {
204 rdpsnd_winmm_close(device);
205 DeleteCriticalSection(&winmm->cs);
206 free(winmm);
207 }
208}
209
210static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
211{
212 MMRESULT result;
213 WAVEFORMATEX out;
214
215 WINPR_UNUSED(device);
216 if (rdpsnd_winmm_convert_format(format, &out))
217 {
218 result = waveOutOpen(nullptr, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY);
219
220 if (result == MMSYSERR_NOERROR)
221 return TRUE;
222 }
223
224 return FALSE;
225}
226
227static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device)
228{
229 MMRESULT mmResult;
230 DWORD dwVolume = UINT32_MAX;
231 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
232
233 if (!winmm->hWaveOut)
234 return dwVolume;
235
236 mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume);
237 if (mmResult != MMSYSERR_NOERROR)
238 {
239 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
240 dwVolume = UINT32_MAX;
241 }
242 return dwVolume;
243}
244
245static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value)
246{
247 MMRESULT mmResult;
248 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
249 winmm->volume = value;
250
251 if (!winmm->hWaveOut)
252 return TRUE;
253
254 mmResult = waveOutSetVolume(winmm->hWaveOut, value);
255 if (mmResult != MMSYSERR_NOERROR)
256 {
257 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
258 return FALSE;
259 }
260 return TRUE;
261}
262
263/* queue size bytes of data, or of silence if data is nullptr; called with cs held */
264static BOOL rdpsnd_winmm_write(rdpsndWinmmPlugin* winmm, const BYTE* data, size_t size)
265{
266 MMRESULT mmResult;
267 LPWAVEHDR lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR));
268 if (!lpWaveHdr)
269 return FALSE;
270
271 lpWaveHdr->lpData = malloc(size);
272 if (!lpWaveHdr->lpData)
273 goto fail;
274 if (data)
275 memcpy(lpWaveHdr->lpData, data, size);
276 else
277 memset(lpWaveHdr->lpData, (winmm->format.wBitsPerSample == 8) ? 0x80 : 0, size);
278 lpWaveHdr->dwBufferLength = (DWORD)size;
279
280 mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
281 if (mmResult != MMSYSERR_NOERROR)
282 {
283 WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult);
284 goto fail;
285 }
286
287 mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
288 if (mmResult != MMSYSERR_NOERROR)
289 {
290 WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult);
291 waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
292 goto fail;
293 }
294
295 winmm->pending++;
296 return TRUE;
297fail:
298 free(lpWaveHdr->lpData);
299 free(lpWaveHdr);
300 return FALSE;
301}
302
303static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
304{
305 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
306
307 if (!winmm->hWaveOut)
308 return 0;
309
310 if (size > UINT32_MAX)
311 return 0;
312
313 EnterCriticalSection(&winmm->cs);
314
315 /* The device plays whatever it has queued and nothing more: when it ran
316 * out (or has not started), put some silence in front of this audio so
317 * that the next packets have time to arrive before it is played out. */
318 if (winmm->pending == 0)
319 {
320 const UINT32 ms = (winmm->latency > 0) ? winmm->latency : WINMM_DEFAULT_PREBUFFER_MS;
321 const size_t align = MAX(winmm->format.nBlockAlign, 1);
322 const size_t silence = (1ull * winmm->format.nAvgBytesPerSec * ms / 1000) / align * align;
323
324 if ((silence > 0) && !rdpsnd_winmm_write(winmm, nullptr, silence))
325 WLog_Print(winmm->log, WLOG_WARN, "could not queue %" PRIu32 " ms of silence", ms);
326 }
327
328 const BOOL rc = rdpsnd_winmm_write(winmm, data, size);
329 LeaveCriticalSection(&winmm->cs);
330 return rc ? winmm->latency : 0;
331}
332
333static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
334{
335 WINPR_UNUSED(device);
336 WINPR_UNUSED(args);
337}
338
344FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_rdpsnd_client_subsystem_entry(
346{
347 const ADDIN_ARGV* args;
348 rdpsndWinmmPlugin* winmm;
349
350 if (waveOutGetNumDevs() == 0)
351 {
352 WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!");
353 return ERROR_DEVICE_NOT_AVAILABLE;
354 }
355
356 winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin));
357 if (!winmm)
358 return CHANNEL_RC_NO_MEMORY;
359
360 winmm->device.Open = rdpsnd_winmm_open;
361 winmm->device.FormatSupported = rdpsnd_winmm_format_supported;
362 winmm->device.GetVolume = rdpsnd_winmm_get_volume;
363 winmm->device.SetVolume = rdpsnd_winmm_set_volume;
364 winmm->device.Play = rdpsnd_winmm_play;
365 winmm->device.Close = rdpsnd_winmm_close;
366 winmm->device.Free = rdpsnd_winmm_free;
367 winmm->log = WLog_Get(TAG);
368 InitializeCriticalSection(&winmm->cs);
369
370 args = pEntryPoints->args;
371 rdpsnd_winmm_parse_addin_args(&winmm->device, args);
372 winmm->volume = 0xFFFFFFFF;
373 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm);
374 return CHANNEL_RC_OK;
375}