cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_paudio.c (18288B)


      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_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_audiomem.h"
     39#include "../SDL_audio_c.h"
     40#include "SDL_paudio.h"
     41
     42#define DEBUG_AUDIO 0
     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
     60static 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
     72static int
     73OpenUserDefinedDevice(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
     93static int
     94OpenAudioPath(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 */
    126static void
    127PAUDIO_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        audio_buffer paud_bufinfo;
    142
    143        /* Use select() for audio synchronization */
    144        struct timeval timeout;
    145        FD_ZERO(&fdset);
    146        FD_SET(this->hidden->audio_fd, &fdset);
    147
    148        if (ioctl(this->hidden->audio_fd, AUDIO_BUFFER, &paud_bufinfo) < 0) {
    149#ifdef DEBUG_AUDIO
    150            fprintf(stderr, "Couldn't get audio buffer information\n");
    151#endif
    152            timeout.tv_sec = 10;
    153            timeout.tv_usec = 0;
    154        } else {
    155            long ms_in_buf = paud_bufinfo.write_buf_time;
    156            timeout.tv_sec = ms_in_buf / 1000;
    157            ms_in_buf = ms_in_buf - timeout.tv_sec * 1000;
    158            timeout.tv_usec = ms_in_buf * 1000;
    159#ifdef DEBUG_AUDIO
    160            fprintf(stderr,
    161                    "Waiting for write_buf_time=%ld,%ld\n",
    162                    timeout.tv_sec, timeout.tv_usec);
    163#endif
    164        }
    165
    166#ifdef DEBUG_AUDIO
    167        fprintf(stderr, "Waiting for audio to get ready\n");
    168#endif
    169        if (select(this->hidden->audio_fd + 1, NULL, &fdset, NULL, &timeout)
    170            <= 0) {
    171            const char *message =
    172                "Audio timeout - buggy audio driver? (disabled)";
    173            /*
    174             * In general we should never print to the screen,
    175             * but in this case we have no other way of letting
    176             * the user know what happened.
    177             */
    178            fprintf(stderr, "SDL: %s - %s\n", strerror(errno), message);
    179            this->enabled = 0;
    180            /* Don't try to close - may hang */
    181            this->hidden->audio_fd = -1;
    182#ifdef DEBUG_AUDIO
    183            fprintf(stderr, "Done disabling audio\n");
    184#endif
    185        }
    186#ifdef DEBUG_AUDIO
    187        fprintf(stderr, "Ready!\n");
    188#endif
    189    }
    190}
    191
    192static void
    193PAUDIO_PlayDevice(_THIS)
    194{
    195    int written = 0;
    196    const Uint8 *mixbuf = this->hidden->mixbuf;
    197    const size_t mixlen = this->hidden->mixlen;
    198
    199    /* Write the audio data, checking for EAGAIN on broken audio drivers */
    200    do {
    201        written = write(this->hidden->audio_fd, mixbuf, mixlen);
    202        if ((written < 0) && ((errno == 0) || (errno == EAGAIN))) {
    203            SDL_Delay(1);       /* Let a little CPU time go by */
    204        }
    205    } while ((written < 0) &&
    206             ((errno == 0) || (errno == EAGAIN) || (errno == EINTR)));
    207
    208    /* If timer synchronization is enabled, set the next write frame */
    209    if (this->hidden->frame_ticks) {
    210        this->hidden->next_frame += this->hidden->frame_ticks;
    211    }
    212
    213    /* If we couldn't write, assume fatal error for now */
    214    if (written < 0) {
    215        this->enabled = 0;
    216    }
    217#ifdef DEBUG_AUDIO
    218    fprintf(stderr, "Wrote %d bytes of audio data\n", written);
    219#endif
    220}
    221
    222static Uint8 *
    223PAUDIO_GetDeviceBuf(_THIS)
    224{
    225    return this->hidden->mixbuf;
    226}
    227
    228static void
    229PAUDIO_CloseDevice(_THIS)
    230{
    231    if (this->hidden != NULL) {
    232        SDL_FreeAudioMem(this->hidden->mixbuf);
    233        this->hidden->mixbuf = NULL;
    234        if (this->hidden->audio_fd >= 0) {
    235            close(this->hidden->audio_fd);
    236            this->hidden->audio_fd = -1;
    237        }
    238        SDL_free(this->hidden);
    239        this->hidden = NULL;
    240    }
    241}
    242
    243static int
    244PAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
    245{
    246    const char *workaround = SDL_getenv("SDL_DSP_NOSELECT");
    247    char audiodev[1024];
    248    const char *err = NULL;
    249    int format;
    250    int bytes_per_sample;
    251    SDL_AudioFormat test_format;
    252    audio_init paud_init;
    253    audio_buffer paud_bufinfo;
    254    audio_status paud_status;
    255    audio_control paud_control;
    256    audio_change paud_change;
    257    int fd = -1;
    258
    259    /* Initialize all variables that we clean on shutdown */
    260    this->hidden = (struct SDL_PrivateAudioData *)
    261        SDL_malloc((sizeof *this->hidden));
    262    if (this->hidden == NULL) {
    263        return SDL_OutOfMemory();
    264    }
    265    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
    266
    267    /* Open the audio device */
    268    fd = OpenAudioPath(audiodev, sizeof(audiodev), OPEN_FLAGS, 0);
    269    this->hidden->audio_fd = fd;
    270    if (fd < 0) {
    271        PAUDIO_CloseDevice(this);
    272        return SDL_SetError("Couldn't open %s: %s", audiodev, strerror(errno));
    273    }
    274
    275    /*
    276     * We can't set the buffer size - just ask the device for the maximum
    277     * that we can have.
    278     */
    279    if (ioctl(fd, AUDIO_BUFFER, &paud_bufinfo) < 0) {
    280        PAUDIO_CloseDevice(this);
    281        return SDL_SetError("Couldn't get audio buffer information");
    282    }
    283
    284    if (this->spec.channels > 1)
    285        this->spec.channels = 2;
    286    else
    287        this->spec.channels = 1;
    288
    289    /*
    290     * Fields in the audio_init structure:
    291     *
    292     * Ignored by us:
    293     *
    294     * paud.loadpath[LOAD_PATH]; * DSP code to load, MWave chip only?
    295     * paud.slot_number;         * slot number of the adapter
    296     * paud.device_id;           * adapter identification number
    297     *
    298     * Input:
    299     *
    300     * paud.srate;           * the sampling rate in Hz
    301     * paud.bits_per_sample; * 8, 16, 32, ...
    302     * paud.bsize;           * block size for this rate
    303     * paud.mode;            * ADPCM, PCM, MU_LAW, A_LAW, SOURCE_MIX
    304     * paud.channels;        * 1=mono, 2=stereo
    305     * paud.flags;           * FIXED - fixed length data
    306     *                       * LEFT_ALIGNED, RIGHT_ALIGNED (var len only)
    307     *                       * TWOS_COMPLEMENT - 2's complement data
    308     *                       * SIGNED - signed? comment seems wrong in sys/audio.h
    309     *                       * BIG_ENDIAN
    310     * paud.operation;       * PLAY, RECORD
    311     *
    312     * Output:
    313     *
    314     * paud.flags;           * PITCH            - pitch is supported
    315     *                       * INPUT            - input is supported
    316     *                       * OUTPUT           - output is supported
    317     *                       * MONITOR          - monitor is supported
    318     *                       * VOLUME           - volume is supported
    319     *                       * VOLUME_DELAY     - volume delay is supported
    320     *                       * BALANCE          - balance is supported
    321     *                       * BALANCE_DELAY    - balance delay is supported
    322     *                       * TREBLE           - treble control is supported
    323     *                       * BASS             - bass control is supported
    324     *                       * BESTFIT_PROVIDED - best fit returned
    325     *                       * LOAD_CODE        - DSP load needed
    326     * paud.rc;              * NO_PLAY         - DSP code can't do play requests
    327     *                       * NO_RECORD       - DSP code can't do record requests
    328     *                       * INVALID_REQUEST - request was invalid
    329     *                       * CONFLICT        - conflict with open's flags
    330     *                       * OVERLOADED      - out of DSP MIPS or memory
    331     * paud.position_resolution; * smallest increment for position
    332     */
    333
    334    paud_init.srate = this->spec.freq;
    335    paud_init.mode = PCM;
    336    paud_init.operation = PLAY;
    337    paud_init.channels = this->spec.channels;
    338
    339    /* Try for a closest match on audio format */
    340    format = 0;
    341    for (test_format = SDL_FirstAudioFormat(this->spec.format);
    342         !format && test_format;) {
    343#ifdef DEBUG_AUDIO
    344        fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
    345#endif
    346        switch (test_format) {
    347        case AUDIO_U8:
    348            bytes_per_sample = 1;
    349            paud_init.bits_per_sample = 8;
    350            paud_init.flags = TWOS_COMPLEMENT | FIXED;
    351            format = 1;
    352            break;
    353        case AUDIO_S8:
    354            bytes_per_sample = 1;
    355            paud_init.bits_per_sample = 8;
    356            paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED;
    357            format = 1;
    358            break;
    359        case AUDIO_S16LSB:
    360            bytes_per_sample = 2;
    361            paud_init.bits_per_sample = 16;
    362            paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED;
    363            format = 1;
    364            break;
    365        case AUDIO_S16MSB:
    366            bytes_per_sample = 2;
    367            paud_init.bits_per_sample = 16;
    368            paud_init.flags = BIG_ENDIAN | SIGNED | TWOS_COMPLEMENT | FIXED;
    369            format = 1;
    370            break;
    371        case AUDIO_U16LSB:
    372            bytes_per_sample = 2;
    373            paud_init.bits_per_sample = 16;
    374            paud_init.flags = TWOS_COMPLEMENT | FIXED;
    375            format = 1;
    376            break;
    377        case AUDIO_U16MSB:
    378            bytes_per_sample = 2;
    379            paud_init.bits_per_sample = 16;
    380            paud_init.flags = BIG_ENDIAN | TWOS_COMPLEMENT | FIXED;
    381            format = 1;
    382            break;
    383        default:
    384            break;
    385        }
    386        if (!format) {
    387            test_format = SDL_NextAudioFormat();
    388        }
    389    }
    390    if (format == 0) {
    391#ifdef DEBUG_AUDIO
    392        fprintf(stderr, "Couldn't find any hardware audio formats\n");
    393#endif
    394        PAUDIO_CloseDevice(this);
    395        return SDL_SetError("Couldn't find any hardware audio formats");
    396    }
    397    this->spec.format = test_format;
    398
    399    /*
    400     * We know the buffer size and the max number of subsequent writes
    401     *  that can be pending. If more than one can pend, allow the application
    402     *  to do something like double buffering between our write buffer and
    403     *  the device's own buffer that we are filling with write() anyway.
    404     *
    405     * We calculate this->spec.samples like this because
    406     *  SDL_CalculateAudioSpec() will give put paud_bufinfo.write_buf_cap
    407     *  (or paud_bufinfo.write_buf_cap/2) into this->spec.size in return.
    408     */
    409    if (paud_bufinfo.request_buf_cap == 1) {
    410        this->spec.samples = paud_bufinfo.write_buf_cap
    411            / bytes_per_sample / this->spec.channels;
    412    } else {
    413        this->spec.samples = paud_bufinfo.write_buf_cap
    414            / bytes_per_sample / this->spec.channels / 2;
    415    }
    416    paud_init.bsize = bytes_per_sample * this->spec.channels;
    417
    418    SDL_CalculateAudioSpec(&this->spec);
    419
    420    /*
    421     * The AIX paud device init can't modify the values of the audio_init
    422     * structure that we pass to it. So we don't need any recalculation
    423     * of this stuff and no reinit call as in linux dsp code.
    424     *
    425     * /dev/paud supports all of the encoding formats, so we don't need
    426     * to do anything like reopening the device, either.
    427     */
    428    if (ioctl(fd, AUDIO_INIT, &paud_init) < 0) {
    429        switch (paud_init.rc) {
    430        case 1:
    431            err = "Couldn't set audio format: DSP can't do play requests";
    432            break;
    433        case 2:
    434            err = "Couldn't set audio format: DSP can't do record requests";
    435            break;
    436        case 4:
    437            err = "Couldn't set audio format: request was invalid";
    438            break;
    439        case 5:
    440            err = "Couldn't set audio format: conflict with open's flags";
    441            break;
    442        case 6:
    443            err = "Couldn't set audio format: out of DSP MIPS or memory";
    444            break;
    445        default:
    446            err = "Couldn't set audio format: not documented in sys/audio.h";
    447            break;
    448        }
    449    }
    450
    451    if (err != NULL) {
    452        PAUDIO_CloseDevice(this);
    453        return SDL_SetError("Paudio: %s", err);
    454    }
    455
    456    /* Allocate mixing buffer */
    457    this->hidden->mixlen = this->spec.size;
    458    this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
    459    if (this->hidden->mixbuf == NULL) {
    460        PAUDIO_CloseDevice(this);
    461        return SDL_OutOfMemory();
    462    }
    463    SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
    464
    465    /*
    466     * Set some paramters: full volume, first speaker that we can find.
    467     * Ignore the other settings for now.
    468     */
    469    paud_change.input = AUDIO_IGNORE;   /* the new input source */
    470    paud_change.output = OUTPUT_1;      /* EXTERNAL_SPEAKER,INTERNAL_SPEAKER,OUTPUT_1 */
    471    paud_change.monitor = AUDIO_IGNORE; /* the new monitor state */
    472    paud_change.volume = 0x7fffffff;    /* volume level [0-0x7fffffff] */
    473    paud_change.volume_delay = AUDIO_IGNORE;    /* the new volume delay */
    474    paud_change.balance = 0x3fffffff;   /* the new balance */
    475    paud_change.balance_delay = AUDIO_IGNORE;   /* the new balance delay */
    476    paud_change.treble = AUDIO_IGNORE;  /* the new treble state */
    477    paud_change.bass = AUDIO_IGNORE;    /* the new bass state */
    478    paud_change.pitch = AUDIO_IGNORE;   /* the new pitch state */
    479
    480    paud_control.ioctl_request = AUDIO_CHANGE;
    481    paud_control.request_info = (char *) &paud_change;
    482    if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) {
    483#ifdef DEBUG_AUDIO
    484        fprintf(stderr, "Can't change audio display settings\n");
    485#endif
    486    }
    487
    488    /*
    489     * Tell the device to expect data. Actual start will wait for
    490     * the first write() call.
    491     */
    492    paud_control.ioctl_request = AUDIO_START;
    493    paud_control.position = 0;
    494    if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) {
    495        PAUDIO_CloseDevice(this);
    496#ifdef DEBUG_AUDIO
    497        fprintf(stderr, "Can't start audio play\n");
    498#endif
    499        return SDL_SetError("Can't start audio play");
    500    }
    501
    502    /* Check to see if we need to use select() workaround */
    503    if (workaround != NULL) {
    504        this->hidden->frame_ticks = (float) (this->spec.samples * 1000) /
    505            this->spec.freq;
    506        this->hidden->next_frame = SDL_GetTicks() + this->hidden->frame_ticks;
    507    }
    508
    509    /* We're ready to rock and roll. :-) */
    510    return 0;
    511}
    512
    513static int
    514PAUDIO_Init(SDL_AudioDriverImpl * impl)
    515{
    516    /* !!! FIXME: not right for device enum? */
    517    int fd = OpenAudioPath(NULL, 0, OPEN_FLAGS, 0);
    518    if (fd < 0) {
    519        SDL_SetError("PAUDIO: Couldn't open audio device");
    520        return 0;
    521    }
    522    close(fd);
    523
    524    /* Set the function pointers */
    525    impl->OpenDevice = DSP_OpenDevice;
    526    impl->PlayDevice = DSP_PlayDevice;
    527    impl->PlayDevice = DSP_WaitDevice;
    528    impl->GetDeviceBuf = DSP_GetDeviceBuf;
    529    impl->CloseDevice = DSP_CloseDevice;
    530    impl->OnlyHasDefaultOutputDevice = 1;       /* !!! FIXME: add device enum! */
    531
    532    return 1;   /* this audio target is available. */
    533}
    534
    535AudioBootStrap PAUDIO_bootstrap = {
    536    "paud", "AIX Paudio", PAUDIO_Init, 0
    537};
    538
    539#endif /* SDL_AUDIO_DRIVER_PAUDIO */
    540
    541/* vi: set ts=4 sw=4 expandtab: */