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