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 ignored", 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  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
296  WINPR_ASSERT(oss);
297  int vol = 0;
298 
299  /* On error return 50% volume. */
300  UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
301  UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
302  UINT32 dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
303 
304  if (device == NULL || oss->mixer_handle == -1)
305  return dwVolume;
306 
307  if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
308  {
309  OSS_LOG_ERR("MIXER_READ", errno);
310  return dwVolume;
311  }
312 
313  dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
314  dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
315  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
316  return dwVolume;
317 }
318 
319 static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
320 {
321  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
322  WINPR_ASSERT(oss);
323 
324  if (device == NULL || oss->mixer_handle == -1)
325  return FALSE;
326 
327  unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF);
328  unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
329 
330  left |= (right << 8);
331 
332  if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
333  {
334  OSS_LOG_ERR("WRITE_MIXER", errno);
335  return FALSE;
336  }
337 
338  return TRUE;
339 }
340 
341 static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
342 {
343  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
344 
345  if (device == NULL || oss->mixer_handle == -1)
346  return 0;
347 
348  while (size > 0)
349  {
350  ssize_t status = write(oss->pcm_handle, data, size);
351 
352  if (status < 0)
353  {
354  OSS_LOG_ERR("write fail", errno);
355  rdpsnd_oss_close(device);
356  rdpsnd_oss_open(device, NULL, oss->latency);
357  break;
358  }
359 
360  data += status;
361 
362  if ((size_t)status <= size)
363  size -= (size_t)status;
364  else
365  size = 0;
366  }
367 
368  return 10; /* TODO: Get real latency in [ms] */
369 }
370 
371 static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
372 {
373  int status = 0;
374  char* str_num = NULL;
375  char* eptr = NULL;
376  DWORD flags = 0;
377  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
378  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
379  COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
380  NULL, NULL, -1, NULL, "device" },
381  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
382  flags =
383  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
384  status =
385  CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL);
386 
387  if (status < 0)
388  return status;
389 
390  arg = rdpsnd_oss_args;
391  errno = 0;
392 
393  do
394  {
395  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
396  continue;
397 
398  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
399  {
400  str_num = _strdup(arg->Value);
401 
402  if (!str_num)
403  return ERROR_OUTOFMEMORY;
404 
405  {
406  long val = strtol(str_num, &eptr, 10);
407 
408  if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
409  {
410  free(str_num);
411  return CHANNEL_RC_NULL_DATA;
412  }
413 
414  oss->dev_unit = (int)val;
415  }
416 
417  if (oss->dev_unit < 0 || *eptr != '\0')
418  oss->dev_unit = -1;
419 
420  free(str_num);
421  }
422  CommandLineSwitchEnd(arg)
423  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
424 
425  return status;
426 }
427 
433 FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry(
435 {
436  const ADDIN_ARGV* args = NULL;
437  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
438 
439  if (!oss)
440  return CHANNEL_RC_NO_MEMORY;
441 
442  oss->device.Open = rdpsnd_oss_open;
443  oss->device.FormatSupported = rdpsnd_oss_format_supported;
444  oss->device.GetVolume = rdpsnd_oss_get_volume;
445  oss->device.SetVolume = rdpsnd_oss_set_volume;
446  oss->device.Play = rdpsnd_oss_play;
447  oss->device.Close = rdpsnd_oss_close;
448  oss->device.Free = rdpsnd_oss_free;
449  oss->pcm_handle = -1;
450  oss->mixer_handle = -1;
451  oss->dev_unit = -1;
452  args = pEntryPoints->args;
453  if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
454  {
455  free(oss);
456  return ERROR_INVALID_PARAMETER;
457  }
458  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
459  return CHANNEL_RC_OK;
460 }
Definition: client/rdpsnd.h:76