Coverage Report

Created: 2025-07-01 06:46

/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
        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 == NULL || format == NULL)
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 == NULL || oss->pcm_handle == -1 || format == NULL)
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
static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
211
0
{
212
0
  char dev_name[PATH_MAX] = "/dev/dsp";
213
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
214
215
0
  if (device == NULL || oss->pcm_handle != -1)
216
0
    return TRUE;
217
218
0
  if (oss->dev_unit != -1)
219
0
    (void)sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit);
220
221
0
  WLog_INFO(TAG, "open: %s", dev_name);
222
223
0
  if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
224
0
  {
225
0
    OSS_LOG_ERR("sound dev open failed", errno);
226
0
    oss->pcm_handle = -1;
227
0
    return FALSE;
228
0
  }
229
230
0
  if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
231
0
  {
232
0
    OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
233
0
    close(oss->pcm_handle);
234
0
    oss->pcm_handle = -1;
235
0
    return FALSE;
236
0
  }
237
238
0
  rdpsnd_oss_set_format(device, format, latency);
239
0
  rdpsnd_oss_open_mixer(oss);
240
0
  return TRUE;
241
0
}
242
243
static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
244
0
{
245
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
246
247
0
  if (device == NULL)
248
0
    return;
249
250
0
  if (oss->pcm_handle != -1)
251
0
  {
252
0
    WLog_INFO(TAG, "close: dsp");
253
0
    close(oss->pcm_handle);
254
0
    oss->pcm_handle = -1;
255
0
  }
256
257
0
  if (oss->mixer_handle != -1)
258
0
  {
259
0
    WLog_INFO(TAG, "close: mixer");
260
0
    close(oss->mixer_handle);
261
0
    oss->mixer_handle = -1;
262
0
  }
263
0
}
264
265
static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
266
0
{
267
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
268
269
0
  if (device == NULL)
270
0
    return;
271
272
0
  rdpsnd_oss_close(device);
273
0
  free(oss);
274
0
}
275
276
static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
277
0
{
278
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
279
0
  WINPR_ASSERT(oss);
280
0
  int vol = 0;
281
282
  /* On error return 50% volume. */
283
0
  UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100);  /* 50% */
284
0
  UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
285
0
  UINT32 dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
286
287
0
  if (device == NULL || oss->mixer_handle == -1)
288
0
    return dwVolume;
289
290
0
  if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
291
0
  {
292
0
    OSS_LOG_ERR("MIXER_READ", errno);
293
0
    return dwVolume;
294
0
  }
295
296
0
  dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
297
0
  dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
298
0
  dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
299
0
  return dwVolume;
300
0
}
301
302
static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
303
0
{
304
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
305
0
  WINPR_ASSERT(oss);
306
307
0
  if (device == NULL || oss->mixer_handle == -1)
308
0
    return FALSE;
309
310
0
  unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF);
311
0
  unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
312
313
0
  left |= (right << 8);
314
315
0
  if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
316
0
  {
317
0
    OSS_LOG_ERR("WRITE_MIXER", errno);
318
0
    return FALSE;
319
0
  }
320
321
0
  return TRUE;
322
0
}
323
324
static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
325
0
{
326
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
327
328
0
  if (device == NULL || oss->mixer_handle == -1)
329
0
    return 0;
330
331
0
  while (size > 0)
332
0
  {
333
0
    ssize_t status = write(oss->pcm_handle, data, size);
334
335
0
    if (status < 0)
336
0
    {
337
0
      OSS_LOG_ERR("write fail", errno);
338
0
      rdpsnd_oss_close(device);
339
0
      rdpsnd_oss_open(device, NULL, oss->latency);
340
0
      break;
341
0
    }
342
343
0
    data += status;
344
345
0
    if ((size_t)status <= size)
346
0
      size -= (size_t)status;
347
0
    else
348
0
      size = 0;
349
0
  }
350
351
0
  return 10; /* TODO: Get real latency in [ms] */
352
0
}
353
354
static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
355
0
{
356
0
  int status = 0;
357
0
  char* str_num = NULL;
358
0
  char* eptr = NULL;
359
0
  DWORD flags = 0;
360
0
  const COMMAND_LINE_ARGUMENT_A* arg = NULL;
361
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
362
0
  COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
363
0
                                                NULL, NULL, -1, NULL, "device" },
364
0
                                              { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
365
0
  flags =
366
0
      COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
367
0
  status =
368
0
      CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL);
369
370
0
  if (status < 0)
371
0
    return status;
372
373
0
  arg = rdpsnd_oss_args;
374
0
  errno = 0;
375
376
0
  do
377
0
  {
378
0
    if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
379
0
      continue;
380
381
0
    CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
382
0
    {
383
0
      str_num = _strdup(arg->Value);
384
385
0
      if (!str_num)
386
0
        return ERROR_OUTOFMEMORY;
387
388
0
      {
389
0
        long val = strtol(str_num, &eptr, 10);
390
391
0
        if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
392
0
        {
393
0
          free(str_num);
394
0
          return CHANNEL_RC_NULL_DATA;
395
0
        }
396
397
0
        oss->dev_unit = (int)val;
398
0
      }
399
400
0
      if (oss->dev_unit < 0 || *eptr != '\0')
401
0
        oss->dev_unit = -1;
402
403
0
      free(str_num);
404
0
    }
405
0
    CommandLineSwitchEnd(arg)
406
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
407
408
0
  return status;
409
0
}
410
411
/**
412
 * Function description
413
 *
414
 * @return 0 on success, otherwise a Win32 error code
415
 */
416
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry(
417
    PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
418
0
{
419
0
  const ADDIN_ARGV* args = NULL;
420
0
  rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
421
422
0
  if (!oss)
423
0
    return CHANNEL_RC_NO_MEMORY;
424
425
0
  oss->device.Open = rdpsnd_oss_open;
426
0
  oss->device.FormatSupported = rdpsnd_oss_format_supported;
427
0
  oss->device.GetVolume = rdpsnd_oss_get_volume;
428
0
  oss->device.SetVolume = rdpsnd_oss_set_volume;
429
0
  oss->device.Play = rdpsnd_oss_play;
430
0
  oss->device.Close = rdpsnd_oss_close;
431
0
  oss->device.Free = rdpsnd_oss_free;
432
0
  oss->pcm_handle = -1;
433
0
  oss->mixer_handle = -1;
434
0
  oss->dev_unit = -1;
435
0
  args = pEntryPoints->args;
436
0
  if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
437
0
  {
438
0
    free(oss);
439
0
    return ERROR_INVALID_PARAMETER;
440
0
  }
441
0
  pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
442
0
  return CHANNEL_RC_OK;
443
0
}