FreeRDP
Loading...
Searching...
No Matches
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] = WINPR_C_ARRAY_INIT;
123 CHAR msg[8192] = WINPR_C_ARRAY_INIT;
124 CHAR cmsg[8192] = WINPR_C_ARRAY_INIT;
125 waveInGetErrorTextA(result, buffer, sizeof(buffer));
126
127 (void)_snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer);
128 (void)_snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'",
129 msg);
130 WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg);
131 if (winmm->rdpcontext)
132 setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg);
133 return FALSE;
134 }
135 return TRUE;
136}
137
138static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
139{
140 MMRESULT rc;
141 WAVEINCAPSA caps = WINPR_C_ARRAY_INIT;
142
143 rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps));
144 if (rc != MMSYSERR_NOERROR)
145 return FALSE;
146
147 switch (pwfx->nChannels)
148 {
149 case 1:
150 if ((caps.dwFormats &
151 (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 |
152 WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0)
153 return FALSE;
154 break;
155 case 2:
156 if ((caps.dwFormats &
157 (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 |
158 WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0)
159 return FALSE;
160 break;
161 default:
162 return FALSE;
163 }
164
165 rc = waveInOpen(nullptr, WAVE_MAPPER, pwfx, 0, 0,
166 WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
167 return (rc == MMSYSERR_NOERROR);
168}
169
170static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
171{
172 AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
173 WAVEHDR waveHdr[4] = WINPR_C_ARRAY_INIT;
174 DWORD status = 0;
175 MMRESULT rc = 0;
176
177 if (!winmm->hWaveIn)
178 {
179 rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc,
180 (DWORD_PTR)winmm,
181 CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
182 if (!log_mmresult(winmm, "waveInOpen", rc))
183 return ERROR_INTERNAL_ERROR;
184 }
185
186 const size_t framesize =
187 1ull * winmm->pwfx_cur->wBitsPerSample / 8ull * winmm->pwfx_cur->nChannels;
188 if (framesize > UINT32_MAX)
189 return ERROR_INTERNAL_ERROR;
190
191 const size_t size = framesize * winmm->frames_per_packet;
192 if (size > UINT32_MAX)
193 return ERROR_INTERNAL_ERROR;
194 for (int i = 0; i < 4; i++)
195 {
196 char* buffer = (char*)calloc(framesize, winmm->frames_per_packet);
197
198 if (!buffer)
199 return CHANNEL_RC_NO_MEMORY;
200
201 waveHdr[i].dwBufferLength = WINPR_ASSERTING_INT_CAST(DWORD, size);
202 waveHdr[i].dwFlags = 0;
203 waveHdr[i].lpData = buffer;
204 rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
205
206 if (!log_mmresult(winmm, "waveInPrepareHeader", rc))
207 {
208 }
209
210 rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
211
212 if (!log_mmresult(winmm, "waveInAddBuffer", rc))
213 {
214 }
215 }
216
217 rc = waveInStart(winmm->hWaveIn);
218
219 if (!log_mmresult(winmm, "waveInStart", rc))
220 {
221 }
222
223 status = WaitForSingleObject(winmm->stopEvent, INFINITE);
224
225 if (status == WAIT_FAILED)
226 {
227 WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed.");
228
229 if (winmm->rdpcontext)
230 setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR,
231 "audin_winmm_thread_func reported an error");
232 }
233
234 rc = waveInReset(winmm->hWaveIn);
235
236 if (!log_mmresult(winmm, "waveInReset", rc))
237 {
238 }
239
240 for (int i = 0; i < 4; i++)
241 {
242 rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
243
244 if (!log_mmresult(winmm, "waveInUnprepareHeader", rc))
245 {
246 }
247
248 free(waveHdr[i].lpData);
249 }
250
251 rc = waveInClose(winmm->hWaveIn);
252
253 if (!log_mmresult(winmm, "waveInClose", rc))
254 {
255 }
256
257 winmm->hWaveIn = nullptr;
258 return 0;
259}
260
266static UINT audin_winmm_free(IAudinDevice* device)
267{
268 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
269
270 if (!winmm)
271 return ERROR_INVALID_PARAMETER;
272
273 for (UINT32 i = 0; i < winmm->cFormats; i++)
274 {
275 free(winmm->ppwfx[i]);
276 }
277
278 free(winmm->ppwfx);
279 free(winmm->device_name);
280 free(winmm);
281 return CHANNEL_RC_OK;
282}
283
289static UINT audin_winmm_close(IAudinDevice* device)
290{
291 DWORD status;
292 UINT error = CHANNEL_RC_OK;
293 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
294
295 if (!winmm)
296 return ERROR_INVALID_PARAMETER;
297
298 (void)SetEvent(winmm->stopEvent);
299 status = WaitForSingleObject(winmm->thread, INFINITE);
300
301 if (status == WAIT_FAILED)
302 {
303 error = GetLastError();
304 WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
305 error);
306 return error;
307 }
308
309 (void)CloseHandle(winmm->thread);
310 (void)CloseHandle(winmm->stopEvent);
311 winmm->thread = nullptr;
312 winmm->stopEvent = nullptr;
313 winmm->receive = nullptr;
314 winmm->user_data = nullptr;
315 return error;
316}
317
323static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
324 UINT32 FramesPerPacket)
325{
326 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
327
328 if (!winmm || !format)
329 return ERROR_INVALID_PARAMETER;
330
331 winmm->frames_per_packet = FramesPerPacket;
332
333 for (UINT32 i = 0; i < winmm->cFormats; i++)
334 {
335 const PWAVEFORMATEX ppwfx = winmm->ppwfx[i];
336 if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) &&
337 (ppwfx->wBitsPerSample == format->wBitsPerSample) &&
338 (ppwfx->nSamplesPerSec == format->nSamplesPerSec))
339 {
340 /* BUG: Many devices report to support stereo recording but fail here.
341 * Ensure we always use mono. */
342 if (ppwfx->nChannels > 1)
343 {
344 ppwfx->nChannels = 1;
345 }
346
347 if (ppwfx->nBlockAlign != 2)
348 {
349 ppwfx->nBlockAlign = 2;
350 ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign;
351 }
352
353 if (!test_format_supported(ppwfx))
354 return ERROR_INVALID_PARAMETER;
355 winmm->pwfx_cur = ppwfx;
356 return CHANNEL_RC_OK;
357 }
358 }
359
360 return ERROR_INVALID_PARAMETER;
361}
362
363static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
364{
365 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
366 PWAVEFORMATEX pwfx;
367 BYTE* data;
368
369 if (!winmm || !format)
370 return FALSE;
371
372 if (format->wFormatTag != WAVE_FORMAT_PCM)
373 return FALSE;
374
375 if (format->nChannels != 1)
376 return FALSE;
377
378 pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize);
379
380 if (!pwfx)
381 return FALSE;
382
383 pwfx->cbSize = format->cbSize;
384 pwfx->wFormatTag = format->wFormatTag;
385 pwfx->nChannels = format->nChannels;
386 pwfx->nSamplesPerSec = format->nSamplesPerSec;
387 pwfx->nBlockAlign = format->nBlockAlign;
388 pwfx->wBitsPerSample = format->wBitsPerSample;
389 data = (BYTE*)pwfx + sizeof(WAVEFORMATEX);
390 memcpy(data, format->data, format->cbSize);
391
392 pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
393
394 if (!test_format_supported(pwfx))
395 goto fail;
396
397 if (winmm->cFormats >= winmm->ppwfx_size)
398 {
399 PWAVEFORMATEX* tmp_ppwfx;
400 tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2);
401
402 if (!tmp_ppwfx)
403 goto fail;
404
405 winmm->ppwfx_size *= 2;
406 winmm->ppwfx = tmp_ppwfx;
407 }
408
409 winmm->ppwfx[winmm->cFormats++] = pwfx;
410 return TRUE;
411
412fail:
413 free(pwfx);
414 return FALSE;
415}
416
422static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data)
423{
424 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
425
426 if (!winmm || !receive || !user_data)
427 return ERROR_INVALID_PARAMETER;
428
429 winmm->receive = receive;
430 winmm->user_data = user_data;
431
432 if (!(winmm->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
433 {
434 WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!");
435 return ERROR_INTERNAL_ERROR;
436 }
437
438 if (!(winmm->thread = CreateThread(nullptr, 0, audin_winmm_thread_func, winmm, 0, nullptr)))
439 {
440 WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!");
441 (void)CloseHandle(winmm->stopEvent);
442 winmm->stopEvent = nullptr;
443 return ERROR_INTERNAL_ERROR;
444 }
445
446 return CHANNEL_RC_OK;
447}
448
454static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args)
455{
456 int status;
457 DWORD flags;
458 const COMMAND_LINE_ARGUMENT_A* arg;
459 AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
460 COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = {
461 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
462 "audio device name" },
463 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
464 };
465
466 flags =
467 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
468 status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm,
469 nullptr, nullptr);
470 arg = audin_winmm_args;
471
472 do
473 {
474 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
475 continue;
476
477 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
478 {
479 winmm->device_name = _strdup(arg->Value);
480
481 if (!winmm->device_name)
482 {
483 WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
484 return CHANNEL_RC_NO_MEMORY;
485 }
486 }
487 CommandLineSwitchEnd(arg)
488 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
489
490 return CHANNEL_RC_OK;
491}
492
498FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_audin_client_subsystem_entry(
500{
501 const ADDIN_ARGV* args;
502 AudinWinmmDevice* winmm;
503 UINT error;
504
505 if (waveInGetNumDevs() == 0)
506 {
507 WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!");
508 return ERROR_DEVICE_NOT_AVAILABLE;
509 }
510
511 winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice));
512
513 if (!winmm)
514 {
515 WLog_ERR(TAG, "calloc failed!");
516 return CHANNEL_RC_NO_MEMORY;
517 }
518
519 winmm->log = WLog_Get(TAG);
520 winmm->iface.Open = audin_winmm_open;
521 winmm->iface.FormatSupported = audin_winmm_format_supported;
522 winmm->iface.SetFormat = audin_winmm_set_format;
523 winmm->iface.Close = audin_winmm_close;
524 winmm->iface.Free = audin_winmm_free;
525 winmm->rdpcontext = pEntryPoints->rdpcontext;
526 args = pEntryPoints->args;
527
528 if ((error = audin_winmm_parse_addin_args(winmm, args)))
529 {
530 WLog_Print(winmm->log, WLOG_ERROR,
531 "audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error);
532 goto error_out;
533 }
534
535 if (!winmm->device_name)
536 {
537 winmm->device_name = _strdup("default");
538
539 if (!winmm->device_name)
540 {
541 WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
542 error = CHANNEL_RC_NO_MEMORY;
543 goto error_out;
544 }
545 }
546
547 winmm->ppwfx_size = 10;
548 winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX));
549
550 if (!winmm->ppwfx)
551 {
552 WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!");
553 error = CHANNEL_RC_NO_MEMORY;
554 goto error_out;
555 }
556
557 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface)))
558 {
559 WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
560 error);
561 goto error_out;
562 }
563
564 return CHANNEL_RC_OK;
565error_out:
566 free(winmm->ppwfx);
567 free(winmm->device_name);
568 free(winmm);
569 return error;
570}