Coverage Report

Created: 2026-06-15 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c
Line
Count
Source
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] = WINPR_C_ARRAY_INIT;                       \
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
        default:
87
0
          break;
88
0
      }
89
90
0
      break;
91
0
    default:
92
0
      break;
93
0
  }
94
95
0
  return 0;
96
0
}
97
98
static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
99
0
{
100
0
  int req_fmt = 0;
101
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
102
103
0
  if (device == nullptr || format == nullptr)
104
0
    return FALSE;
105
106
0
  switch (format->wFormatTag)
107
0
  {
108
0
    case WAVE_FORMAT_PCM:
109
0
      if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
110
0
          (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
111
0
          (format->nChannels != 1 && format->nChannels != 2))
112
0
        return FALSE;
113
114
0
      break;
115
116
0
    default:
117
0
      return FALSE;
118
0
  }
119
120
0
  req_fmt = rdpsnd_oss_get_format(format);
121
122
  /* Check really supported formats by dev. */
123
0
  if (oss->pcm_handle != -1)
124
0
  {
125
0
    if ((req_fmt & oss->supported_formats) == 0)
126
0
      return FALSE;
127
0
  }
128
0
  else
129
0
  {
130
0
    if (req_fmt == 0)
131
0
      return FALSE;
132
0
  }
133
134
0
  return TRUE;
135
0
}
136
137
static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
138
                                  UINT32 latency)
139
0
{
140
0
  int tmp = 0;
141
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
142
143
0
  if (device == nullptr || oss->pcm_handle == -1 || format == nullptr)
144
0
    return FALSE;
145
146
0
  oss->latency = latency;
147
0
  CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT));
148
0
  tmp = rdpsnd_oss_get_format(format);
149
150
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
151
0
  {
152
0
    OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
153
0
    return FALSE;
154
0
  }
155
156
0
  tmp = format->nChannels;
157
158
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
159
0
  {
160
0
    OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
161
0
    return FALSE;
162
0
  }
163
164
0
  tmp = (int)format->nSamplesPerSec;
165
166
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
167
0
  {
168
0
    OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
169
0
    return FALSE;
170
0
  }
171
172
0
  tmp = format->nBlockAlign;
173
174
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
175
0
  {
176
0
    OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
177
0
    return FALSE;
178
0
  }
179
180
0
  return TRUE;
181
0
}
182
183
static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss)
184
0
{
185
0
  int devmask = 0;
186
0
  char mixer_name[PATH_MAX] = "/dev/mixer";
187
188
0
  if (oss->mixer_handle != -1)
189
0
    return;
190
191
0
  if (oss->dev_unit != -1)
192
0
    (void)sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
193
194
0
  if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0)
195
0
  {
196
0
    OSS_LOG_ERR("mixer open failed", errno);
197
0
    oss->mixer_handle = -1;
198
0
    return;
199
0
  }
200
201
0
  if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
202
0
  {
203
0
    OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno);
204
0
    close(oss->mixer_handle);
205
0
    oss->mixer_handle = -1;
206
0
    return;
207
0
  }
208
0
}
209
210
/* Build the OSS PCM device path (/dev/dsp or /dev/dsp<unit>). */
211
static void rdpsnd_oss_pcm_device_name(const rdpsndOssPlugin* oss, char* dev_name, size_t size)
212
0
{
213
0
  if (oss->dev_unit != -1)
214
0
    (void)sprintf_s(dev_name, size, "/dev/dsp%i", oss->dev_unit);
215
0
  else
216
0
    (void)sprintf_s(dev_name, size, "/dev/dsp");
217
0
}
218
219
static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
220
0
{
221
0
  char dev_name[PATH_MAX] = { 0 };
222
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
223
224
0
  if (device == nullptr || oss->pcm_handle != -1)
225
0
    return TRUE;
226
227
0
  rdpsnd_oss_pcm_device_name(oss, dev_name, sizeof(dev_name));
228
229
0
  WLog_INFO(TAG, "open: %s", dev_name);
230
231
0
  if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
232
0
  {
233
0
    OSS_LOG_ERR("sound dev open failed", errno);
234
0
    oss->pcm_handle = -1;
235
0
    return FALSE;
236
0
  }
237
238
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
239
0
  {
240
0
    OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
241
0
    close(oss->pcm_handle);
242
0
    oss->pcm_handle = -1;
243
0
    return FALSE;
244
0
  }
245
246
0
  rdpsnd_oss_set_format(device, format, latency);
247
0
  rdpsnd_oss_open_mixer(oss);
248
0
  return TRUE;
249
0
}
250
251
static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
252
0
{
253
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
254
255
0
  if (device == nullptr)
256
0
    return;
257
258
0
  if (oss->pcm_handle != -1)
259
0
  {
260
0
    WLog_INFO(TAG, "close: dsp");
261
0
    close(oss->pcm_handle);
262
0
    oss->pcm_handle = -1;
263
0
  }
264
265
0
  if (oss->mixer_handle != -1)
266
0
  {
267
0
    WLog_INFO(TAG, "close: mixer");
268
0
    close(oss->mixer_handle);
269
0
    oss->mixer_handle = -1;
270
0
  }
271
0
}
272
273
static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
274
0
{
275
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
276
277
0
  if (device == nullptr)
278
0
    return;
279
280
0
  rdpsnd_oss_close(device);
281
0
  free(oss);
282
0
}
283
284
static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
285
0
{
286
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
287
0
  WINPR_ASSERT(oss);
288
0
  int vol = 0;
289
290
  /* On error return 50% volume. */
291
0
  UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100);  /* 50% */
292
0
  UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
293
0
  UINT32 dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
294
295
0
  if (device == nullptr || oss->mixer_handle == -1)
296
0
    return dwVolume;
297
298
0
  if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
299
0
  {
300
0
    OSS_LOG_ERR("MIXER_READ", errno);
301
0
    return dwVolume;
302
0
  }
303
304
0
  dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
305
0
  dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
306
0
  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
307
0
  return dwVolume;
308
0
}
309
310
static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
311
0
{
312
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
313
0
  WINPR_ASSERT(oss);
314
315
0
  if (device == nullptr || oss->mixer_handle == -1)
316
0
    return FALSE;
317
318
0
  unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF);
319
0
  unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
320
321
0
  left |= (right << 8);
322
323
0
  if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
324
0
  {
325
0
    OSS_LOG_ERR("WRITE_MIXER", errno);
326
0
    return FALSE;
327
0
  }
328
329
0
  return TRUE;
330
0
}
331
332
static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
333
0
{
334
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
335
336
0
  if (device == nullptr || oss->mixer_handle == -1)
337
0
    return 0;
338
339
0
  while (size > 0)
340
0
  {
341
0
    ssize_t status = write(oss->pcm_handle, data, size);
342
343
0
    if (status < 0)
344
0
    {
345
0
      OSS_LOG_ERR("write fail", errno);
346
0
      rdpsnd_oss_close(device);
347
0
      rdpsnd_oss_open(device, nullptr, oss->latency);
348
0
      break;
349
0
    }
350
351
0
    data += status;
352
353
0
    if ((size_t)status <= size)
354
0
      size -= (size_t)status;
355
0
    else
356
0
      size = 0;
357
0
  }
358
359
0
  return 10; /* TODO: Get real latency in [ms] */
360
0
}
361
362
static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
363
0
{
364
0
  int status = 0;
365
0
  char* str_num = nullptr;
366
0
  char* eptr = nullptr;
367
0
  DWORD flags = 0;
368
0
  const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
369
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
370
0
  COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = {
371
0
    { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
372
0
    { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
373
0
  };
374
0
  flags =
375
0
      COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
376
0
  status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss,
377
0
                                      nullptr, nullptr);
378
379
0
  if (status < 0)
380
0
    return status;
381
382
0
  arg = rdpsnd_oss_args;
383
0
  errno = 0;
384
385
0
  do
386
0
  {
387
0
    if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
388
0
      continue;
389
390
0
    CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
391
0
    {
392
0
      str_num = _strdup(arg->Value);
393
394
0
      if (!str_num)
395
0
        return ERROR_OUTOFMEMORY;
396
397
0
      {
398
0
        long val = strtol(str_num, &eptr, 10);
399
400
0
        if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
401
0
        {
402
0
          free(str_num);
403
0
          return CHANNEL_RC_NULL_DATA;
404
0
        }
405
406
0
        oss->dev_unit = (int)val;
407
0
      }
408
409
0
      if (oss->dev_unit < 0 || *eptr != '\0')
410
0
        oss->dev_unit = -1;
411
412
0
      free(str_num);
413
0
    }
414
0
    CommandLineSwitchEnd(arg)
415
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
416
417
0
  return status;
418
0
}
419
420
/**
421
 * Function description
422
 *
423
 * @return 0 on success, otherwise a Win32 error code
424
 */
425
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry(
426
    PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
427
0
{
428
0
  const ADDIN_ARGV* args = nullptr;
429
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
430
431
0
  if (!oss)
432
0
    return CHANNEL_RC_NO_MEMORY;
433
434
0
  oss->device.Open = rdpsnd_oss_open;
435
0
  oss->device.FormatSupported = rdpsnd_oss_format_supported;
436
0
  oss->device.GetVolume = rdpsnd_oss_get_volume;
437
0
  oss->device.SetVolume = rdpsnd_oss_set_volume;
438
0
  oss->device.Play = rdpsnd_oss_play;
439
0
  oss->device.Close = rdpsnd_oss_close;
440
0
  oss->device.Free = rdpsnd_oss_free;
441
0
  oss->pcm_handle = -1;
442
0
  oss->mixer_handle = -1;
443
0
  oss->dev_unit = -1;
444
0
  args = pEntryPoints->args;
445
0
  if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
446
0
  {
447
0
    free(oss);
448
0
    return ERROR_INVALID_PARAMETER;
449
0
  }
450
451
  /* Only register the backend if the OSS device actually exists and is
452
   * writable. On systems without OSS (no /dev/dsp) this leaves rdpsnd->device
453
   * unset so subsystem selection falls through to the next available backend.
454
   * Otherwise oss gets selected and the first audio the server streams fails
455
   * to open the device, which aborts the whole RDP connection. */
456
0
  {
457
0
    char dev_name[PATH_MAX] = { 0 };
458
0
    rdpsnd_oss_pcm_device_name(oss, dev_name, sizeof(dev_name));
459
460
0
    if (access(dev_name, W_OK) != 0)
461
0
    {
462
0
      WLog_WARN(TAG, "OSS device %s not available (errno %d), skipping oss backend", dev_name,
463
0
                errno);
464
0
      free(oss);
465
0
      return CHANNEL_RC_INITIALIZATION_ERROR;
466
0
    }
467
0
  }
468
469
0
  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
470
0
  return CHANNEL_RC_OK;
471
0
}