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