spice: add audio
authorGerd Hoffmann <kraxel@redhat.com>
Tue, 9 Nov 2010 16:29:46 +0000 (17:29 +0100)
committermalc <av1474@comtv.ru>
Tue, 9 Nov 2010 20:39:30 +0000 (23:39 +0300)
Add support for the spice audio interface.  With this patch applied
audio can be forwarded over the network from/to the spice client.  Both
recording and playback is supported.

The driver is first in the driver list, but the can_be_default flag is
set only in case spice is active.  So if you have the spice protocol
enabled the spice audio driver is the default one, otherwise whatever
comes first after spice in the list.  Overriding the default using
QEMU_AUDIO_DRV works in any case.

[ v2: audio codestyle: add spaces before open parenthesis ]
[ v2: add const to silence array ]

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Cc: malc <av1474@comtv.ru>
Signed-off-by: malc <av1474@comtv.ru>
Makefile.objs
audio/audio.c
audio/audio_int.h
audio/spiceaudio.c [new file with mode: 0644]
ui/qemu-spice.h
ui/spice-core.c

index faf485ed1bcae3b16fa49c2f3d658057e50560c5..15569afa1402849984e81588081e40fd545dc969 100644 (file)
@@ -102,6 +102,7 @@ common-obj-$(CONFIG_SPICE) += ui/spice-core.o ui/spice-input.o ui/spice-display.
 audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o
 audio-obj-$(CONFIG_SDL) += sdlaudio.o
 audio-obj-$(CONFIG_OSS) += ossaudio.o
+audio-obj-$(CONFIG_SPICE) += spiceaudio.o
 audio-obj-$(CONFIG_COREAUDIO) += coreaudio.o
 audio-obj-$(CONFIG_ALSA) += alsaaudio.o
 audio-obj-$(CONFIG_DSOUND) += dsoundaudio.o
index ad51077f3226a687ca78824282bdb6379858054f..ade342e85650daf4f6f2804e15e19e1a190dbfcf 100644 (file)
@@ -44,6 +44,9 @@
     that we generate the list.
 */
 static struct audio_driver *drvtab[] = {
+#ifdef CONFIG_SPICE
+    &spice_audio_driver,
+#endif
     CONFIG_AUDIO_DRIVERS
     &no_audio_driver,
     &wav_audio_driver
index d8560b662bdbe01b09a36b73d6049292bade9a30..d66f2c3bf6fea2d7d7aa6bfba95f68887ac13502 100644 (file)
@@ -209,6 +209,7 @@ extern struct audio_driver coreaudio_audio_driver;
 extern struct audio_driver dsound_audio_driver;
 extern struct audio_driver esd_audio_driver;
 extern struct audio_driver pa_audio_driver;
+extern struct audio_driver spice_audio_driver;
 extern struct audio_driver winwave_audio_driver;
 extern struct mixeng_volume nominal_volume;
 
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
new file mode 100644 (file)
index 0000000..51ba53a
--- /dev/null
@@ -0,0 +1,327 @@
+#include "hw/hw.h"
+#include "qemu-timer.h"
+#include "ui/qemu-spice.h"
+
+#define AUDIO_CAP "spice"
+#include "audio.h"
+#include "audio_int.h"
+
+#define LINE_IN_SAMPLES 1024
+#define LINE_OUT_SAMPLES 1024
+
+typedef struct SpiceRateCtl {
+    int64_t               start_ticks;
+    int64_t               bytes_sent;
+} SpiceRateCtl;
+
+typedef struct SpiceVoiceOut {
+    HWVoiceOut            hw;
+    SpicePlaybackInstance sin;
+    SpiceRateCtl          rate;
+    int                   active;
+    uint32_t              *frame;
+    uint32_t              *fpos;
+    uint32_t              fsize;
+} SpiceVoiceOut;
+
+typedef struct SpiceVoiceIn {
+    HWVoiceIn             hw;
+    SpiceRecordInstance   sin;
+    SpiceRateCtl          rate;
+    int                   active;
+    uint32_t              samples[LINE_IN_SAMPLES];
+} SpiceVoiceIn;
+
+static const SpicePlaybackInterface playback_sif = {
+    .base.type          = SPICE_INTERFACE_PLAYBACK,
+    .base.description   = "playback",
+    .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR,
+};
+
+static const SpiceRecordInterface record_sif = {
+    .base.type          = SPICE_INTERFACE_RECORD,
+    .base.description   = "record",
+    .base.major_version = SPICE_INTERFACE_RECORD_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
+};
+
+static void *spice_audio_init (void)
+{
+    if (!using_spice) {
+        return NULL;
+    }
+    return &spice_audio_init;
+}
+
+static void spice_audio_fini (void *opaque)
+{
+    /* nothing */
+}
+
+static void rate_start (SpiceRateCtl *rate)
+{
+    memset (rate, 0, sizeof (*rate));
+    rate->start_ticks = qemu_get_clock (vm_clock);
+}
+
+static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
+{
+    int64_t now;
+    int64_t ticks;
+    int64_t bytes;
+    int64_t samples;
+
+    now = qemu_get_clock (vm_clock);
+    ticks = now - rate->start_ticks;
+    bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ());
+    samples = (bytes - rate->bytes_sent) >> info->shift;
+    if (samples < 0 || samples > 65536) {
+        fprintf (stderr, "Resetting rate control (%" PRId64 " samples)\n", samples);
+        rate_start (rate);
+        samples = 0;
+    }
+    rate->bytes_sent += samples << info->shift;
+    return samples;
+}
+
+/* playback */
+
+static int line_out_init (HWVoiceOut *hw, struct audsettings *as)
+{
+    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
+    struct audsettings settings;
+
+    settings.freq       = SPICE_INTERFACE_PLAYBACK_FREQ;
+    settings.nchannels  = SPICE_INTERFACE_PLAYBACK_CHAN;
+    settings.fmt        = AUD_FMT_S16;
+    settings.endianness = AUDIO_HOST_ENDIANNESS;
+
+    audio_pcm_init_info (&hw->info, &settings);
+    hw->samples = LINE_OUT_SAMPLES;
+    out->active = 0;
+
+    out->sin.base.sif = &playback_sif.base;
+    qemu_spice_add_interface (&out->sin.base);
+    return 0;
+}
+
+static void line_out_fini (HWVoiceOut *hw)
+{
+    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
+
+    spice_server_remove_interface (&out->sin.base);
+}
+
+static int line_out_run (HWVoiceOut *hw, int live)
+{
+    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
+    int rpos, decr;
+    int samples;
+
+    if (!live) {
+        return 0;
+    }
+
+    decr = rate_get_samples (&hw->info, &out->rate);
+    decr = audio_MIN (live, decr);
+
+    samples = decr;
+    rpos = hw->rpos;
+    while (samples) {
+        int left_till_end_samples = hw->samples - rpos;
+        int len = audio_MIN (samples, left_till_end_samples);
+
+        if (!out->frame) {
+            spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize);
+            out->fpos = out->frame;
+        }
+        if (out->frame) {
+            len = audio_MIN (len, out->fsize);
+            hw->clip (out->fpos, hw->mix_buf + rpos, len);
+            out->fsize -= len;
+            out->fpos  += len;
+            if (out->fsize == 0) {
+                spice_server_playback_put_samples (&out->sin, out->frame);
+                out->frame = out->fpos = NULL;
+            }
+        }
+        rpos = (rpos + len) % hw->samples;
+        samples -= len;
+    }
+    hw->rpos = rpos;
+    return decr;
+}
+
+static int line_out_write (SWVoiceOut *sw, void *buf, int len)
+{
+    return audio_pcm_sw_write (sw, buf, len);
+}
+
+static int line_out_ctl (HWVoiceOut *hw, int cmd, ...)
+{
+    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
+
+    switch (cmd) {
+    case VOICE_ENABLE:
+        if (out->active) {
+            break;
+        }
+        out->active = 1;
+        rate_start (&out->rate);
+        spice_server_playback_start (&out->sin);
+        break;
+    case VOICE_DISABLE:
+        if (!out->active) {
+            break;
+        }
+        out->active = 0;
+        if (out->frame) {
+            memset (out->fpos, 0, out->fsize << 2);
+            spice_server_playback_put_samples (&out->sin, out->frame);
+            out->frame = out->fpos = NULL;
+        }
+        spice_server_playback_stop (&out->sin);
+        break;
+    }
+    return 0;
+}
+
+/* record */
+
+static int line_in_init (HWVoiceIn *hw, struct audsettings *as)
+{
+    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+    struct audsettings settings;
+
+    settings.freq       = SPICE_INTERFACE_RECORD_FREQ;
+    settings.nchannels  = SPICE_INTERFACE_RECORD_CHAN;
+    settings.fmt        = AUD_FMT_S16;
+    settings.endianness = AUDIO_HOST_ENDIANNESS;
+
+    audio_pcm_init_info (&hw->info, &settings);
+    hw->samples = LINE_IN_SAMPLES;
+    in->active = 0;
+
+    in->sin.base.sif = &record_sif.base;
+    qemu_spice_add_interface (&in->sin.base);
+    return 0;
+}
+
+static void line_in_fini (HWVoiceIn *hw)
+{
+    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+
+    spice_server_remove_interface (&in->sin.base);
+}
+
+static int line_in_run (HWVoiceIn *hw)
+{
+    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+    int num_samples;
+    int ready;
+    int len[2];
+    uint64_t delta_samp;
+    const uint32_t *samples;
+
+    if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) {
+        return 0;
+    }
+
+    delta_samp = rate_get_samples (&hw->info, &in->rate);
+    num_samples = audio_MIN (num_samples, delta_samp);
+
+    ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples);
+    samples = in->samples;
+    if (ready == 0) {
+        static const uint32_t silence[LINE_IN_SAMPLES];
+        samples = silence;
+        ready = LINE_IN_SAMPLES;
+    }
+
+    num_samples = audio_MIN (ready, num_samples);
+
+    if (hw->wpos + num_samples > hw->samples) {
+        len[0] = hw->samples - hw->wpos;
+        len[1] = num_samples - len[0];
+    } else {
+        len[0] = num_samples;
+        len[1] = 0;
+    }
+
+    hw->conv (hw->conv_buf + hw->wpos, samples, len[0], &nominal_volume);
+
+    if (len[1]) {
+        hw->conv (hw->conv_buf, samples + len[0], len[1],
+                  &nominal_volume);
+    }
+
+    hw->wpos = (hw->wpos + num_samples) % hw->samples;
+
+    return num_samples;
+}
+
+static int line_in_read (SWVoiceIn *sw, void *buf, int size)
+{
+    return audio_pcm_sw_read (sw, buf, size);
+}
+
+static int line_in_ctl (HWVoiceIn *hw, int cmd, ...)
+{
+    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+
+    switch (cmd) {
+    case VOICE_ENABLE:
+        if (in->active) {
+            break;
+        }
+        in->active = 1;
+        rate_start (&in->rate);
+        spice_server_record_start (&in->sin);
+        break;
+    case VOICE_DISABLE:
+        if (!in->active) {
+            break;
+        }
+        in->active = 0;
+        spice_server_record_stop (&in->sin);
+        break;
+    }
+    return 0;
+}
+
+static struct audio_option audio_options[] = {
+    { /* end of list */ },
+};
+
+static struct audio_pcm_ops audio_callbacks = {
+    .init_out = line_out_init,
+    .fini_out = line_out_fini,
+    .run_out  = line_out_run,
+    .write    = line_out_write,
+    .ctl_out  = line_out_ctl,
+
+    .init_in  = line_in_init,
+    .fini_in  = line_in_fini,
+    .run_in   = line_in_run,
+    .read     = line_in_read,
+    .ctl_in   = line_in_ctl,
+};
+
+struct audio_driver spice_audio_driver = {
+    .name           = "spice",
+    .descr          = "spice audio driver",
+    .options        = audio_options,
+    .init           = spice_audio_init,
+    .fini           = spice_audio_fini,
+    .pcm_ops        = &audio_callbacks,
+    .max_voices_out = 1,
+    .max_voices_in  = 1,
+    .voice_size_out = sizeof (SpiceVoiceOut),
+    .voice_size_in  = sizeof (SpiceVoiceIn),
+};
+
+void qemu_spice_audio_init (void)
+{
+    spice_audio_driver.can_be_default = 1;
+}
index 063c7dc8c88f3a694c286cc038d3946fc0c92cfd..0e3ad9b8c0877d440aa7eca07cf47cac5fb12111 100644 (file)
@@ -29,6 +29,7 @@ extern int using_spice;
 
 void qemu_spice_init(void);
 void qemu_spice_input_init(void);
+void qemu_spice_audio_init(void);
 void qemu_spice_display_init(DisplayState *ds);
 int qemu_spice_add_interface(SpiceBaseInstance *sin);
 
index 6a1cf17e49aa660f6f13c8ebc1cd235054ca2474..6c404b39fb8a945512dbe009eb3425b68f47b90a 100644 (file)
@@ -361,6 +361,7 @@ void qemu_spice_init(void)
     using_spice = 1;
 
     qemu_spice_input_init();
+    qemu_spice_audio_init();
 
     qemu_free(x509_key_file);
     qemu_free(x509_cert_file);