PulseAudio: Added multiple device support, other cleanups.
authorRyan C. Gordon <icculus@icculus.org>
Wed, 18 Mar 2015 09:59:22 -0400
changeset 93888d26d736af94
parent 9387 63c918e10bd7
child 9389 2c4fa44a32a7
PulseAudio: Added multiple device support, other cleanups.

Thanks to Dominik Frizel for most of the effort on this!

Fixes Bugzilla #2730.
src/audio/pulseaudio/SDL_pulseaudio.c
src/audio/pulseaudio/SDL_pulseaudio.h
     1.1 --- a/src/audio/pulseaudio/SDL_pulseaudio.c	Wed Mar 18 00:56:33 2015 -0400
     1.2 +++ b/src/audio/pulseaudio/SDL_pulseaudio.c	Wed Mar 18 09:59:22 2015 -0400
     1.3 @@ -26,6 +26,7 @@
     1.4     St├ęphan Kochen: stephan .a.t. kochen.nl
     1.5  */
     1.6  #include "../../SDL_internal.h"
     1.7 +#include "SDL_assert.h"
     1.8  
     1.9  #if SDL_AUDIO_DRIVER_PULSEAUDIO
    1.10  
    1.11 @@ -38,7 +39,6 @@
    1.12  #include <sys/types.h>
    1.13  #include <errno.h>
    1.14  #include <pulse/pulseaudio.h>
    1.15 -#include <pulse/simple.h>
    1.16  
    1.17  #include "SDL_timer.h"
    1.18  #include "SDL_audio.h"
    1.19 @@ -66,10 +66,6 @@
    1.20  
    1.21  
    1.22  static const char *(*PULSEAUDIO_pa_get_library_version) (void);
    1.23 -static pa_simple *(*PULSEAUDIO_pa_simple_new) (const char *, const char *,
    1.24 -    pa_stream_direction_t, const char *, const char *, const pa_sample_spec *,
    1.25 -    const pa_channel_map *, const pa_buffer_attr *, int *);
    1.26 -static void (*PULSEAUDIO_pa_simple_free) (pa_simple *);
    1.27  static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
    1.28      pa_channel_map *, unsigned, pa_channel_map_def_t);
    1.29  static const char * (*PULSEAUDIO_pa_strerror) (int);
    1.30 @@ -87,6 +83,7 @@
    1.31      const char *);
    1.32  static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
    1.33      pa_context_flags_t, const pa_spawn_api *);
    1.34 +static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
    1.35  static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
    1.36  static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
    1.37  static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
    1.38 @@ -179,8 +176,6 @@
    1.39  load_pulseaudio_syms(void)
    1.40  {
    1.41      SDL_PULSEAUDIO_SYM(pa_get_library_version);
    1.42 -    SDL_PULSEAUDIO_SYM(pa_simple_new);
    1.43 -    SDL_PULSEAUDIO_SYM(pa_simple_free);
    1.44      SDL_PULSEAUDIO_SYM(pa_mainloop_new);
    1.45      SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
    1.46      SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
    1.47 @@ -190,6 +185,7 @@
    1.48      SDL_PULSEAUDIO_SYM(pa_operation_unref);
    1.49      SDL_PULSEAUDIO_SYM(pa_context_new);
    1.50      SDL_PULSEAUDIO_SYM(pa_context_connect);
    1.51 +    SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
    1.52      SDL_PULSEAUDIO_SYM(pa_context_get_state);
    1.53      SDL_PULSEAUDIO_SYM(pa_context_disconnect);
    1.54      SDL_PULSEAUDIO_SYM(pa_context_unref);
    1.55 @@ -206,28 +202,96 @@
    1.56      return 0;
    1.57  }
    1.58  
    1.59 +static SDL_INLINE int
    1.60 +squashVersion(const int major, const int minor, const int patch)
    1.61 +{
    1.62 +    return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
    1.63 +}
    1.64  
    1.65 -/* Check to see if we can connect to PulseAudio */
    1.66 -static SDL_bool
    1.67 -CheckPulseAudioAvailable()
    1.68 +/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
    1.69 +static const char *
    1.70 +getAppName(void)
    1.71  {
    1.72 -    pa_simple *s;
    1.73 -    pa_sample_spec ss;
    1.74 +    const char *verstr = PULSEAUDIO_pa_get_library_version();
    1.75 +    if (verstr != NULL) {
    1.76 +        int maj, min, patch;
    1.77 +        if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
    1.78 +            if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
    1.79 +                return NULL;  /* 0.9.15+ handles NULL correctly. */
    1.80 +            }
    1.81 +        }
    1.82 +    }
    1.83 +    return "SDL Application";  /* oh well. */
    1.84 +}
    1.85  
    1.86 -    ss.format = PA_SAMPLE_S16NE;
    1.87 -    ss.channels = 1;
    1.88 -    ss.rate = 22050;
    1.89 -
    1.90 -    s = PULSEAUDIO_pa_simple_new(NULL, "SDL", PA_STREAM_PLAYBACK, NULL,
    1.91 -                                 "Test", &ss, NULL, NULL, NULL);
    1.92 -    if (s) {
    1.93 -        PULSEAUDIO_pa_simple_free(s);
    1.94 -        return SDL_TRUE;
    1.95 -    } else {
    1.96 -        return SDL_FALSE;
    1.97 +static void
    1.98 +DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
    1.99 +{
   1.100 +    if (context) {
   1.101 +        PULSEAUDIO_pa_context_disconnect(context);
   1.102 +        PULSEAUDIO_pa_context_unref(context);
   1.103 +    }
   1.104 +    if (mainloop != NULL) {
   1.105 +        PULSEAUDIO_pa_mainloop_free(mainloop);
   1.106      }
   1.107  }
   1.108  
   1.109 +static int
   1.110 +ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context)
   1.111 +{
   1.112 +    pa_mainloop *mainloop = NULL;
   1.113 +    pa_context *context = NULL;
   1.114 +    pa_mainloop_api *mainloop_api = NULL;
   1.115 +    int state = 0;
   1.116 +
   1.117 +    *_mainloop = NULL;
   1.118 +    *_context = NULL;
   1.119 +
   1.120 +    /* Set up a new main loop */
   1.121 +    if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) {
   1.122 +        return SDL_SetError("pa_mainloop_new() failed");
   1.123 +    }
   1.124 +
   1.125 +    *_mainloop = mainloop;
   1.126 +
   1.127 +    mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop);
   1.128 +    SDL_assert(mainloop_api);  /* this never fails, right? */
   1.129 +
   1.130 +    context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
   1.131 +    if (!context) {
   1.132 +        return SDL_SetError("pa_context_new() failed");
   1.133 +    }
   1.134 +    *_context = context;
   1.135 +
   1.136 +    /* Connect to the PulseAudio server */
   1.137 +    if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) {
   1.138 +        return SDL_SetError("Could not setup connection to PulseAudio");
   1.139 +    }
   1.140 +
   1.141 +    do {
   1.142 +        if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
   1.143 +            return SDL_SetError("pa_mainloop_iterate() failed");
   1.144 +        }
   1.145 +        state = PULSEAUDIO_pa_context_get_state(context);
   1.146 +        if (!PA_CONTEXT_IS_GOOD(state)) {
   1.147 +            return SDL_SetError("Could not connect to PulseAudio");
   1.148 +        }
   1.149 +    } while (state != PA_CONTEXT_READY);
   1.150 +
   1.151 +    return 0;  /* connected and ready! */
   1.152 +}
   1.153 +
   1.154 +static int
   1.155 +ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context)
   1.156 +{
   1.157 +    const int retval = ConnectToPulseServer_Internal(_mainloop, _context);
   1.158 +    if (retval < 0) {
   1.159 +        DisconnectFromPulseServer(*_mainloop, *_context);
   1.160 +    }
   1.161 +    return retval;
   1.162 +}
   1.163 +
   1.164 +
   1.165  /* This function waits until it is possible to write a full sound buffer */
   1.166  static void
   1.167  PULSEAUDIO_WaitDevice(_THIS)
   1.168 @@ -307,43 +371,12 @@
   1.169              PULSEAUDIO_pa_stream_unref(this->hidden->stream);
   1.170              this->hidden->stream = NULL;
   1.171          }
   1.172 -        if (this->hidden->context != NULL) {
   1.173 -            PULSEAUDIO_pa_context_disconnect(this->hidden->context);
   1.174 -            PULSEAUDIO_pa_context_unref(this->hidden->context);
   1.175 -            this->hidden->context = NULL;
   1.176 -        }
   1.177 -        if (this->hidden->mainloop != NULL) {
   1.178 -            PULSEAUDIO_pa_mainloop_free(this->hidden->mainloop);
   1.179 -            this->hidden->mainloop = NULL;
   1.180 -        }
   1.181 +        DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
   1.182          SDL_free(this->hidden);
   1.183          this->hidden = NULL;
   1.184      }
   1.185  }
   1.186  
   1.187 -
   1.188 -static SDL_INLINE int
   1.189 -squashVersion(const int major, const int minor, const int patch)
   1.190 -{
   1.191 -    return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
   1.192 -}
   1.193 -
   1.194 -/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
   1.195 -static const char *
   1.196 -getAppName(void)
   1.197 -{
   1.198 -    const char *verstr = PULSEAUDIO_pa_get_library_version();
   1.199 -    if (verstr != NULL) {
   1.200 -        int maj, min, patch;
   1.201 -        if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
   1.202 -            if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
   1.203 -                return NULL;  /* 0.9.15+ handles NULL correctly. */
   1.204 -            }
   1.205 -        }
   1.206 -    }
   1.207 -    return "SDL Application";  /* oh well. */
   1.208 -}
   1.209 -
   1.210  static int
   1.211  PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
   1.212  {
   1.213 @@ -442,42 +475,16 @@
   1.214      paattr.minreq = h->mixlen;
   1.215  #endif
   1.216  
   1.217 +    if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) {
   1.218 +        PULSEAUDIO_CloseDevice(this);
   1.219 +        return SDL_SetError("Could not connect to PulseAudio server");
   1.220 +    }
   1.221 +
   1.222      /* The SDL ALSA output hints us that we use Windows' channel mapping */
   1.223      /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
   1.224      PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
   1.225                                          PA_CHANNEL_MAP_WAVEEX);
   1.226  
   1.227 -    /* Set up a new main loop */
   1.228 -    if (!(h->mainloop = PULSEAUDIO_pa_mainloop_new())) {
   1.229 -        PULSEAUDIO_CloseDevice(this);
   1.230 -        return SDL_SetError("pa_mainloop_new() failed");
   1.231 -    }
   1.232 -
   1.233 -    h->mainloop_api = PULSEAUDIO_pa_mainloop_get_api(h->mainloop);
   1.234 -    h->context = PULSEAUDIO_pa_context_new(h->mainloop_api, getAppName());
   1.235 -    if (!h->context) {
   1.236 -        PULSEAUDIO_CloseDevice(this);
   1.237 -        return SDL_SetError("pa_context_new() failed");
   1.238 -    }
   1.239 -
   1.240 -    /* Connect to the PulseAudio server */
   1.241 -    if (PULSEAUDIO_pa_context_connect(h->context, NULL, 0, NULL) < 0) {
   1.242 -        PULSEAUDIO_CloseDevice(this);
   1.243 -        return SDL_SetError("Could not setup connection to PulseAudio");
   1.244 -    }
   1.245 -
   1.246 -    do {
   1.247 -        if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
   1.248 -            PULSEAUDIO_CloseDevice(this);
   1.249 -            return SDL_SetError("pa_mainloop_iterate() failed");
   1.250 -        }
   1.251 -        state = PULSEAUDIO_pa_context_get_state(h->context);
   1.252 -        if (!PA_CONTEXT_IS_GOOD(state)) {
   1.253 -            PULSEAUDIO_CloseDevice(this);
   1.254 -            return SDL_SetError("Could not connect to PulseAudio");
   1.255 -        }
   1.256 -    } while (state != PA_CONTEXT_READY);
   1.257 -
   1.258      h->stream = PULSEAUDIO_pa_stream_new(
   1.259          h->context,
   1.260          "Simple DirectMedia Layer", /* stream description */
   1.261 @@ -490,7 +497,7 @@
   1.262          return SDL_SetError("Could not set up PulseAudio stream");
   1.263      }
   1.264  
   1.265 -    if (PULSEAUDIO_pa_stream_connect_playback(h->stream, NULL, &paattr, flags,
   1.266 +    if (PULSEAUDIO_pa_stream_connect_playback(h->stream, devname, &paattr, flags,
   1.267              NULL, NULL) < 0) {
   1.268          PULSEAUDIO_CloseDevice(this);
   1.269          return SDL_SetError("Could not connect PulseAudio stream");
   1.270 @@ -512,6 +519,50 @@
   1.271      return 0;
   1.272  }
   1.273  
   1.274 +typedef struct
   1.275 +{
   1.276 +    uint8_t last;
   1.277 +    SDL_AddAudioDevice addfn;
   1.278 +} sink_struct;
   1.279 +
   1.280 +static void
   1.281 +get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
   1.282 +{
   1.283 +    sink_struct *a = (sink_struct *) userdata;
   1.284 +    a->last = is_last;
   1.285 +    if (i) {
   1.286 +        a->addfn(i->name);
   1.287 +    }
   1.288 +}
   1.289 +
   1.290 +static void
   1.291 +PULSEAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
   1.292 +{
   1.293 +    pa_mainloop *mainloop = NULL;
   1.294 +    pa_context *context = NULL;
   1.295 +
   1.296 +    if (ConnectToPulseServer(&mainloop, &context) < 0) {
   1.297 +        return;
   1.298 +    }
   1.299 +
   1.300 +    if (!iscapture) {
   1.301 +        sink_struct a;
   1.302 +        a.last = 0;
   1.303 +        a.addfn = addfn;
   1.304 +        pa_operation* o = PULSEAUDIO_pa_context_get_sink_info_list(context,
   1.305 +                get_sink_info_callback, &a);
   1.306 +        while (!a.last) {
   1.307 +            if (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_CANCELLED) {
   1.308 +                break;
   1.309 +            }
   1.310 +            if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
   1.311 +                break;
   1.312 +            }
   1.313 +        }
   1.314 +    }
   1.315 +
   1.316 +    DisconnectFromPulseServer(mainloop, context);
   1.317 +}
   1.318  
   1.319  static void
   1.320  PULSEAUDIO_Deinitialize(void)
   1.321 @@ -522,16 +573,22 @@
   1.322  static int
   1.323  PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
   1.324  {
   1.325 +    pa_mainloop *mainloop = NULL;
   1.326 +    pa_context *context = NULL;
   1.327 +
   1.328      if (LoadPulseAudioLibrary() < 0) {
   1.329          return 0;
   1.330      }
   1.331  
   1.332 -    if (!CheckPulseAudioAvailable()) {
   1.333 +    if (ConnectToPulseServer(&mainloop, &context) < 0) {
   1.334          UnloadPulseAudioLibrary();
   1.335          return 0;
   1.336      }
   1.337  
   1.338 +    DisconnectFromPulseServer(mainloop, context);
   1.339 +
   1.340      /* Set the function pointers */
   1.341 +    impl->DetectDevices = PULSEAUDIO_DetectDevices;
   1.342      impl->OpenDevice = PULSEAUDIO_OpenDevice;
   1.343      impl->PlayDevice = PULSEAUDIO_PlayDevice;
   1.344      impl->WaitDevice = PULSEAUDIO_WaitDevice;
   1.345 @@ -539,12 +596,10 @@
   1.346      impl->CloseDevice = PULSEAUDIO_CloseDevice;
   1.347      impl->WaitDone = PULSEAUDIO_WaitDone;
   1.348      impl->Deinitialize = PULSEAUDIO_Deinitialize;
   1.349 -    impl->OnlyHasDefaultOutputDevice = 1;
   1.350  
   1.351      return 1;   /* this audio target is available. */
   1.352  }
   1.353  
   1.354 -
   1.355  AudioBootStrap PULSEAUDIO_bootstrap = {
   1.356      "pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
   1.357  };
     2.1 --- a/src/audio/pulseaudio/SDL_pulseaudio.h	Wed Mar 18 00:56:33 2015 -0400
     2.2 +++ b/src/audio/pulseaudio/SDL_pulseaudio.h	Wed Mar 18 09:59:22 2015 -0400
     2.3 @@ -34,7 +34,6 @@
     2.4  {
     2.5      /* pulseaudio structures */
     2.6      pa_mainloop *mainloop;
     2.7 -    pa_mainloop_api *mainloop_api;
     2.8      pa_context *context;
     2.9      pa_stream *stream;
    2.10