FreeRDP
audin_oss.c
1 
22 #include <freerdp/config.h>
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <winpr/crt.h>
29 #include <winpr/synch.h>
30 #include <winpr/string.h>
31 #include <winpr/thread.h>
32 #include <winpr/cmdline.h>
33 
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <libgen.h>
38 #include <limits.h>
39 #include <unistd.h>
40 #include <oss-includes.h>
41 #include <sys/ioctl.h>
42 
43 #include <freerdp/freerdp.h>
44 #include <freerdp/addin.h>
45 #include <freerdp/channels/rdpsnd.h>
46 
47 #include "audin_main.h"
48 
49 typedef struct
50 {
51  IAudinDevice iface;
52 
53  HANDLE thread;
54  HANDLE stopEvent;
55 
56  AUDIO_FORMAT format;
57  UINT32 FramesPerPacket;
58  int dev_unit;
59 
60  AudinReceive receive;
61  void* user_data;
62 
63  rdpContext* rdpcontext;
64 } AudinOSSDevice;
65 
66 static void OSS_LOG_ERR(const char* _text, int _error)
67 {
68  if ((_error) != 0)
69  {
70  char buffer[256] = { 0 };
71  WLog_ERR(TAG, "%s: %i - %s\n", (_text), (_error),
72  winpr_strerror((_error), buffer, sizeof(buffer)));
73  }
74 }
75 
76 static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format)
77 {
78  switch (format->wFormatTag)
79  {
80  case WAVE_FORMAT_PCM:
81  switch (format->wBitsPerSample)
82  {
83  case 8:
84  return AFMT_S8;
85 
86  case 16:
87  return AFMT_S16_LE;
88 
89  default:
90  break;
91  }
92 
93  break;
94  default:
95  break;
96  }
97 
98  return 0;
99 }
100 
101 static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
102 {
103  if (device == NULL || format == NULL)
104  return FALSE;
105 
106  switch (format->wFormatTag)
107  {
108  case WAVE_FORMAT_PCM:
109  if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
110  (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
111  (format->nChannels != 1 && format->nChannels != 2))
112  return FALSE;
113 
114  break;
115 
116  default:
117  return FALSE;
118  }
119 
120  return TRUE;
121 }
122 
128 static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
129  UINT32 FramesPerPacket)
130 {
131  AudinOSSDevice* oss = (AudinOSSDevice*)device;
132 
133  if (device == NULL || format == NULL)
134  return ERROR_INVALID_PARAMETER;
135 
136  oss->FramesPerPacket = FramesPerPacket;
137  oss->format = *format;
138  return CHANNEL_RC_OK;
139 }
140 
141 static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
142 {
143  char dev_name[PATH_MAX] = "/dev/dsp";
144  char mixer_name[PATH_MAX] = "/dev/mixer";
145  int pcm_handle = -1;
146  int mixer_handle = 0;
147  BYTE* buffer = NULL;
148  unsigned long tmp = 0;
149  size_t buffer_size = 0;
150  AudinOSSDevice* oss = (AudinOSSDevice*)arg;
151  UINT error = 0;
152  DWORD status = 0;
153 
154  if (oss == NULL)
155  {
156  error = ERROR_INVALID_PARAMETER;
157  goto err_out;
158  }
159 
160  if (oss->dev_unit != -1)
161  {
162  (void)sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
163  (void)sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
164  }
165 
166  WLog_INFO(TAG, "open: %s", dev_name);
167 
168  if ((pcm_handle = open(dev_name, O_RDONLY)) < 0)
169  {
170  OSS_LOG_ERR("sound dev open failed", errno);
171  error = ERROR_INTERNAL_ERROR;
172  goto err_out;
173  }
174 
175  /* Set rec volume to 100%. */
176  if ((mixer_handle = open(mixer_name, O_RDWR)) < 0)
177  {
178  OSS_LOG_ERR("mixer open failed, not critical", errno);
179  }
180  else
181  {
182  tmp = (100 | (100 << 8));
183 
184  if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_MIC), &tmp) == -1)
185  OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_MIC, not critical", errno);
186 
187  tmp = (100 | (100 << 8));
188 
189  if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_RECLEV), &tmp) == -1)
190  OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_RECLEV, not critical", errno);
191 
192  close(mixer_handle);
193  }
194 
195 #if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_INPUT flag. */
196  tmp = 0;
197 
198  if (ioctl(pcm_handle, SNDCTL_DSP_GETCAPS, &tmp) == -1)
199  {
200  OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
201  }
202  else if ((tmp & PCM_CAP_INPUT) == 0)
203  {
204  OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
205  goto err_out;
206  }
207 
208 #endif
209  /* Set format. */
210  tmp = audin_oss_get_format(&oss->format);
211 
212  if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
213  OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
214 
215  tmp = oss->format.nChannels;
216 
217  if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
218  OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
219 
220  tmp = oss->format.nSamplesPerSec;
221 
222  if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
223  OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
224 
225  tmp = oss->format.nBlockAlign;
226 
227  if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
228  OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
229 
230  buffer_size =
231  (1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull));
232  buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
233 
234  if (NULL == buffer)
235  {
236  OSS_LOG_ERR("malloc() fail", errno);
237  error = ERROR_NOT_ENOUGH_MEMORY;
238  goto err_out;
239  }
240 
241  while (1)
242  {
243  SSIZE_T stmp = -1;
244  status = WaitForSingleObject(oss->stopEvent, 0);
245 
246  if (status == WAIT_FAILED)
247  {
248  error = GetLastError();
249  WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
250  goto err_out;
251  }
252 
253  if (status == WAIT_OBJECT_0)
254  break;
255 
256  stmp = read(pcm_handle, buffer, buffer_size);
257 
258  /* Error happen. */
259  if (stmp < 0)
260  {
261  OSS_LOG_ERR("read() error", errno);
262  continue;
263  }
264 
265  if ((size_t)stmp < buffer_size) /* Not enouth data. */
266  continue;
267 
268  if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data)))
269  {
270  WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error);
271  break;
272  }
273  }
274 
275 err_out:
276 
277  if (error && oss && oss->rdpcontext)
278  setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error");
279 
280  if (pcm_handle != -1)
281  {
282  WLog_INFO(TAG, "close: %s", dev_name);
283  close(pcm_handle);
284  }
285 
286  free(buffer);
287  ExitThread(error);
288  return error;
289 }
290 
296 static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data)
297 {
298  AudinOSSDevice* oss = (AudinOSSDevice*)device;
299  oss->receive = receive;
300  oss->user_data = user_data;
301 
302  if (!(oss->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
303  {
304  WLog_ERR(TAG, "CreateEvent failed!");
305  return ERROR_INTERNAL_ERROR;
306  }
307 
308  if (!(oss->thread = CreateThread(NULL, 0, audin_oss_thread_func, oss, 0, NULL)))
309  {
310  WLog_ERR(TAG, "CreateThread failed!");
311  (void)CloseHandle(oss->stopEvent);
312  oss->stopEvent = NULL;
313  return ERROR_INTERNAL_ERROR;
314  }
315 
316  return CHANNEL_RC_OK;
317 }
318 
324 static UINT audin_oss_close(IAudinDevice* device)
325 {
326  UINT error = 0;
327  AudinOSSDevice* oss = (AudinOSSDevice*)device;
328 
329  if (device == NULL)
330  return ERROR_INVALID_PARAMETER;
331 
332  if (oss->stopEvent != NULL)
333  {
334  (void)SetEvent(oss->stopEvent);
335 
336  if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
337  {
338  error = GetLastError();
339  WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
340  return error;
341  }
342 
343  (void)CloseHandle(oss->stopEvent);
344  oss->stopEvent = NULL;
345  (void)CloseHandle(oss->thread);
346  oss->thread = NULL;
347  }
348 
349  oss->receive = NULL;
350  oss->user_data = NULL;
351  return CHANNEL_RC_OK;
352 }
353 
359 static UINT audin_oss_free(IAudinDevice* device)
360 {
361  AudinOSSDevice* oss = (AudinOSSDevice*)device;
362  UINT error = 0;
363 
364  if (device == NULL)
365  return ERROR_INVALID_PARAMETER;
366 
367  if ((error = audin_oss_close(device)))
368  {
369  WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error);
370  }
371 
372  free(oss);
373  return CHANNEL_RC_OK;
374 }
375 
381 static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args)
382 {
383  int status = 0;
384  char* str_num = NULL;
385  char* eptr = NULL;
386  DWORD flags = 0;
387  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
388  AudinOSSDevice* oss = device;
389  COMMAND_LINE_ARGUMENT_A audin_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
390  NULL, NULL, -1, NULL, "audio device name" },
391  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
392 
393  flags =
394  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
395  status =
396  CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, NULL, NULL);
397 
398  if (status < 0)
399  return ERROR_INVALID_PARAMETER;
400 
401  arg = audin_oss_args;
402  errno = 0;
403 
404  do
405  {
406  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
407  continue;
408 
409  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
410  {
411  str_num = _strdup(arg->Value);
412 
413  if (!str_num)
414  {
415  WLog_ERR(TAG, "_strdup failed!");
416  return CHANNEL_RC_NO_MEMORY;
417  }
418 
419  {
420  long val = strtol(str_num, &eptr, 10);
421 
422  if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
423  {
424  free(str_num);
425  return CHANNEL_RC_NULL_DATA;
426  }
427 
428  oss->dev_unit = (INT32)val;
429  }
430 
431  if (oss->dev_unit < 0 || *eptr != '\0')
432  oss->dev_unit = -1;
433 
434  free(str_num);
435  }
436  CommandLineSwitchEnd(arg)
437  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
438 
439  return CHANNEL_RC_OK;
440 }
441 
447 FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_audin_client_subsystem_entry(
449 {
450  const ADDIN_ARGV* args = NULL;
451  AudinOSSDevice* oss = NULL;
452  UINT error = 0;
453  oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice));
454 
455  if (!oss)
456  {
457  WLog_ERR(TAG, "calloc failed!");
458  return CHANNEL_RC_NO_MEMORY;
459  }
460 
461  oss->iface.Open = audin_oss_open;
462  oss->iface.FormatSupported = audin_oss_format_supported;
463  oss->iface.SetFormat = audin_oss_set_format;
464  oss->iface.Close = audin_oss_close;
465  oss->iface.Free = audin_oss_free;
466  oss->rdpcontext = pEntryPoints->rdpcontext;
467  oss->dev_unit = -1;
468  args = pEntryPoints->args;
469 
470  if ((error = audin_oss_parse_addin_args(oss, args)))
471  {
472  WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error);
473  goto error_out;
474  }
475 
476  if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss)))
477  {
478  WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
479  goto error_out;
480  }
481 
482  return CHANNEL_RC_OK;
483 error_out:
484  free(oss);
485  return error;
486 }
Definition: client/audin.h:59