cscg22-gearboy

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

SDL_pulseaudio.c (17421B)


      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
     22/*
     23  The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with
     24   the appropriate parts replaced with the 1.2 PulseAudio target code. This
     25   was the cleanest way to move it to 1.3. The 1.2 target was written by
     26   Stéphan Kochen: stephan .a.t. kochen.nl
     27*/
     28#include "../../SDL_internal.h"
     29
     30#if SDL_AUDIO_DRIVER_PULSEAUDIO
     31
     32/* Allow access to a raw mixing buffer */
     33
     34#ifdef HAVE_SIGNAL_H
     35#include <signal.h>
     36#endif
     37#include <unistd.h>
     38#include <sys/types.h>
     39#include <errno.h>
     40#include <pulse/pulseaudio.h>
     41#include <pulse/simple.h>
     42
     43#include "SDL_timer.h"
     44#include "SDL_audio.h"
     45#include "../SDL_audiomem.h"
     46#include "../SDL_audio_c.h"
     47#include "SDL_pulseaudio.h"
     48#include "SDL_loadso.h"
     49
     50#if (PA_API_VERSION < 12)
     51/** Return non-zero if the passed state is one of the connected states */
     52static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
     53    return
     54        x == PA_CONTEXT_CONNECTING ||
     55        x == PA_CONTEXT_AUTHORIZING ||
     56        x == PA_CONTEXT_SETTING_NAME ||
     57        x == PA_CONTEXT_READY;
     58}
     59/** Return non-zero if the passed state is one of the connected states */
     60static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
     61    return
     62        x == PA_STREAM_CREATING ||
     63        x == PA_STREAM_READY;
     64}
     65#endif /* pulseaudio <= 0.9.10 */
     66
     67
     68static const char *(*PULSEAUDIO_pa_get_library_version) (void);
     69static pa_simple *(*PULSEAUDIO_pa_simple_new) (const char *, const char *,
     70    pa_stream_direction_t, const char *, const char *, const pa_sample_spec *,
     71    const pa_channel_map *, const pa_buffer_attr *, int *);
     72static void (*PULSEAUDIO_pa_simple_free) (pa_simple *);
     73static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
     74    pa_channel_map *, unsigned, pa_channel_map_def_t);
     75static const char * (*PULSEAUDIO_pa_strerror) (int);
     76static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void);
     77static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *);
     78static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *);
     79static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *);
     80
     81static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) (
     82    pa_operation *);
     83static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *);
     84static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *);
     85
     86static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *,
     87    const char *);
     88static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
     89    pa_context_flags_t, const pa_spawn_api *);
     90static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
     91static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
     92static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
     93
     94static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *, const char *,
     95    const pa_sample_spec *, const pa_channel_map *);
     96static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *, const char *,
     97    const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *);
     98static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *);
     99static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *);
    100static int (*PULSEAUDIO_pa_stream_write) (pa_stream *, const void *, size_t,
    101    pa_free_cb_t, int64_t, pa_seek_mode_t);
    102static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *,
    103    pa_stream_success_cb_t, void *);
    104static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *);
    105static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *);
    106
    107static int load_pulseaudio_syms(void);
    108
    109
    110#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
    111
    112static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
    113static void *pulseaudio_handle = NULL;
    114
    115static int
    116load_pulseaudio_sym(const char *fn, void **addr)
    117{
    118    *addr = SDL_LoadFunction(pulseaudio_handle, fn);
    119    if (*addr == NULL) {
    120        /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
    121        return 0;
    122    }
    123
    124    return 1;
    125}
    126
    127/* cast funcs to char* first, to please GCC's strict aliasing rules. */
    128#define SDL_PULSEAUDIO_SYM(x) \
    129    if (!load_pulseaudio_sym(#x, (void **) (char *) &PULSEAUDIO_##x)) return -1
    130
    131static void
    132UnloadPulseAudioLibrary(void)
    133{
    134    if (pulseaudio_handle != NULL) {
    135        SDL_UnloadObject(pulseaudio_handle);
    136        pulseaudio_handle = NULL;
    137    }
    138}
    139
    140static int
    141LoadPulseAudioLibrary(void)
    142{
    143    int retval = 0;
    144    if (pulseaudio_handle == NULL) {
    145        pulseaudio_handle = SDL_LoadObject(pulseaudio_library);
    146        if (pulseaudio_handle == NULL) {
    147            retval = -1;
    148            /* Don't call SDL_SetError(): SDL_LoadObject already did. */
    149        } else {
    150            retval = load_pulseaudio_syms();
    151            if (retval < 0) {
    152                UnloadPulseAudioLibrary();
    153            }
    154        }
    155    }
    156    return retval;
    157}
    158
    159#else
    160
    161#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x
    162
    163static void
    164UnloadPulseAudioLibrary(void)
    165{
    166}
    167
    168static int
    169LoadPulseAudioLibrary(void)
    170{
    171    load_pulseaudio_syms();
    172    return 0;
    173}
    174
    175#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */
    176
    177
    178static int
    179load_pulseaudio_syms(void)
    180{
    181    SDL_PULSEAUDIO_SYM(pa_get_library_version);
    182    SDL_PULSEAUDIO_SYM(pa_simple_new);
    183    SDL_PULSEAUDIO_SYM(pa_simple_free);
    184    SDL_PULSEAUDIO_SYM(pa_mainloop_new);
    185    SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
    186    SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
    187    SDL_PULSEAUDIO_SYM(pa_mainloop_free);
    188    SDL_PULSEAUDIO_SYM(pa_operation_get_state);
    189    SDL_PULSEAUDIO_SYM(pa_operation_cancel);
    190    SDL_PULSEAUDIO_SYM(pa_operation_unref);
    191    SDL_PULSEAUDIO_SYM(pa_context_new);
    192    SDL_PULSEAUDIO_SYM(pa_context_connect);
    193    SDL_PULSEAUDIO_SYM(pa_context_get_state);
    194    SDL_PULSEAUDIO_SYM(pa_context_disconnect);
    195    SDL_PULSEAUDIO_SYM(pa_context_unref);
    196    SDL_PULSEAUDIO_SYM(pa_stream_new);
    197    SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
    198    SDL_PULSEAUDIO_SYM(pa_stream_get_state);
    199    SDL_PULSEAUDIO_SYM(pa_stream_writable_size);
    200    SDL_PULSEAUDIO_SYM(pa_stream_write);
    201    SDL_PULSEAUDIO_SYM(pa_stream_drain);
    202    SDL_PULSEAUDIO_SYM(pa_stream_disconnect);
    203    SDL_PULSEAUDIO_SYM(pa_stream_unref);
    204    SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
    205    SDL_PULSEAUDIO_SYM(pa_strerror);
    206    return 0;
    207}
    208
    209
    210/* Check to see if we can connect to PulseAudio */
    211static SDL_bool
    212CheckPulseAudioAvailable()
    213{
    214    pa_simple *s;
    215    pa_sample_spec ss;
    216
    217    ss.format = PA_SAMPLE_S16NE;
    218    ss.channels = 1;
    219    ss.rate = 22050;
    220
    221    s = PULSEAUDIO_pa_simple_new(NULL, "SDL", PA_STREAM_PLAYBACK, NULL,
    222                                 "Test", &ss, NULL, NULL, NULL);
    223    if (s) {
    224        PULSEAUDIO_pa_simple_free(s);
    225        return SDL_TRUE;
    226    } else {
    227        return SDL_FALSE;
    228    }
    229}
    230
    231/* This function waits until it is possible to write a full sound buffer */
    232static void
    233PULSEAUDIO_WaitDevice(_THIS)
    234{
    235    struct SDL_PrivateAudioData *h = this->hidden;
    236
    237    while(1) {
    238        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
    239            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
    240            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    241            this->enabled = 0;
    242            return;
    243        }
    244        if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) {
    245            return;
    246        }
    247    }
    248}
    249
    250static void
    251PULSEAUDIO_PlayDevice(_THIS)
    252{
    253    /* Write the audio data */
    254    struct SDL_PrivateAudioData *h = this->hidden;
    255    if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL,
    256                                   PA_SEEK_RELATIVE) < 0) {
    257        this->enabled = 0;
    258    }
    259}
    260
    261static void
    262stream_drain_complete(pa_stream *s, int success, void *userdata)
    263{
    264    /* no-op for pa_stream_drain() to use for callback. */
    265}
    266
    267static void
    268PULSEAUDIO_WaitDone(_THIS)
    269{
    270    struct SDL_PrivateAudioData *h = this->hidden;
    271    pa_operation *o;
    272
    273    o = PULSEAUDIO_pa_stream_drain(h->stream, stream_drain_complete, NULL);
    274    if (!o) {
    275        return;
    276    }
    277
    278    while (PULSEAUDIO_pa_operation_get_state(o) != PA_OPERATION_DONE) {
    279        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
    280            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
    281            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    282            PULSEAUDIO_pa_operation_cancel(o);
    283            break;
    284        }
    285    }
    286
    287    PULSEAUDIO_pa_operation_unref(o);
    288}
    289
    290
    291
    292static Uint8 *
    293PULSEAUDIO_GetDeviceBuf(_THIS)
    294{
    295    return (this->hidden->mixbuf);
    296}
    297
    298
    299static void
    300PULSEAUDIO_CloseDevice(_THIS)
    301{
    302    if (this->hidden != NULL) {
    303        SDL_FreeAudioMem(this->hidden->mixbuf);
    304        this->hidden->mixbuf = NULL;
    305        if (this->hidden->stream) {
    306            PULSEAUDIO_pa_stream_disconnect(this->hidden->stream);
    307            PULSEAUDIO_pa_stream_unref(this->hidden->stream);
    308            this->hidden->stream = NULL;
    309        }
    310        if (this->hidden->context != NULL) {
    311            PULSEAUDIO_pa_context_disconnect(this->hidden->context);
    312            PULSEAUDIO_pa_context_unref(this->hidden->context);
    313            this->hidden->context = NULL;
    314        }
    315        if (this->hidden->mainloop != NULL) {
    316            PULSEAUDIO_pa_mainloop_free(this->hidden->mainloop);
    317            this->hidden->mainloop = NULL;
    318        }
    319        SDL_free(this->hidden);
    320        this->hidden = NULL;
    321    }
    322}
    323
    324
    325static SDL_INLINE int
    326squashVersion(const int major, const int minor, const int patch)
    327{
    328    return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
    329}
    330
    331/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
    332static const char *
    333getAppName(void)
    334{
    335    const char *verstr = PULSEAUDIO_pa_get_library_version();
    336    if (verstr != NULL) {
    337        int maj, min, patch;
    338        if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
    339            if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
    340                return NULL;  /* 0.9.15+ handles NULL correctly. */
    341            }
    342        }
    343    }
    344    return "SDL Application";  /* oh well. */
    345}
    346
    347static int
    348PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
    349{
    350    struct SDL_PrivateAudioData *h = NULL;
    351    Uint16 test_format = 0;
    352    pa_sample_spec paspec;
    353    pa_buffer_attr paattr;
    354    pa_channel_map pacmap;
    355    pa_stream_flags_t flags = 0;
    356    int state = 0;
    357
    358    /* Initialize all variables that we clean on shutdown */
    359    this->hidden = (struct SDL_PrivateAudioData *)
    360        SDL_malloc((sizeof *this->hidden));
    361    if (this->hidden == NULL) {
    362        return SDL_OutOfMemory();
    363    }
    364    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
    365    h = this->hidden;
    366
    367    paspec.format = PA_SAMPLE_INVALID;
    368
    369    /* Try for a closest match on audio format */
    370    for (test_format = SDL_FirstAudioFormat(this->spec.format);
    371         (paspec.format == PA_SAMPLE_INVALID) && test_format;) {
    372#ifdef DEBUG_AUDIO
    373        fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
    374#endif
    375        switch (test_format) {
    376        case AUDIO_U8:
    377            paspec.format = PA_SAMPLE_U8;
    378            break;
    379        case AUDIO_S16LSB:
    380            paspec.format = PA_SAMPLE_S16LE;
    381            break;
    382        case AUDIO_S16MSB:
    383            paspec.format = PA_SAMPLE_S16BE;
    384            break;
    385        case AUDIO_S32LSB:
    386            paspec.format = PA_SAMPLE_S32LE;
    387            break;
    388        case AUDIO_S32MSB:
    389            paspec.format = PA_SAMPLE_S32BE;
    390            break;
    391        case AUDIO_F32LSB:
    392            paspec.format = PA_SAMPLE_FLOAT32LE;
    393            break;
    394        case AUDIO_F32MSB:
    395            paspec.format = PA_SAMPLE_FLOAT32BE;
    396            break;
    397        default:
    398            paspec.format = PA_SAMPLE_INVALID;
    399            break;
    400        }
    401        if (paspec.format == PA_SAMPLE_INVALID) {
    402            test_format = SDL_NextAudioFormat();
    403        }
    404    }
    405    if (paspec.format == PA_SAMPLE_INVALID) {
    406        PULSEAUDIO_CloseDevice(this);
    407        return SDL_SetError("Couldn't find any hardware audio formats");
    408    }
    409    this->spec.format = test_format;
    410
    411    /* Calculate the final parameters for this audio specification */
    412#ifdef PA_STREAM_ADJUST_LATENCY
    413    this->spec.samples /= 2; /* Mix in smaller chunck to avoid underruns */
    414#endif
    415    SDL_CalculateAudioSpec(&this->spec);
    416
    417    /* Allocate mixing buffer */
    418    h->mixlen = this->spec.size;
    419    h->mixbuf = (Uint8 *) SDL_AllocAudioMem(h->mixlen);
    420    if (h->mixbuf == NULL) {
    421        PULSEAUDIO_CloseDevice(this);
    422        return SDL_OutOfMemory();
    423    }
    424    SDL_memset(h->mixbuf, this->spec.silence, this->spec.size);
    425
    426    paspec.channels = this->spec.channels;
    427    paspec.rate = this->spec.freq;
    428
    429    /* Reduced prebuffering compared to the defaults. */
    430#ifdef PA_STREAM_ADJUST_LATENCY
    431    /* 2x original requested bufsize */
    432    paattr.tlength = h->mixlen * 4;
    433    paattr.prebuf = -1;
    434    paattr.maxlength = -1;
    435    /* -1 can lead to pa_stream_writable_size() >= mixlen never being true */
    436    paattr.minreq = h->mixlen;
    437    flags = PA_STREAM_ADJUST_LATENCY;
    438#else
    439    paattr.tlength = h->mixlen*2;
    440    paattr.prebuf = h->mixlen*2;
    441    paattr.maxlength = h->mixlen*2;
    442    paattr.minreq = h->mixlen;
    443#endif
    444
    445    /* The SDL ALSA output hints us that we use Windows' channel mapping */
    446    /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
    447    PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
    448                                        PA_CHANNEL_MAP_WAVEEX);
    449
    450    /* Set up a new main loop */
    451    if (!(h->mainloop = PULSEAUDIO_pa_mainloop_new())) {
    452        PULSEAUDIO_CloseDevice(this);
    453        return SDL_SetError("pa_mainloop_new() failed");
    454    }
    455
    456    h->mainloop_api = PULSEAUDIO_pa_mainloop_get_api(h->mainloop);
    457    h->context = PULSEAUDIO_pa_context_new(h->mainloop_api, getAppName());
    458    if (!h->context) {
    459        PULSEAUDIO_CloseDevice(this);
    460        return SDL_SetError("pa_context_new() failed");
    461    }
    462
    463    /* Connect to the PulseAudio server */
    464    if (PULSEAUDIO_pa_context_connect(h->context, NULL, 0, NULL) < 0) {
    465        PULSEAUDIO_CloseDevice(this);
    466        return SDL_SetError("Could not setup connection to PulseAudio");
    467    }
    468
    469    do {
    470        if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    471            PULSEAUDIO_CloseDevice(this);
    472            return SDL_SetError("pa_mainloop_iterate() failed");
    473        }
    474        state = PULSEAUDIO_pa_context_get_state(h->context);
    475        if (!PA_CONTEXT_IS_GOOD(state)) {
    476            PULSEAUDIO_CloseDevice(this);
    477            return SDL_SetError("Could not connect to PulseAudio");
    478        }
    479    } while (state != PA_CONTEXT_READY);
    480
    481    h->stream = PULSEAUDIO_pa_stream_new(
    482        h->context,
    483        "Simple DirectMedia Layer", /* stream description */
    484        &paspec,    /* sample format spec */
    485        &pacmap     /* channel map */
    486        );
    487
    488    if (h->stream == NULL) {
    489        PULSEAUDIO_CloseDevice(this);
    490        return SDL_SetError("Could not set up PulseAudio stream");
    491    }
    492
    493    if (PULSEAUDIO_pa_stream_connect_playback(h->stream, NULL, &paattr, flags,
    494            NULL, NULL) < 0) {
    495        PULSEAUDIO_CloseDevice(this);
    496        return SDL_SetError("Could not connect PulseAudio stream");
    497    }
    498
    499    do {
    500        if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    501            PULSEAUDIO_CloseDevice(this);
    502            return SDL_SetError("pa_mainloop_iterate() failed");
    503        }
    504        state = PULSEAUDIO_pa_stream_get_state(h->stream);
    505        if (!PA_STREAM_IS_GOOD(state)) {
    506            PULSEAUDIO_CloseDevice(this);
    507            return SDL_SetError("Could not create to PulseAudio stream");
    508        }
    509    } while (state != PA_STREAM_READY);
    510
    511    /* We're ready to rock and roll. :-) */
    512    return 0;
    513}
    514
    515
    516static void
    517PULSEAUDIO_Deinitialize(void)
    518{
    519    UnloadPulseAudioLibrary();
    520}
    521
    522static int
    523PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
    524{
    525    if (LoadPulseAudioLibrary() < 0) {
    526        return 0;
    527    }
    528
    529    if (!CheckPulseAudioAvailable()) {
    530        UnloadPulseAudioLibrary();
    531        return 0;
    532    }
    533
    534    /* Set the function pointers */
    535    impl->OpenDevice = PULSEAUDIO_OpenDevice;
    536    impl->PlayDevice = PULSEAUDIO_PlayDevice;
    537    impl->WaitDevice = PULSEAUDIO_WaitDevice;
    538    impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
    539    impl->CloseDevice = PULSEAUDIO_CloseDevice;
    540    impl->WaitDone = PULSEAUDIO_WaitDone;
    541    impl->Deinitialize = PULSEAUDIO_Deinitialize;
    542    impl->OnlyHasDefaultOutputDevice = 1;
    543
    544    return 1;   /* this audio target is available. */
    545}
    546
    547
    548AudioBootStrap PULSEAUDIO_bootstrap = {
    549    "pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
    550};
    551
    552#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO */
    553
    554/* vi: set ts=4 sw=4 expandtab: */