1 /**
2 * FreeRDP: A Remote Desktop Protocol Implementation
3 * Audio Output Virtual Channel
4 *
5 * Copyright 2009-2012 Jay Sorg
6 * Copyright 2010-2012 Vic Lee
7 * Copyright 2015 Thincast Technologies GmbH
8 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
9 * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <windows.h>
33 #include <mmsystem.h>
34
35 #include <winpr/crt.h>
36 #include <winpr/cmdline.h>
37 #include <winpr/sysinfo.h>
38
39 #include <freerdp/types.h>
40 #include <freerdp/channels/log.h>
41
42 #include "rdpsnd_main.h"
43
44 typedef struct rdpsnd_winmm_plugin rdpsndWinmmPlugin;
45
46 struct rdpsnd_winmm_plugin
47 {
48 rdpsndDevicePlugin device;
49
50 HWAVEOUT hWaveOut;
51 WAVEFORMATEX format;
52 UINT32 volume;
53 wLog* log;
54 UINT32 latency;
55 HANDLE hThread;
56 DWORD threadId;
57 CRITICAL_SECTION cs;
58 };
59
rdpsnd_winmm_convert_format(const AUDIO_FORMAT * in,WAVEFORMATEX * out)60 static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out)
61 {
62 if (!in || !out)
63 return FALSE;
64
65 ZeroMemory(out, sizeof(WAVEFORMATEX));
66 out->wFormatTag = WAVE_FORMAT_PCM;
67 out->nChannels = in->nChannels;
68 out->nSamplesPerSec = in->nSamplesPerSec;
69
70 switch (in->wFormatTag)
71 {
72 case WAVE_FORMAT_PCM:
73 out->wBitsPerSample = in->wBitsPerSample;
74 break;
75
76 default:
77 return FALSE;
78 }
79
80 out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8;
81 out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign;
82 return TRUE;
83 }
84
rdpsnd_winmm_set_format(rdpsndDevicePlugin * device,const AUDIO_FORMAT * format,UINT32 latency)85 static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
86 UINT32 latency)
87 {
88 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
89
90 winmm->latency = latency;
91 if (!rdpsnd_winmm_convert_format(format, &winmm->format))
92 return FALSE;
93
94 return TRUE;
95 }
96
waveOutProc(LPVOID lpParameter)97 static DWORD WINAPI waveOutProc(LPVOID lpParameter)
98 {
99 MSG msg;
100 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter;
101 while (GetMessage(&msg, NULL, 0, 0))
102 {
103 if (msg.message == MM_WOM_CLOSE)
104 {
105 /* device was closed - exit thread */
106 break;
107 }
108 else if (msg.message == MM_WOM_DONE)
109 {
110 /* free buffer */
111 LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam;
112 EnterCriticalSection(&winmm->cs);
113 waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR));
114 LeaveCriticalSection(&winmm->cs);
115 free(waveHdr->lpData);
116 free(waveHdr);
117 }
118 }
119
120 return 0;
121 }
122
rdpsnd_winmm_open(rdpsndDevicePlugin * device,const AUDIO_FORMAT * format,UINT32 latency)123 static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
124 UINT32 latency)
125 {
126 MMRESULT mmResult;
127 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
128
129 if (winmm->hWaveOut)
130 return TRUE;
131
132 if (!rdpsnd_winmm_set_format(device, format, latency))
133 return FALSE;
134
135 winmm->hThread = CreateThread(NULL, 0, waveOutProc, winmm, 0, &winmm->threadId);
136 if (!winmm->hThread)
137 {
138 WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError());
139 return FALSE;
140 }
141
142 mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format,
143 (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD);
144
145 if (mmResult != MMSYSERR_NOERROR)
146 {
147 WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult);
148 return FALSE;
149 }
150
151 mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume);
152
153 if (mmResult != MMSYSERR_NOERROR)
154 {
155 WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult);
156 return FALSE;
157 }
158
159 return TRUE;
160 }
161
rdpsnd_winmm_close(rdpsndDevicePlugin * device)162 static void rdpsnd_winmm_close(rdpsndDevicePlugin* device)
163 {
164 MMRESULT mmResult;
165 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
166
167 if (winmm->hWaveOut)
168 {
169 EnterCriticalSection(&winmm->cs);
170
171 mmResult = waveOutReset(winmm->hWaveOut);
172 if (mmResult != MMSYSERR_NOERROR)
173 WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult);
174
175 mmResult = waveOutClose(winmm->hWaveOut);
176 if (mmResult != MMSYSERR_NOERROR)
177 WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult);
178
179 LeaveCriticalSection(&winmm->cs);
180
181 winmm->hWaveOut = NULL;
182 }
183
184 if (winmm->hThread)
185 {
186 WaitForSingleObject(winmm->hThread, INFINITE);
187 CloseHandle(winmm->hThread);
188 winmm->hThread = NULL;
189 }
190 }
191
rdpsnd_winmm_free(rdpsndDevicePlugin * device)192 static void rdpsnd_winmm_free(rdpsndDevicePlugin* device)
193 {
194 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
195
196 if (winmm)
197 {
198 rdpsnd_winmm_close(device);
199 DeleteCriticalSection(&winmm->cs);
200 free(winmm);
201 }
202 }
203
rdpsnd_winmm_format_supported(rdpsndDevicePlugin * device,const AUDIO_FORMAT * format)204 static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
205 {
206 MMRESULT result;
207 WAVEFORMATEX out;
208
209 WINPR_UNUSED(device);
210 if (rdpsnd_winmm_convert_format(format, &out))
211 {
212 result = waveOutOpen(NULL, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY);
213
214 if (result == MMSYSERR_NOERROR)
215 return TRUE;
216 }
217
218 return FALSE;
219 }
220
rdpsnd_winmm_get_volume(rdpsndDevicePlugin * device)221 static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device)
222 {
223 MMRESULT mmResult;
224 DWORD dwVolume = UINT32_MAX;
225 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
226
227 if (!winmm->hWaveOut)
228 return dwVolume;
229
230 mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume);
231 if (mmResult != MMSYSERR_NOERROR)
232 {
233 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
234 dwVolume = UINT32_MAX;
235 }
236 return dwVolume;
237 }
238
rdpsnd_winmm_set_volume(rdpsndDevicePlugin * device,UINT32 value)239 static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value)
240 {
241 MMRESULT mmResult;
242 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
243 winmm->volume = value;
244
245 if (!winmm->hWaveOut)
246 return TRUE;
247
248 mmResult = waveOutSetVolume(winmm->hWaveOut, value);
249 if (mmResult != MMSYSERR_NOERROR)
250 {
251 WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
252 return FALSE;
253 }
254 return TRUE;
255 }
256
rdpsnd_winmm_play(rdpsndDevicePlugin * device,const BYTE * data,size_t size)257 static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
258 {
259 MMRESULT mmResult;
260 LPWAVEHDR lpWaveHdr;
261 rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
262
263 if (!winmm->hWaveOut)
264 return 0;
265
266 if (size > UINT32_MAX)
267 return 0;
268
269 lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR));
270 if (!lpWaveHdr)
271 return 0;
272
273 lpWaveHdr->dwFlags = 0;
274 lpWaveHdr->dwLoops = 0;
275 lpWaveHdr->lpData = malloc(size);
276 if (!lpWaveHdr->lpData)
277 goto fail;
278 memcpy(lpWaveHdr->lpData, data, size);
279 lpWaveHdr->dwBufferLength = (DWORD)size;
280
281 EnterCriticalSection(&winmm->cs);
282
283 mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
284 if (mmResult != MMSYSERR_NOERROR)
285 {
286 WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult);
287 goto failCS;
288 }
289
290 mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
291 if (mmResult != MMSYSERR_NOERROR)
292 {
293 WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult);
294 waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
295 goto failCS;
296 }
297
298 LeaveCriticalSection(&winmm->cs);
299 return winmm->latency;
300 failCS:
301 LeaveCriticalSection(&winmm->cs);
302 fail:
303 if (lpWaveHdr)
304 free(lpWaveHdr->lpData);
305 free(lpWaveHdr);
306 return 0;
307 }
308
rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin * device,ADDIN_ARGV * args)309 static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args)
310 {
311 WINPR_UNUSED(device);
312 WINPR_UNUSED(args);
313 }
314
315 #ifdef BUILTIN_CHANNELS
316 #define freerdp_rdpsnd_client_subsystem_entry winmm_freerdp_rdpsnd_client_subsystem_entry
317 #else
318 #define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry
319 #endif
320
321 /**
322 * Function description
323 *
324 * @return 0 on success, otherwise a Win32 error code
325 */
freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)326 UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)
327 {
328 ADDIN_ARGV* args;
329 rdpsndWinmmPlugin* winmm;
330
331 if (waveOutGetNumDevs() == 0)
332 {
333 WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!");
334 return ERROR_DEVICE_NOT_AVAILABLE;
335 }
336
337 winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin));
338 if (!winmm)
339 return CHANNEL_RC_NO_MEMORY;
340
341 winmm->device.Open = rdpsnd_winmm_open;
342 winmm->device.FormatSupported = rdpsnd_winmm_format_supported;
343 winmm->device.GetVolume = rdpsnd_winmm_get_volume;
344 winmm->device.SetVolume = rdpsnd_winmm_set_volume;
345 winmm->device.Play = rdpsnd_winmm_play;
346 winmm->device.Close = rdpsnd_winmm_close;
347 winmm->device.Free = rdpsnd_winmm_free;
348 winmm->log = WLog_Get(TAG);
349 InitializeCriticalSection(&winmm->cs);
350
351 args = pEntryPoints->args;
352 rdpsnd_winmm_parse_addin_args((rdpsndDevicePlugin*)winmm, args);
353 winmm->volume = 0xFFFFFFFF;
354 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm);
355 return CHANNEL_RC_OK;
356 }
357