PulseAudio: Hotplug support!
authorRyan C. Gordon <icculus@icculus.org>
Thu, 19 Mar 2015 22:08:12 -0400
changeset 94006f662d802380
parent 9399 a684dbd185c7
child 9401 529007547fb6
PulseAudio: Hotplug support!
src/audio/pulseaudio/SDL_pulseaudio.c
src/audio/pulseaudio/SDL_pulseaudio.h
     1.1 --- a/src/audio/pulseaudio/SDL_pulseaudio.c	Thu Mar 19 15:43:00 2015 -0400
     1.2 +++ b/src/audio/pulseaudio/SDL_pulseaudio.c	Thu Mar 19 22:08:12 2015 -0400
     1.3 @@ -72,6 +72,8 @@
     1.4  static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (void);
     1.5  static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *);
     1.6  static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *, int, int *);
     1.7 +static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *, int *);
     1.8 +static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *, int);
     1.9  static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *);
    1.10  
    1.11  static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) (
    1.12 @@ -83,9 +85,13 @@
    1.13      const char *);
    1.14  static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
    1.15      pa_context_flags_t, const pa_spawn_api *);
    1.16 -static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
    1.17 -static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list)(pa_context *, pa_source_info_cb_t, void *);
    1.18 +static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t, void *);
    1.19 +static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t, void *);
    1.20 +static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *, uint32_t, pa_sink_info_cb_t, void *);
    1.21 +static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *, uint32_t, pa_source_info_cb_t, void *);
    1.22  static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
    1.23 +static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *);
    1.24 +static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t, void *);
    1.25  static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
    1.26  static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
    1.27  
    1.28 @@ -180,6 +186,8 @@
    1.29      SDL_PULSEAUDIO_SYM(pa_mainloop_new);
    1.30      SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
    1.31      SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
    1.32 +    SDL_PULSEAUDIO_SYM(pa_mainloop_run);
    1.33 +    SDL_PULSEAUDIO_SYM(pa_mainloop_quit);
    1.34      SDL_PULSEAUDIO_SYM(pa_mainloop_free);
    1.35      SDL_PULSEAUDIO_SYM(pa_operation_get_state);
    1.36      SDL_PULSEAUDIO_SYM(pa_operation_cancel);
    1.37 @@ -188,7 +196,11 @@
    1.38      SDL_PULSEAUDIO_SYM(pa_context_connect);
    1.39      SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
    1.40      SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
    1.41 +    SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index);
    1.42 +    SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index);
    1.43      SDL_PULSEAUDIO_SYM(pa_context_get_state);
    1.44 +    SDL_PULSEAUDIO_SYM(pa_context_subscribe);
    1.45 +    SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback);
    1.46      SDL_PULSEAUDIO_SYM(pa_context_disconnect);
    1.47      SDL_PULSEAUDIO_SYM(pa_context_unref);
    1.48      SDL_PULSEAUDIO_SYM(pa_stream_new);
    1.49 @@ -227,6 +239,19 @@
    1.50  }
    1.51  
    1.52  static void
    1.53 +WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o)
    1.54 +{
    1.55 +    /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */
    1.56 +    if (mainloop && o) {
    1.57 +        SDL_bool okay = SDL_TRUE;
    1.58 +        while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) {
    1.59 +            okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) >= 0);
    1.60 +        }
    1.61 +        PULSEAUDIO_pa_operation_unref(o);
    1.62 +    }
    1.63 +}
    1.64 +
    1.65 +static void
    1.66  DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
    1.67  {
    1.68      if (context) {
    1.69 @@ -300,7 +325,7 @@
    1.70  {
    1.71      struct SDL_PrivateAudioData *h = this->hidden;
    1.72  
    1.73 -    while(1) {
    1.74 +    while (this->enabled) {
    1.75          if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
    1.76              PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
    1.77              PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    1.78 @@ -318,9 +343,10 @@
    1.79  {
    1.80      /* Write the audio data */
    1.81      struct SDL_PrivateAudioData *h = this->hidden;
    1.82 -    if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL,
    1.83 -                                   PA_SEEK_RELATIVE) < 0) {
    1.84 -        SDL_OpenedAudioDeviceDisconnected(this);
    1.85 +    if (this->enabled) {
    1.86 +        if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
    1.87 +            SDL_OpenedAudioDeviceDisconnected(this);
    1.88 +        }
    1.89      }
    1.90  }
    1.91  
    1.92 @@ -333,24 +359,21 @@
    1.93  static void
    1.94  PULSEAUDIO_WaitDone(_THIS)
    1.95  {
    1.96 -    struct SDL_PrivateAudioData *h = this->hidden;
    1.97 -    pa_operation *o;
    1.98 -
    1.99 -    o = PULSEAUDIO_pa_stream_drain(h->stream, stream_drain_complete, NULL);
   1.100 -    if (!o) {
   1.101 -        return;
   1.102 -    }
   1.103 -
   1.104 -    while (PULSEAUDIO_pa_operation_get_state(o) != PA_OPERATION_DONE) {
   1.105 -        if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
   1.106 -            PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
   1.107 -            PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
   1.108 -            PULSEAUDIO_pa_operation_cancel(o);
   1.109 -            break;
   1.110 +    if (this->enabled) {
   1.111 +        struct SDL_PrivateAudioData *h = this->hidden;
   1.112 +        pa_operation *o = PULSEAUDIO_pa_stream_drain(h->stream, stream_drain_complete, NULL);
   1.113 +        if (o) {
   1.114 +            while (PULSEAUDIO_pa_operation_get_state(o) != PA_OPERATION_DONE) {
   1.115 +                if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
   1.116 +                    PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
   1.117 +                    PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
   1.118 +                    PULSEAUDIO_pa_operation_cancel(o);
   1.119 +                    break;
   1.120 +                }
   1.121 +            }
   1.122 +            PULSEAUDIO_pa_operation_unref(o);
   1.123          }
   1.124      }
   1.125 -
   1.126 -    PULSEAUDIO_pa_operation_unref(o);
   1.127  }
   1.128  
   1.129  
   1.130 @@ -367,11 +390,10 @@
   1.131  {
   1.132      if (this->hidden != NULL) {
   1.133          SDL_FreeAudioMem(this->hidden->mixbuf);
   1.134 -        this->hidden->mixbuf = NULL;
   1.135 +        SDL_free(this->hidden->device_name);
   1.136          if (this->hidden->stream) {
   1.137              PULSEAUDIO_pa_stream_disconnect(this->hidden->stream);
   1.138              PULSEAUDIO_pa_stream_unref(this->hidden->stream);
   1.139 -            this->hidden->stream = NULL;
   1.140          }
   1.141          DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
   1.142          SDL_free(this->hidden);
   1.143 @@ -379,10 +401,31 @@
   1.144      }
   1.145  }
   1.146  
   1.147 +static void
   1.148 +DeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
   1.149 +{
   1.150 +    if (i) {
   1.151 +        char **devname = (char **) data;
   1.152 +        *devname = SDL_strdup(i->name);
   1.153 +    }
   1.154 +}
   1.155 +
   1.156 +static SDL_bool
   1.157 +FindDeviceName(struct SDL_PrivateAudioData *h, void *handle)
   1.158 +{
   1.159 +    const uint32_t idx = ((uint32_t) ((size_t) handle)) - 1;
   1.160 +
   1.161 +    if (handle == NULL) {  /* NULL == default device. */
   1.162 +        return SDL_TRUE;
   1.163 +    }
   1.164 +
   1.165 +    WaitForPulseOperation(h->mainloop, PULSEAUDIO_pa_context_get_sink_info_by_index(h->context, idx, DeviceNameCallback, &h->device_name));
   1.166 +    return (h->device_name != NULL);
   1.167 +}
   1.168 +
   1.169  static int
   1.170  PULSEAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   1.171  {
   1.172 -    const char *devstr = (const char *) handle;  /* NULL==default in Pulse. */
   1.173      struct SDL_PrivateAudioData *h = NULL;
   1.174      Uint16 test_format = 0;
   1.175      pa_sample_spec paspec;
   1.176 @@ -483,6 +526,11 @@
   1.177          return SDL_SetError("Could not connect to PulseAudio server");
   1.178      }
   1.179  
   1.180 +    if (!FindDeviceName(h, handle)) {
   1.181 +        PULSEAUDIO_CloseDevice(this);
   1.182 +        return SDL_SetError("Requested PulseAudio sink missing?");
   1.183 +    }
   1.184 +
   1.185      /* The SDL ALSA output hints us that we use Windows' channel mapping */
   1.186      /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
   1.187      PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
   1.188 @@ -500,7 +548,13 @@
   1.189          return SDL_SetError("Could not set up PulseAudio stream");
   1.190      }
   1.191  
   1.192 -    if (PULSEAUDIO_pa_stream_connect_playback(h->stream, devstr, &paattr, flags,
   1.193 +    /* now that we have multi-device support, don't move a stream from
   1.194 +        a device that was unplugged to something else, unless we're default. */
   1.195 +    if (h->device_name != NULL) {
   1.196 +        flags |= PA_STREAM_DONT_MOVE;
   1.197 +    }
   1.198 +
   1.199 +    if (PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags,
   1.200              NULL, NULL) < 0) {
   1.201          PULSEAUDIO_CloseDevice(this);
   1.202          return SDL_SetError("Could not connect PulseAudio stream");
   1.203 @@ -514,7 +568,7 @@
   1.204          state = PULSEAUDIO_pa_stream_get_state(h->stream);
   1.205          if (!PA_STREAM_IS_GOOD(state)) {
   1.206              PULSEAUDIO_CloseDevice(this);
   1.207 -            return SDL_SetError("Could not create to PulseAudio stream");
   1.208 +            return SDL_SetError("Could not connect PulseAudio stream");
   1.209          }
   1.210      } while (state != PA_STREAM_READY);
   1.211  
   1.212 @@ -522,95 +576,107 @@
   1.213      return 0;
   1.214  }
   1.215  
   1.216 +static pa_mainloop *hotplug_mainloop = NULL;
   1.217 +static pa_context *hotplug_context = NULL;
   1.218 +static SDL_Thread *hotplug_thread = NULL;
   1.219 +
   1.220 +/* device handles are device index + 1, cast to void*, so we never pass a NULL. */
   1.221 +
   1.222 +/* This is called when PulseAudio adds an output ("sink") device. */
   1.223  static void
   1.224 -get_sinks_cb(pa_context *c, const pa_sink_info *i, int is_last, void *data)
   1.225 +SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
   1.226  {
   1.227 -    SDL_bool *done = (SDL_bool *) data;
   1.228 +    if (i) {
   1.229 +        SDL_AddAudioDevice(SDL_FALSE, i->description, (void *) ((size_t) i->index+1));
   1.230 +    }
   1.231 +}
   1.232  
   1.233 -    *done = (is_list != 0);
   1.234 +/* This is called when PulseAudio adds a capture ("source") device. */
   1.235 +static void
   1.236 +SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
   1.237 +{
   1.238      if (i) {
   1.239 -        char *handle = SDL_strdup(i->name);
   1.240 -        if (handle != NULL) {
   1.241 -            SDL_AddAudioDevice(SDL_FALSE, i->description, handle);
   1.242 +        /* Skip "monitor" sources. These are just output from other sinks. */
   1.243 +        if (i->monitor_of_sink == PA_INVALID_INDEX) {
   1.244 +            SDL_AddAudioDevice(SDL_TRUE, i->description, (void *) ((size_t) i->index+1));
   1.245          }
   1.246      }
   1.247  }
   1.248  
   1.249 +/* This is called when PulseAudio has a device connected/removed/changed. */
   1.250  static void
   1.251 -get_sources_cb(pa_context *c, const pa_sink_info *i, int is_last, void *data)
   1.252 +HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data)
   1.253  {
   1.254 -    SDL_bool *done = (SDL_bool *) data;
   1.255 +    const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
   1.256 +    const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
   1.257  
   1.258 -    *done = (is_list != 0);
   1.259 -    if (i) {
   1.260 -        char *handle = SDL_strdup(i->name);
   1.261 -        if (handle != NULL) {
   1.262 -            SDL_AddAudioDevice(SDL_TRUE, i->description, handle);
   1.263 +    if (added || removed) {  /* we only care about add/remove events. */
   1.264 +        const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
   1.265 +        const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
   1.266 +
   1.267 +        /* adds need sink details from the PulseAudio server. Another callback... */
   1.268 +        if (added && sink) {
   1.269 +            PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, NULL);
   1.270 +        } else if (added && source) {
   1.271 +            PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, NULL);
   1.272 +        } else if (removed && (sink || source)) {
   1.273 +            /* removes we can handle just with the device index. */
   1.274 +            SDL_RemoveAudioDevice(source != 0, (void *) ((size_t) idx+1));
   1.275          }
   1.276      }
   1.277  }
   1.278  
   1.279 -static void
   1.280 -RunPulseDetectCallback(pa_mainloop *mainloop, pa_operation *o, SDL_bool *done)
   1.281 +/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */
   1.282 +static int SDLCALL
   1.283 +HotplugThread(void *data)
   1.284  {
   1.285 -    do {
   1.286 -        if (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_CANCELLED) {
   1.287 -            break;
   1.288 -        } else if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
   1.289 -            break;
   1.290 -        }
   1.291 -    } while (*done == SDL_FALSE);
   1.292 +    pa_operation *o;
   1.293 +    SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
   1.294 +    PULSEAUDIO_pa_context_set_subscribe_callback(hotplug_context, HotplugCallback, NULL);
   1.295 +    o = PULSEAUDIO_pa_context_subscribe(hotplug_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL);
   1.296 +    PULSEAUDIO_pa_operation_unref(o);  /* don't wait for it, just do our thing. */
   1.297 +    PULSEAUDIO_pa_mainloop_run(hotplug_mainloop, NULL);
   1.298 +    return 0;
   1.299  }
   1.300  
   1.301  static void
   1.302  PULSEAUDIO_DetectDevices()
   1.303  {
   1.304 -    pa_mainloop *mainloop = NULL;
   1.305 -    pa_context *context = NULL;
   1.306 -    pa_operation *o = NULL;
   1.307 -    SDL_bool done;
   1.308 +    WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, NULL));
   1.309 +    WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, NULL));
   1.310  
   1.311 -    if (ConnectToPulseServer(&mainloop, &context) < 0) {
   1.312 -        return;
   1.313 -    }
   1.314 -
   1.315 -    done = SDL_FALSE;
   1.316 -    RunPulseDetectCallback(mainloop, PULSEAUDIO_pa_context_get_sink_info_list(context, get_sinks_cb, &done), &done);
   1.317 -    done = SDL_FALSE;
   1.318 -    RunPulseDetectCallback(mainloop, PULSEAUDIO_pa_context_get_source_info_list(context, get_sources_cb, &done), &done);
   1.319 -
   1.320 -    DisconnectFromPulseServer(mainloop, context);
   1.321 -}
   1.322 -
   1.323 -static void
   1.324 -PULSEAUDIO_FreeDeviceHandle(void *handle)
   1.325 -{
   1.326 -    SDL_free(handle);  /* just a string we copied. */
   1.327 +    /* ok, we have a sane list, let's set up hotplug notifications now... */
   1.328 +    hotplug_thread = SDL_CreateThread(HotplugThread, "PulseHotplug", NULL);
   1.329  }
   1.330  
   1.331  static void
   1.332  PULSEAUDIO_Deinitialize(void)
   1.333  {
   1.334 +    if (hotplug_thread) {
   1.335 +        PULSEAUDIO_pa_mainloop_quit(hotplug_mainloop, 0);
   1.336 +        SDL_WaitThread(hotplug_thread, NULL);
   1.337 +        hotplug_thread = NULL;
   1.338 +    }
   1.339 +
   1.340 +    DisconnectFromPulseServer(hotplug_mainloop, hotplug_context);
   1.341 +    hotplug_mainloop = NULL;
   1.342 +    hotplug_context = NULL;
   1.343 +
   1.344      UnloadPulseAudioLibrary();
   1.345  }
   1.346  
   1.347  static int
   1.348  PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
   1.349  {
   1.350 -    pa_mainloop *mainloop = NULL;
   1.351 -    pa_context *context = NULL;
   1.352 -
   1.353      if (LoadPulseAudioLibrary() < 0) {
   1.354          return 0;
   1.355      }
   1.356  
   1.357 -    if (ConnectToPulseServer(&mainloop, &context) < 0) {
   1.358 +    if (ConnectToPulseServer(&hotplug_mainloop, &hotplug_context) < 0) {
   1.359          UnloadPulseAudioLibrary();
   1.360          return 0;
   1.361      }
   1.362  
   1.363 -    DisconnectFromPulseServer(mainloop, context);
   1.364 -
   1.365      /* Set the function pointers */
   1.366      impl->DetectDevices = PULSEAUDIO_DetectDevices;
   1.367      impl->OpenDevice = PULSEAUDIO_OpenDevice;
   1.368 @@ -619,7 +685,6 @@
   1.369      impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
   1.370      impl->CloseDevice = PULSEAUDIO_CloseDevice;
   1.371      impl->WaitDone = PULSEAUDIO_WaitDone;
   1.372 -    impl->FreeDeviceHandle = PULSEAUDIO_FreeDeviceHandle;
   1.373      impl->Deinitialize = PULSEAUDIO_Deinitialize;
   1.374  
   1.375      return 1;   /* this audio target is available. */
     2.1 --- a/src/audio/pulseaudio/SDL_pulseaudio.h	Thu Mar 19 15:43:00 2015 -0400
     2.2 +++ b/src/audio/pulseaudio/SDL_pulseaudio.h	Thu Mar 19 22:08:12 2015 -0400
     2.3 @@ -32,6 +32,8 @@
     2.4  
     2.5  struct SDL_PrivateAudioData
     2.6  {
     2.7 +    char *device_name;
     2.8 +
     2.9      /* pulseaudio structures */
    2.10      pa_mainloop *mainloop;
    2.11      pa_context *context;