1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2006 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24/* Allow access to a raw mixing buffer */
25
26#include <fcntl.h>
27#include <errno.h>
28#ifdef __NETBSD__
29#include <sys/ioctl.h>
30#include <sys/audioio.h>
31#endif
32#ifdef __SVR4
33#include <sys/audioio.h>
34#else
35#include <sys/time.h>
36#include <sys/types.h>
37#endif
38#include <unistd.h>
39
40#include "SDL_timer.h"
41#include "SDL_audio.h"
42#include "../SDL_audiomem.h"
43#include "../SDL_audio_c.h"
44#include "../SDL_audiodev_c.h"
45#include "SDL_sunaudio.h"
46
47/* Open the audio device for playback, and don't block if busy */
48#define OPEN_FLAGS	(O_WRONLY|O_NONBLOCK)
49
50/* Audio driver functions */
51static int DSP_OpenAudio(_THIS, SDL_AudioSpec *spec);
52static void DSP_WaitAudio(_THIS);
53static void DSP_PlayAudio(_THIS);
54static Uint8 *DSP_GetAudioBuf(_THIS);
55static void DSP_CloseAudio(_THIS);
56
57static Uint8 snd2au(int sample);
58
59/* Audio driver bootstrap functions */
60
61static int Audio_Available(void)
62{
63	int fd;
64	int available;
65
66	available = 0;
67	fd = SDL_OpenAudioPath(NULL, 0, OPEN_FLAGS, 1);
68	if ( fd >= 0 ) {
69		available = 1;
70		close(fd);
71	}
72	return(available);
73}
74
75static void Audio_DeleteDevice(SDL_AudioDevice *device)
76{
77	SDL_free(device->hidden);
78	SDL_free(device);
79}
80
81static SDL_AudioDevice *Audio_CreateDevice(int devindex)
82{
83	SDL_AudioDevice *this;
84
85	/* Initialize all variables that we clean on shutdown */
86	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
87	if ( this ) {
88		SDL_memset(this, 0, (sizeof *this));
89		this->hidden = (struct SDL_PrivateAudioData *)
90				SDL_malloc((sizeof *this->hidden));
91	}
92	if ( (this == NULL) || (this->hidden == NULL) ) {
93		SDL_OutOfMemory();
94		if ( this ) {
95			SDL_free(this);
96		}
97		return(0);
98	}
99	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
100	audio_fd = -1;
101
102	/* Set the function pointers */
103	this->OpenAudio = DSP_OpenAudio;
104	this->WaitAudio = DSP_WaitAudio;
105	this->PlayAudio = DSP_PlayAudio;
106	this->GetAudioBuf = DSP_GetAudioBuf;
107	this->CloseAudio = DSP_CloseAudio;
108
109	this->free = Audio_DeleteDevice;
110
111	return this;
112}
113
114AudioBootStrap SUNAUDIO_bootstrap = {
115	"audio", "UNIX /dev/audio interface",
116	Audio_Available, Audio_CreateDevice
117};
118
119#ifdef DEBUG_AUDIO
120void CheckUnderflow(_THIS)
121{
122#ifdef AUDIO_GETINFO
123	audio_info_t info;
124	int left;
125
126	ioctl(audio_fd, AUDIO_GETINFO, &info);
127	left = (written - info.play.samples);
128	if ( written && (left == 0) ) {
129		fprintf(stderr, "audio underflow!\n");
130	}
131#endif
132}
133#endif
134
135void DSP_WaitAudio(_THIS)
136{
137#ifdef AUDIO_GETINFO
138#define SLEEP_FUDGE	10		/* 10 ms scheduling fudge factor */
139	audio_info_t info;
140	Sint32 left;
141
142	ioctl(audio_fd, AUDIO_GETINFO, &info);
143	left = (written - info.play.samples);
144	if ( left > fragsize ) {
145		Sint32 sleepy;
146
147		sleepy = ((left - fragsize)/frequency);
148		sleepy -= SLEEP_FUDGE;
149		if ( sleepy > 0 ) {
150			SDL_Delay(sleepy);
151		}
152	}
153#else
154	fd_set fdset;
155
156	FD_ZERO(&fdset);
157	FD_SET(audio_fd, &fdset);
158	select(audio_fd+1, NULL, &fdset, NULL, NULL);
159#endif
160}
161
162void DSP_PlayAudio(_THIS)
163{
164	/* Write the audio data */
165	if ( ulaw_only ) {
166		/* Assuming that this->spec.freq >= 8000 Hz */
167		int accum, incr, pos;
168		Uint8 *aubuf;
169
170		accum = 0;
171		incr  = this->spec.freq/8;
172		aubuf = ulaw_buf;
173		switch (audio_fmt & 0xFF) {
174			case 8: {
175				Uint8 *sndbuf;
176
177				sndbuf = mixbuf;
178				for ( pos=0; pos < fragsize; ++pos ) {
179					*aubuf = snd2au((0x80-*sndbuf)*64);
180					accum += incr;
181					while ( accum > 0 ) {
182						accum -= 1000;
183						sndbuf += 1;
184					}
185					aubuf += 1;
186				}
187			}
188			break;
189			case 16: {
190				Sint16 *sndbuf;
191
192				sndbuf = (Sint16 *)mixbuf;
193				for ( pos=0; pos < fragsize; ++pos ) {
194					*aubuf = snd2au(*sndbuf/4);
195					accum += incr;
196					while ( accum > 0 ) {
197						accum -= 1000;
198						sndbuf += 1;
199					}
200					aubuf += 1;
201				}
202			}
203			break;
204		}
205#ifdef DEBUG_AUDIO
206		CheckUnderflow(this);
207#endif
208		if ( write(audio_fd, ulaw_buf, fragsize) < 0 ) {
209			/* Assume fatal error, for now */
210			this->enabled = 0;
211		}
212		written += fragsize;
213	} else {
214#ifdef DEBUG_AUDIO
215		CheckUnderflow(this);
216#endif
217		if ( write(audio_fd, mixbuf, this->spec.size) < 0 ) {
218			/* Assume fatal error, for now */
219			this->enabled = 0;
220		}
221		written += fragsize;
222	}
223}
224
225Uint8 *DSP_GetAudioBuf(_THIS)
226{
227	return(mixbuf);
228}
229
230void DSP_CloseAudio(_THIS)
231{
232	if ( mixbuf != NULL ) {
233		SDL_FreeAudioMem(mixbuf);
234		mixbuf = NULL;
235	}
236	if ( ulaw_buf != NULL ) {
237		SDL_free(ulaw_buf);
238		ulaw_buf = NULL;
239	}
240	close(audio_fd);
241}
242
243int DSP_OpenAudio(_THIS, SDL_AudioSpec *spec)
244{
245	char audiodev[1024];
246#ifdef AUDIO_SETINFO
247	int enc;
248#endif
249	int desired_freq = spec->freq;
250
251	/* Initialize our freeable variables, in case we fail*/
252	audio_fd = -1;
253	mixbuf = NULL;
254	ulaw_buf = NULL;
255
256	/* Determine the audio parameters from the AudioSpec */
257	switch ( spec->format & 0xFF ) {
258
259		case 8: { /* Unsigned 8 bit audio data */
260			spec->format = AUDIO_U8;
261#ifdef AUDIO_SETINFO
262			enc = AUDIO_ENCODING_LINEAR8;
263#endif
264		}
265		break;
266
267		case 16: { /* Signed 16 bit audio data */
268		        spec->format = AUDIO_S16SYS;
269#ifdef AUDIO_SETINFO
270			enc = AUDIO_ENCODING_LINEAR;
271#endif
272		}
273		break;
274
275		default: {
276			SDL_SetError("Unsupported audio format");
277			return(-1);
278		}
279	}
280	audio_fmt = spec->format;
281
282	/* Open the audio device */
283	audio_fd = SDL_OpenAudioPath(audiodev, sizeof(audiodev), OPEN_FLAGS, 1);
284	if ( audio_fd < 0 ) {
285		SDL_SetError("Couldn't open %s: %s", audiodev,
286			     strerror(errno));
287		return(-1);
288	}
289
290	ulaw_only = 0;		/* modern Suns do support linear audio */
291#ifdef AUDIO_SETINFO
292	for(;;) {
293	    audio_info_t info;
294	    AUDIO_INITINFO(&info); /* init all fields to "no change" */
295
296	    /* Try to set the requested settings */
297	    info.play.sample_rate = spec->freq;
298	    info.play.channels = spec->channels;
299	    info.play.precision = (enc == AUDIO_ENCODING_ULAW)
300		                  ? 8 : spec->format & 0xff;
301	    info.play.encoding = enc;
302	    if( ioctl(audio_fd, AUDIO_SETINFO, &info) == 0 ) {
303
304		/* Check to be sure we got what we wanted */
305		if(ioctl(audio_fd, AUDIO_GETINFO, &info) < 0) {
306		    SDL_SetError("Error getting audio parameters: %s",
307				 strerror(errno));
308		    return -1;
309		}
310		if(info.play.encoding == enc
311		   && info.play.precision == (spec->format & 0xff)
312		   && info.play.channels == spec->channels) {
313		    /* Yow! All seems to be well! */
314		    spec->freq = info.play.sample_rate;
315		    break;
316		}
317	    }
318
319	    switch(enc) {
320	    case AUDIO_ENCODING_LINEAR8:
321		/* unsigned 8bit apparently not supported here */
322		enc = AUDIO_ENCODING_LINEAR;
323		spec->format = AUDIO_S16SYS;
324		break;	/* try again */
325
326	    case AUDIO_ENCODING_LINEAR:
327		/* linear 16bit didn't work either, resort to µ-law */
328		enc = AUDIO_ENCODING_ULAW;
329		spec->channels = 1;
330		spec->freq = 8000;
331		spec->format = AUDIO_U8;
332		ulaw_only = 1;
333		break;
334
335	    default:
336		/* oh well... */
337		SDL_SetError("Error setting audio parameters: %s",
338			     strerror(errno));
339		return -1;
340	    }
341	}
342#endif /* AUDIO_SETINFO */
343	written = 0;
344
345	/* We can actually convert on-the-fly to U-Law */
346	if ( ulaw_only ) {
347	        spec->freq = desired_freq;
348		fragsize = (spec->samples*1000)/(spec->freq/8);
349		frequency = 8;
350		ulaw_buf = (Uint8 *)SDL_malloc(fragsize);
351		if ( ulaw_buf == NULL ) {
352			SDL_OutOfMemory();
353			return(-1);
354		}
355		spec->channels = 1;
356	} else {
357		fragsize = spec->samples;
358		frequency = spec->freq/1000;
359	}
360#ifdef DEBUG_AUDIO
361	fprintf(stderr, "Audio device %s U-Law only\n",
362				ulaw_only ? "is" : "is not");
363	fprintf(stderr, "format=0x%x chan=%d freq=%d\n",
364		spec->format, spec->channels, spec->freq);
365#endif
366
367	/* Update the fragment size as size in bytes */
368	SDL_CalculateAudioSpec(spec);
369
370	/* Allocate mixing buffer */
371	mixbuf = (Uint8 *)SDL_AllocAudioMem(spec->size);
372	if ( mixbuf == NULL ) {
373		SDL_OutOfMemory();
374		return(-1);
375	}
376	SDL_memset(mixbuf, spec->silence, spec->size);
377
378	/* We're ready to rock and roll. :-) */
379	return(0);
380}
381
382/************************************************************************/
383/* This function (snd2au()) copyrighted:                                */
384/************************************************************************/
385/*      Copyright 1989 by Rich Gopstein and Harris Corporation          */
386/*                                                                      */
387/*      Permission to use, copy, modify, and distribute this software   */
388/*      and its documentation for any purpose and without fee is        */
389/*      hereby granted, provided that the above copyright notice        */
390/*      appears in all copies and that both that copyright notice and   */
391/*      this permission notice appear in supporting documentation, and  */
392/*      that the name of Rich Gopstein and Harris Corporation not be    */
393/*      used in advertising or publicity pertaining to distribution     */
394/*      of the software without specific, written prior permission.     */
395/*      Rich Gopstein and Harris Corporation make no representations    */
396/*      about the suitability of this software for any purpose.  It     */
397/*      provided "as is" without express or implied warranty.           */
398/************************************************************************/
399
400static Uint8 snd2au(int sample)
401{
402
403	int mask;
404
405	if (sample < 0) {
406		sample = -sample;
407		mask = 0x7f;
408	} else {
409		mask = 0xff;
410	}
411
412	if (sample < 32) {
413		sample = 0xF0 | (15 - sample / 2);
414	} else if (sample < 96) {
415		sample = 0xE0 | (15 - (sample - 32) / 4);
416	} else if (sample < 224) {
417		sample = 0xD0 | (15 - (sample - 96) / 8);
418	} else if (sample < 480) {
419		sample = 0xC0 | (15 - (sample - 224) / 16);
420	} else if (sample < 992) {
421		sample = 0xB0 | (15 - (sample - 480) / 32);
422	} else if (sample < 2016) {
423		sample = 0xA0 | (15 - (sample - 992) / 64);
424	} else if (sample < 4064) {
425		sample = 0x90 | (15 - (sample - 2016) / 128);
426	} else if (sample < 8160) {
427		sample = 0x80 | (15 - (sample - 4064) /  256);
428	} else {
429		sample = 0x80;
430	}
431	return (mask & sample);
432}
433