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