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_PAUDIO
24
25 /* Allow access to a raw mixing buffer */
26
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <sys/time.h>
31 #include <sys/ioctl.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35 #include "SDL_timer.h"
36 #include "SDL_audio.h"
37 #include "SDL_stdinc.h"
38 #include "../SDL_audio_c.h"
39 #include "../../core/unix/SDL_poll.h"
40 #include "SDL_paudio.h"
41
42 /* #define DEBUG_AUDIO */
43
44 /* A conflict within AIX 4.3.3 <sys/> headers and probably others as well.
45 * I guess nobody ever uses audio... Shame over AIX header files. */
46 #include <sys/machine.h>
47 #undef BIG_ENDIAN
48 #include <sys/audio.h>
49
50 /* Open the audio device for playback, and don't block if busy */
51 /* #define OPEN_FLAGS (O_WRONLY|O_NONBLOCK) */
52 #define OPEN_FLAGS O_WRONLY
53
54 /* Get the name of the audio device we use for output */
55
56 #ifndef _PATH_DEV_DSP
57 #define _PATH_DEV_DSP "/dev/%caud%c/%c"
58 #endif
59
60 static char devsettings[][3] = {
61 {'p', '0', '1'}, {'p', '0', '2'}, {'p', '0', '3'}, {'p', '0', '4'},
62 {'p', '1', '1'}, {'p', '1', '2'}, {'p', '1', '3'}, {'p', '1', '4'},
63 {'p', '2', '1'}, {'p', '2', '2'}, {'p', '2', '3'}, {'p', '2', '4'},
64 {'p', '3', '1'}, {'p', '3', '2'}, {'p', '3', '3'}, {'p', '3', '4'},
65 {'b', '0', '1'}, {'b', '0', '2'}, {'b', '0', '3'}, {'b', '0', '4'},
66 {'b', '1', '1'}, {'b', '1', '2'}, {'b', '1', '3'}, {'b', '1', '4'},
67 {'b', '2', '1'}, {'b', '2', '2'}, {'b', '2', '3'}, {'b', '2', '4'},
68 {'b', '3', '1'}, {'b', '3', '2'}, {'b', '3', '3'}, {'b', '3', '4'},
69 {'\0', '\0', '\0'}
70 };
71
72 static int
OpenUserDefinedDevice(char * path,int maxlen,int flags)73 OpenUserDefinedDevice(char *path, int maxlen, int flags)
74 {
75 const char *audiodev;
76 int fd;
77
78 /* Figure out what our audio device is */
79 if ((audiodev = SDL_getenv("SDL_PATH_DSP")) == NULL) {
80 audiodev = SDL_getenv("AUDIODEV");
81 }
82 if (audiodev == NULL) {
83 return -1;
84 }
85 fd = open(audiodev, flags, 0);
86 if (path != NULL) {
87 SDL_strlcpy(path, audiodev, maxlen);
88 path[maxlen - 1] = '\0';
89 }
90 return fd;
91 }
92
93 static int
OpenAudioPath(char * path,int maxlen,int flags,int classic)94 OpenAudioPath(char *path, int maxlen, int flags, int classic)
95 {
96 struct stat sb;
97 int cycle = 0;
98 int fd = OpenUserDefinedDevice(path, maxlen, flags);
99
100 if (fd != -1) {
101 return fd;
102 }
103
104 /* !!! FIXME: do we really need a table here? */
105 while (devsettings[cycle][0] != '\0') {
106 char audiopath[1024];
107 SDL_snprintf(audiopath, SDL_arraysize(audiopath),
108 _PATH_DEV_DSP,
109 devsettings[cycle][0],
110 devsettings[cycle][1], devsettings[cycle][2]);
111
112 if (stat(audiopath, &sb) == 0) {
113 fd = open(audiopath, flags, 0);
114 if (fd >= 0) {
115 if (path != NULL) {
116 SDL_strlcpy(path, audiopath, maxlen);
117 }
118 return fd;
119 }
120 }
121 }
122 return -1;
123 }
124
125 /* This function waits until it is possible to write a full sound buffer */
126 static void
PAUDIO_WaitDevice(_THIS)127 PAUDIO_WaitDevice(_THIS)
128 {
129 fd_set fdset;
130
131 /* See if we need to use timed audio synchronization */
132 if (this->hidden->frame_ticks) {
133 /* Use timer for general audio synchronization */
134 Sint32 ticks;
135
136 ticks = ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS;
137 if (ticks > 0) {
138 SDL_Delay(ticks);
139 }
140 } else {
141 int timeoutMS;
142 audio_buffer paud_bufinfo;
143
144 if (ioctl(this->hidden->audio_fd, AUDIO_BUFFER, &paud_bufinfo) < 0) {
145 #ifdef DEBUG_AUDIO
146 fprintf(stderr, "Couldn't get audio buffer information\n");
147 #endif
148 timeoutMS = 10 * 1000;
149 } else {
150 timeoutMS = paud_bufinfo.write_buf_time;
151 #ifdef DEBUG_AUDIO
152 fprintf(stderr, "Waiting for write_buf_time=%d ms\n", timeoutMS);
153 #endif
154 }
155
156 #ifdef DEBUG_AUDIO
157 fprintf(stderr, "Waiting for audio to get ready\n");
158 #endif
159 if (SDL_IOReady(this->hidden->audio_fd, SDL_TRUE, timeoutMS) <= 0) {
160 /*
161 * In general we should never print to the screen,
162 * but in this case we have no other way of letting
163 * the user know what happened.
164 */
165 fprintf(stderr, "SDL: %s - Audio timeout - buggy audio driver? (disabled)\n", strerror(errno));
166 SDL_OpenedAudioDeviceDisconnected(this);
167 /* Don't try to close - may hang */
168 this->hidden->audio_fd = -1;
169 #ifdef DEBUG_AUDIO
170 fprintf(stderr, "Done disabling audio\n");
171 #endif
172 }
173 #ifdef DEBUG_AUDIO
174 fprintf(stderr, "Ready!\n");
175 #endif
176 }
177 }
178
179 static void
PAUDIO_PlayDevice(_THIS)180 PAUDIO_PlayDevice(_THIS)
181 {
182 int written = 0;
183 const Uint8 *mixbuf = this->hidden->mixbuf;
184 const size_t mixlen = this->hidden->mixlen;
185
186 /* Write the audio data, checking for EAGAIN on broken audio drivers */
187 do {
188 written = write(this->hidden->audio_fd, mixbuf, mixlen);
189 if ((written < 0) && ((errno == 0) || (errno == EAGAIN))) {
190 SDL_Delay(1); /* Let a little CPU time go by */
191 }
192 } while ((written < 0) &&
193 ((errno == 0) || (errno == EAGAIN) || (errno == EINTR)));
194
195 /* If timer synchronization is enabled, set the next write frame */
196 if (this->hidden->frame_ticks) {
197 this->hidden->next_frame += this->hidden->frame_ticks;
198 }
199
200 /* If we couldn't write, assume fatal error for now */
201 if (written < 0) {
202 SDL_OpenedAudioDeviceDisconnected(this);
203 }
204 #ifdef DEBUG_AUDIO
205 fprintf(stderr, "Wrote %d bytes of audio data\n", written);
206 #endif
207 }
208
209 static Uint8 *
PAUDIO_GetDeviceBuf(_THIS)210 PAUDIO_GetDeviceBuf(_THIS)
211 {
212 return this->hidden->mixbuf;
213 }
214
215 static void
PAUDIO_CloseDevice(_THIS)216 PAUDIO_CloseDevice(_THIS)
217 {
218 if (this->hidden->audio_fd >= 0) {
219 close(this->hidden->audio_fd);
220 }
221 SDL_free(this->hidden->mixbuf);
222 SDL_free(this->hidden);
223 }
224
225 static int
PAUDIO_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)226 PAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
227 {
228 const char *workaround = SDL_getenv("SDL_DSP_NOSELECT");
229 char audiodev[1024];
230 const char *err = NULL;
231 int format;
232 int bytes_per_sample;
233 SDL_AudioFormat test_format;
234 audio_init paud_init;
235 audio_buffer paud_bufinfo;
236 audio_control paud_control;
237 audio_change paud_change;
238 int fd = -1;
239
240 /* Initialize all variables that we clean on shutdown */
241 this->hidden = (struct SDL_PrivateAudioData *)
242 SDL_malloc((sizeof *this->hidden));
243 if (this->hidden == NULL) {
244 return SDL_OutOfMemory();
245 }
246 SDL_zerop(this->hidden);
247
248 /* Open the audio device */
249 fd = OpenAudioPath(audiodev, sizeof(audiodev), OPEN_FLAGS, 0);
250 this->hidden->audio_fd = fd;
251 if (fd < 0) {
252 return SDL_SetError("Couldn't open %s: %s", audiodev, strerror(errno));
253 }
254
255 /*
256 * We can't set the buffer size - just ask the device for the maximum
257 * that we can have.
258 */
259 if (ioctl(fd, AUDIO_BUFFER, &paud_bufinfo) < 0) {
260 return SDL_SetError("Couldn't get audio buffer information");
261 }
262
263 if (this->spec.channels > 1)
264 this->spec.channels = 2;
265 else
266 this->spec.channels = 1;
267
268 /*
269 * Fields in the audio_init structure:
270 *
271 * Ignored by us:
272 *
273 * paud.loadpath[LOAD_PATH]; * DSP code to load, MWave chip only?
274 * paud.slot_number; * slot number of the adapter
275 * paud.device_id; * adapter identification number
276 *
277 * Input:
278 *
279 * paud.srate; * the sampling rate in Hz
280 * paud.bits_per_sample; * 8, 16, 32, ...
281 * paud.bsize; * block size for this rate
282 * paud.mode; * ADPCM, PCM, MU_LAW, A_LAW, SOURCE_MIX
283 * paud.channels; * 1=mono, 2=stereo
284 * paud.flags; * FIXED - fixed length data
285 * * LEFT_ALIGNED, RIGHT_ALIGNED (var len only)
286 * * TWOS_COMPLEMENT - 2's complement data
287 * * SIGNED - signed? comment seems wrong in sys/audio.h
288 * * BIG_ENDIAN
289 * paud.operation; * PLAY, RECORD
290 *
291 * Output:
292 *
293 * paud.flags; * PITCH - pitch is supported
294 * * INPUT - input is supported
295 * * OUTPUT - output is supported
296 * * MONITOR - monitor is supported
297 * * VOLUME - volume is supported
298 * * VOLUME_DELAY - volume delay is supported
299 * * BALANCE - balance is supported
300 * * BALANCE_DELAY - balance delay is supported
301 * * TREBLE - treble control is supported
302 * * BASS - bass control is supported
303 * * BESTFIT_PROVIDED - best fit returned
304 * * LOAD_CODE - DSP load needed
305 * paud.rc; * NO_PLAY - DSP code can't do play requests
306 * * NO_RECORD - DSP code can't do record requests
307 * * INVALID_REQUEST - request was invalid
308 * * CONFLICT - conflict with open's flags
309 * * OVERLOADED - out of DSP MIPS or memory
310 * paud.position_resolution; * smallest increment for position
311 */
312
313 paud_init.srate = this->spec.freq;
314 paud_init.mode = PCM;
315 paud_init.operation = PLAY;
316 paud_init.channels = this->spec.channels;
317
318 /* Try for a closest match on audio format */
319 format = 0;
320 for (test_format = SDL_FirstAudioFormat(this->spec.format);
321 !format && test_format;) {
322 #ifdef DEBUG_AUDIO
323 fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
324 #endif
325 switch (test_format) {
326 case AUDIO_U8:
327 bytes_per_sample = 1;
328 paud_init.bits_per_sample = 8;
329 paud_init.flags = TWOS_COMPLEMENT | FIXED;
330 format = 1;
331 break;
332 case AUDIO_S8:
333 bytes_per_sample = 1;
334 paud_init.bits_per_sample = 8;
335 paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED;
336 format = 1;
337 break;
338 case AUDIO_S16LSB:
339 bytes_per_sample = 2;
340 paud_init.bits_per_sample = 16;
341 paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED;
342 format = 1;
343 break;
344 case AUDIO_S16MSB:
345 bytes_per_sample = 2;
346 paud_init.bits_per_sample = 16;
347 paud_init.flags = BIG_ENDIAN | SIGNED | TWOS_COMPLEMENT | FIXED;
348 format = 1;
349 break;
350 case AUDIO_U16LSB:
351 bytes_per_sample = 2;
352 paud_init.bits_per_sample = 16;
353 paud_init.flags = TWOS_COMPLEMENT | FIXED;
354 format = 1;
355 break;
356 case AUDIO_U16MSB:
357 bytes_per_sample = 2;
358 paud_init.bits_per_sample = 16;
359 paud_init.flags = BIG_ENDIAN | TWOS_COMPLEMENT | FIXED;
360 format = 1;
361 break;
362 default:
363 break;
364 }
365 if (!format) {
366 test_format = SDL_NextAudioFormat();
367 }
368 }
369 if (format == 0) {
370 #ifdef DEBUG_AUDIO
371 fprintf(stderr, "Couldn't find any hardware audio formats\n");
372 #endif
373 return SDL_SetError("Couldn't find any hardware audio formats");
374 }
375 this->spec.format = test_format;
376
377 /*
378 * We know the buffer size and the max number of subsequent writes
379 * that can be pending. If more than one can pend, allow the application
380 * to do something like double buffering between our write buffer and
381 * the device's own buffer that we are filling with write() anyway.
382 *
383 * We calculate this->spec.samples like this because
384 * SDL_CalculateAudioSpec() will give put paud_bufinfo.write_buf_cap
385 * (or paud_bufinfo.write_buf_cap/2) into this->spec.size in return.
386 */
387 if (paud_bufinfo.request_buf_cap == 1) {
388 this->spec.samples = paud_bufinfo.write_buf_cap
389 / bytes_per_sample / this->spec.channels;
390 } else {
391 this->spec.samples = paud_bufinfo.write_buf_cap
392 / bytes_per_sample / this->spec.channels / 2;
393 }
394 paud_init.bsize = bytes_per_sample * this->spec.channels;
395
396 SDL_CalculateAudioSpec(&this->spec);
397
398 /*
399 * The AIX paud device init can't modify the values of the audio_init
400 * structure that we pass to it. So we don't need any recalculation
401 * of this stuff and no reinit call as in linux dsp code.
402 *
403 * /dev/paud supports all of the encoding formats, so we don't need
404 * to do anything like reopening the device, either.
405 */
406 if (ioctl(fd, AUDIO_INIT, &paud_init) < 0) {
407 switch (paud_init.rc) {
408 case 1:
409 err = "Couldn't set audio format: DSP can't do play requests";
410 break;
411 case 2:
412 err = "Couldn't set audio format: DSP can't do record requests";
413 break;
414 case 4:
415 err = "Couldn't set audio format: request was invalid";
416 break;
417 case 5:
418 err = "Couldn't set audio format: conflict with open's flags";
419 break;
420 case 6:
421 err = "Couldn't set audio format: out of DSP MIPS or memory";
422 break;
423 default:
424 err = "Couldn't set audio format: not documented in sys/audio.h";
425 break;
426 }
427 }
428
429 if (err != NULL) {
430 return SDL_SetError("Paudio: %s", err);
431 }
432
433 /* Allocate mixing buffer */
434 this->hidden->mixlen = this->spec.size;
435 this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
436 if (this->hidden->mixbuf == NULL) {
437 return SDL_OutOfMemory();
438 }
439 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
440
441 /*
442 * Set some paramters: full volume, first speaker that we can find.
443 * Ignore the other settings for now.
444 */
445 paud_change.input = AUDIO_IGNORE; /* the new input source */
446 paud_change.output = OUTPUT_1; /* EXTERNAL_SPEAKER,INTERNAL_SPEAKER,OUTPUT_1 */
447 paud_change.monitor = AUDIO_IGNORE; /* the new monitor state */
448 paud_change.volume = 0x7fffffff; /* volume level [0-0x7fffffff] */
449 paud_change.volume_delay = AUDIO_IGNORE; /* the new volume delay */
450 paud_change.balance = 0x3fffffff; /* the new balance */
451 paud_change.balance_delay = AUDIO_IGNORE; /* the new balance delay */
452 paud_change.treble = AUDIO_IGNORE; /* the new treble state */
453 paud_change.bass = AUDIO_IGNORE; /* the new bass state */
454 paud_change.pitch = AUDIO_IGNORE; /* the new pitch state */
455
456 paud_control.ioctl_request = AUDIO_CHANGE;
457 paud_control.request_info = (char *) &paud_change;
458 if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) {
459 #ifdef DEBUG_AUDIO
460 fprintf(stderr, "Can't change audio display settings\n");
461 #endif
462 }
463
464 /*
465 * Tell the device to expect data. Actual start will wait for
466 * the first write() call.
467 */
468 paud_control.ioctl_request = AUDIO_START;
469 paud_control.position = 0;
470 if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) {
471 #ifdef DEBUG_AUDIO
472 fprintf(stderr, "Can't start audio play\n");
473 #endif
474 return SDL_SetError("Can't start audio play");
475 }
476
477 /* Check to see if we need to use SDL_IOReady() workaround */
478 if (workaround != NULL) {
479 this->hidden->frame_ticks = (float) (this->spec.samples * 1000) /
480 this->spec.freq;
481 this->hidden->next_frame = SDL_GetTicks() + this->hidden->frame_ticks;
482 }
483
484 /* We're ready to rock and roll. :-) */
485 return 0;
486 }
487
488 static int
PAUDIO_Init(SDL_AudioDriverImpl * impl)489 PAUDIO_Init(SDL_AudioDriverImpl * impl)
490 {
491 /* !!! FIXME: not right for device enum? */
492 int fd = OpenAudioPath(NULL, 0, OPEN_FLAGS, 0);
493 if (fd < 0) {
494 SDL_SetError("PAUDIO: Couldn't open audio device");
495 return 0;
496 }
497 close(fd);
498
499 /* Set the function pointers */
500 impl->OpenDevice = PAUDIO_OpenDevice;
501 impl->PlayDevice = PAUDIO_PlayDevice;
502 impl->PlayDevice = PAUDIO_WaitDevice;
503 impl->GetDeviceBuf = PAUDIO_GetDeviceBuf;
504 impl->CloseDevice = PAUDIO_CloseDevice;
505 impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME: add device enum! */
506
507 return 1; /* this audio target is available. */
508 }
509
510 AudioBootStrap PAUDIO_bootstrap = {
511 "paud", "AIX Paudio", PAUDIO_Init, 0
512 };
513
514 #endif /* SDL_AUDIO_DRIVER_PAUDIO */
515
516 /* vi: set ts=4 sw=4 expandtab: */
517