FreeRDP
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 
45 typedef 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 
64 static 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 
118 static 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 
137 static 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 
169 static 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 
264 static 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 
287 static 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 
321 static 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 
361 static 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 
410 fail:
411  free(pwfx);
412  return FALSE;
413 }
414 
420 static 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 
452 static 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 
494 FREERDP_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;
561 error_out:
562  free(winmm->ppwfx);
563  free(winmm->device_name);
564  free(winmm);
565  return error;
566 }
Definition: client/audin.h:59