1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_AUDIO_DRIVER_ARTS
24 
25 /* Allow access to a raw mixing buffer */
26 
27 #ifdef HAVE_SIGNAL_H
28 #include <signal.h>
29 #endif
30 #include <unistd.h>
31 #include <errno.h>
32 
33 #include "SDL_timer.h"
34 #include "SDL_audio.h"
35 #include "../SDL_audio_c.h"
36 #include "SDL_artsaudio.h"
37 
38 #ifdef SDL_AUDIO_DRIVER_ARTS_DYNAMIC
39 #include "SDL_name.h"
40 #include "SDL_loadso.h"
41 #else
42 #define SDL_NAME(X) X
43 #endif
44 
45 #ifdef SDL_AUDIO_DRIVER_ARTS_DYNAMIC
46 
47 static const char *arts_library = SDL_AUDIO_DRIVER_ARTS_DYNAMIC;
48 static void *arts_handle = NULL;
49 
50 /* !!! FIXME: I hate this SDL_NAME clutter...it makes everything so messy! */
51 static int (*SDL_NAME(arts_init)) (void);
52 static void (*SDL_NAME(arts_free)) (void);
53 static arts_stream_t(*SDL_NAME(arts_play_stream)) (int rate, int bits,
54                                                    int channels,
55                                                    const char *name);
56 static int (*SDL_NAME(arts_stream_set)) (arts_stream_t s,
57                                          arts_parameter_t param, int value);
58 static int (*SDL_NAME(arts_stream_get)) (arts_stream_t s,
59                                          arts_parameter_t param);
60 static int (*SDL_NAME(arts_write)) (arts_stream_t s, const void *buffer,
61                                     int count);
62 static void (*SDL_NAME(arts_close_stream)) (arts_stream_t s);
63 static int (*SDL_NAME(arts_suspend))(void);
64 static int (*SDL_NAME(arts_suspended)) (void);
65 static const char *(*SDL_NAME(arts_error_text)) (int errorcode);
66 
67 #define SDL_ARTS_SYM(x) { #x, (void **) (char *) &SDL_NAME(x) }
68 static struct
69 {
70     const char *name;
71     void **func;
72 } arts_functions[] = {
73 /* *INDENT-OFF* */
74     SDL_ARTS_SYM(arts_init),
75     SDL_ARTS_SYM(arts_free),
76     SDL_ARTS_SYM(arts_play_stream),
77     SDL_ARTS_SYM(arts_stream_set),
78     SDL_ARTS_SYM(arts_stream_get),
79     SDL_ARTS_SYM(arts_write),
80     SDL_ARTS_SYM(arts_close_stream),
81     SDL_ARTS_SYM(arts_suspend),
82     SDL_ARTS_SYM(arts_suspended),
83     SDL_ARTS_SYM(arts_error_text),
84 /* *INDENT-ON* */
85 };
86 
87 #undef SDL_ARTS_SYM
88 
89 static void
UnloadARTSLibrary()90 UnloadARTSLibrary()
91 {
92     if (arts_handle != NULL) {
93         SDL_UnloadObject(arts_handle);
94         arts_handle = NULL;
95     }
96 }
97 
98 static int
LoadARTSLibrary(void)99 LoadARTSLibrary(void)
100 {
101     int i, retval = -1;
102 
103     if (arts_handle == NULL) {
104         arts_handle = SDL_LoadObject(arts_library);
105         if (arts_handle != NULL) {
106             retval = 0;
107             for (i = 0; i < SDL_arraysize(arts_functions); ++i) {
108                 *arts_functions[i].func =
109                     SDL_LoadFunction(arts_handle, arts_functions[i].name);
110                 if (!*arts_functions[i].func) {
111                     retval = -1;
112                     UnloadARTSLibrary();
113                     break;
114                 }
115             }
116         }
117     }
118 
119     return retval;
120 }
121 
122 #else
123 
124 static void
UnloadARTSLibrary()125 UnloadARTSLibrary()
126 {
127     return;
128 }
129 
130 static int
LoadARTSLibrary(void)131 LoadARTSLibrary(void)
132 {
133     return 0;
134 }
135 
136 #endif /* SDL_AUDIO_DRIVER_ARTS_DYNAMIC */
137 
138 /* This function waits until it is possible to write a full sound buffer */
139 static void
ARTS_WaitDevice(_THIS)140 ARTS_WaitDevice(_THIS)
141 {
142     Sint32 ticks;
143 
144     /* Check to see if the thread-parent process is still alive */
145     {
146         static int cnt = 0;
147         /* Note that this only works with thread implementations
148            that use a different process id for each thread.
149          */
150         /* Check every 10 loops */
151         if (this->hidden->parent && (((++cnt) % 10) == 0)) {
152             if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
153                 SDL_OpenedAudioDeviceDisconnected(this);
154             }
155         }
156     }
157 
158     /* Use timer for general audio synchronization */
159     ticks =
160         ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS;
161     if (ticks > 0) {
162         SDL_Delay(ticks);
163     }
164 }
165 
166 static void
ARTS_PlayDevice(_THIS)167 ARTS_PlayDevice(_THIS)
168 {
169     /* Write the audio data */
170     int written = SDL_NAME(arts_write) (this->hidden->stream,
171                                         this->hidden->mixbuf,
172                                         this->hidden->mixlen);
173 
174     /* If timer synchronization is enabled, set the next write frame */
175     if (this->hidden->frame_ticks) {
176         this->hidden->next_frame += this->hidden->frame_ticks;
177     }
178 
179     /* If we couldn't write, assume fatal error for now */
180     if (written < 0) {
181         SDL_OpenedAudioDeviceDisconnected(this);
182     }
183 #ifdef DEBUG_AUDIO
184     fprintf(stderr, "Wrote %d bytes of audio data\n", written);
185 #endif
186 }
187 
188 static Uint8 *
ARTS_GetDeviceBuf(_THIS)189 ARTS_GetDeviceBuf(_THIS)
190 {
191     return (this->hidden->mixbuf);
192 }
193 
194 
195 static void
ARTS_CloseDevice(_THIS)196 ARTS_CloseDevice(_THIS)
197 {
198     if (this->hidden->stream) {
199         SDL_NAME(arts_close_stream) (this->hidden->stream);
200     }
201     SDL_NAME(arts_free) ();
202     SDL_free(this->hidden->mixbuf);
203     SDL_free(this->hidden);
204 }
205 
206 static int
ARTS_Suspend(void)207 ARTS_Suspend(void)
208 {
209     const Uint32 abortms = SDL_GetTicks() + 3000; /* give up after 3 secs */
210     while ( (!SDL_NAME(arts_suspended)()) && !SDL_TICKS_PASSED(SDL_GetTicks(), abortms) ) {
211         if ( SDL_NAME(arts_suspend)() ) {
212             break;
213         }
214     }
215     return SDL_NAME(arts_suspended)();
216 }
217 
218 static int
ARTS_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)219 ARTS_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
220 {
221     int rc = 0;
222     int bits = 0, frag_spec = 0;
223     SDL_AudioFormat test_format = 0, format = 0;
224 
225     /* Initialize all variables that we clean on shutdown */
226     this->hidden = (struct SDL_PrivateAudioData *)
227         SDL_malloc((sizeof *this->hidden));
228     if (this->hidden == NULL) {
229         return SDL_OutOfMemory();
230     }
231     SDL_zerop(this->hidden);
232 
233     /* Try for a closest match on audio format */
234     for (test_format = SDL_FirstAudioFormat(this->spec.format);
235          !format && test_format;) {
236 #ifdef DEBUG_AUDIO
237         fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
238 #endif
239         switch (test_format) {
240         case AUDIO_U8:
241             bits = 8;
242             format = 1;
243             break;
244         case AUDIO_S16LSB:
245             bits = 16;
246             format = 1;
247             break;
248         default:
249             format = 0;
250             break;
251         }
252         if (!format) {
253             test_format = SDL_NextAudioFormat();
254         }
255     }
256     if (format == 0) {
257         return SDL_SetError("Couldn't find any hardware audio formats");
258     }
259     this->spec.format = test_format;
260 
261     if ((rc = SDL_NAME(arts_init) ()) != 0) {
262         return SDL_SetError("Unable to initialize ARTS: %s",
263                             SDL_NAME(arts_error_text) (rc));
264     }
265 
266     if (!ARTS_Suspend()) {
267         return SDL_SetError("ARTS can not open audio device");
268     }
269 
270     this->hidden->stream = SDL_NAME(arts_play_stream) (this->spec.freq,
271                                                        bits,
272                                                        this->spec.channels,
273                                                        "SDL");
274 
275     /* Play nothing so we have at least one write (server bug workaround). */
276     SDL_NAME(arts_write) (this->hidden->stream, "", 0);
277 
278     /* Calculate the final parameters for this audio specification */
279     SDL_CalculateAudioSpec(&this->spec);
280 
281     /* Determine the power of two of the fragment size */
282     for (frag_spec = 0; (0x01 << frag_spec) < this->spec.size; ++frag_spec);
283     if ((0x01 << frag_spec) != this->spec.size) {
284         return SDL_SetError("Fragment size must be a power of two");
285     }
286     frag_spec |= 0x00020000;    /* two fragments, for low latency */
287 
288 #ifdef ARTS_P_PACKET_SETTINGS
289     SDL_NAME(arts_stream_set) (this->hidden->stream,
290                                ARTS_P_PACKET_SETTINGS, frag_spec);
291 #else
292     SDL_NAME(arts_stream_set) (this->hidden->stream, ARTS_P_PACKET_SIZE,
293                                frag_spec & 0xffff);
294     SDL_NAME(arts_stream_set) (this->hidden->stream, ARTS_P_PACKET_COUNT,
295                                frag_spec >> 16);
296 #endif
297     this->spec.size = SDL_NAME(arts_stream_get) (this->hidden->stream,
298                                                  ARTS_P_PACKET_SIZE);
299 
300     /* Allocate mixing buffer */
301     this->hidden->mixlen = this->spec.size;
302     this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
303     if (this->hidden->mixbuf == NULL) {
304         return SDL_OutOfMemory();
305     }
306     SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
307 
308     /* Get the parent process id (we're the parent of the audio thread) */
309     this->hidden->parent = getpid();
310 
311     /* We're ready to rock and roll. :-) */
312     return 0;
313 }
314 
315 
316 static void
ARTS_Deinitialize(void)317 ARTS_Deinitialize(void)
318 {
319     UnloadARTSLibrary();
320 }
321 
322 
323 static int
ARTS_Init(SDL_AudioDriverImpl * impl)324 ARTS_Init(SDL_AudioDriverImpl * impl)
325 {
326     if (LoadARTSLibrary() < 0) {
327         return 0;
328     } else {
329         if (SDL_NAME(arts_init) () != 0) {
330             UnloadARTSLibrary();
331             SDL_SetError("ARTS: arts_init failed (no audio server?)");
332             return 0;
333         }
334 
335         /* Play a stream so aRts doesn't crash */
336         if (ARTS_Suspend()) {
337             arts_stream_t stream;
338             stream = SDL_NAME(arts_play_stream) (44100, 16, 2, "SDL");
339             SDL_NAME(arts_write) (stream, "", 0);
340             SDL_NAME(arts_close_stream) (stream);
341         }
342 
343         SDL_NAME(arts_free) ();
344     }
345 
346     /* Set the function pointers */
347     impl->OpenDevice = ARTS_OpenDevice;
348     impl->PlayDevice = ARTS_PlayDevice;
349     impl->WaitDevice = ARTS_WaitDevice;
350     impl->GetDeviceBuf = ARTS_GetDeviceBuf;
351     impl->CloseDevice = ARTS_CloseDevice;
352     impl->Deinitialize = ARTS_Deinitialize;
353     impl->OnlyHasDefaultOutputDevice = 1;
354 
355     return 1;   /* this audio target is available. */
356 }
357 
358 
359 AudioBootStrap ARTS_bootstrap = {
360     "arts", "Analog RealTime Synthesizer", ARTS_Init, 0
361 };
362 
363 #endif /* SDL_AUDIO_DRIVER_ARTS */
364 
365 /* vi: set ts=4 sw=4 expandtab: */
366