1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 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_ESD
24 
25 /* Allow access to an ESD network stream mixing buffer */
26 
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <signal.h>
30 #include <errno.h>
31 #include <esd.h>
32 
33 #include "SDL_timer.h"
34 #include "SDL_audio.h"
35 #include "../SDL_audio_c.h"
36 #include "SDL_esdaudio.h"
37 
38 #ifdef SDL_AUDIO_DRIVER_ESD_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_ESD_DYNAMIC
46 
47 static const char *esd_library = SDL_AUDIO_DRIVER_ESD_DYNAMIC;
48 static void *esd_handle = NULL;
49 
50 static int (*SDL_NAME(esd_open_sound)) (const char *host);
51 static int (*SDL_NAME(esd_close)) (int esd);
52 static int (*SDL_NAME(esd_play_stream)) (esd_format_t format, int rate,
53                                          const char *host, const char *name);
54 
55 #define SDL_ESD_SYM(x) { #x, (void **) (char *) &SDL_NAME(x) }
56 static struct
57 {
58     const char *name;
59     void **func;
60 } const esd_functions[] = {
61     SDL_ESD_SYM(esd_open_sound),
62     SDL_ESD_SYM(esd_close), SDL_ESD_SYM(esd_play_stream),
63 };
64 
65 #undef SDL_ESD_SYM
66 
67 static void
UnloadESDLibrary()68 UnloadESDLibrary()
69 {
70     if (esd_handle != NULL) {
71         SDL_UnloadObject(esd_handle);
72         esd_handle = NULL;
73     }
74 }
75 
76 static int
LoadESDLibrary(void)77 LoadESDLibrary(void)
78 {
79     int i, retval = -1;
80 
81     if (esd_handle == NULL) {
82         esd_handle = SDL_LoadObject(esd_library);
83         if (esd_handle) {
84             retval = 0;
85             for (i = 0; i < SDL_arraysize(esd_functions); ++i) {
86                 *esd_functions[i].func =
87                     SDL_LoadFunction(esd_handle, esd_functions[i].name);
88                 if (!*esd_functions[i].func) {
89                     retval = -1;
90                     UnloadESDLibrary();
91                     break;
92                 }
93             }
94         }
95     }
96     return retval;
97 }
98 
99 #else
100 
101 static void
UnloadESDLibrary()102 UnloadESDLibrary()
103 {
104     return;
105 }
106 
107 static int
LoadESDLibrary(void)108 LoadESDLibrary(void)
109 {
110     return 0;
111 }
112 
113 #endif /* SDL_AUDIO_DRIVER_ESD_DYNAMIC */
114 
115 
116 /* This function waits until it is possible to write a full sound buffer */
117 static void
ESD_WaitDevice(_THIS)118 ESD_WaitDevice(_THIS)
119 {
120     Sint32 ticks;
121 
122     /* Check to see if the thread-parent process is still alive */
123     {
124         static int cnt = 0;
125         /* Note that this only works with thread implementations
126            that use a different process id for each thread.
127          */
128         /* Check every 10 loops */
129         if (this->hidden->parent && (((++cnt) % 10) == 0)) {
130             if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
131                 SDL_OpenedAudioDeviceDisconnected(this);
132             }
133         }
134     }
135 
136     /* Use timer for general audio synchronization */
137     ticks = ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS;
138     if (ticks > 0) {
139         SDL_Delay(ticks);
140     }
141 }
142 
143 static void
ESD_PlayDevice(_THIS)144 ESD_PlayDevice(_THIS)
145 {
146     int written = 0;
147 
148     /* Write the audio data, checking for EAGAIN on broken audio drivers */
149     do {
150         written = write(this->hidden->audio_fd,
151                         this->hidden->mixbuf, this->hidden->mixlen);
152         if ((written < 0) && ((errno == 0) || (errno == EAGAIN))) {
153             SDL_Delay(1);       /* Let a little CPU time go by */
154         }
155     } while ((written < 0) &&
156              ((errno == 0) || (errno == EAGAIN) || (errno == EINTR)));
157 
158     /* Set the next write frame */
159     this->hidden->next_frame += this->hidden->frame_ticks;
160 
161     /* If we couldn't write, assume fatal error for now */
162     if (written < 0) {
163         SDL_OpenedAudioDeviceDisconnected(this);
164     }
165 }
166 
167 static Uint8 *
ESD_GetDeviceBuf(_THIS)168 ESD_GetDeviceBuf(_THIS)
169 {
170     return (this->hidden->mixbuf);
171 }
172 
173 static void
ESD_CloseDevice(_THIS)174 ESD_CloseDevice(_THIS)
175 {
176     if (this->hidden->audio_fd >= 0) {
177         SDL_NAME(esd_close) (this->hidden->audio_fd);
178     }
179     SDL_free(this->hidden->mixbuf);
180     SDL_free(this->hidden);
181 }
182 
183 /* Try to get the name of the program */
184 static char *
get_progname(void)185 get_progname(void)
186 {
187     char *progname = NULL;
188 #ifdef __LINUX__
189     FILE *fp;
190     static char temp[BUFSIZ];
191 
192     SDL_snprintf(temp, SDL_arraysize(temp), "/proc/%d/cmdline", getpid());
193     fp = fopen(temp, "r");
194     if (fp != NULL) {
195         if (fgets(temp, sizeof(temp) - 1, fp)) {
196             progname = SDL_strrchr(temp, '/');
197             if (progname == NULL) {
198                 progname = temp;
199             } else {
200                 progname = progname + 1;
201             }
202         }
203         fclose(fp);
204     }
205 #endif
206     return (progname);
207 }
208 
209 
210 static int
ESD_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)211 ESD_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
212 {
213     esd_format_t format = (ESD_STREAM | ESD_PLAY);
214     SDL_AudioFormat test_format = 0;
215     int found = 0;
216 
217     /* Initialize all variables that we clean on shutdown */
218     this->hidden = (struct SDL_PrivateAudioData *)
219         SDL_malloc((sizeof *this->hidden));
220     if (this->hidden == NULL) {
221         return SDL_OutOfMemory();
222     }
223     SDL_zerop(this->hidden);
224     this->hidden->audio_fd = -1;
225 
226     /* Convert audio spec to the ESD audio format */
227     /* Try for a closest match on audio format */
228     for (test_format = SDL_FirstAudioFormat(this->spec.format);
229          !found && test_format; test_format = SDL_NextAudioFormat()) {
230 #ifdef DEBUG_AUDIO
231         fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
232 #endif
233         found = 1;
234         switch (test_format) {
235         case AUDIO_U8:
236             format |= ESD_BITS8;
237             break;
238         case AUDIO_S16SYS:
239             format |= ESD_BITS16;
240             break;
241         default:
242             found = 0;
243             break;
244         }
245     }
246 
247     if (!found) {
248         return SDL_SetError("Couldn't find any hardware audio formats");
249     }
250 
251     if (this->spec.channels == 1) {
252         format |= ESD_MONO;
253     } else {
254         format |= ESD_STEREO;
255     }
256 #if 0
257     this->spec.samples = ESD_BUF_SIZE;  /* Darn, no way to change this yet */
258 #endif
259 
260     /* Open a connection to the ESD audio server */
261     this->hidden->audio_fd =
262         SDL_NAME(esd_play_stream) (format, this->spec.freq, NULL,
263                                    get_progname());
264 
265     if (this->hidden->audio_fd < 0) {
266         return SDL_SetError("Couldn't open ESD connection");
267     }
268 
269     /* Calculate the final parameters for this audio specification */
270     SDL_CalculateAudioSpec(&this->spec);
271     this->hidden->frame_ticks =
272         (float) (this->spec.samples * 1000) / this->spec.freq;
273     this->hidden->next_frame = SDL_GetTicks() + this->hidden->frame_ticks;
274 
275     /* Allocate mixing buffer */
276     this->hidden->mixlen = this->spec.size;
277     this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
278     if (this->hidden->mixbuf == NULL) {
279         return SDL_OutOfMemory();
280     }
281     SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
282 
283     /* Get the parent process id (we're the parent of the audio thread) */
284     this->hidden->parent = getpid();
285 
286     /* We're ready to rock and roll. :-) */
287     return 0;
288 }
289 
290 static void
ESD_Deinitialize(void)291 ESD_Deinitialize(void)
292 {
293     UnloadESDLibrary();
294 }
295 
296 static int
ESD_Init(SDL_AudioDriverImpl * impl)297 ESD_Init(SDL_AudioDriverImpl * impl)
298 {
299     if (LoadESDLibrary() < 0) {
300         return 0;
301     } else {
302         int connection = 0;
303 
304         /* Don't start ESD if it's not running */
305         SDL_setenv("ESD_NO_SPAWN", "1", 0);
306 
307         connection = SDL_NAME(esd_open_sound) (NULL);
308         if (connection < 0) {
309             UnloadESDLibrary();
310             SDL_SetError("ESD: esd_open_sound failed (no audio server?)");
311             return 0;
312         }
313         SDL_NAME(esd_close) (connection);
314     }
315 
316     /* Set the function pointers */
317     impl->OpenDevice = ESD_OpenDevice;
318     impl->PlayDevice = ESD_PlayDevice;
319     impl->WaitDevice = ESD_WaitDevice;
320     impl->GetDeviceBuf = ESD_GetDeviceBuf;
321     impl->CloseDevice = ESD_CloseDevice;
322     impl->Deinitialize = ESD_Deinitialize;
323     impl->OnlyHasDefaultOutputDevice = 1;
324 
325     return 1;   /* this audio target is available. */
326 }
327 
328 
329 AudioBootStrap ESD_bootstrap = {
330     "esd", "Enlightened Sound Daemon", ESD_Init, 0
331 };
332 
333 #endif /* SDL_AUDIO_DRIVER_ESD */
334 
335 /* vi: set ts=4 sw=4 expandtab: */
336