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