From 5be519164576f884515fdc6244dae456e73bc114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20S=C3=B6ntgen?= Date: Wed, 3 Jun 2015 14:39:01 +0200 Subject: [PATCH] vbox: enable preliminary audio support With this commit preliminary audio support in VirtualBox is enabled. The backend supports playback as well as recording sounds from within a guest VM. Depending on how the guest configures the device model the audio output may sound disorted. If the guest uses buffers that are too small, i.e., 10 ms or less, frequent buffer underruns will occure. To get this low-latency one has also to increase vbox' update hz to 200 (i.e., 5ms). Fixes #1647. --- repos/ports/lib/mk/virtualbox-devices.mk | 8 + repos/ports/lib/mk/virtualbox-drivers.mk | 6 +- repos/ports/ports/virtualbox.hash | 2 +- repos/ports/run/vbox_win.inc | 20 + repos/ports/src/virtualbox/audiodrv.cpp | 427 ++++++++++++++++++ repos/ports/src/virtualbox/devices.cc | 2 + repos/ports/src/virtualbox/drivers.cc | 1 + repos/ports/src/virtualbox/libc.cc | 1 + .../src/virtualbox/patches/vbox_main.patch | 4 +- 9 files changed, 466 insertions(+), 5 deletions(-) create mode 100644 repos/ports/src/virtualbox/audiodrv.cpp diff --git a/repos/ports/lib/mk/virtualbox-devices.mk b/repos/ports/lib/mk/virtualbox-devices.mk index 97e87dd71..03126fb71 100644 --- a/repos/ports/lib/mk/virtualbox-devices.mk +++ b/repos/ports/lib/mk/virtualbox-devices.mk @@ -34,6 +34,14 @@ SRC_CC += GuestHost/HGSMI/HGSMICommon.cpp SRC_CC += Devices/Serial/DevSerial.cpp SRC_CC += Devices/PC/DevIoApic.cpp +SRC_C += Devices/Audio/audio.c +SRC_C += Devices/Audio/audiosniffer.c +SRC_C += Devices/Audio/filteraudio.c +SRC_C += Devices/Audio/mixeng.c +SRC_C += Devices/Audio/noaudio.c +SRC_CC += Devices/Audio/DevIchAc97.cpp +SRC_CC += Devices/Audio/DevIchHda.cpp +SRC_CC += Devices/Audio/DevIchHdaCodec.cpp SRC_CC += Devices/USB/DevOHCI.cpp SRC_CC += Devices/USB/USBProxyDevice.cpp SRC_CC += Devices/USB/VUSBDevice.cpp diff --git a/repos/ports/lib/mk/virtualbox-drivers.mk b/repos/ports/lib/mk/virtualbox-drivers.mk index 8a81deb6c..739764728 100644 --- a/repos/ports/lib/mk/virtualbox-drivers.mk +++ b/repos/ports/lib/mk/virtualbox-drivers.mk @@ -12,6 +12,10 @@ SRC_CC += Devices/Serial/DrvChar.cpp SRC_CC += Devices/Serial/DrvRawFile.cpp SRC_CC += Devices/Serial/DrvHostSerial.cpp +SRC_CC += audiodrv.cpp SRC_CC += network.cpp -vpath network.cpp $(REP_DIR)/src/virtualbox +INC_DIR += $(VBOX_DIR)/Devices/Audio + +vpath audiodrv.cpp $(REP_DIR)/src/virtualbox +vpath network.cpp $(REP_DIR)/src/virtualbox diff --git a/repos/ports/ports/virtualbox.hash b/repos/ports/ports/virtualbox.hash index 12a633dff..96cca8a61 100644 --- a/repos/ports/ports/virtualbox.hash +++ b/repos/ports/ports/virtualbox.hash @@ -1 +1 @@ -59e99301300f881be06c12cd50fbe8b1164e1845 +d5121c2c57249fd1a5438b583db976d0db85c547 diff --git a/repos/ports/run/vbox_win.inc b/repos/ports/run/vbox_win.inc index a70cc2a51..479ffec60 100644 --- a/repos/ports/run/vbox_win.inc +++ b/repos/ports/run/vbox_win.inc @@ -13,6 +13,7 @@ set overlay_image "overlay_${flavor}.vdi" set build_components { server/input_merger drivers/nic + drivers/audio server/nitpicker app/vbox_pointer server/nit_fb @@ -22,6 +23,7 @@ set build_components { set boot_modules { input_merger nic_drv + audio_drv nitpicker vbox_pointer nit_fb @@ -67,6 +69,24 @@ append config_of_app { + + + + + + + + + + + + + + diff --git a/repos/ports/src/virtualbox/audiodrv.cpp b/repos/ports/src/virtualbox/audiodrv.cpp new file mode 100644 index 000000000..38025863d --- /dev/null +++ b/repos/ports/src/virtualbox/audiodrv.cpp @@ -0,0 +1,427 @@ +/* + * \brief Genode audio driver backend + * \author Josef Soentgen + * \date 2015-05-17 + * + * TODO: + * - avoid excessive copying by mixing hw.mix_buf directly to + * Audio_out packet + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include + +/* VirtualBox includes */ +#include "VBoxDD.h" +#include "vl_vbox.h" +extern "C" { +#include "audio.h" +} +#include + +#define AUDIO_CAP "genode" +extern "C" { +#include "audio_int.h" +} + + +static bool const verbose = false; +#define PLOGV(...) if (verbose) PLOG(__VA_ARGS__); + + +enum { + AUDIO_CHANNELS = 2, + AUDIO_SAMPLE_SIZE = sizeof(short), + AUDIO_FREQ = 44100, + SIZE_PER_FRAME = AUDIO_CHANNELS * AUDIO_SAMPLE_SIZE, +}; +static const char *channel_names[] = { "front left", "front right" }; + + +struct GenodeVoiceOut +{ + HWVoiceOut hw; + Audio_out::Connection *audio[AUDIO_CHANNELS]; + Audio_out::Packet *packet[AUDIO_CHANNELS]; + uint8_t *packet_buf; + uint8_t *pcm_buf; + int wpos; + unsigned packets; + int last_submitted; +}; + + +struct GenodeVoiceIn { + HWVoiceIn hw; + Audio_in::Connection *audio; + void *pcm_buf; + uint8_t *packet_buf; + int wpos; + int rpos; + unsigned packets; +}; + + +static inline void copy_samples(void *dst, void const *src, int samples) { + Genode::memcpy(dst, src, samples * SIZE_PER_FRAME); } + + +static int write_samples(GenodeVoiceOut *out, int16_t *src, int samples) +{ + int16_t *buf = (int16_t*)out->packet_buf; + + /* fill and submit packet */ + if (out->wpos >= Audio_out::PERIOD) { + /* alloc new packet */ + while (true) { + Audio_out::Connection *c = out->audio[0]; + try { + out->packet[0] = c->stream()->alloc(); + break; + } catch (Audio_out::Stream::Alloc_failed) { + c->wait_for_alloc(); + } + } + + unsigned const pos = out->audio[0]->stream()->packet_position(out->packet[0]); + out->packet[1] = out->audio[1]->stream()->get(pos); + + /* copy */ + float *left_content = out->packet[0]->content(); + float *right_content = out->packet[1]->content(); + + for (int i = 0; i < Audio_out::PERIOD; i++) { + left_content[i] = (float)(buf[i * AUDIO_CHANNELS + 0]) / 32768.0f; + right_content[i] = (float)(buf[i * AUDIO_CHANNELS + 1]) / 32768.0f; + } + + /* submit */ + for (int i = 0; i < AUDIO_CHANNELS; i++) { + out->audio[i]->submit(out->packet[i]); + out->packet[i] = nullptr; + } + out->last_submitted = pos; + + /* move remaining samples if any to front of buffer */ + int const remaining_samples = out->wpos - Audio_out::PERIOD; + if (remaining_samples) { + copy_samples(buf, buf + (2*Audio_out::PERIOD), remaining_samples); + } + out->wpos = remaining_samples; + out->packets++; + } + + /* copy new samples */ + copy_samples(buf + (2*out->wpos), src, samples); + out->wpos += samples; + + return samples; +} + + +static int genode_run_out(HWVoiceOut *hw) +{ + GenodeVoiceOut *out = (GenodeVoiceOut *)hw; + + int const live = audio_pcm_hw_get_live_out(&out->hw); + if (!live) + return 0; + + int const samples = audio_MIN(live, out->hw.samples); + int const rpos = out->hw.rpos; + + out->hw.clip(out->pcm_buf, (st_sample_t*)(out->hw.mix_buf + rpos), samples); + write_samples(out, (int16_t*)out->pcm_buf, samples); + out->hw.rpos = (rpos + samples) % out->hw.samples; + + return samples; +} + + +static int genode_write(SWVoiceOut *sw, void *buf, int len) { + return audio_pcm_sw_write(sw, buf, len); } + + +static int genode_init_out(HWVoiceOut *hw, audsettings_t *as) +{ + GenodeVoiceOut *out = (GenodeVoiceOut *)hw; + + PLOG("--- using Audio_out session ---"); + PLOGV("freq: %d", as->freq); + PLOGV("channels: %d", as->nchannels); + PLOGV("format: %d", as->fmt); + PLOGV("endianness: %d", as->endianness); + + if (as->nchannels != AUDIO_CHANNELS) { + PERR("only %d channels supported (%d were requested)", + AUDIO_CHANNELS, as->nchannels); + return -1; + } + + if (as->freq != AUDIO_FREQ) { + PERR("only %d frequency supported (%d were requested)", + AUDIO_FREQ, as->freq); + return -1; + } + + for (int i = 0; i < AUDIO_CHANNELS; i++) { + try { + out->audio[i] = new (Genode::env()->heap()) + Audio_out::Connection(channel_names[i]); + } catch (...) { + PERR("could not establish Audio_out connection"); + while (--i > 0) + Genode::destroy(Genode::env()->heap(), out->audio[i]); + return -1; + } + + out->packet[i] = nullptr; + } + + out->wpos = 0; + out->last_submitted = 0; + out->packets = 0; + + audio_pcm_init_info(&out->hw.info, as); + out->hw.samples = Audio_out::PERIOD; + + size_t const bytes = (out->hw.samples << out->hw.info.shift); + out->pcm_buf = (uint8_t*)RTMemAllocZ(bytes); + out->packet_buf = (uint8_t*)RTMemAllocZ(2 * bytes); + + return 0; +} + + +static void genode_fini_out(HWVoiceOut *hw) +{ + GenodeVoiceOut *out = (GenodeVoiceOut *)hw; + + for (int i = 0; i < AUDIO_CHANNELS; i++) + Genode::destroy(Genode::env()->heap(), out->audio[i]); + + RTMemFree((void*)out->pcm_buf); + RTMemFree((void*)out->packet_buf); +} + + +static int genode_ctl_out(HWVoiceOut *hw, int cmd, ...) +{ + GenodeVoiceOut *out = (GenodeVoiceOut*)hw; + switch (cmd) { + case VOICE_ENABLE: + { + PLOGV("enable playback"); + out->packets = 0; + + for (int i = 0; i < AUDIO_CHANNELS; i++) { + out->audio[i]->stream()->reset(); + out->audio[i]->start(); + } + + break; + } + case VOICE_DISABLE: + { + PLOGV("disable playback (packets played: %u)", out->packets); + + for (int i = 0; i < AUDIO_CHANNELS; i++) { + out->audio[i]->stop(); + out->audio[i]->stream()->invalidate_all(); + out->packet[i] = nullptr; + } + out->wpos = 0; + + break; + } + } + + return 0; +} + + +static int genode_init_in(HWVoiceIn *hw, audsettings_t *as) +{ + GenodeVoiceIn *in = (GenodeVoiceIn*)hw; + + PLOG("--- using Audio_in session ---"); + PLOGV("freq: %d", as->freq); + PLOGV("channels: %d", as->nchannels); + PLOGV("format: %d", as->fmt); + PLOGV("endianness: %d", as->endianness); + + try { + in->audio = new (Genode::env()->heap()) Audio_in::Connection("left"); + } catch (...) { + PERR("could not establish Audio_in connection"); + return -1; + } + + audio_pcm_init_info(&in->hw.info, as); + in->hw.samples = Audio_in::PERIOD; + in->pcm_buf = RTMemAllocZ(in->hw.samples << in->hw.info.shift); + in->packet_buf = (uint8_t*)RTMemAllocZ(in->hw.samples << in->hw.info.shift); + + in->rpos = 0; + in->wpos = 0; + in->packets = 0; + + return 0; +} + + +static void genode_fini_in(HWVoiceIn *hw) +{ + GenodeVoiceIn *in = (GenodeVoiceIn*)hw; + + Genode::destroy(Genode::env()->heap(), in->audio); + RTMemFree(in->pcm_buf); + RTMemFree(in->packet_buf); +} + + +static int read_samples(GenodeVoiceIn *in, int16_t *dst, int samples) +{ + enum { PACKET_BUF_LEN = 2 * Audio_in::PERIOD }; + + /* + * Acquire next Audio_in packet first and copy the content into the + * packet buffer. + */ + Audio_in::Stream &stream = *in->audio->stream(); + Audio_in::Packet *p = stream.get(stream.pos()); + + if (!p->valid()) + return 0; + + if ((in->wpos + (Audio_in::PERIOD)) > PACKET_BUF_LEN) + in->wpos = 0; + + int16_t *buf = (int16_t*)in->packet_buf + (in->wpos * sizeof(int16_t)); + float *content = p->content(); + for (int i = 0; i < PACKET_BUF_LEN; i++) { + int16_t const v = content[i/2] * 32767; + buf[i] = v; + buf[i+1] = v; + } + + p->invalidate(); + p->mark_as_recorded(); + stream.increment_position(); + + in->packets++; + in->wpos += Audio_in::PERIOD; + + /* + * Copy number of given samples from packet buffer to dst buffer. + */ + int remaining_samples = samples; + if ((in->rpos + remaining_samples) > PACKET_BUF_LEN) { + int n = PACKET_BUF_LEN - in->rpos; + copy_samples(dst, buf + in->rpos, n); + remaining_samples -= n; + dst += (sizeof(int16_t) * n); /* advance in bytes */ + in->rpos = 0; + } + + copy_samples(dst, buf + in->rpos, remaining_samples); + in->rpos = (in->rpos + remaining_samples) % PACKET_BUF_LEN; + + return samples; +} + + +static int genode_run_in(HWVoiceIn *hw) +{ + GenodeVoiceIn *in = (GenodeVoiceIn*)hw; + + int const live = audio_pcm_hw_get_live_in(&in->hw); + if (!(in->hw.samples - live)) + return 0; + + int const dead = in->hw.samples - live; + int const samples = read_samples(in, (int16_t*)in->pcm_buf, dead); + + in->hw.conv(in->hw.conv_buf, in->pcm_buf, samples, &pcm_in_volume); + in->hw.wpos = (in->hw.wpos + samples) % in->hw.samples; + return samples; +} + + +static int genode_read(SWVoiceIn *sw, void *buf, int size) { + return audio_pcm_sw_read(sw, buf, size); } + + +static int genode_ctl_in(HWVoiceIn *hw, int cmd, ...) +{ + GenodeVoiceIn *in = (GenodeVoiceIn*)hw; + switch (cmd) { + case VOICE_ENABLE: + { + PLOGV("enable recording"); + in->audio->start(); + break; + } + case VOICE_DISABLE: + { + PLOGV("disable recording"); + in->audio->stop(); + break; + } + } + + return 0; +} + + +static void *genode_audio_init(void) { + return &oss_audio_driver; } + + +static void genode_audio_fini(void *) { } + + +static struct audio_pcm_ops genode_pcm_ops = { + genode_init_out, + genode_fini_out, + genode_run_out, + genode_write, + genode_ctl_out, + + genode_init_in, + genode_fini_in, + genode_run_in, + genode_read, + genode_ctl_in +}; + + +/* + * We claim to be the OSS driver so that we do not have to + * patch the VirtualBox source because we already claim to + * be FreeBSD. + */ +struct audio_driver oss_audio_driver = { + INIT_FIELD (name = ) "oss", + INIT_FIELD (descr = ) "Genode Audio_out/Audio_in", + INIT_FIELD (options = ) NULL, + INIT_FIELD (init = ) genode_audio_init, + INIT_FIELD (fini = ) genode_audio_fini, + INIT_FIELD (pcm_ops = ) &genode_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) INT_MAX, + INIT_FIELD (voice_size_out = ) sizeof(GenodeVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof(GenodeVoiceIn) +}; diff --git a/repos/ports/src/virtualbox/devices.cc b/repos/ports/src/virtualbox/devices.cc index de6dba8b5..9a9083825 100644 --- a/repos/ports/src/virtualbox/devices.cc +++ b/repos/ports/src/virtualbox/devices.cc @@ -54,6 +54,8 @@ extern "C" int VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version) REGISTER(DeviceE1000); REGISTER(DeviceVMMDev); REGISTER(DeviceOHCI); + REGISTER(DeviceICHAC97); + REGISTER(DeviceICH6_HDA); return VINF_SUCCESS; } diff --git a/repos/ports/src/virtualbox/drivers.cc b/repos/ports/src/virtualbox/drivers.cc index eb65a3279..07acef184 100644 --- a/repos/ports/src/virtualbox/drivers.cc +++ b/repos/ports/src/virtualbox/drivers.cc @@ -33,6 +33,7 @@ extern "C" int VBoxDriversRegister(PCPDMDRVREGCB pCallbacks, uint32_t u32Version &g_DrvVD, &g_DrvHostInterface, &g_DrvVUSBRootHub, + &g_DrvAUDIO, 0 }; diff --git a/repos/ports/src/virtualbox/libc.cc b/repos/ports/src/virtualbox/libc.cc index bfb337e89..f44a38882 100644 --- a/repos/ports/src/virtualbox/libc.cc +++ b/repos/ports/src/virtualbox/libc.cc @@ -121,6 +121,7 @@ extern "C" char *getenv(const char *name) // "+hgcm.e.l.f" // "+shared_folders.e.l.f" // "+drv_host_serial.e.l.f" +// "+dev_audio.e.l.f" ; if (Genode::strcmp(name, "VBOX_LOG_FLAGS") == 0 || diff --git a/repos/ports/src/virtualbox/patches/vbox_main.patch b/repos/ports/src/virtualbox/patches/vbox_main.patch index a7bcc394c..6df439977 100644 --- a/repos/ports/src/virtualbox/patches/vbox_main.patch +++ b/repos/ports/src/virtualbox/patches/vbox_main.patch @@ -1044,16 +1044,14 @@ index caed4be..6d02496 100644 InsertConfigNode(pDevices, "AudioSniffer", &pDev); InsertConfigNode(pDev, "0", &pInst); InsertConfigNode(pInst, "Config", &pCfg); -@@ -2300,9 +2311,12 @@ int Console::configConstructorInner(PUVM pUVM, PVM pVM, AutoWriteLock *pAlock) +@@ -2300,9 +2311,10 @@ int Console::configConstructorInner(PUVM pUVM, PVM pVM, AutoWriteLock *pAlock) /* * AC'97 ICH / SoundBlaster16 audio / Intel HD Audio */ +#endif BOOL fAudioEnabled = FALSE; ComPtr audioAdapter; -+#if 0 hrc = pMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); H(); -+#endif if (audioAdapter) hrc = audioAdapter->COMGETTER(Enabled)(&fAudioEnabled); H();