FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
audin_winmm.c
1
22#include <freerdp/config.h>
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include <windows.h>
29#include <mmsystem.h>
30
31#include <winpr/crt.h>
32#include <winpr/wtsapi.h>
33#include <winpr/cmdline.h>
34#include <freerdp/freerdp.h>
35#include <freerdp/addin.h>
36#include <freerdp/client/audin.h>
37
38#include "audin_main.h"
39
40/* fix missing definitions in mingw */
41#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE
42#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010
43#endif
44
45typedef struct
46{
47 IAudinDevice iface;
48
49 char* device_name;
50 AudinReceive receive;
51 void* user_data;
52 HANDLE thread;
53 HANDLE stopEvent;
54 HWAVEIN hWaveIn;
55 PWAVEFORMATEX* ppwfx;
56 PWAVEFORMATEX pwfx_cur;
57 UINT32 ppwfx_size;
58 UINT32 cFormats;
59 UINT32 frames_per_packet;
60 rdpContext* rdpcontext;
61 wLog* log;
62} AudinWinmmDevice;
63
64static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance,
65 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
66{
67 AudinWinmmDevice* winmm = (AudinWinmmDevice*)dwInstance;
68 PWAVEHDR pWaveHdr;
69 UINT error = CHANNEL_RC_OK;
70 MMRESULT mmResult;
71
72 switch (uMsg)
73 {
74 case WIM_CLOSE:
75 break;
76
77 case WIM_DATA:
78 pWaveHdr = (WAVEHDR*)dwParam1;
79
80 if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags))
81 {
82 if (pWaveHdr->dwBytesRecorded &&
83 !(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0))
84 {
85 AUDIO_FORMAT format;
86 format.cbSize = winmm->pwfx_cur->cbSize;
87 format.nBlockAlign = winmm->pwfx_cur->nBlockAlign;
88 format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec;
89 format.nChannels = winmm->pwfx_cur->nChannels;
90 format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec;
91 format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample;
92 format.wFormatTag = winmm->pwfx_cur->wFormatTag;
93
94 if ((error = winmm->receive(&format, pWaveHdr->lpData,
95 pWaveHdr->dwBytesRecorded, winmm->user_data)))
96 break;
97
98 mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
99
100 if (mmResult != MMSYSERR_NOERROR)
101 error = ERROR_INTERNAL_ERROR;
102 }
103 }
104
105 break;
106
107 case WIM_OPEN:
108 break;
109
110 default:
111 break;
112 }
113
114 if (error && winmm->rdpcontext)
115 setChannelError(winmm->rdpcontext, error, "waveInProc reported an error");
116}
117
118static BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result)
119{
120 if (result != MMSYSERR_NOERROR)
121 {
122 CHAR buffer[8192] = { 0 };
123 CHAR msg[8192] = { 0 };
124 CHAR cmsg[8192] = { 0 };
125 waveInGetErrorTextA(result, buffer, sizeof(buffer));
126
127 _snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer);
128 _snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg);
129 WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg);
130 if (winmm->rdpcontext)
131 setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg);
132 return FALSE;
133 }
134 return TRUE;
135}
136
137static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
138{
139 MMRESULT rc;
140 WAVEINCAPSA caps = { 0 };
141
142 rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps));
143 if (rc != MMSYSERR_NOERROR)
144 return FALSE;
145
146 switch (pwfx->nChannels)
147 {
148 case 1:
149 if ((caps.dwFormats &
150 (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 |
151 WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0)
152 return FALSE;
153 break;
154 case 2:
155 if ((caps.dwFormats &
156 (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 |
157 WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0)
158 return FALSE;
159 break;
160 default:
161 return FALSE;
162 }
163
164 rc = waveInOpen(NULL, WAVE_MAPPER, pwfx, 0, 0,
165 WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
166 return (rc == MMSYSERR_NOERROR);
167}
168
169static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
170{
171 AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
172 char* buffer = NULL;
173 int size = 0;
174 WAVEHDR waveHdr[4] = { 0 };
175 DWORD status = 0;
176 MMRESULT rc = 0;
177
178 if (!winmm->hWaveIn)
179 {
180 rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc,
181 (DWORD_PTR)winmm,
182 CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
183 if (!log_mmresult(winmm, "waveInOpen", rc))
184 return ERROR_INTERNAL_ERROR;
185 }
186
187 size =
188 (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet +
189 7) /
190 8;
191
192 for (int i = 0; i < 4; i++)
193 {
194 buffer = (char*)malloc(size);
195
196 if (!buffer)
197 return CHANNEL_RC_NO_MEMORY;
198
199 waveHdr[i].dwBufferLength = size;
200 waveHdr[i].dwFlags = 0;
201 waveHdr[i].lpData = buffer;
202 rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
203
204 if (!log_mmresult(winmm, "waveInPrepareHeader", rc))
205 {
206 }
207
208 rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
209
210 if (!log_mmresult(winmm, "waveInAddBuffer", rc))
211 {
212 }
213 }
214
215 rc = waveInStart(winmm->hWaveIn);
216
217 if (!log_mmresult(winmm, "waveInStart", rc))
218 {
219 }
220
221 status = WaitForSingleObject(winmm->stopEvent, INFINITE);
222
223 if (status == WAIT_FAILED)
224 {
225 WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed.");
226
227 if (winmm->rdpcontext)
228 setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR,
229 "audin_winmm_thread_func reported an error");
230 }
231
232 rc = waveInReset(winmm->hWaveIn);
233
234 if (!log_mmresult(winmm, "waveInReset", rc))
235 {
236 }
237
238 for (int i = 0; i < 4; i++)
239 {
240 rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
241
242 if (!log_mmresult(winmm, "waveInUnprepareHeader", rc))
243 {
244 }
245
246 free(waveHdr[i].lpData);
247 }
248
249 rc = waveInClose(winmm->hWaveIn);
250
251 if (!log_mmresult(winmm, "waveInClose", rc))
252 {
253 }
254
255 winmm->hWaveIn = NULL;
256 return 0;
257}
258
264static UINT audin_winmm_free(IAudinDevice* device)
265{
266 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
267
268 if (!winmm)
269 return ERROR_INVALID_PARAMETER;
270
271 for (UINT32 i = 0; i < winmm->cFormats; i++)
272 {
273 free(winmm->ppwfx[i]);
274 }
275
276 free(winmm->ppwfx);
277 free(winmm->device_name);
278 free(winmm);
279 return CHANNEL_RC_OK;
280}
281
287static UINT audin_winmm_close(IAudinDevice* device)
288{
289 DWORD status;
290 UINT error = CHANNEL_RC_OK;
291 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
292
293 if (!winmm)
294 return ERROR_INVALID_PARAMETER;
295
296 (void)SetEvent(winmm->stopEvent);
297 status = WaitForSingleObject(winmm->thread, INFINITE);
298
299 if (status == WAIT_FAILED)
300 {
301 error = GetLastError();
302 WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
303 error);
304 return error;
305 }
306
307 (void)CloseHandle(winmm->thread);
308 (void)CloseHandle(winmm->stopEvent);
309 winmm->thread = NULL;
310 winmm->stopEvent = NULL;
311 winmm->receive = NULL;
312 winmm->user_data = NULL;
313 return error;
314}
315
321static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
322 UINT32 FramesPerPacket)
323{
324 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
325
326 if (!winmm || !format)
327 return ERROR_INVALID_PARAMETER;
328
329 winmm->frames_per_packet = FramesPerPacket;
330
331 for (UINT32 i = 0; i < winmm->cFormats; i++)
332 {
333 const PWAVEFORMATEX ppwfx = winmm->ppwfx[i];
334 if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) &&
335 (ppwfx->wBitsPerSample == format->wBitsPerSample) &&
336 (ppwfx->nSamplesPerSec == format->nSamplesPerSec))
337 {
338 /* BUG: Many devices report to support stereo recording but fail here.
339 * Ensure we always use mono. */
340 if (ppwfx->nChannels > 1)
341 {
342 ppwfx->nChannels = 1;
343 }
344
345 if (ppwfx->nBlockAlign != 2)
346 {
347 ppwfx->nBlockAlign = 2;
348 ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign;
349 }
350
351 if (!test_format_supported(ppwfx))
352 return ERROR_INVALID_PARAMETER;
353 winmm->pwfx_cur = ppwfx;
354 return CHANNEL_RC_OK;
355 }
356 }
357
358 return ERROR_INVALID_PARAMETER;
359}
360
361static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
362{
363 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
364 PWAVEFORMATEX pwfx;
365 BYTE* data;
366
367 if (!winmm || !format)
368 return FALSE;
369
370 if (format->wFormatTag != WAVE_FORMAT_PCM)
371 return FALSE;
372
373 if (format->nChannels != 1)
374 return FALSE;
375
376 pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize);
377
378 if (!pwfx)
379 return FALSE;
380
381 pwfx->cbSize = format->cbSize;
382 pwfx->wFormatTag = format->wFormatTag;
383 pwfx->nChannels = format->nChannels;
384 pwfx->nSamplesPerSec = format->nSamplesPerSec;
385 pwfx->nBlockAlign = format->nBlockAlign;
386 pwfx->wBitsPerSample = format->wBitsPerSample;
387 data = (BYTE*)pwfx + sizeof(WAVEFORMATEX);
388 memcpy(data, format->data, format->cbSize);
389
390 pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
391
392 if (!test_format_supported(pwfx))
393 goto fail;
394
395 if (winmm->cFormats >= winmm->ppwfx_size)
396 {
397 PWAVEFORMATEX* tmp_ppwfx;
398 tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2);
399
400 if (!tmp_ppwfx)
401 goto fail;
402
403 winmm->ppwfx_size *= 2;
404 winmm->ppwfx = tmp_ppwfx;
405 }
406
407 winmm->ppwfx[winmm->cFormats++] = pwfx;
408 return TRUE;
409
410fail:
411 free(pwfx);
412 return FALSE;
413}
414
420static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data)
421{
422 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
423
424 if (!winmm || !receive || !user_data)
425 return ERROR_INVALID_PARAMETER;
426
427 winmm->receive = receive;
428 winmm->user_data = user_data;
429
430 if (!(winmm->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
431 {
432 WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!");
433 return ERROR_INTERNAL_ERROR;
434 }
435
436 if (!(winmm->thread = CreateThread(NULL, 0, audin_winmm_thread_func, winmm, 0, NULL)))
437 {
438 WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!");
439 (void)CloseHandle(winmm->stopEvent);
440 winmm->stopEvent = NULL;
441 return ERROR_INTERNAL_ERROR;
442 }
443
444 return CHANNEL_RC_OK;
445}
446
452static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args)
453{
454 int status;
455 DWORD flags;
456 const COMMAND_LINE_ARGUMENT_A* arg;
457 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
458 COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
459 NULL, NULL, -1, NULL, "audio device name" },
460 { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
461
462 flags =
463 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
464 status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm,
465 NULL, NULL);
466 arg = audin_winmm_args;
467
468 do
469 {
470 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
471 continue;
472
473 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
474 {
475 winmm->device_name = _strdup(arg->Value);
476
477 if (!winmm->device_name)
478 {
479 WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
480 return CHANNEL_RC_NO_MEMORY;
481 }
482 }
483 CommandLineSwitchEnd(arg)
484 } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
485
486 return CHANNEL_RC_OK;
487}
488
494FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_audin_client_subsystem_entry(
496{
497 const ADDIN_ARGV* args;
498 AudinWinmmDevice* winmm;
499 UINT error;
500
501 if (waveInGetNumDevs() == 0)
502 {
503 WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!");
504 return ERROR_DEVICE_NOT_AVAILABLE;
505 }
506
507 winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice));
508
509 if (!winmm)
510 {
511 WLog_ERR(TAG, "calloc failed!");
512 return CHANNEL_RC_NO_MEMORY;
513 }
514
515 winmm->log = WLog_Get(TAG);
516 winmm->iface.Open = audin_winmm_open;
517 winmm->iface.FormatSupported = audin_winmm_format_supported;
518 winmm->iface.SetFormat = audin_winmm_set_format;
519 winmm->iface.Close = audin_winmm_close;
520 winmm->iface.Free = audin_winmm_free;
521 winmm->rdpcontext = pEntryPoints->rdpcontext;
522 args = pEntryPoints->args;
523
524 if ((error = audin_winmm_parse_addin_args(winmm, args)))
525 {
526 WLog_Print(winmm->log, WLOG_ERROR,
527 "audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error);
528 goto error_out;
529 }
530
531 if (!winmm->device_name)
532 {
533 winmm->device_name = _strdup("default");
534
535 if (!winmm->device_name)
536 {
537 WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
538 error = CHANNEL_RC_NO_MEMORY;
539 goto error_out;
540 }
541 }
542
543 winmm->ppwfx_size = 10;
544 winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX));
545
546 if (!winmm->ppwfx)
547 {
548 WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!");
549 error = CHANNEL_RC_NO_MEMORY;
550 goto error_out;
551 }
552
553 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface)))
554 {
555 WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
556 error);
557 goto error_out;
558 }
559
560 return CHANNEL_RC_OK;
561error_out:
562 free(winmm->ppwfx);
563 free(winmm->device_name);
564 free(winmm);
565 return error;
566}