FreeRDP
rdpsnd_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/string.h>
30 #include <winpr/cmdline.h>
31 #include <winpr/sysinfo.h>
32 #include <winpr/collections.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/types.h>
44 #include <freerdp/settings.h>
45 #include <freerdp/channels/log.h>
46 
47 #include "rdpsnd_main.h"
48 
49 typedef struct
50 {
51  rdpsndDevicePlugin device;
52 
53  int pcm_handle;
54  int mixer_handle;
55  int dev_unit;
56 
57  int supported_formats;
58 
59  UINT32 latency;
60  AUDIO_FORMAT format;
61 } rdpsndOssPlugin;
62 
63 #define OSS_LOG_ERR(_text, _error) \
64  do \
65  { \
66  if ((_error) != 0) \
67  { \
68  char ebuffer[256] = { 0 }; \
69  WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \
70  winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \
71  } \
72  } while (0)
73 
74 static int rdpsnd_oss_get_format(const AUDIO_FORMAT* format)
75 {
76  switch (format->wFormatTag)
77  {
78  case WAVE_FORMAT_PCM:
79  switch (format->wBitsPerSample)
80  {
81  case 8:
82  return AFMT_S8;
83 
84  case 16:
85  return AFMT_S16_LE;
86  default:
87  break;
88  }
89 
90  break;
91  default:
92  break;
93  }
94 
95  return 0;
96 }
97 
98 static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
99 {
100  int req_fmt = 0;
101  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
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  req_fmt = rdpsnd_oss_get_format(format);
121 
122  /* Check really supported formats by dev. */
123  if (oss->pcm_handle != -1)
124  {
125  if ((req_fmt & oss->supported_formats) == 0)
126  return FALSE;
127  }
128  else
129  {
130  if (req_fmt == 0)
131  return FALSE;
132  }
133 
134  return TRUE;
135 }
136 
137 static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
138  UINT32 latency)
139 {
140  int tmp = 0;
141  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
142 
143  if (device == NULL || oss->pcm_handle == -1 || format == NULL)
144  return FALSE;
145 
146  oss->latency = latency;
147  CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT));
148  tmp = rdpsnd_oss_get_format(format);
149 
150  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
151  {
152  OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
153  return FALSE;
154  }
155 
156  tmp = format->nChannels;
157 
158  if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
159  {
160  OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
161  return FALSE;
162  }
163 
164  tmp = (int)format->nSamplesPerSec;
165 
166  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
167  {
168  OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
169  return FALSE;
170  }
171 
172  tmp = format->nBlockAlign;
173 
174  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
175  {
176  OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
177  return FALSE;
178  }
179 
180  return TRUE;
181 }
182 
183 static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss)
184 {
185  int devmask = 0;
186  char mixer_name[PATH_MAX] = "/dev/mixer";
187 
188  if (oss->mixer_handle != -1)
189  return;
190 
191  if (oss->dev_unit != -1)
192  (void)sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
193 
194  if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0)
195  {
196  OSS_LOG_ERR("mixer open failed", errno);
197  oss->mixer_handle = -1;
198  return;
199  }
200 
201  if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
202  {
203  OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno);
204  close(oss->mixer_handle);
205  oss->mixer_handle = -1;
206  return;
207  }
208 }
209 
210 static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
211 {
212  char dev_name[PATH_MAX] = "/dev/dsp";
213  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
214 
215  if (device == NULL || oss->pcm_handle != -1)
216  return TRUE;
217 
218  if (oss->dev_unit != -1)
219  (void)sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit);
220 
221  WLog_INFO(TAG, "open: %s", dev_name);
222 
223  if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
224  {
225  OSS_LOG_ERR("sound dev open failed", errno);
226  oss->pcm_handle = -1;
227  return FALSE;
228  }
229 
230 #if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */
231  int mask = 0;
232 
233  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1)
234  {
235  OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
236  }
237  else if ((mask & PCM_CAP_OUTPUT) == 0)
238  {
239  OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
240  close(oss->pcm_handle);
241  oss->pcm_handle = -1;
242  return;
243  }
244 
245 #endif
246 
247  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
248  {
249  OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
250  close(oss->pcm_handle);
251  oss->pcm_handle = -1;
252  return FALSE;
253  }
254 
255  rdpsnd_oss_set_format(device, format, latency);
256  rdpsnd_oss_open_mixer(oss);
257  return TRUE;
258 }
259 
260 static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
261 {
262  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
263 
264  if (device == NULL)
265  return;
266 
267  if (oss->pcm_handle != -1)
268  {
269  WLog_INFO(TAG, "close: dsp");
270  close(oss->pcm_handle);
271  oss->pcm_handle = -1;
272  }
273 
274  if (oss->mixer_handle != -1)
275  {
276  WLog_INFO(TAG, "close: mixer");
277  close(oss->mixer_handle);
278  oss->mixer_handle = -1;
279  }
280 }
281 
282 static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
283 {
284  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
285 
286  if (device == NULL)
287  return;
288 
289  rdpsnd_oss_close(device);
290  free(oss);
291 }
292 
293 static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
294 {
295  int vol = 0;
296  UINT32 dwVolume = 0;
297  UINT16 dwVolumeLeft = 0;
298  UINT16 dwVolumeRight = 0;
299  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
300  /* On error return 50% volume. */
301  dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
302  dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
303  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
304 
305  if (device == NULL || oss->mixer_handle == -1)
306  return dwVolume;
307 
308  if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
309  {
310  OSS_LOG_ERR("MIXER_READ", errno);
311  return dwVolume;
312  }
313 
314  dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
315  dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
316  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
317  return dwVolume;
318 }
319 
320 static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
321 {
322  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
323  WINPR_ASSERT(oss);
324 
325  if (device == NULL || oss->mixer_handle == -1)
326  return FALSE;
327 
328  unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF);
329  unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
330 
331  left |= (right << 8);
332 
333  if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
334  {
335  OSS_LOG_ERR("WRITE_MIXER", errno);
336  return FALSE;
337  }
338 
339  return TRUE;
340 }
341 
342 static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
343 {
344  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
345 
346  if (device == NULL || oss->mixer_handle == -1)
347  return 0;
348 
349  while (size > 0)
350  {
351  ssize_t status = write(oss->pcm_handle, data, size);
352 
353  if (status < 0)
354  {
355  OSS_LOG_ERR("write fail", errno);
356  rdpsnd_oss_close(device);
357  rdpsnd_oss_open(device, NULL, oss->latency);
358  break;
359  }
360 
361  data += status;
362 
363  if ((size_t)status <= size)
364  size -= (size_t)status;
365  else
366  size = 0;
367  }
368 
369  return 10; /* TODO: Get real latency in [ms] */
370 }
371 
372 static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
373 {
374  int status = 0;
375  char* str_num = NULL;
376  char* eptr = NULL;
377  DWORD flags = 0;
378  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
379  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
380  COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
381  NULL, NULL, -1, NULL, "device" },
382  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
383  flags =
384  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
385  status =
386  CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL);
387 
388  if (status < 0)
389  return status;
390 
391  arg = rdpsnd_oss_args;
392  errno = 0;
393 
394  do
395  {
396  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
397  continue;
398 
399  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
400  {
401  str_num = _strdup(arg->Value);
402 
403  if (!str_num)
404  return ERROR_OUTOFMEMORY;
405 
406  {
407  long val = strtol(str_num, &eptr, 10);
408 
409  if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
410  {
411  free(str_num);
412  return CHANNEL_RC_NULL_DATA;
413  }
414 
415  oss->dev_unit = (int)val;
416  }
417 
418  if (oss->dev_unit < 0 || *eptr != '\0')
419  oss->dev_unit = -1;
420 
421  free(str_num);
422  }
423  CommandLineSwitchEnd(arg)
424  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
425 
426  return status;
427 }
428 
434 FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry(
436 {
437  const ADDIN_ARGV* args = NULL;
438  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
439 
440  if (!oss)
441  return CHANNEL_RC_NO_MEMORY;
442 
443  oss->device.Open = rdpsnd_oss_open;
444  oss->device.FormatSupported = rdpsnd_oss_format_supported;
445  oss->device.GetVolume = rdpsnd_oss_get_volume;
446  oss->device.SetVolume = rdpsnd_oss_set_volume;
447  oss->device.Play = rdpsnd_oss_play;
448  oss->device.Close = rdpsnd_oss_close;
449  oss->device.Free = rdpsnd_oss_free;
450  oss->pcm_handle = -1;
451  oss->mixer_handle = -1;
452  oss->dev_unit = -1;
453  args = pEntryPoints->args;
454  if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
455  {
456  free(oss);
457  return ERROR_INVALID_PARAMETER;
458  }
459  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
460  return CHANNEL_RC_OK;
461 }
Definition: client/rdpsnd.h:76