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_OSS
24 
25 /* Allow access to a raw mixing buffer */
26 
27 #include <stdio.h>              /* For perror() */
28 #include <string.h>             /* For strerror() */
29 #include <errno.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <sys/time.h>
34 #include <sys/ioctl.h>
35 #include <sys/stat.h>
36 
37 #if SDL_AUDIO_DRIVER_OSS_SOUNDCARD_H
38 /* This is installed on some systems */
39 #include <soundcard.h>
40 #else
41 /* This is recommended by OSS */
42 #include <sys/soundcard.h>
43 #endif
44 
45 #include "SDL_timer.h"
46 #include "SDL_audio.h"
47 #include "../SDL_audio_c.h"
48 #include "../SDL_audiodev_c.h"
49 #include "SDL_dspaudio.h"
50 
51 
52 static void
DSP_DetectDevices(void)53 DSP_DetectDevices(void)
54 {
55     SDL_EnumUnixAudioDevices(0, NULL);
56 }
57 
58 
59 static void
DSP_CloseDevice(_THIS)60 DSP_CloseDevice(_THIS)
61 {
62     if (this->hidden->audio_fd >= 0) {
63         close(this->hidden->audio_fd);
64     }
65     SDL_free(this->hidden->mixbuf);
66     SDL_free(this->hidden);
67 }
68 
69 
70 static int
DSP_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)71 DSP_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
72 {
73     const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
74     int format;
75     int value;
76     int frag_spec;
77     SDL_AudioFormat test_format;
78 
79     /* We don't care what the devname is...we'll try to open anything. */
80     /*  ...but default to first name in the list... */
81     if (devname == NULL) {
82         devname = SDL_GetAudioDeviceName(0, iscapture);
83         if (devname == NULL) {
84             return SDL_SetError("No such audio device");
85         }
86     }
87 
88     /* Make sure fragment size stays a power of 2, or OSS fails. */
89     /* I don't know which of these are actually legal values, though... */
90     if (this->spec.channels > 8)
91         this->spec.channels = 8;
92     else if (this->spec.channels > 4)
93         this->spec.channels = 4;
94     else if (this->spec.channels > 2)
95         this->spec.channels = 2;
96 
97     /* Initialize all variables that we clean on shutdown */
98     this->hidden = (struct SDL_PrivateAudioData *)
99         SDL_malloc((sizeof *this->hidden));
100     if (this->hidden == NULL) {
101         return SDL_OutOfMemory();
102     }
103     SDL_zerop(this->hidden);
104 
105     /* Open the audio device */
106     this->hidden->audio_fd = open(devname, flags, 0);
107     if (this->hidden->audio_fd < 0) {
108         return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno));
109     }
110 
111     /* Make the file descriptor use blocking i/o with fcntl() */
112     {
113         long ctlflags;
114         ctlflags = fcntl(this->hidden->audio_fd, F_GETFL);
115         ctlflags &= ~O_NONBLOCK;
116         if (fcntl(this->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
117             return SDL_SetError("Couldn't set audio blocking mode");
118         }
119     }
120 
121     /* Get a list of supported hardware formats */
122     if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
123         perror("SNDCTL_DSP_GETFMTS");
124         return SDL_SetError("Couldn't get audio format list");
125     }
126 
127     /* Try for a closest match on audio format */
128     format = 0;
129     for (test_format = SDL_FirstAudioFormat(this->spec.format);
130          !format && test_format;) {
131 #ifdef DEBUG_AUDIO
132         fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
133 #endif
134         switch (test_format) {
135         case AUDIO_U8:
136             if (value & AFMT_U8) {
137                 format = AFMT_U8;
138             }
139             break;
140         case AUDIO_S16LSB:
141             if (value & AFMT_S16_LE) {
142                 format = AFMT_S16_LE;
143             }
144             break;
145         case AUDIO_S16MSB:
146             if (value & AFMT_S16_BE) {
147                 format = AFMT_S16_BE;
148             }
149             break;
150 #if 0
151 /*
152  * These formats are not used by any real life systems so they are not
153  * needed here.
154  */
155         case AUDIO_S8:
156             if (value & AFMT_S8) {
157                 format = AFMT_S8;
158             }
159             break;
160         case AUDIO_U16LSB:
161             if (value & AFMT_U16_LE) {
162                 format = AFMT_U16_LE;
163             }
164             break;
165         case AUDIO_U16MSB:
166             if (value & AFMT_U16_BE) {
167                 format = AFMT_U16_BE;
168             }
169             break;
170 #endif
171         default:
172             format = 0;
173             break;
174         }
175         if (!format) {
176             test_format = SDL_NextAudioFormat();
177         }
178     }
179     if (format == 0) {
180         return SDL_SetError("Couldn't find any hardware audio formats");
181     }
182     this->spec.format = test_format;
183 
184     /* Set the audio format */
185     value = format;
186     if ((ioctl(this->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
187         (value != format)) {
188         perror("SNDCTL_DSP_SETFMT");
189         return SDL_SetError("Couldn't set audio format");
190     }
191 
192     /* Set the number of channels of output */
193     value = this->spec.channels;
194     if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
195         perror("SNDCTL_DSP_CHANNELS");
196         return SDL_SetError("Cannot set the number of channels");
197     }
198     this->spec.channels = value;
199 
200     /* Set the DSP frequency */
201     value = this->spec.freq;
202     if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
203         perror("SNDCTL_DSP_SPEED");
204         return SDL_SetError("Couldn't set audio frequency");
205     }
206     this->spec.freq = value;
207 
208     /* Calculate the final parameters for this audio specification */
209     SDL_CalculateAudioSpec(&this->spec);
210 
211     /* Determine the power of two of the fragment size */
212     for (frag_spec = 0; (0x01U << frag_spec) < this->spec.size; ++frag_spec);
213     if ((0x01U << frag_spec) != this->spec.size) {
214         return SDL_SetError("Fragment size must be a power of two");
215     }
216     frag_spec |= 0x00020000;    /* two fragments, for low latency */
217 
218     /* Set the audio buffering parameters */
219 #ifdef DEBUG_AUDIO
220     fprintf(stderr, "Requesting %d fragments of size %d\n",
221             (frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
222 #endif
223     if (ioctl(this->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
224         perror("SNDCTL_DSP_SETFRAGMENT");
225     }
226 #ifdef DEBUG_AUDIO
227     {
228         audio_buf_info info;
229         ioctl(this->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
230         fprintf(stderr, "fragments = %d\n", info.fragments);
231         fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
232         fprintf(stderr, "fragsize = %d\n", info.fragsize);
233         fprintf(stderr, "bytes = %d\n", info.bytes);
234     }
235 #endif
236 
237     /* Allocate mixing buffer */
238     if (!iscapture) {
239         this->hidden->mixlen = this->spec.size;
240         this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
241         if (this->hidden->mixbuf == NULL) {
242             return SDL_OutOfMemory();
243         }
244         SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
245     }
246 
247     /* We're ready to rock and roll. :-) */
248     return 0;
249 }
250 
251 
252 static void
DSP_PlayDevice(_THIS)253 DSP_PlayDevice(_THIS)
254 {
255     struct SDL_PrivateAudioData *h = this->hidden;
256     if (write(h->audio_fd, h->mixbuf, h->mixlen) == -1) {
257         perror("Audio write");
258         SDL_OpenedAudioDeviceDisconnected(this);
259     }
260 #ifdef DEBUG_AUDIO
261     fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
262 #endif
263 }
264 
265 static Uint8 *
DSP_GetDeviceBuf(_THIS)266 DSP_GetDeviceBuf(_THIS)
267 {
268     return (this->hidden->mixbuf);
269 }
270 
271 static int
DSP_CaptureFromDevice(_THIS,void * buffer,int buflen)272 DSP_CaptureFromDevice(_THIS, void *buffer, int buflen)
273 {
274     return (int) read(this->hidden->audio_fd, buffer, buflen);
275 }
276 
277 static void
DSP_FlushCapture(_THIS)278 DSP_FlushCapture(_THIS)
279 {
280     struct SDL_PrivateAudioData *h = this->hidden;
281     audio_buf_info info;
282     if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
283         while (info.bytes > 0) {
284             char buf[512];
285             const size_t len = SDL_min(sizeof (buf), info.bytes);
286             const ssize_t br = read(h->audio_fd, buf, len);
287             if (br <= 0) {
288                 break;
289             }
290             info.bytes -= br;
291         }
292     }
293 }
294 
295 static int
DSP_Init(SDL_AudioDriverImpl * impl)296 DSP_Init(SDL_AudioDriverImpl * impl)
297 {
298     /* Set the function pointers */
299     impl->DetectDevices = DSP_DetectDevices;
300     impl->OpenDevice = DSP_OpenDevice;
301     impl->PlayDevice = DSP_PlayDevice;
302     impl->GetDeviceBuf = DSP_GetDeviceBuf;
303     impl->CloseDevice = DSP_CloseDevice;
304     impl->CaptureFromDevice = DSP_CaptureFromDevice;
305     impl->FlushCapture = DSP_FlushCapture;
306 
307     impl->AllowsArbitraryDeviceNames = 1;
308     impl->HasCaptureSupport = SDL_TRUE;
309 
310     return 1;   /* this audio target is available. */
311 }
312 
313 
314 AudioBootStrap DSP_bootstrap = {
315     "dsp", "OSS /dev/dsp standard audio", DSP_Init, 0
316 };
317 
318 #endif /* SDL_AUDIO_DRIVER_OSS */
319 
320 /* vi: set ts=4 sw=4 expandtab: */
321