ALSA driver improvements:

* alsa hotplug thread is low priority
* give a chance for other threads to catch up when audio playback is not progressing
* use nonblocking for alsa audio capture
  There is a bug with SDL hanging when an audio capture USB device is removed, because poll never returns
This commit is contained in:
Sam Lantinga 2017-03-14 07:20:14 -07:00
parent c4d54504fa
commit 6814f5dbc0

View File

@ -364,6 +364,13 @@ ALSA_PlayDevice(_THIS)
} }
continue; continue;
} }
else if (status == 0) {
/* No frames were written (no available space in pcm device).
Allow other threads to catch up. */
Uint32 delay = (frames_left / 2 * 1000) / this->spec.freq;
SDL_Delay(delay);
}
sample_buf += status * frame_size; sample_buf += status * frame_size;
frames_left -= status; frames_left -= status;
} }
@ -383,23 +390,22 @@ ALSA_CaptureFromDevice(_THIS, void *buffer, int buflen)
this->spec.channels; this->spec.channels;
const int total_frames = buflen / frame_size; const int total_frames = buflen / frame_size;
snd_pcm_uframes_t frames_left = total_frames; snd_pcm_uframes_t frames_left = total_frames;
snd_pcm_uframes_t wait_time = frame_size / 2;
SDL_assert((buflen % frame_size) == 0); SDL_assert((buflen % frame_size) == 0);
while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) { while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
/* !!! FIXME: This works, but needs more testing before going live */ int status;
/* ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1); */
int status = ALSA_snd_pcm_readi(this->hidden->pcm_handle, status = ALSA_snd_pcm_readi(this->hidden->pcm_handle,
sample_buf, frames_left); sample_buf, frames_left);
if (status < 0) { if (status == -EAGAIN) {
ALSA_snd_pcm_wait(this->hidden->pcm_handle, wait_time);
status = 0;
}
else if (status < 0) {
/*printf("ALSA: capture error %d\n", status);*/ /*printf("ALSA: capture error %d\n", status);*/
if (status == -EAGAIN) {
/* Apparently snd_pcm_recover() doesn't handle this case -
does it assume snd_pcm_wait() above? */
SDL_Delay(1);
continue;
}
status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0); status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
if (status < 0) { if (status < 0) {
/* Hmm, not much we can do - abort */ /* Hmm, not much we can do - abort */
@ -745,8 +751,9 @@ ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen); SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
} }
/* Switch to blocking mode for playback */ if (!iscapture) {
ALSA_snd_pcm_nonblock(pcm_handle, 0); ALSA_snd_pcm_nonblock(pcm_handle, 0);
}
/* We're ready to rock and roll. :-) */ /* We're ready to rock and roll. :-) */
return 0; return 0;
@ -763,18 +770,28 @@ static void
add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen) add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
{ {
ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device)); ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
char *desc = ALSA_snd_device_name_get_hint(hint, "DESC"); char *desc;
char *handle = NULL; char *handle = NULL;
char *ptr; char *ptr;
if (!desc) { if (!dev) {
SDL_free(dev);
return;
} else if (!dev) {
free(desc);
return; return;
} }
/* Not all alsa devices are enumerable via snd_device_name_get_hint
(i.e. bluetooth devices). Therefore if hint is passed in to this
function as NULL, assume name contains desc.
Make sure not to free the storage associated with desc in this case */
if (hint) {
desc = ALSA_snd_device_name_get_hint(hint, "DESC");
if (!desc) {
SDL_free(dev);
return;
}
} else {
desc = (char *) name;
}
SDL_assert(name != NULL); SDL_assert(name != NULL);
/* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output". /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
@ -788,14 +805,16 @@ add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSee
handle = SDL_strdup(name); handle = SDL_strdup(name);
if (!handle) { if (!handle) {
free(desc); if (hint) {
free(desc);
}
SDL_free(dev); SDL_free(dev);
return; return;
} }
SDL_AddAudioDevice(iscapture, desc, handle); SDL_AddAudioDevice(iscapture, desc, handle);
free(desc); if (hint)
free(desc);
dev->name = handle; dev->name = handle;
dev->iscapture = iscapture; dev->iscapture = iscapture;
dev->next = *pSeen; dev->next = *pSeen;
@ -815,12 +834,15 @@ ALSA_HotplugThread(void *arg)
ALSA_Device *dev; ALSA_Device *dev;
Uint32 ticks; Uint32 ticks;
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) { while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
void **hints = NULL; void **hints = NULL;
ALSA_Device *unseen;
ALSA_Device *seen;
ALSA_Device *prev;
if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) { if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) {
ALSA_Device *unseen = devices;
ALSA_Device *seen = NULL;
ALSA_Device *prev;
int i, j; int i, j;
const char *match = NULL; const char *match = NULL;
int bestmatch = 0xFFFF; int bestmatch = 0xFFFF;
@ -830,6 +852,8 @@ ALSA_HotplugThread(void *arg)
"hw:", "sysdefault:", "default:", NULL "hw:", "sysdefault:", "default:", NULL
}; };
unseen = devices;
seen = NULL;
/* Apparently there are several different ways that ALSA lists /* Apparently there are several different ways that ALSA lists
actual hardware. It could be prefixed with "hw:" or "default:" actual hardware. It could be prefixed with "hw:" or "default:"
or "sysdefault:" and maybe others. Go through the list and see or "sysdefault:" and maybe others. Go through the list and see
@ -924,7 +948,7 @@ ALSA_HotplugThread(void *arg)
/* report anything still in unseen as removed. */ /* report anything still in unseen as removed. */
for (dev = unseen; dev; dev = next) { for (dev = unseen; dev; dev = next) {
/*printf("ALSA: removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/ /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
next = dev->next; next = dev->next;
SDL_RemoveAudioDevice(dev->iscapture, dev->name); SDL_RemoveAudioDevice(dev->iscapture, dev->name);
SDL_free(dev->name); SDL_free(dev->name);