sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_paudio.c (17463B)


      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
     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
     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
    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
    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 *
    210 PAUDIO_GetDeviceBuf(_THIS)
    211 {
    212     return this->hidden->mixbuf;
    213 }
    214 
    215 static void
    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
    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
    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: */