FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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
43typedef struct
44{
45 rdpsndDevicePlugin device;
46
47 HWAVEOUT hWaveOut;
48 WAVEFORMATEX format;
49 UINT32 volume;
50 wLog* log;
51 UINT32 latency;
52 HANDLE hThread;
53 DWORD threadId;
55} rdpsndWinmmPlugin;
56
57static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out)
58{
59 if (!in || !out)
60 return FALSE;
61
62 ZeroMemory(out, sizeof(WAVEFORMATEX));
63 out->wFormatTag = WAVE_FORMAT_PCM;
64 out->nChannels = in->nChannels;
65 out->nSamplesPerSec = in->nSamplesPerSec;
66
67 switch (in->wFormatTag)
68 {
69 case WAVE_FORMAT_PCM:
70 out->wBitsPerSample = in->wBitsPerSample;
71 break;
72
73 default:
74 return FALSE;
75 }
76
77 out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8;
78 out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign;
79 return TRUE;
80}
81
82static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
83 UINT32 latency)
84{
85 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
86
87 winmm->latency = latency;
88 if (!rdpsnd_winmm_convert_format(format, &winmm->format))
89 return FALSE;
90
91 return TRUE;
92}
93
94static DWORD WINAPI waveOutProc(LPVOID lpParameter)
95{
96 MSG msg;
97 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter;
98 while (GetMessage(&msg, NULL, 0, 0))
99 {
100 if (msg.message == MM_WOM_CLOSE)
101 {
102 /* device was closed - exit thread */
103 break;
104 }
105 else if (msg.message == MM_WOM_DONE)
106 {
107 /* free buffer */
108 LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam;
109 EnterCriticalSection(&winmm->cs);
110 waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR));
111 LeaveCriticalSection(&winmm->cs);
112 free(waveHdr->lpData);
113 free(waveHdr);
114 }
115 }
116
117 return 0;
118}
119
120static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
121 UINT32 latency)
122{
123 MMRESULT mmResult;
124 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
125
126 if (winmm->hWaveOut)
127 return TRUE;
128
129 if (!rdpsnd_winmm_set_format(device, format, latency))
130 return FALSE;
131
132 winmm->hThread = CreateThread(NULL, 0, waveOutProc, winmm, 0, &winmm->threadId);
133 if (!winmm->hThread)
134 {
135 WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError());
136 return FALSE;
137 }
138
139 mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format,
140 (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD);
141
142 if (mmResult != MMSYSERR_NOERROR)
143 {
144 WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult);
145 return FALSE;
146 }
147
148 mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume);
149
150 if (mmResult != MMSYSERR_NOERROR)
151 {
152 WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult);
153 return FALSE;
154 }
155
156 return TRUE;
157}
158
159static void rdpsnd_winmm_close(rdpsndDevicePlugin* device)
160{
161 MMRESULT mmResult;
162 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
163
164 if (winmm->hWaveOut)
165 {
166 EnterCriticalSection(&winmm->cs);
167
168 mmResult = waveOutReset(winmm->hWaveOut);
169 if (mmResult != MMSYSERR_NOERROR)
170 WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult);
171
172 mmResult = waveOutClose(winmm->hWaveOut);
173 if (mmResult != MMSYSERR_NOERROR)
174 WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult);
175
176 LeaveCriticalSection(&winmm->cs);
177
178 winmm->hWaveOut = NULL;
179 }
180
181 if (winmm->hThread)
182 {
183 (void)WaitForSingleObject(winmm->hThread, INFINITE);
184 (void)CloseHandle(winmm->hThread);
185 winmm->hThread = NULL;
186 }
187}
188
189static void rdpsnd_winmm_free(rdpsndDevicePlugin* device)
190{
191 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
192
193 if (winmm)
194 {
195 rdpsnd_winmm_close(device);
196 DeleteCriticalSection(&winmm->cs);
197 free(winmm);
198 }
199}
200
201static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
202{
203 MMRESULT result;
204 WAVEFORMATEX out;
205
206 WINPR_UNUSED(device);
207 if (rdpsnd_winmm_convert_format(format, &out))
208 {
209 result = waveOutOpen(NULL, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY);
210
211 if (result == MMSYSERR_NOERROR)
212 return TRUE;
213 }
214
215 return FALSE;
216}
217
218static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device)
219{
220 MMRESULT mmResult;
221 DWORD dwVolume = UINT32_MAX;
222 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
223
224 if (!winmm->hWaveOut)
225 return dwVolume;
226
227 mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume);
228 if (mmResult != MMSYSERR_NOERROR)
229 {
230 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
231 dwVolume = UINT32_MAX;
232 }
233 return dwVolume;
234}
235
236static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value)
237{
238 MMRESULT mmResult;
239 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
240 winmm->volume = value;
241
242 if (!winmm->hWaveOut)
243 return TRUE;
244
245 mmResult = waveOutSetVolume(winmm->hWaveOut, value);
246 if (mmResult != MMSYSERR_NOERROR)
247 {
248 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
249 return FALSE;
250 }
251 return TRUE;
252}
253
254static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
255{
256 MMRESULT mmResult;
257 LPWAVEHDR lpWaveHdr;
258 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
259
260 if (!winmm->hWaveOut)
261 return 0;
262
263 if (size > UINT32_MAX)
264 return 0;
265
266 lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR));
267 if (!lpWaveHdr)
268 return 0;
269
270 lpWaveHdr->dwFlags = 0;
271 lpWaveHdr->dwLoops = 0;
272 lpWaveHdr->lpData = malloc(size);
273 if (!lpWaveHdr->lpData)
274 goto fail;
275 memcpy(lpWaveHdr->lpData, data, size);
276 lpWaveHdr->dwBufferLength = (DWORD)size;
277
278 EnterCriticalSection(&winmm->cs);
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 failCS;
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 failCS;
293 }
294
295 LeaveCriticalSection(&winmm->cs);
296 return winmm->latency;
297failCS:
298 LeaveCriticalSection(&winmm->cs);
299fail:
300 if (lpWaveHdr)
301 free(lpWaveHdr->lpData);
302 free(lpWaveHdr);
303 return 0;
304}
305
306static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
307{
308 WINPR_UNUSED(device);
309 WINPR_UNUSED(args);
310}
311
317FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_rdpsnd_client_subsystem_entry(
319{
320 const ADDIN_ARGV* args;
321 rdpsndWinmmPlugin* winmm;
322
323 if (waveOutGetNumDevs() == 0)
324 {
325 WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!");
326 return ERROR_DEVICE_NOT_AVAILABLE;
327 }
328
329 winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin));
330 if (!winmm)
331 return CHANNEL_RC_NO_MEMORY;
332
333 winmm->device.Open = rdpsnd_winmm_open;
334 winmm->device.FormatSupported = rdpsnd_winmm_format_supported;
335 winmm->device.GetVolume = rdpsnd_winmm_get_volume;
336 winmm->device.SetVolume = rdpsnd_winmm_set_volume;
337 winmm->device.Play = rdpsnd_winmm_play;
338 winmm->device.Close = rdpsnd_winmm_close;
339 winmm->device.Free = rdpsnd_winmm_free;
340 winmm->log = WLog_Get(TAG);
341 InitializeCriticalSection(&winmm->cs);
342
343 args = pEntryPoints->args;
344 rdpsnd_winmm_parse_addin_args(&winmm->device, args);
345 winmm->volume = 0xFFFFFFFF;
346 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm);
347 return CHANNEL_RC_OK;
348}