--- Makefile.objs | 1 + audio/audio.c | 3 + audio/audio_int.h | 1 + audio/spiceaudio.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 320 insertions(+), 0 deletions(-) create mode 100644 audio/spiceaudio.c
diff --git a/Makefile.objs b/Makefile.objs index ab1af88..b11db4c 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -84,6 +84,7 @@ common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o 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 diff --git a/audio/audio.c b/audio/audio.c index dbf0b96..67fc1d3 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -45,6 +45,9 @@ */ static struct audio_driver *drvtab[] = { CONFIG_AUDIO_DRIVERS +#ifdef CONFIG_SPICE + &spice_audio_driver, +#endif &no_audio_driver, &wav_audio_driver }; diff --git a/audio/audio_int.h b/audio/audio_int.h index 06e313f..d1f6c2d 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -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 index 0000000..0e18f2f --- /dev/null +++ b/audio/spiceaudio.c @@ -0,0 +1,315 @@ +#include "hw/hw.h" +#include "qemu-timer.h" +#include "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 SpiceVoiceOut { + HWVoiceOut hw; + SpicePlaybackInstance sin; + uint32_t *frame; + uint32_t *fpos; + uint32_t fsize; + uint64_t prev_ticks; + int active; +} SpiceVoiceOut; + +typedef struct SpiceVoiceIn { + HWVoiceIn hw; + SpiceRecordInstance sin; + uint64_t prev_ticks; + int active; + uint32_t samples[LINE_IN_SAMPLES]; +} SpiceVoiceIn; + +static 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 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 qemu_malloc(42); +} + +static void spice_audio_fini(void *opaque) +{ + qemu_free(opaque); +} + +static uint64_t get_monotonic_time(void) +{ + struct timespec time_space; + clock_gettime(CLOCK_MONOTONIC, &time_space); + return (uint64_t)time_space.tv_sec * 1000 * 1000 * 1000 + time_space.tv_nsec; +} + +/* 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; + spice_server_add_interface(spice_server, &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; + uint64_t now; + uint64_t ticks; + uint64_t bytes; + + if (!live) { + return 0; + } + + now = get_monotonic_time(); + ticks = now - out->prev_ticks; + bytes = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000); + out->prev_ticks = now; + + decr = (bytes > INT_MAX) + ? INT_MAX >> hw->info.shift + : (bytes + (1 << (hw->info.shift - 1))) >> hw->info.shift; + 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; + out->prev_ticks = get_monotonic_time(); + 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; + spice_server_add_interface(spice_server, &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 now; + uint64_t ticks; + uint64_t delta_samp; + uint32_t *samples; + + if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in(hw))) { + return 0; + } + + now = get_monotonic_time(); + ticks = now - in->prev_ticks; + in->prev_ticks = now; + delta_samp = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000); + delta_samp = (delta_samp + (1 << (hw->info.shift - 1))) >> hw->info.shift; + + 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 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; + in->prev_ticks = get_monotonic_time(); + 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, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 1, + .voice_size_out = sizeof(SpiceVoiceOut), + .voice_size_in = sizeof(SpiceVoiceIn), +}; -- 1.6.6.1