From 2c4d4d6f3321238b35866386ba4ec050de3381f6 Mon Sep 17 00:00:00 2001 From: Yaniv Kamay <yaniv@qumranet.com> Date: Sun, 4 Jan 2009 19:17:30 +0200 Subject: [PATCH 22/54] qemu: add audio interface [ehabkost: folded "fix audio recording" patch onto this one] RH-Type: improvement(qxl) RH-Upstream-status: pending --- qemu/Makefile | 3 + qemu/audio/audio.c | 3 + qemu/audio/audio_int.h | 4 + qemu/audio/vd_interface_audio.c | 482 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 492 insertions(+), 0 deletions(-) create mode 100644 qemu/audio/vd_interface_audio.c diff --git a/qemu/Makefile b/qemu/Makefile index 6f6bfd7..06b9f25 100644 --- a/qemu/Makefile +++ b/qemu/Makefile @@ -116,6 +116,9 @@ endif ifdef CONFIG_DSOUND AUDIO_OBJS += dsoundaudio.o endif +ifeq ($(CONFIG_SPICE),yes) +AUDIO_OBJS += vd_interface_audio.o +endif ifdef CONFIG_FMOD AUDIO_OBJS += fmodaudio.o audio/audio.o audio/fmodaudio.o: CPPFLAGS := -I$(CONFIG_FMOD_INC) $(CPPFLAGS) diff --git a/qemu/audio/audio.c b/qemu/audio/audio.c index 762c2e3..b01c1c0 100644 --- a/qemu/audio/audio.c +++ b/qemu/audio/audio.c @@ -38,6 +38,9 @@ #define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" static struct audio_driver *drvtab[] = { +#ifdef CONFIG_SPICE + &interface_audio_driver, +#endif AUDIO_DRIVERS &no_audio_driver, &wav_audio_driver diff --git a/qemu/audio/audio_int.h b/qemu/audio/audio_int.h index c1da710..3e816ac 100644 --- a/qemu/audio/audio_int.h +++ b/qemu/audio/audio_int.h @@ -204,6 +204,10 @@ 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; +#ifdef CONFIG_SPICE +extern struct audio_driver interface_audio_driver; +#endif + extern struct mixeng_volume nominal_volume; void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); diff --git a/qemu/audio/vd_interface_audio.c b/qemu/audio/vd_interface_audio.c new file mode 100644 index 0000000..714bda3 --- /dev/null +++ b/qemu/audio/vd_interface_audio.c @@ -0,0 +1,482 @@ +#include "hw/hw.h" +#include "audio.h" +#define AUDIO_CAP "vd_interface_audio" +#include "audio_int.h" +#include "qemu-timer.h" +#include "interface.h" + +#define dprintf(format, ...) \ + printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__ ) + +#define ASSERT(x) if (!(x)) { \ + printf("%s: ASSERT %s failed\n", __FUNCTION__, #x); \ + abort(); \ +} + +#define LINE_IN_SAMPLES 1024 +#define LINE_OUT_SAMPLES 1024 + +typedef struct InterfaceVoiceOut { + HWVoiceOut base; + uint64_t prev_ticks; +} InterfaceVoiceOut; + +typedef struct InterfaceVoiceIn { + HWVoiceOut base; + uint64_t prev_ticks; +} InterfaceVoiceIn; + +static struct audio_option options[] = { + {NULL, 0, NULL, NULL, NULL, 0} +}; + +typedef struct Interface_audio { + PlaybackInterface *play_interface; + PlaybackPlug *play_plug; + uint32_t play_avail; + uint32_t *play_frame; + uint32_t *play_now; + + RecordInterface *record_interface; + RecordPlug *record_plug; + uint32_t record_avail; + uint32_t *record_now; + uint32_t silence[LINE_IN_SAMPLES]; + + InterfaceVoiceOut *voic_out; + HWVoiceIn *voic_in; +} Interface_audio; + +static Interface_audio driver = { + .play_interface = NULL, + .play_plug = NULL, + .play_avail = 0, + .play_frame = NULL, + .play_now = NULL, + + .record_interface = NULL, + .record_plug = NULL, + .record_avail = 0, + .record_now = NULL, + + .voic_out = NULL, + .voic_in = NULL, +}; + +static VDObjectRef interface_play_plug(PlaybackInterface *playback, PlaybackPlug* plug, + int *enabled) +{ + if (driver.play_plug) { + dprintf("plug failed"); + return INVALID_VD_OBJECT_REF; + } + ASSERT(plug && playback == driver.play_interface && enabled); + driver.play_plug = plug; + *enabled = driver.voic_out ? driver.voic_out->base.enabled : 0; + return (VDObjectRef)plug; +} + +static void interface_play_unplug(PlaybackInterface *playback, VDObjectRef obj) +{ + if (!driver.play_plug || obj != (VDObjectRef)driver.play_plug) { + dprintf("unplug failed"); + return; + } + ASSERT(playback == driver.play_interface); + driver.play_plug = NULL; + driver.play_avail = 0; + driver.play_frame = NULL; + driver.play_now = NULL; +} + +static void regitser_playback(void) +{ + PlaybackInterface *interface = (PlaybackInterface *)qemu_mallocz(sizeof(*interface)); + static int playback_interface_id = 0; + + if (!interface) { + printf("%s: malloc failed\n", __FUNCTION__); + exit(-1); + } + interface->base.base_vertion = VM_INTERFACE_VERTION; + interface->base.type = VD_INTERFACE_PLAYBACK; + interface->base.id = playback_interface_id++; + interface->base.description = "playback"; + interface->base.major_vertion = VD_INTERFACE_PLAYBACK_MAJOR; + interface->base.minor_vertion = VD_INTERFACE_PLAYBACK_MINOR; + + interface->plug = interface_play_plug; + interface->unplug = interface_play_unplug; + + driver.play_interface = interface; + add_interface(&interface->base); +} + +static VDObjectRef interface_record_plug(RecordInterface *recorder, RecordPlug* plug, + int *enabled) +{ + ASSERT(!driver.record_plug && plug && recorder == driver.record_interface + && enabled); + driver.record_plug = plug; + *enabled = driver.voic_in ? driver.voic_in->enabled : 0; + return (VDObjectRef)plug; +} + +static void interface_record_unplug(RecordInterface *recorder, VDObjectRef obj) +{ + ASSERT(driver.record_plug && recorder == driver.record_interface); + driver.record_plug = NULL; + driver.record_avail = 0; + driver.record_now = NULL; +} + +static void regitser_record(void) +{ + RecordInterface *interface = (RecordInterface *)qemu_mallocz(sizeof(*interface)); + static int record_interface_id = 0; + + if (!interface) { + printf("%s: malloc failed\n", __FUNCTION__); + exit(-1); + } + interface->base.base_vertion = VM_INTERFACE_VERTION; + interface->base.type = VD_INTERFACE_RECORD; + interface->base.id = record_interface_id++; + interface->base.description = "record"; + interface->base.major_vertion = VD_INTERFACE_RECORD_MAJOR; + interface->base.minor_vertion = VD_INTERFACE_RECORD_MINOR; + + interface->plug = interface_record_plug; + interface->unplug = interface_record_unplug; + + driver.record_interface = interface; + add_interface(&interface->base); +} + +static void *interface_audio_init(void) +{ + dprintf(""); + if (VD_INTERFACE_PLAYBACK_FMT != VD_INTERFACE_AUDIO_FMT_S16 || + VD_INTERFACE_PLAYBACK_CHAN != 2 || + VD_INTERFACE_RECORD_FMT != VD_INTERFACE_AUDIO_FMT_S16 || + VD_INTERFACE_RECORD_CHAN != 2) { + dprintf("bad dormat"); + exit(-1); + } + + ASSERT(driver.play_interface == NULL && driver.record_interface == NULL); + memset(driver.silence, 0, sizeof(driver.silence)); + regitser_playback(); + regitser_record(); + return &driver; +} + +static void interface_audio_fini(void *opaque) +{ + dprintf(""); + + if (driver.play_interface) { + remove_interface(&driver.play_interface->base); + ASSERT(!driver.play_plug); + free(driver.play_interface); + driver.play_interface = NULL; + } + + if (driver.record_interface) { + remove_interface(&driver.record_interface->base); + ASSERT(!driver.record_plug); + free(driver.record_interface); + driver.record_interface = NULL; + } +} + +static const char *fmt_to_str(audfmt_e fmt) +{ + switch(fmt) { + case AUD_FMT_U8: + return "AUD_FMT_U8"; + case AUD_FMT_S8: + return "AUD_FMT_S8"; + case AUD_FMT_U16: + return "AUD_FMT_U16"; + case AUD_FMT_S16: + return "AUD_FMT_S16"; + case AUD_FMT_U32: + return "AUD_FMT_U32"; + case AUD_FMT_S32: + return "AUD_FMT_S32"; + default: + return "???"; + } +} + +static int line_out_init(HWVoiceOut *hw, struct audsettings *as) +{ + InterfaceVoiceOut *voice_out = (InterfaceVoiceOut *)hw; + struct audsettings settings; + + dprintf("freq %d channels %d format %s%s", + as->freq, + as->nchannels, + fmt_to_str(as->fmt), + as->endianness == AUDIO_HOST_ENDIANNESS ? " HOST_ENDIANNESS" : "" ); + + + settings.freq = VD_INTERFACE_PLAYBACK_FREQ; + settings.nchannels = VD_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; + driver.voic_out = voice_out; + return 0; +} + +static void line_out_fini(HWVoiceOut *hw) +{ + driver.voic_out = NULL; +} + +static uint64_t get_monotonic_time(void) +{ + struct timespec time_space; + clock_gettime(CLOCK_MONOTONIC, &time_space); + return time_space.tv_sec * 1000 * 1000 * 1000 + time_space.tv_nsec; +} + +static int line_out_run(HWVoiceOut *hw) +{ + InterfaceVoiceOut *voice_out = (InterfaceVoiceOut *)hw; + int rpos, live, decr; + int samples; + uint64_t now; + uint64_t ticks; + uint64_t bytes; + + if (!(live = audio_pcm_hw_get_live_out(hw))) { + return 0; + } + + now = get_monotonic_time(); + ticks = now - voice_out->prev_ticks; + bytes = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000); + + voice_out->prev_ticks = now; + decr = (bytes > INT_MAX) ? INT_MAX >> hw->info.shift : bytes >> 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 (driver.play_plug && !driver.play_frame) { + driver.play_plug->get_frame(driver.play_plug, + &driver.play_frame, + &driver.play_avail); + driver.play_now = driver.play_frame; + } + if (driver.play_avail) { + len = audio_MIN(len, driver.play_avail); + hw->clip(driver.play_now, hw->mix_buf + rpos, len); + if (!(driver.play_avail -= len)) { + driver.play_plug->put_frame(driver.play_plug, + driver.play_frame); + driver.play_frame = driver.play_now = NULL; + } else { + driver.play_now += len; + } + } + 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, ...) +{ + InterfaceVoiceOut *voice_out = (InterfaceVoiceOut *)hw; + + switch (cmd) { + case VOICE_ENABLE: + dprintf("VOICE_ENABLE"); + voice_out->prev_ticks = get_monotonic_time(); + if (driver.play_plug) { + driver.play_plug->start(driver.play_plug); + } + break; + case VOICE_DISABLE: + dprintf("VOICE_DISABLE"); + if (driver.play_plug) { + if (driver.play_frame) { + uint32_t *frame = driver.play_frame; + memset(driver.play_now, 0, driver.play_avail << 2); + driver.play_avail = 0; + driver.play_now = driver.play_frame = NULL; + driver.play_plug->put_frame(driver.play_plug, frame); + } + driver.play_plug->stop(driver.play_plug); + } + break; + } + return 0; +} + +static int line_in_init(HWVoiceIn *hw, struct audsettings *as) +{ + struct audsettings settings; + + dprintf(""); + + settings.freq = VD_INTERFACE_RECORD_FREQ; + settings.nchannels = VD_INTERFACE_RECORD_CHAN; + if (VD_INTERFACE_PLAYBACK_FMT != VD_INTERFACE_AUDIO_FMT_S16) { + dprintf("bad vd interface format"); + return -1; + } + settings.fmt = AUD_FMT_S16; + settings.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info(&hw->info, &settings); + hw->samples = LINE_IN_SAMPLES; + driver.voic_in = hw; + return 0; +} + +static void line_in_fini(HWVoiceIn *hw) +{ + driver.voic_in = NULL; +} + +static int line_in_run(HWVoiceIn *hw) +{ + InterfaceVoiceIn *voice_in = (InterfaceVoiceIn *)hw; + int live; + int dead; + int samples; + int ready; + int len[2]; + uint64_t now; + uint64_t ticks; + uint64_t bytes; + + + live = audio_pcm_hw_get_live_in(hw); + + if (!(dead = hw->samples - live)) { + return 0; + } + + now = get_monotonic_time(); + ticks = now - voice_in->prev_ticks; + voice_in->prev_ticks = now; + bytes = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000); + + if (driver.record_avail) { + ready = driver.record_avail; + } else if (driver.record_plug) { + driver.record_plug->advance(driver.record_plug, + &driver.record_now, + &driver.record_avail); + ready = driver.record_avail; + } else { + ready = INT_MAX; + } + samples = audio_MIN(ready, dead); + samples = audio_MIN(samples, bytes >> hw->info.shift); + + if (hw->wpos + samples > hw->samples) { + len[0] = hw->samples - hw->wpos; + len[1] = samples - len[0]; + } else { + len[0] = samples; + len[1] = 0; + } + + if (driver.record_avail) { + hw->conv(hw->conv_buf + hw->wpos, driver.record_now, len[0], + &nominal_volume); + if (len[1]) { + hw->conv(hw->conv_buf, driver.record_now + len[0], len[1], + &nominal_volume); + } + driver.record_now += samples; + driver.record_avail -= samples; + } else { + hw->conv(hw->conv_buf + hw->wpos, driver.silence, len[0], + &nominal_volume); + if (len[1]) { + hw->conv(hw->conv_buf, driver.silence, len[1], + &nominal_volume); + } + } + + hw->wpos = (hw->wpos + samples) % hw->samples; + + return 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, ...) +{ + InterfaceVoiceIn *voice_in = (InterfaceVoiceIn *)hw; + + switch (cmd) { + case VOICE_ENABLE: + voice_in->prev_ticks = get_monotonic_time(); + dprintf("VOICE_ENABLE"); + if (driver.record_plug) { + driver.record_plug->start(driver.record_plug); + } + break; + case VOICE_DISABLE: + dprintf("VOICE_DISABLE"); + if (driver.record_plug) { + driver.record_plug->stop(driver.record_plug); + } + break; + } + return 0; +} + + +static struct audio_pcm_ops audio_callbacks = { + line_out_init, + line_out_fini, + line_out_run, + line_out_write, + line_out_ctl, + + line_in_init, + line_in_fini, + line_in_run, + line_in_read, + line_in_ctl, +}; + +struct audio_driver interface_audio_driver = { + INIT_FIELD (name = ) "vd_interface", + INIT_FIELD (descr = ) "vd_interface audio driver", + INIT_FIELD (options = ) options, + INIT_FIELD (init = ) interface_audio_init, + INIT_FIELD (fini = ) interface_audio_fini, + INIT_FIELD (pcm_ops = ) &audio_callbacks, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (InterfaceVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (InterfaceVoiceIn), +}; + -- 1.6.1