FreeRDP
Loading...
Searching...
No Matches
audin_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/synch.h>
30#include <winpr/string.h>
31#include <winpr/thread.h>
32#include <winpr/cmdline.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/freerdp.h>
44#include <freerdp/addin.h>
45#include <freerdp/channels/rdpsnd.h>
46
47#include "audin_main.h"
48
49typedef struct
50{
51 IAudinDevice iface;
52
53 HANDLE thread;
54 HANDLE stopEvent;
55
56 AUDIO_FORMAT format;
57 UINT32 FramesPerPacket;
58 int dev_unit;
59
60 AudinReceive receive;
61 void* user_data;
62
63 rdpContext* rdpcontext;
64} AudinOSSDevice;
65
66static void OSS_LOG_ERR(const char* _text, int _error)
67{
68 if ((_error) != 0)
69 {
70 char buffer[256] = WINPR_C_ARRAY_INIT;
71 WLog_ERR(TAG, "%s: %i - %s\n", (_text), (_error),
72 winpr_strerror((_error), buffer, sizeof(buffer)));
73 }
74}
75
76static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format)
77{
78 switch (format->wFormatTag)
79 {
80 case WAVE_FORMAT_PCM:
81 switch (format->wBitsPerSample)
82 {
83 case 8:
84 return AFMT_S8;
85
86 case 16:
87 return AFMT_S16_LE;
88
89 default:
90 break;
91 }
92
93 break;
94 default:
95 break;
96 }
97
98 return 0;
99}
100
101static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
102{
103 if (device == nullptr || format == nullptr)
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 return TRUE;
121}
122
128static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
129 UINT32 FramesPerPacket)
130{
131 AudinOSSDevice* oss = (AudinOSSDevice*)device;
132
133 if (device == nullptr || format == nullptr)
134 return ERROR_INVALID_PARAMETER;
135
136 oss->FramesPerPacket = FramesPerPacket;
137 oss->format = *format;
138 return CHANNEL_RC_OK;
139}
140
141static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
142{
143 char dev_name[PATH_MAX] = "/dev/dsp";
144 int pcm_handle = -1;
145 BYTE* buffer = nullptr;
146 unsigned long tmp = 0;
147 size_t buffer_size = 0;
148 AudinOSSDevice* oss = (AudinOSSDevice*)arg;
149 UINT error = 0;
150 DWORD status = 0;
151
152 if (oss == nullptr)
153 {
154 error = ERROR_INVALID_PARAMETER;
155 goto err_out;
156 }
157
158 if (oss->dev_unit != -1)
159 (void)sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
160
161 WLog_INFO(TAG, "open: %s", dev_name);
162
163 if ((pcm_handle = open(dev_name, O_RDONLY)) < 0)
164 {
165 OSS_LOG_ERR("sound dev open failed", errno);
166 error = ERROR_INTERNAL_ERROR;
167 goto err_out;
168 }
169
170 /* Set format. */
171 tmp = audin_oss_get_format(&oss->format);
172
173 if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
174 OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
175
176 tmp = oss->format.nChannels;
177
178 if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
179 OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
180
181 tmp = oss->format.nSamplesPerSec;
182
183 if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
184 OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
185
186 tmp = oss->format.nBlockAlign;
187
188 if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
189 OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
190
191 buffer_size =
192 (1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull));
193 if (buffer_size > INT32_MAX)
194 goto err_out;
195
196 buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
197
198 if (nullptr == buffer)
199 {
200 OSS_LOG_ERR("malloc() fail", errno);
201 error = ERROR_NOT_ENOUGH_MEMORY;
202 goto err_out;
203 }
204
205 while (1)
206 {
207 SSIZE_T stmp = -1;
208 status = WaitForSingleObject(oss->stopEvent, 0);
209
210 if (status == WAIT_FAILED)
211 {
212 error = GetLastError();
213 WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
214 goto err_out;
215 }
216
217 if (status == WAIT_OBJECT_0)
218 break;
219
220 stmp = read(pcm_handle, buffer, buffer_size);
221
222 /* Error happen. */
223 if (stmp < 0)
224 {
225 OSS_LOG_ERR("read() error", errno);
226 continue;
227 }
228
229 if ((size_t)stmp < buffer_size) /* Not enough data. */
230 continue;
231
232 if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data)))
233 {
234 WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error);
235 break;
236 }
237 }
238
239err_out:
240
241 if (error && oss && oss->rdpcontext)
242 setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error");
243
244 if (pcm_handle != -1)
245 {
246 WLog_INFO(TAG, "close: %s", dev_name);
247 close(pcm_handle);
248 }
249
250 free(buffer);
251 ExitThread(error);
252 return error;
253}
254
260static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data)
261{
262 AudinOSSDevice* oss = (AudinOSSDevice*)device;
263 oss->receive = receive;
264 oss->user_data = user_data;
265
266 if (!(oss->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
267 {
268 WLog_ERR(TAG, "CreateEvent failed!");
269 return ERROR_INTERNAL_ERROR;
270 }
271
272 if (!(oss->thread = CreateThread(nullptr, 0, audin_oss_thread_func, oss, 0, nullptr)))
273 {
274 WLog_ERR(TAG, "CreateThread failed!");
275 (void)CloseHandle(oss->stopEvent);
276 oss->stopEvent = nullptr;
277 return ERROR_INTERNAL_ERROR;
278 }
279
280 return CHANNEL_RC_OK;
281}
282
288static UINT audin_oss_close(IAudinDevice* device)
289{
290 UINT error = 0;
291 AudinOSSDevice* oss = (AudinOSSDevice*)device;
292
293 if (device == nullptr)
294 return ERROR_INVALID_PARAMETER;
295
296 if (oss->stopEvent != nullptr)
297 {
298 (void)SetEvent(oss->stopEvent);
299
300 if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
301 {
302 error = GetLastError();
303 WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
304 return error;
305 }
306
307 (void)CloseHandle(oss->stopEvent);
308 oss->stopEvent = nullptr;
309 (void)CloseHandle(oss->thread);
310 oss->thread = nullptr;
311 }
312
313 oss->receive = nullptr;
314 oss->user_data = nullptr;
315 return CHANNEL_RC_OK;
316}
317
323static UINT audin_oss_free(IAudinDevice* device)
324{
325 AudinOSSDevice* oss = (AudinOSSDevice*)device;
326 UINT error = 0;
327
328 if (device == nullptr)
329 return ERROR_INVALID_PARAMETER;
330
331 if ((error = audin_oss_close(device)))
332 {
333 WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error);
334 }
335
336 free(oss);
337 return CHANNEL_RC_OK;
338}
339
345static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args)
346{
347 int status = 0;
348 char* str_num = nullptr;
349 char* eptr = nullptr;
350 DWORD flags = 0;
351 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
352 AudinOSSDevice* oss = device;
353 COMMAND_LINE_ARGUMENT_A audin_oss_args[] = {
354 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
355 "audio device name" },
356 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
357 };
358
359 flags =
360 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
361 status = CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, nullptr,
362 nullptr);
363
364 if (status < 0)
365 return ERROR_INVALID_PARAMETER;
366
367 arg = audin_oss_args;
368 errno = 0;
369
370 do
371 {
372 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
373 continue;
374
375 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
376 {
377 str_num = _strdup(arg->Value);
378
379 if (!str_num)
380 {
381 WLog_ERR(TAG, "_strdup failed!");
382 return CHANNEL_RC_NO_MEMORY;
383 }
384
385 {
386 long val = strtol(str_num, &eptr, 10);
387
388 if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
389 {
390 free(str_num);
391 return CHANNEL_RC_NULL_DATA;
392 }
393
394 oss->dev_unit = (INT32)val;
395 }
396
397 if (oss->dev_unit < 0 || *eptr != '\0')
398 oss->dev_unit = -1;
399
400 free(str_num);
401 }
402 CommandLineSwitchEnd(arg)
403 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
404
405 return CHANNEL_RC_OK;
406}
407
413FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_audin_client_subsystem_entry(
415{
416 const ADDIN_ARGV* args = nullptr;
417 AudinOSSDevice* oss = nullptr;
418 UINT error = 0;
419 oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice));
420
421 if (!oss)
422 {
423 WLog_ERR(TAG, "calloc failed!");
424 return CHANNEL_RC_NO_MEMORY;
425 }
426
427 oss->iface.Open = audin_oss_open;
428 oss->iface.FormatSupported = audin_oss_format_supported;
429 oss->iface.SetFormat = audin_oss_set_format;
430 oss->iface.Close = audin_oss_close;
431 oss->iface.Free = audin_oss_free;
432 oss->rdpcontext = pEntryPoints->rdpcontext;
433 oss->dev_unit = -1;
434 args = pEntryPoints->args;
435
436 if ((error = audin_oss_parse_addin_args(oss, args)))
437 {
438 WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error);
439 goto error_out;
440 }
441
442 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss)))
443 {
444 WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
445 goto error_out;
446 }
447
448 return CHANNEL_RC_OK;
449error_out:
450 free(oss);
451 return error;
452}