src/audio/alsa/SDL_alsa_audio.c
changeset 10286 3b884985835c
parent 10257 f17581d00c26
parent 10230 9859cda24699
child 10466 b7267e214a77
     1.1 --- a/src/audio/alsa/SDL_alsa_audio.c	Fri Aug 12 22:50:48 2016 -0400
     1.2 +++ b/src/audio/alsa/SDL_alsa_audio.c	Sun Aug 28 13:36:13 2016 -0400
     1.3 @@ -699,90 +699,196 @@
     1.4      return 0;
     1.5  }
     1.6  
     1.7 +typedef struct ALSA_Device
     1.8 +{
     1.9 +    char *name;
    1.10 +    SDL_bool iscapture;
    1.11 +    struct ALSA_Device *next;
    1.12 +} ALSA_Device;
    1.13 +
    1.14  static void
    1.15 -ALSA_Deinitialize(void)
    1.16 +add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
    1.17  {
    1.18 -    UnloadALSALibrary();
    1.19 +    ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
    1.20 +    char *desc = ALSA_snd_device_name_get_hint(hint, "DESC");
    1.21 +    char *handle = NULL;
    1.22 +    char *ptr;
    1.23 +
    1.24 +    if (!desc) {
    1.25 +        SDL_free(dev);
    1.26 +        return;
    1.27 +    } else if (!dev) {
    1.28 +        free(desc);
    1.29 +        return;
    1.30 +    }
    1.31 +
    1.32 +    SDL_assert(name != NULL);
    1.33 +
    1.34 +    /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
    1.35 +       just chop the extra lines off, this seems to get a reasonable device
    1.36 +       name without extra details. */
    1.37 +    if ((ptr = strchr(desc, '\n')) != NULL) {
    1.38 +        *ptr = '\0';
    1.39 +    }
    1.40 +
    1.41 +    /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/
    1.42 +
    1.43 +    handle = SDL_strdup(name);
    1.44 +    if (!handle) {
    1.45 +        free(desc);
    1.46 +        SDL_free(dev);
    1.47 +        return;
    1.48 +    }
    1.49 +
    1.50 +    SDL_AddAudioDevice(iscapture, desc, handle);
    1.51 +    free(desc);
    1.52 +
    1.53 +    dev->name = handle;
    1.54 +    dev->iscapture = iscapture;
    1.55 +    dev->next = *pSeen;
    1.56 +    *pSeen = dev;
    1.57  }
    1.58  
    1.59 -static void
    1.60 -add_device(const int iscapture, const char *name, const char *_desc)
    1.61 +
    1.62 +static SDL_atomic_t ALSA_hotplug_shutdown;
    1.63 +static SDL_Thread *ALSA_hotplug_thread;
    1.64 +
    1.65 +static int SDLCALL
    1.66 +ALSA_HotplugThread(void *arg)
    1.67  {
    1.68 -    char *desc = NULL;
    1.69 -    char *handle = NULL;
    1.70 -    char *ptr = NULL;
    1.71 +    SDL_sem *first_run_semaphore = (SDL_sem *) arg;
    1.72 +    ALSA_Device *devices = NULL;
    1.73 +    ALSA_Device *next;
    1.74 +    ALSA_Device *dev;
    1.75 +    Uint32 ticks;
    1.76  
    1.77 -    if (!name || !_desc) {
    1.78 -        return;  /* nothing we can do with this...? */
    1.79 +    while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
    1.80 +        void **hints = NULL;
    1.81 +        if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) {
    1.82 +            ALSA_Device *unseen = devices;
    1.83 +            ALSA_Device *seen = NULL;
    1.84 +            ALSA_Device *prev;
    1.85 +            int i;
    1.86 +
    1.87 +            for (i = 0; hints[i]; i++) {
    1.88 +                char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
    1.89 +                if (!name) {
    1.90 +                    continue;
    1.91 +                }
    1.92 +
    1.93 +                /* only want physical hardware interfaces */
    1.94 +                if (SDL_strncmp(name, "hw:", 3) == 0) {
    1.95 +                    char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
    1.96 +                    const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
    1.97 +                    const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
    1.98 +                    SDL_bool have_output = SDL_FALSE;
    1.99 +                    SDL_bool have_input = SDL_FALSE;
   1.100 +
   1.101 +                    free(ioid);
   1.102 +
   1.103 +                    if (!isoutput && !isinput) {
   1.104 +                        free(name);
   1.105 +                        continue;
   1.106 +                    }
   1.107 +
   1.108 +                    prev = NULL;
   1.109 +                    for (dev = unseen; dev; dev = next) {
   1.110 +                        next = dev->next;
   1.111 +                        if ( (SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture)) ) {
   1.112 +                            if (prev) {
   1.113 +                                prev->next = next;
   1.114 +                            } else {
   1.115 +                                unseen = next;
   1.116 +                            }
   1.117 +                            dev->next = seen;
   1.118 +                            seen = dev;
   1.119 +                            if (isinput) have_input = SDL_TRUE;
   1.120 +                            if (isoutput) have_output = SDL_TRUE;
   1.121 +                        } else {
   1.122 +                            prev = dev;
   1.123 +                        }
   1.124 +                    }
   1.125 +
   1.126 +                    if (isinput && !have_input) {
   1.127 +                        add_device(SDL_TRUE, name, hints[i], &seen);
   1.128 +                    }
   1.129 +                    if (isoutput && !have_output) {
   1.130 +                        add_device(SDL_FALSE, name, hints[i], &seen);
   1.131 +                    }
   1.132 +                }
   1.133 +
   1.134 +                free(name);
   1.135 +            }
   1.136 +
   1.137 +            ALSA_snd_device_name_free_hint(hints);
   1.138 +
   1.139 +            devices = seen;   /* now we have a known-good list of attached devices. */
   1.140 +
   1.141 +            /* report anything still in unseen as removed. */
   1.142 +            for (dev = unseen; dev; dev = next) {
   1.143 +                /*printf("ALSA: removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
   1.144 +                next = dev->next;
   1.145 +                SDL_RemoveAudioDevice(dev->iscapture, dev->name);
   1.146 +                SDL_free(dev->name);
   1.147 +                SDL_free(dev);
   1.148 +            }
   1.149 +        }
   1.150 +
   1.151 +        /* On first run, tell ALSA_DetectDevices() that we have a complete device list so it can return. */
   1.152 +        if (first_run_semaphore) {
   1.153 +            SDL_SemPost(first_run_semaphore);
   1.154 +            first_run_semaphore = NULL;  /* let other thread clean it up. */
   1.155 +        }
   1.156 +
   1.157 +        /* Block awhile before checking again, unless we're told to stop. */
   1.158 +        ticks = SDL_GetTicks() + 5000;
   1.159 +        while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && !SDL_TICKS_PASSED(SDL_GetTicks(), ticks)) {
   1.160 +            SDL_Delay(100);
   1.161 +        }
   1.162      }
   1.163  
   1.164 -    desc = SDL_strdup(_desc);
   1.165 -    if (!desc) {
   1.166 -        return;  /* oh well, out of memory. Skip it. */
   1.167 +    /* Shutting down! Clean up any data we've gathered. */
   1.168 +    for (dev = devices; dev; dev = next) {
   1.169 +        /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
   1.170 +        next = dev->next;
   1.171 +        SDL_free(dev->name);
   1.172 +        SDL_free(dev);
   1.173      }
   1.174  
   1.175 -    /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output" */
   1.176 -    for (ptr = strchr(desc, '\n'); ptr; ptr = strchr(ptr + 1, '\n')) {
   1.177 -        *ptr = ' ';
   1.178 -    }
   1.179 -
   1.180 -    handle = SDL_strdup(name);
   1.181 -    if (handle != NULL) {
   1.182 -        SDL_AddAudioDevice(iscapture, desc, handle);
   1.183 -    }
   1.184 -
   1.185 -    SDL_free(desc);
   1.186 +    return 0;
   1.187  }
   1.188  
   1.189  static void
   1.190  ALSA_DetectDevices(void)
   1.191  {
   1.192 -    void **hints = NULL;
   1.193 -    int i;
   1.194 -
   1.195 -    /* !!! FIXME: use udev instead. */
   1.196 -    /* We won't deal with disconnects and hotplugs without udev, but at least
   1.197 -       you'll get a reasonable device list at startup. */
   1.198 -#if 1 /*!SDL_USE_LIBUDEV */
   1.199 -    if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == -1) {
   1.200 +    /* Start the device detection thread here, wait for an initial iteration to complete. */
   1.201 +    SDL_sem *semaphore = SDL_CreateSemaphore(0);
   1.202 +    if (!semaphore) {
   1.203          return;  /* oh well. */
   1.204      }
   1.205  
   1.206 -    for (i = 0; hints[i]; i++) {
   1.207 -        char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
   1.208 -        char *desc = ALSA_snd_device_name_get_hint(hints[i], "DESC");
   1.209 -        char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
   1.210 +    SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
   1.211  
   1.212 -        if ((ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0)) {
   1.213 -            add_device(SDL_FALSE, name, desc);
   1.214 -        }
   1.215 -
   1.216 -        if ((ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0)) {
   1.217 -            add_device(SDL_TRUE, name, desc);
   1.218 -        }
   1.219 -
   1.220 -        free(name);
   1.221 -        free(desc);
   1.222 -        free(ioid);
   1.223 +    ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", semaphore);
   1.224 +    if (ALSA_hotplug_thread) {
   1.225 +        SDL_SemWait(semaphore);  /* wait for the first iteration to finish. */
   1.226      }
   1.227  
   1.228 -    ALSA_snd_device_name_free_hint(hints);
   1.229 -#else
   1.230 -#error Fill in udev support here.
   1.231 -#endif
   1.232 +    SDL_DestroySemaphore(semaphore);
   1.233  }
   1.234  
   1.235  static void
   1.236 -ALSA_FreeDeviceHandle(void *handle)
   1.237 +ALSA_Deinitialize(void)
   1.238  {
   1.239 -#if 1 /*!SDL_USE_LIBUDEV*/
   1.240 -    SDL_free(handle);
   1.241 -#else
   1.242 -#error Fill in udev support here.
   1.243 -#endif
   1.244 +    if (ALSA_hotplug_thread != NULL) {
   1.245 +        SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
   1.246 +        SDL_WaitThread(ALSA_hotplug_thread, NULL);
   1.247 +        ALSA_hotplug_thread = NULL;
   1.248 +    }
   1.249 +
   1.250 +    UnloadALSALibrary();
   1.251  }
   1.252  
   1.253 -
   1.254  static int
   1.255  ALSA_Init(SDL_AudioDriverImpl * impl)
   1.256  {
   1.257 @@ -798,7 +904,6 @@
   1.258      impl->PlayDevice = ALSA_PlayDevice;
   1.259      impl->CloseDevice = ALSA_CloseDevice;
   1.260      impl->Deinitialize = ALSA_Deinitialize;
   1.261 -    impl->FreeDeviceHandle = ALSA_FreeDeviceHandle;
   1.262      impl->CaptureFromDevice = ALSA_CaptureFromDevice;
   1.263      impl->FlushCapture = ALSA_FlushCapture;
   1.264