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