diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/audio/pulseaudio/SDL_pulseaudio.c')
| -rw-r--r-- | source/3rd-party/SDL2/src/audio/pulseaudio/SDL_pulseaudio.c | 782 | 
1 files changed, 782 insertions, 0 deletions
| diff --git a/source/3rd-party/SDL2/src/audio/pulseaudio/SDL_pulseaudio.c b/source/3rd-party/SDL2/src/audio/pulseaudio/SDL_pulseaudio.c new file mode 100644 index 0000000..053a1c3 --- /dev/null +++ b/source/3rd-party/SDL2/src/audio/pulseaudio/SDL_pulseaudio.c @@ -0,0 +1,782 @@ +/* +  Simple DirectMedia Layer +  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + +  This software is provided 'as-is', without any express or implied +  warranty.  In no event will the authors be held liable for any damages +  arising from the use of this software. + +  Permission is granted to anyone to use this software for any purpose, +  including commercial applications, and to alter it and redistribute it +  freely, subject to the following restrictions: + +  1. The origin of this software must not be misrepresented; you must not +     claim that you wrote the original software. If you use this software +     in a product, an acknowledgment in the product documentation would be +     appreciated but is not required. +  2. Altered source versions must be plainly marked as such, and must not be +     misrepresented as being the original software. +  3. This notice may not be removed or altered from any source distribution. +*/ + +/* +  The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with +   the appropriate parts replaced with the 1.2 PulseAudio target code. This +   was the cleanest way to move it to 1.3. The 1.2 target was written by +   Stéphan Kochen: stephan .a.t. kochen.nl +*/ +#include "../../SDL_internal.h" +#include "SDL_assert.h" + +#if SDL_AUDIO_DRIVER_PULSEAUDIO + +/* Allow access to a raw mixing buffer */ + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#include <unistd.h> +#include <sys/types.h> +#include <pulse/pulseaudio.h> + +#include "SDL_timer.h" +#include "SDL_audio.h" +#include "../SDL_audio_c.h" +#include "SDL_pulseaudio.h" +#include "SDL_loadso.h" +#include "../../thread/SDL_systhread.h" + +#if (PA_API_VERSION < 12) +/** Return non-zero if the passed state is one of the connected states */ +static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { +    return +        x == PA_CONTEXT_CONNECTING || +        x == PA_CONTEXT_AUTHORIZING || +        x == PA_CONTEXT_SETTING_NAME || +        x == PA_CONTEXT_READY; +} +/** Return non-zero if the passed state is one of the connected states */ +static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t x) { +    return +        x == PA_STREAM_CREATING || +        x == PA_STREAM_READY; +} +#endif /* pulseaudio <= 0.9.10 */ + + +static const char *(*PULSEAUDIO_pa_get_library_version) (void); +static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) ( +    pa_channel_map *, unsigned, pa_channel_map_def_t); +static const char * (*PULSEAUDIO_pa_strerror) (int); +static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void); +static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *); +static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *); +static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *, int *); +static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *, int); +static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *); + +static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) ( +    pa_operation *); +static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *); +static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *); + +static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *, +    const char *); +static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *, +    pa_context_flags_t, const pa_spawn_api *); +static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t, void *); +static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t, void *); +static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *, uint32_t, pa_sink_info_cb_t, void *); +static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *, uint32_t, pa_source_info_cb_t, void *); +static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *); +static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *); +static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t, void *); +static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *); +static void (*PULSEAUDIO_pa_context_unref) (pa_context *); + +static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *, const char *, +    const pa_sample_spec *, const pa_channel_map *); +static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *, const char *, +    const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *); +static int (*PULSEAUDIO_pa_stream_connect_record) (pa_stream *, const char *, +    const pa_buffer_attr *, pa_stream_flags_t); +static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *); +static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *); +static size_t (*PULSEAUDIO_pa_stream_readable_size) (pa_stream *); +static int (*PULSEAUDIO_pa_stream_write) (pa_stream *, const void *, size_t, +    pa_free_cb_t, int64_t, pa_seek_mode_t); +static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *, +    pa_stream_success_cb_t, void *); +static int (*PULSEAUDIO_pa_stream_peek) (pa_stream *, const void **, size_t *); +static int (*PULSEAUDIO_pa_stream_drop) (pa_stream *); +static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *, +    pa_stream_success_cb_t, void *); +static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *); +static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *); + +static int load_pulseaudio_syms(void); + + +#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC + +static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; +static void *pulseaudio_handle = NULL; + +static int +load_pulseaudio_sym(const char *fn, void **addr) +{ +    *addr = SDL_LoadFunction(pulseaudio_handle, fn); +    if (*addr == NULL) { +        /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ +        return 0; +    } + +    return 1; +} + +/* cast funcs to char* first, to please GCC's strict aliasing rules. */ +#define SDL_PULSEAUDIO_SYM(x) \ +    if (!load_pulseaudio_sym(#x, (void **) (char *) &PULSEAUDIO_##x)) return -1 + +static void +UnloadPulseAudioLibrary(void) +{ +    if (pulseaudio_handle != NULL) { +        SDL_UnloadObject(pulseaudio_handle); +        pulseaudio_handle = NULL; +    } +} + +static int +LoadPulseAudioLibrary(void) +{ +    int retval = 0; +    if (pulseaudio_handle == NULL) { +        pulseaudio_handle = SDL_LoadObject(pulseaudio_library); +        if (pulseaudio_handle == NULL) { +            retval = -1; +            /* Don't call SDL_SetError(): SDL_LoadObject already did. */ +        } else { +            retval = load_pulseaudio_syms(); +            if (retval < 0) { +                UnloadPulseAudioLibrary(); +            } +        } +    } +    return retval; +} + +#else + +#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x + +static void +UnloadPulseAudioLibrary(void) +{ +} + +static int +LoadPulseAudioLibrary(void) +{ +    load_pulseaudio_syms(); +    return 0; +} + +#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ + + +static int +load_pulseaudio_syms(void) +{ +    SDL_PULSEAUDIO_SYM(pa_get_library_version); +    SDL_PULSEAUDIO_SYM(pa_mainloop_new); +    SDL_PULSEAUDIO_SYM(pa_mainloop_get_api); +    SDL_PULSEAUDIO_SYM(pa_mainloop_iterate); +    SDL_PULSEAUDIO_SYM(pa_mainloop_run); +    SDL_PULSEAUDIO_SYM(pa_mainloop_quit); +    SDL_PULSEAUDIO_SYM(pa_mainloop_free); +    SDL_PULSEAUDIO_SYM(pa_operation_get_state); +    SDL_PULSEAUDIO_SYM(pa_operation_cancel); +    SDL_PULSEAUDIO_SYM(pa_operation_unref); +    SDL_PULSEAUDIO_SYM(pa_context_new); +    SDL_PULSEAUDIO_SYM(pa_context_connect); +    SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list); +    SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list); +    SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index); +    SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index); +    SDL_PULSEAUDIO_SYM(pa_context_get_state); +    SDL_PULSEAUDIO_SYM(pa_context_subscribe); +    SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback); +    SDL_PULSEAUDIO_SYM(pa_context_disconnect); +    SDL_PULSEAUDIO_SYM(pa_context_unref); +    SDL_PULSEAUDIO_SYM(pa_stream_new); +    SDL_PULSEAUDIO_SYM(pa_stream_connect_playback); +    SDL_PULSEAUDIO_SYM(pa_stream_connect_record); +    SDL_PULSEAUDIO_SYM(pa_stream_get_state); +    SDL_PULSEAUDIO_SYM(pa_stream_writable_size); +    SDL_PULSEAUDIO_SYM(pa_stream_readable_size); +    SDL_PULSEAUDIO_SYM(pa_stream_write); +    SDL_PULSEAUDIO_SYM(pa_stream_drain); +    SDL_PULSEAUDIO_SYM(pa_stream_disconnect); +    SDL_PULSEAUDIO_SYM(pa_stream_peek); +    SDL_PULSEAUDIO_SYM(pa_stream_drop); +    SDL_PULSEAUDIO_SYM(pa_stream_flush); +    SDL_PULSEAUDIO_SYM(pa_stream_unref); +    SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto); +    SDL_PULSEAUDIO_SYM(pa_strerror); +    return 0; +} + +static SDL_INLINE int +squashVersion(const int major, const int minor, const int patch) +{ +    return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF); +} + +/* Workaround for older pulse: pa_context_new() must have non-NULL appname */ +static const char * +getAppName(void) +{ +    const char *verstr = PULSEAUDIO_pa_get_library_version(); +    if (verstr != NULL) { +        int maj, min, patch; +        if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) { +            if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) { +                return NULL;  /* 0.9.15+ handles NULL correctly. */ +            } +        } +    } +    return "SDL Application";  /* oh well. */ +} + +static void +WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o) +{ +    /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */ +    if (mainloop && o) { +        SDL_bool okay = SDL_TRUE; +        while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) { +            okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) >= 0); +        } +        PULSEAUDIO_pa_operation_unref(o); +    } +} + +static void +DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context) +{ +    if (context) { +        PULSEAUDIO_pa_context_disconnect(context); +        PULSEAUDIO_pa_context_unref(context); +    } +    if (mainloop != NULL) { +        PULSEAUDIO_pa_mainloop_free(mainloop); +    } +} + +static int +ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context) +{ +    pa_mainloop *mainloop = NULL; +    pa_context *context = NULL; +    pa_mainloop_api *mainloop_api = NULL; +    int state = 0; + +    *_mainloop = NULL; +    *_context = NULL; + +    /* Set up a new main loop */ +    if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) { +        return SDL_SetError("pa_mainloop_new() failed"); +    } + +    *_mainloop = mainloop; + +    mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop); +    SDL_assert(mainloop_api);  /* this never fails, right? */ + +    context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName()); +    if (!context) { +        return SDL_SetError("pa_context_new() failed"); +    } +    *_context = context; + +    /* Connect to the PulseAudio server */ +    if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) { +        return SDL_SetError("Could not setup connection to PulseAudio"); +    } + +    do { +        if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) { +            return SDL_SetError("pa_mainloop_iterate() failed"); +        } +        state = PULSEAUDIO_pa_context_get_state(context); +        if (!PA_CONTEXT_IS_GOOD(state)) { +            return SDL_SetError("Could not connect to PulseAudio"); +        } +    } while (state != PA_CONTEXT_READY); + +    return 0;  /* connected and ready! */ +} + +static int +ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context) +{ +    const int retval = ConnectToPulseServer_Internal(_mainloop, _context); +    if (retval < 0) { +        DisconnectFromPulseServer(*_mainloop, *_context); +    } +    return retval; +} + + +/* This function waits until it is possible to write a full sound buffer */ +static void +PULSEAUDIO_WaitDevice(_THIS) +{ +    struct SDL_PrivateAudioData *h = this->hidden; + +    while (SDL_AtomicGet(&this->enabled)) { +        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || +            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || +            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { +            SDL_OpenedAudioDeviceDisconnected(this); +            return; +        } +        if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) { +            return; +        } +    } +} + +static void +PULSEAUDIO_PlayDevice(_THIS) +{ +    /* Write the audio data */ +    struct SDL_PrivateAudioData *h = this->hidden; +    if (SDL_AtomicGet(&this->enabled)) { +        if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL, PA_SEEK_RELATIVE) < 0) { +            SDL_OpenedAudioDeviceDisconnected(this); +        } +    } +} + +static Uint8 * +PULSEAUDIO_GetDeviceBuf(_THIS) +{ +    return (this->hidden->mixbuf); +} + + +static int +PULSEAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) +{ +    struct SDL_PrivateAudioData *h = this->hidden; +    const void *data = NULL; +    size_t nbytes = 0; + +    while (SDL_AtomicGet(&this->enabled)) { +        if (h->capturebuf != NULL) { +            const int cpy = SDL_min(buflen, h->capturelen); +            SDL_memcpy(buffer, h->capturebuf, cpy); +            /*printf("PULSEAUDIO: fed %d captured bytes\n", cpy);*/ +            h->capturebuf += cpy; +            h->capturelen -= cpy; +            if (h->capturelen == 0) { +                h->capturebuf = NULL; +                PULSEAUDIO_pa_stream_drop(h->stream);  /* done with this fragment. */ +            } +            return cpy;  /* new data, return it. */ +        } + +        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || +            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || +            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { +            SDL_OpenedAudioDeviceDisconnected(this); +            return -1;  /* uhoh, pulse failed! */ +        } + +        if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) { +            continue;  /* no data available yet. */ +        } + +        /* a new fragment is available! */ +        PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); +        SDL_assert(nbytes > 0); +        if (data == NULL) {  /* NULL==buffer had a hole. Ignore that. */ +            PULSEAUDIO_pa_stream_drop(h->stream);  /* drop this fragment. */ +        } else { +            /* store this fragment's data, start feeding it to SDL. */ +            /*printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);*/ +            h->capturebuf = (const Uint8 *) data; +            h->capturelen = nbytes; +        } +    } + +    return -1;  /* not enabled? */ +} + +static void +PULSEAUDIO_FlushCapture(_THIS) +{ +    struct SDL_PrivateAudioData *h = this->hidden; +    const void *data = NULL; +    size_t nbytes = 0; + +    if (h->capturebuf != NULL) { +        PULSEAUDIO_pa_stream_drop(h->stream); +        h->capturebuf = NULL; +        h->capturelen = 0; +    } + +    while (SDL_TRUE) { +        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY || +            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY || +            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { +            SDL_OpenedAudioDeviceDisconnected(this); +            return;  /* uhoh, pulse failed! */ +        } + +        if (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0) { +            break;  /* no data available, so we're done. */ +        } + +        /* a new fragment is available! Just dump it. */ +        PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); +        PULSEAUDIO_pa_stream_drop(h->stream);  /* drop this fragment. */ +    } +} + +static void +PULSEAUDIO_CloseDevice(_THIS) +{ +    if (this->hidden->stream) { +        if (this->hidden->capturebuf != NULL) { +            PULSEAUDIO_pa_stream_drop(this->hidden->stream); +        } +        PULSEAUDIO_pa_stream_disconnect(this->hidden->stream); +        PULSEAUDIO_pa_stream_unref(this->hidden->stream); +    } + +    DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context); +    SDL_free(this->hidden->mixbuf); +    SDL_free(this->hidden->device_name); +    SDL_free(this->hidden); +} + +static void +SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) +{ +    if (i) { +        char **devname = (char **) data; +        *devname = SDL_strdup(i->name); +    } +} + +static void +SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) +{ +    if (i) { +        char **devname = (char **) data; +        *devname = SDL_strdup(i->name); +    } +} + +static SDL_bool +FindDeviceName(struct SDL_PrivateAudioData *h, const int iscapture, void *handle) +{ +    const uint32_t idx = ((uint32_t) ((size_t) handle)) - 1; + +    if (handle == NULL) {  /* NULL == default device. */ +        return SDL_TRUE; +    } + +    if (iscapture) { +        WaitForPulseOperation(h->mainloop, +            PULSEAUDIO_pa_context_get_source_info_by_index(h->context, idx, +                SourceDeviceNameCallback, &h->device_name)); +    } else { +        WaitForPulseOperation(h->mainloop, +            PULSEAUDIO_pa_context_get_sink_info_by_index(h->context, idx, +                SinkDeviceNameCallback, &h->device_name)); +    } + +    return (h->device_name != NULL); +} + +static int +PULSEAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) +{ +    struct SDL_PrivateAudioData *h = NULL; +    Uint16 test_format = 0; +    pa_sample_spec paspec; +    pa_buffer_attr paattr; +    pa_channel_map pacmap; +    pa_stream_flags_t flags = 0; +    int state = 0; +    int rc = 0; + +    /* Initialize all variables that we clean on shutdown */ +    h = this->hidden = (struct SDL_PrivateAudioData *) +        SDL_malloc((sizeof *this->hidden)); +    if (this->hidden == NULL) { +        return SDL_OutOfMemory(); +    } +    SDL_zerop(this->hidden); + +    paspec.format = PA_SAMPLE_INVALID; + +    /* Try for a closest match on audio format */ +    for (test_format = SDL_FirstAudioFormat(this->spec.format); +         (paspec.format == PA_SAMPLE_INVALID) && test_format;) { +#ifdef DEBUG_AUDIO +        fprintf(stderr, "Trying format 0x%4.4x\n", test_format); +#endif +        switch (test_format) { +        case AUDIO_U8: +            paspec.format = PA_SAMPLE_U8; +            break; +        case AUDIO_S16LSB: +            paspec.format = PA_SAMPLE_S16LE; +            break; +        case AUDIO_S16MSB: +            paspec.format = PA_SAMPLE_S16BE; +            break; +        case AUDIO_S32LSB: +            paspec.format = PA_SAMPLE_S32LE; +            break; +        case AUDIO_S32MSB: +            paspec.format = PA_SAMPLE_S32BE; +            break; +        case AUDIO_F32LSB: +            paspec.format = PA_SAMPLE_FLOAT32LE; +            break; +        case AUDIO_F32MSB: +            paspec.format = PA_SAMPLE_FLOAT32BE; +            break; +        default: +            paspec.format = PA_SAMPLE_INVALID; +            break; +        } +        if (paspec.format == PA_SAMPLE_INVALID) { +            test_format = SDL_NextAudioFormat(); +        } +    } +    if (paspec.format == PA_SAMPLE_INVALID) { +        return SDL_SetError("Couldn't find any hardware audio formats"); +    } +    this->spec.format = test_format; + +    /* Calculate the final parameters for this audio specification */ +#ifdef PA_STREAM_ADJUST_LATENCY +    this->spec.samples /= 2; /* Mix in smaller chunck to avoid underruns */ +#endif +    SDL_CalculateAudioSpec(&this->spec); + +    /* Allocate mixing buffer */ +    if (!iscapture) { +        h->mixlen = this->spec.size; +        h->mixbuf = (Uint8 *) SDL_malloc(h->mixlen); +        if (h->mixbuf == NULL) { +            return SDL_OutOfMemory(); +        } +        SDL_memset(h->mixbuf, this->spec.silence, this->spec.size); +    } + +    paspec.channels = this->spec.channels; +    paspec.rate = this->spec.freq; + +    /* Reduced prebuffering compared to the defaults. */ +#ifdef PA_STREAM_ADJUST_LATENCY +    /* 2x original requested bufsize */ +    paattr.tlength = h->mixlen * 4; +    paattr.prebuf = -1; +    paattr.maxlength = -1; +    /* -1 can lead to pa_stream_writable_size() >= mixlen never being true */ +    paattr.minreq = h->mixlen; +    flags = PA_STREAM_ADJUST_LATENCY; +#else +    paattr.tlength = h->mixlen*2; +    paattr.prebuf = h->mixlen*2; +    paattr.maxlength = h->mixlen*2; +    paattr.minreq = h->mixlen; +#endif + +    if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) { +        return SDL_SetError("Could not connect to PulseAudio server"); +    } + +    if (!FindDeviceName(h, iscapture, handle)) { +        return SDL_SetError("Requested PulseAudio sink/source missing?"); +    } + +    /* The SDL ALSA output hints us that we use Windows' channel mapping */ +    /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */ +    PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels, +                                        PA_CHANNEL_MAP_WAVEEX); + +    h->stream = PULSEAUDIO_pa_stream_new( +        h->context, +        "Simple DirectMedia Layer", /* stream description */ +        &paspec,    /* sample format spec */ +        &pacmap     /* channel map */ +        ); + +    if (h->stream == NULL) { +        return SDL_SetError("Could not set up PulseAudio stream"); +    } + +    /* now that we have multi-device support, don't move a stream from +        a device that was unplugged to something else, unless we're default. */ +    if (h->device_name != NULL) { +        flags |= PA_STREAM_DONT_MOVE; +    } + +    if (iscapture) { +        rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags); +    } else { +        rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL); +    } + +    if (rc < 0) { +        return SDL_SetError("Could not connect PulseAudio stream"); +    } + +    do { +        if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) { +            return SDL_SetError("pa_mainloop_iterate() failed"); +        } +        state = PULSEAUDIO_pa_stream_get_state(h->stream); +        if (!PA_STREAM_IS_GOOD(state)) { +            return SDL_SetError("Could not connect PulseAudio stream"); +        } +    } while (state != PA_STREAM_READY); + +    /* We're ready to rock and roll. :-) */ +    return 0; +} + +static pa_mainloop *hotplug_mainloop = NULL; +static pa_context *hotplug_context = NULL; +static SDL_Thread *hotplug_thread = NULL; + +/* device handles are device index + 1, cast to void*, so we never pass a NULL. */ + +/* This is called when PulseAudio adds an output ("sink") device. */ +static void +SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) +{ +    if (i) { +        SDL_AddAudioDevice(SDL_FALSE, i->description, (void *) ((size_t) i->index+1)); +    } +} + +/* This is called when PulseAudio adds a capture ("source") device. */ +static void +SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) +{ +    if (i) { +        /* Skip "monitor" sources. These are just output from other sinks. */ +        if (i->monitor_of_sink == PA_INVALID_INDEX) { +            SDL_AddAudioDevice(SDL_TRUE, i->description, (void *) ((size_t) i->index+1)); +        } +    } +} + +/* This is called when PulseAudio has a device connected/removed/changed. */ +static void +HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data) +{ +    const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); +    const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); + +    if (added || removed) {  /* we only care about add/remove events. */ +        const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); +        const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + +        /* adds need sink details from the PulseAudio server. Another callback... */ +        if (added && sink) { +            PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, NULL); +        } else if (added && source) { +            PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, NULL); +        } else if (removed && (sink || source)) { +            /* removes we can handle just with the device index. */ +            SDL_RemoveAudioDevice(source != 0, (void *) ((size_t) idx+1)); +        } +    } +} + +/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */ +static int SDLCALL +HotplugThread(void *data) +{ +    pa_operation *o; +    SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); +    PULSEAUDIO_pa_context_set_subscribe_callback(hotplug_context, HotplugCallback, NULL); +    o = PULSEAUDIO_pa_context_subscribe(hotplug_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL); +    PULSEAUDIO_pa_operation_unref(o);  /* don't wait for it, just do our thing. */ +    PULSEAUDIO_pa_mainloop_run(hotplug_mainloop, NULL); +    return 0; +} + +static void +PULSEAUDIO_DetectDevices() +{ +    WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, NULL)); +    WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, NULL)); + +    /* ok, we have a sane list, let's set up hotplug notifications now... */ +    hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL); +} + +static void +PULSEAUDIO_Deinitialize(void) +{ +    if (hotplug_thread) { +        PULSEAUDIO_pa_mainloop_quit(hotplug_mainloop, 0); +        SDL_WaitThread(hotplug_thread, NULL); +        hotplug_thread = NULL; +    } + +    DisconnectFromPulseServer(hotplug_mainloop, hotplug_context); +    hotplug_mainloop = NULL; +    hotplug_context = NULL; + +    UnloadPulseAudioLibrary(); +} + +static int +PULSEAUDIO_Init(SDL_AudioDriverImpl * impl) +{ +    if (LoadPulseAudioLibrary() < 0) { +        return 0; +    } + +    if (ConnectToPulseServer(&hotplug_mainloop, &hotplug_context) < 0) { +        UnloadPulseAudioLibrary(); +        return 0; +    } + +    /* Set the function pointers */ +    impl->DetectDevices = PULSEAUDIO_DetectDevices; +    impl->OpenDevice = PULSEAUDIO_OpenDevice; +    impl->PlayDevice = PULSEAUDIO_PlayDevice; +    impl->WaitDevice = PULSEAUDIO_WaitDevice; +    impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; +    impl->CloseDevice = PULSEAUDIO_CloseDevice; +    impl->Deinitialize = PULSEAUDIO_Deinitialize; +    impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; +    impl->FlushCapture = PULSEAUDIO_FlushCapture; + +    impl->HasCaptureSupport = SDL_TRUE; + +    return 1;   /* this audio target is available. */ +} + +AudioBootStrap PULSEAUDIO_bootstrap = { +    "pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0 +}; + +#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO */ + +/* vi: set ts=4 sw=4 expandtab: */ | 
