Coverage Report

Created: 2024-05-20 06:11

/src/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Audio Output Virtual Channel
4
 *
5
 * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
6
 * Copyright 2015 Thincast Technologies GmbH
7
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
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
0
  do                                                                    \
65
0
  {                                                                     \
66
0
    if ((_error) != 0)                                                \
67
0
    {                                                                 \
68
0
      char ebuffer[256] = { 0 };                                    \
69
0
      WLog_ERR(TAG, "%s: %i - %s", (_text), (_error),               \
70
0
               winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \
71
0
    }                                                                 \
72
0
  } while (0)
73
74
static int rdpsnd_oss_get_format(const AUDIO_FORMAT* format)
75
0
{
76
0
  switch (format->wFormatTag)
77
0
  {
78
0
    case WAVE_FORMAT_PCM:
79
0
      switch (format->wBitsPerSample)
80
0
      {
81
0
        case 8:
82
0
          return AFMT_S8;
83
84
0
        case 16:
85
0
          return AFMT_S16_LE;
86
0
      }
87
88
0
      break;
89
90
0
    case WAVE_FORMAT_ALAW:
91
0
      return AFMT_A_LAW;
92
93
0
    case WAVE_FORMAT_MULAW:
94
0
      return AFMT_MU_LAW;
95
0
  }
96
97
0
  return 0;
98
0
}
99
100
static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
101
0
{
102
0
  int req_fmt = 0;
103
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
104
105
0
  if (device == NULL || format == NULL)
106
0
    return FALSE;
107
108
0
  switch (format->wFormatTag)
109
0
  {
110
0
    case WAVE_FORMAT_PCM:
111
0
      if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
112
0
          (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
113
0
          (format->nChannels != 1 && format->nChannels != 2))
114
0
        return FALSE;
115
116
0
      break;
117
118
0
    default:
119
0
      return FALSE;
120
0
  }
121
122
0
  req_fmt = rdpsnd_oss_get_format(format);
123
124
  /* Check really supported formats by dev. */
125
0
  if (oss->pcm_handle != -1)
126
0
  {
127
0
    if ((req_fmt & oss->supported_formats) == 0)
128
0
      return FALSE;
129
0
  }
130
0
  else
131
0
  {
132
0
    if (req_fmt == 0)
133
0
      return FALSE;
134
0
  }
135
136
0
  return TRUE;
137
0
}
138
139
static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
140
                                  UINT32 latency)
141
0
{
142
0
  int tmp = 0;
143
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
144
145
0
  if (device == NULL || oss->pcm_handle == -1 || format == NULL)
146
0
    return FALSE;
147
148
0
  oss->latency = latency;
149
0
  CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT));
150
0
  tmp = rdpsnd_oss_get_format(format);
151
152
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
153
0
  {
154
0
    OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
155
0
    return FALSE;
156
0
  }
157
158
0
  tmp = format->nChannels;
159
160
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
161
0
  {
162
0
    OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
163
0
    return FALSE;
164
0
  }
165
166
0
  tmp = format->nSamplesPerSec;
167
168
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
169
0
  {
170
0
    OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
171
0
    return FALSE;
172
0
  }
173
174
0
  tmp = format->nBlockAlign;
175
176
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
177
0
  {
178
0
    OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
179
0
    return FALSE;
180
0
  }
181
182
0
  return TRUE;
183
0
}
184
185
static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss)
186
0
{
187
0
  int devmask = 0;
188
0
  char mixer_name[PATH_MAX] = "/dev/mixer";
189
190
0
  if (oss->mixer_handle != -1)
191
0
    return;
192
193
0
  if (oss->dev_unit != -1)
194
0
    sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
195
196
0
  if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0)
197
0
  {
198
0
    OSS_LOG_ERR("mixer open failed", errno);
199
0
    oss->mixer_handle = -1;
200
0
    return;
201
0
  }
202
203
0
  if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
204
0
  {
205
0
    OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno);
206
0
    close(oss->mixer_handle);
207
0
    oss->mixer_handle = -1;
208
0
    return;
209
0
  }
210
0
}
211
212
static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
213
0
{
214
0
  char dev_name[PATH_MAX] = "/dev/dsp";
215
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
216
217
0
  if (device == NULL || oss->pcm_handle != -1)
218
0
    return TRUE;
219
220
0
  if (oss->dev_unit != -1)
221
0
    sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit);
222
223
0
  WLog_INFO(TAG, "open: %s", dev_name);
224
225
0
  if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
226
0
  {
227
0
    OSS_LOG_ERR("sound dev open failed", errno);
228
0
    oss->pcm_handle = -1;
229
0
    return FALSE;
230
0
  }
231
232
#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */
233
  int mask = 0;
234
235
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1)
236
  {
237
    OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
238
  }
239
  else if ((mask & PCM_CAP_OUTPUT) == 0)
240
  {
241
    OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
242
    close(oss->pcm_handle);
243
    oss->pcm_handle = -1;
244
    return;
245
  }
246
247
#endif
248
249
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
250
0
  {
251
0
    OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
252
0
    close(oss->pcm_handle);
253
0
    oss->pcm_handle = -1;
254
0
    return FALSE;
255
0
  }
256
257
0
  rdpsnd_oss_set_format(device, format, latency);
258
0
  rdpsnd_oss_open_mixer(oss);
259
0
  return TRUE;
260
0
}
261
262
static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
263
0
{
264
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
265
266
0
  if (device == NULL)
267
0
    return;
268
269
0
  if (oss->pcm_handle != -1)
270
0
  {
271
0
    WLog_INFO(TAG, "close: dsp");
272
0
    close(oss->pcm_handle);
273
0
    oss->pcm_handle = -1;
274
0
  }
275
276
0
  if (oss->mixer_handle != -1)
277
0
  {
278
0
    WLog_INFO(TAG, "close: mixer");
279
0
    close(oss->mixer_handle);
280
0
    oss->mixer_handle = -1;
281
0
  }
282
0
}
283
284
static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
285
0
{
286
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
287
288
0
  if (device == NULL)
289
0
    return;
290
291
0
  rdpsnd_oss_close(device);
292
0
  free(oss);
293
0
}
294
295
static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
296
0
{
297
0
  int vol = 0;
298
0
  UINT32 dwVolume = 0;
299
0
  UINT16 dwVolumeLeft = 0;
300
0
  UINT16 dwVolumeRight = 0;
301
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
302
  /* On error return 50% volume. */
303
0
  dwVolumeLeft = ((50 * 0xFFFF) / 100);  /* 50% */
304
0
  dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
305
0
  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
306
307
0
  if (device == NULL || oss->mixer_handle == -1)
308
0
    return dwVolume;
309
310
0
  if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
311
0
  {
312
0
    OSS_LOG_ERR("MIXER_READ", errno);
313
0
    return dwVolume;
314
0
  }
315
316
0
  dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
317
0
  dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
318
0
  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
319
0
  return dwVolume;
320
0
}
321
322
static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
323
0
{
324
0
  int left = 0;
325
0
  int right = 0;
326
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
327
328
0
  if (device == NULL || oss->mixer_handle == -1)
329
0
    return FALSE;
330
331
0
  left = (((value & 0xFFFF) * 100) / 0xFFFF);
332
0
  right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
333
334
0
  if (left < 0)
335
0
    left = 0;
336
0
  else if (left > 100)
337
0
    left = 100;
338
339
0
  if (right < 0)
340
0
    right = 0;
341
0
  else if (right > 100)
342
0
    right = 100;
343
344
0
  left |= (right << 8);
345
346
0
  if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
347
0
  {
348
0
    OSS_LOG_ERR("WRITE_MIXER", errno);
349
0
    return FALSE;
350
0
  }
351
352
0
  return TRUE;
353
0
}
354
355
static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
356
0
{
357
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
358
359
0
  if (device == NULL || oss->mixer_handle == -1)
360
0
    return 0;
361
362
0
  while (size > 0)
363
0
  {
364
0
    ssize_t status = write(oss->pcm_handle, data, size);
365
366
0
    if (status < 0)
367
0
    {
368
0
      OSS_LOG_ERR("write fail", errno);
369
0
      rdpsnd_oss_close(device);
370
0
      rdpsnd_oss_open(device, NULL, oss->latency);
371
0
      break;
372
0
    }
373
374
0
    data += status;
375
376
0
    if ((size_t)status <= size)
377
0
      size -= (size_t)status;
378
0
    else
379
0
      size = 0;
380
0
  }
381
382
0
  return 10; /* TODO: Get real latency in [ms] */
383
0
}
384
385
static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
386
0
{
387
0
  int status = 0;
388
0
  char* str_num = NULL;
389
0
  char* eptr = NULL;
390
0
  DWORD flags = 0;
391
0
  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
392
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
393
0
  COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
394
0
                                                NULL, NULL, -1, NULL, "device" },
395
0
                                              { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
396
0
  flags =
397
0
      COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
398
0
  status =
399
0
      CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL);
400
401
0
  if (status < 0)
402
0
    return status;
403
404
0
  arg = rdpsnd_oss_args;
405
0
  errno = 0;
406
407
0
  do
408
0
  {
409
0
    if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
410
0
      continue;
411
412
0
    CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
413
0
    {
414
0
      str_num = _strdup(arg->Value);
415
416
0
      if (!str_num)
417
0
        return ERROR_OUTOFMEMORY;
418
419
0
      {
420
0
        long val = strtol(str_num, &eptr, 10);
421
422
0
        if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
423
0
        {
424
0
          free(str_num);
425
0
          return CHANNEL_RC_NULL_DATA;
426
0
        }
427
428
0
        oss->dev_unit = val;
429
0
      }
430
431
0
      if (oss->dev_unit < 0 || *eptr != '\0')
432
0
        oss->dev_unit = -1;
433
434
0
      free(str_num);
435
0
    }
436
0
    CommandLineSwitchEnd(arg)
437
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
438
439
0
  return status;
440
0
}
441
442
/**
443
 * Function description
444
 *
445
 * @return 0 on success, otherwise a Win32 error code
446
 */
447
FREERDP_ENTRY_POINT(UINT oss_freerdp_rdpsnd_client_subsystem_entry(
448
    PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
449
0
{
450
0
  const ADDIN_ARGV* args = NULL;
451
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
452
453
0
  if (!oss)
454
0
    return CHANNEL_RC_NO_MEMORY;
455
456
0
  oss->device.Open = rdpsnd_oss_open;
457
0
  oss->device.FormatSupported = rdpsnd_oss_format_supported;
458
0
  oss->device.GetVolume = rdpsnd_oss_get_volume;
459
0
  oss->device.SetVolume = rdpsnd_oss_set_volume;
460
0
  oss->device.Play = rdpsnd_oss_play;
461
0
  oss->device.Close = rdpsnd_oss_close;
462
0
  oss->device.Free = rdpsnd_oss_free;
463
0
  oss->pcm_handle = -1;
464
0
  oss->mixer_handle = -1;
465
0
  oss->dev_unit = -1;
466
0
  args = pEntryPoints->args;
467
0
  if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
468
0
  {
469
0
    free(oss);
470
0
    return ERROR_INVALID_PARAMETER;
471
0
  }
472
0
  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
473
0
  return CHANNEL_RC_OK;
474
0
}