alsa: poll mode handling
Signed-off-by: malc <av1474@comtv.ru>
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index 5ea49a6..28c245d 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -23,6 +23,7 @@
*/
#include <alsa/asoundlib.h>
#include "qemu-common.h"
+#include "qemu-char.h"
#include "audio.h"
#if QEMU_GNUC_PREREQ(4, 3)
@@ -32,16 +33,24 @@
#define AUDIO_CAP "alsa"
#include "audio_int.h"
+struct pollhlp {
+ snd_pcm_t *handle;
+ struct pollfd *pfds;
+ int count;
+};
+
typedef struct ALSAVoiceOut {
HWVoiceOut hw;
void *pcm_buf;
snd_pcm_t *handle;
+ struct pollhlp pollhlp;
} ALSAVoiceOut;
typedef struct ALSAVoiceIn {
HWVoiceIn hw;
snd_pcm_t *handle;
void *pcm_buf;
+ struct pollhlp pollhlp;
} ALSAVoiceIn;
static struct {
@@ -123,6 +132,156 @@
*handlep = NULL;
}
+static int alsa_recover (snd_pcm_t *handle)
+{
+ int err = snd_pcm_prepare (handle);
+ if (err < 0) {
+ alsa_logerr (err, "Failed to prepare handle %p\n", handle);
+ return -1;
+ }
+ return 0;
+}
+
+static int alsa_resume (snd_pcm_t *handle)
+{
+ int err = snd_pcm_resume (handle);
+ if (err < 0) {
+ alsa_logerr (err, "Failed to resume handle %p\n", handle);
+ return -1;
+ }
+ return 0;
+}
+
+static void alsa_poll_handler (void *opaque)
+{
+ int err, count;
+ snd_pcm_state_t state;
+ struct pollhlp *hlp = opaque;
+ unsigned short revents;
+
+ count = poll (hlp->pfds, hlp->count, 0);
+ if (count < 0) {
+ dolog ("alsa_poll_handler: poll %s\n", strerror (errno));
+ return;
+ }
+
+ if (!count) {
+ return;
+ }
+
+ /* XXX: ALSA example uses initial count, not the one returned by
+ poll, correct? */
+ err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds,
+ hlp->count, &revents);
+ if (err < 0) {
+ alsa_logerr (err, "snd_pcm_poll_descriptors_revents");
+ return;
+ }
+
+ if (!(revents & POLLOUT)) {
+ if (conf.verbose) {
+ dolog ("revents = %d\n", revents);
+ }
+ return;
+ }
+
+ state = snd_pcm_state (hlp->handle);
+ switch (state) {
+ case SND_PCM_STATE_XRUN:
+ alsa_recover (hlp->handle);
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ alsa_resume (hlp->handle);
+ break;
+
+ case SND_PCM_STATE_PREPARED:
+ audio_run ("alsa run (prepared)");
+ break;
+
+ case SND_PCM_STATE_RUNNING:
+ audio_run ("alsa run (running)");
+ break;
+
+ default:
+ dolog ("Unexpected state %d\n", state);
+ }
+}
+
+static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp)
+{
+ int i, count, err;
+ struct pollfd *pfds;
+
+ count = snd_pcm_poll_descriptors_count (handle);
+ if (count <= 0) {
+ dolog ("Could not initialize poll mode\n"
+ "Invalid number of poll descriptors %d\n", count);
+ return -1;
+ }
+
+ pfds = audio_calloc ("alsa_poll_helper", count, sizeof (*pfds));
+ if (!pfds) {
+ dolog ("Could not initialize poll mode\n");
+ return -1;
+ }
+
+ err = snd_pcm_poll_descriptors (handle, pfds, count);
+ if (err < 0) {
+ alsa_logerr (err, "Could not initialize poll mode\n"
+ "Could not obtain poll descriptors\n");
+ qemu_free (pfds);
+ return -1;
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (pfds[i].events & POLLIN) {
+ err = qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler,
+ NULL, hlp);
+ }
+ if (pfds[i].events & POLLOUT) {
+ if (conf.verbose) {
+ dolog ("POLLOUT %d %d\n", i, pfds[i].fd);
+ }
+ err = qemu_set_fd_handler (pfds[i].fd, NULL,
+ alsa_poll_handler, hlp);
+ }
+ if (conf.verbose) {
+ dolog ("Set handler events=%#x index=%d fd=%d err=%d\n",
+ pfds[i].events, i, pfds[i].fd, err);
+ }
+
+ if (err) {
+ dolog ("Failed to set handler events=%#x index=%d fd=%d err=%d\n",
+ pfds[i].events, i, pfds[i].fd, err);
+
+ while (i--) {
+ qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL);
+ }
+ qemu_free (pfds);
+ return -1;
+ }
+ }
+ hlp->pfds = pfds;
+ hlp->count = count;
+ hlp->handle = handle;
+ return 0;
+}
+
+static int alsa_poll_out (HWVoiceOut *hw)
+{
+ ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+
+ return alsa_poll_helper (alsa->handle, &alsa->pollhlp);
+}
+
+static int alsa_poll_in (HWVoiceIn *hw)
+{
+ ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+
+ return alsa_poll_helper (alsa->handle, &alsa->pollhlp);
+}
+
static int alsa_write (SWVoiceOut *sw, void *buf, int len)
{
return audio_pcm_sw_write (sw, buf, len);
@@ -493,26 +652,6 @@
return -1;
}
-static int alsa_recover (snd_pcm_t *handle)
-{
- int err = snd_pcm_prepare (handle);
- if (err < 0) {
- alsa_logerr (err, "Failed to prepare handle %p\n", handle);
- return -1;
- }
- return 0;
-}
-
-static int alsa_resume (snd_pcm_t *handle)
-{
- int err = snd_pcm_resume (handle);
- if (err < 0) {
- alsa_logerr (err, "Failed to resume handle %p\n", handle);
- return -1;
- }
- return 0;
-}
-
static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle)
{
snd_pcm_sframes_t avail;
@@ -626,6 +765,22 @@
return decr;
}
+static void alsa_fini_poll (struct pollhlp *hlp)
+{
+ int i;
+ struct pollfd *pfds = hlp->pfds;
+
+ if (pfds) {
+ for (i = 0; i < hlp->count; ++i) {
+ qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL);
+ }
+ qemu_free (pfds);
+ }
+ hlp->pfds = NULL;
+ hlp->count = 0;
+ hlp->handle = NULL;
+}
+
static void alsa_fini_out (HWVoiceOut *hw)
{
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
@@ -637,6 +792,8 @@
qemu_free (alsa->pcm_buf);
alsa->pcm_buf = NULL;
}
+
+ alsa_fini_poll (&alsa->pollhlp);
}
static int alsa_init_out (HWVoiceOut *hw, struct audsettings *as)
@@ -705,11 +862,21 @@
static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
{
+ va_list ap;
+ int poll_mode;
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+ va_start (ap, cmd);
+ poll_mode = va_arg (ap, int);
+ va_end (ap);
+
switch (cmd) {
case VOICE_ENABLE:
ldebug ("enabling voice\n");
+ if (poll_mode && alsa_poll_out (hw)) {
+ poll_mode = 0;
+ }
+ hw->poll_mode = poll_mode;
return alsa_voice_ctl (alsa->handle, "playback", 0);
case VOICE_DISABLE:
@@ -772,6 +939,7 @@
qemu_free (alsa->pcm_buf);
alsa->pcm_buf = NULL;
}
+ alsa_fini_poll (&alsa->pollhlp);
}
static int alsa_run_in (HWVoiceIn *hw)
@@ -909,15 +1077,30 @@
static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
{
+ va_list ap;
+ int poll_mode;
ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+ va_start (ap, cmd);
+ poll_mode = va_arg (ap, int);
+ va_end (ap);
+
switch (cmd) {
case VOICE_ENABLE:
ldebug ("enabling voice\n");
+ if (poll_mode && alsa_poll_in (hw)) {
+ poll_mode = 0;
+ }
+ hw->poll_mode = poll_mode;
+
return alsa_voice_ctl (alsa->handle, "capture", 0);
case VOICE_DISABLE:
ldebug ("disabling voice\n");
+ if (hw->poll_mode) {
+ hw->poll_mode = 0;
+ alsa_fini_poll (&alsa->pollhlp);
+ }
return alsa_voice_ctl (alsa->handle, "capture", 1);
}
@@ -1014,7 +1197,7 @@
.fini_in = alsa_fini_in,
.run_in = alsa_run_in,
.read = alsa_read,
- .ctl_in = alsa_ctl_in
+ .ctl_in = alsa_ctl_in,
};
struct audio_driver alsa_audio_driver = {