From 0d4f4f70d219a2ccf3863b4f6bcae7cbea411f47 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Tue, 4 Apr 2017 17:00:58 +0200 Subject: [PATCH] vbox5: enable audio support Issue #2338 --- repos/ports/lib/mk/virtualbox5-common.inc | 2 + repos/ports/lib/mk/virtualbox5-drivers.mk | 4 +- repos/ports/run/vm_win7_64.vbox | 2 +- repos/ports/src/virtualbox/libc.cc | 15 +- repos/ports/src/virtualbox5/audio.cc | 538 ++++++++++++++++++++++ repos/ports/src/virtualbox5/drivers.cc | 4 +- 6 files changed, 547 insertions(+), 18 deletions(-) create mode 100644 repos/ports/src/virtualbox5/audio.cc diff --git a/repos/ports/lib/mk/virtualbox5-common.inc b/repos/ports/lib/mk/virtualbox5-common.inc index b48ad2fa47..0cc5861e19 100644 --- a/repos/ports/lib/mk/virtualbox5-common.inc +++ b/repos/ports/lib/mk/virtualbox5-common.inc @@ -55,6 +55,8 @@ VBOX_CC_OPT += -DVBOX_WITH_E1000 VBOX_CC_OPT += -DVBOX_WITH_AHCI VBOX_CC_OPT += -DVBOX_WITH_MULTI_CORE +VBOX_CC_OPT += -DVBOX_WITH_AUDIO_OSS + VIRTUALBOX_VERSION_MAJOR := $(shell cat $(VIRTUALBOX_DIR)/Config.kmk 2>/dev/null | grep "VBOX_VERSION_MAJOR = " | grep -v "'VBOX_VERSION_MAJOR" | sed "s/^.*= //") VIRTUALBOX_VERSION_MINOR := $(shell cat $(VIRTUALBOX_DIR)/Config.kmk 2>/dev/null | grep "VBOX_VERSION_MINOR = " | grep -v "'VBOX_VERSION_MINOR" | sed "s/^.*= //") VIRTUALBOX_VERSION_BUILD := $(shell cat $(VIRTUALBOX_DIR)/Config.kmk 2>/dev/null | grep "VBOX_VERSION_BUILD = " | grep -v "'VBOX_VERSION_BUILD" | sed "s/^.*= //") diff --git a/repos/ports/lib/mk/virtualbox5-drivers.mk b/repos/ports/lib/mk/virtualbox5-drivers.mk index fa6526727d..90365b8211 100644 --- a/repos/ports/lib/mk/virtualbox5-drivers.mk +++ b/repos/ports/lib/mk/virtualbox5-drivers.mk @@ -17,10 +17,10 @@ SRC_CC += Devices/Serial/DrvHostSerial.cpp SRC_CC += Devices/Audio/DrvAudio.cpp SRC_CC += Devices/Audio/DrvHostNullAudio.cpp -#SRC_CC += audiodrv.cpp SRC_CC += network.cpp +SRC_CC += audio.cc INC_DIR += $(VBOX_DIR)/Devices/Audio -#vpath audiodrv.cpp $(REP_DIR)/src/virtualbox vpath network.cpp $(REP_DIR)/src/virtualbox +vpath audio.cc $(REP_DIR)/src/virtualbox5 diff --git a/repos/ports/run/vm_win7_64.vbox b/repos/ports/run/vm_win7_64.vbox index c4c59b663d..89780f3a4a 100644 --- a/repos/ports/run/vm_win7_64.vbox +++ b/repos/ports/run/vm_win7_64.vbox @@ -72,7 +72,7 @@ - + diff --git a/repos/ports/src/virtualbox/libc.cc b/repos/ports/src/virtualbox/libc.cc index 5feff63d46..fb4133d648 100644 --- a/repos/ports/src/virtualbox/libc.cc +++ b/repos/ports/src/virtualbox/libc.cc @@ -19,7 +19,6 @@ /* libc includes */ #include #include -#include #include /* statfs */ #include #include @@ -162,6 +161,9 @@ extern "C" char *getenv(const char *name) // "+shared_folders.e.l.f" // "+drv_host_serial.e.l.f" // "+dev_audio.e.l.f" +// "+dev_hda.e" +// "+drv_host_audio.e.l.f" +// "+drv_audio.e.l.f" ; if (Genode::strcmp(name, "VBOX_LOG_FLAGS") == 0 || @@ -185,17 +187,6 @@ extern "C" int sigaction(int signum, const struct sigaction *act, } -/* our libc provides a _nanosleep function */ -extern "C" int _nanosleep(const struct timespec *req, struct timespec *rem); -extern "C" int nanosleep(const struct timespec *req, struct timespec *rem) -{ - Assert(req); - - return _nanosleep(req, rem); -} - - - /* Some dummy implementation for LibC functions */ extern "C" pid_t getpid(void) diff --git a/repos/ports/src/virtualbox5/audio.cc b/repos/ports/src/virtualbox5/audio.cc new file mode 100644 index 0000000000..f40f66217a --- /dev/null +++ b/repos/ports/src/virtualbox5/audio.cc @@ -0,0 +1,538 @@ +/* + * \brief Genode audio driver backend + * \author Josef Soentgen + * \author Alexander Boettcher + * \date 2015-05-17 + */ + +/* + * Copyright (C) 2015-2017 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO + +/* Genode includes */ +#include +#include +#include + +#include + +/* VBox Genode specific */ +#include "vmm.h" + +/* VBox includes */ +#include "AudioMixBuffer.h" +#include "VBoxDD.h" /* for g_DrvHostOSSAudio */ +#include /* for PDMIBASE_2_PDMDRV */ + + +enum { + VBOX_CHANNELS = 2, + VBOX_SAMPLE_SIZE = sizeof(int16_t), + + OUT_PACKET_NUM = 16, /* number of buffered packets */ + + OUT_SAMPLES = Audio_out::PERIOD, + IN_SAMPLES = Audio_in::PERIOD, +}; + +static char const * const channel_names[] = { "front left", "front right" }; + +struct GenodeVoiceOut +{ + PDMAUDIOHSTSTRMOUT pStreamOut; + Genode::Constructible audio[VBOX_CHANNELS]; + Audio_out::Packet *packet { nullptr }; + uint32_t sample_pos { 0U }; +}; + +struct GenodeVoiceIn +{ + PDMAUDIOHSTSTRMIN pStreamIn; + Genode::Constructible audio; + Audio_in::Packet *packet { nullptr }; + uint32_t sample_pos { 0U }; +}; + +typedef struct DRVHOSTOSSAUDIO +{ + PPDMDRVINS pDrvIns; + PDMIHOSTAUDIO IHostAudio; +} DRVHOSTOSSAUDIO, *PDRVHOSTOSSAUDIO; + +static uint32_t read_samples(GenodeVoiceIn *in, + PPDMAUDIOMIXBUF const mixer_buf) +{ + Audio_in::Stream &stream = *in->audio->stream(); + Audio_in::Packet *p = in->packet; + + /* reset if packet became invalid */ + if (p && !p->valid()) { + in->packet = p = nullptr; + in->sample_pos = 0; + } + + uint32_t samples = RT_MIN(AudioMixBufFree(mixer_buf), + IN_SAMPLES - in->sample_pos); + uint32_t read_samples = 0; + + while (samples) { + if (!p || !p->valid()) { + /* get new packet if current is invalid */ + p = stream.get(stream.pos()); + if (!p || !p->valid()) + return read_samples; + } + + /* convert samples */ + uint32_t const bytes = AUDIOMIXBUF_S2B(mixer_buf, samples); + int16_t buf[bytes / sizeof(int16_t)]; + for (unsigned i = 0; i < samples; i++) { + int16_t const v = p->content()[in->sample_pos + i] * 32767; + buf[(i * 2) + 0] = v; + buf[(i * 2) + 1] = v; + } + + /* transfer samples to mixer */ + uint32_t samples_written = 0; + int rc = AudioMixBufWriteCirc(mixer_buf, buf, sizeof(buf), + &samples_written); + if (RT_FAILURE(rc)) + Genode::error("AudioMixBufWriteCirc failed rc=%d", rc); + + Assert(samples >= samples_written); + read_samples += samples_written; + + /* stop processing when mixer does not take all of our samples */ + if (samples_written != samples) { + Assert (in->sample_pos + samples_written < IN_SAMPLES); + + /* remember packet for later processing */ + in->packet = p; + in->sample_pos += samples_written; + + return read_samples; + } + + /* reset an previously only partial processed packet */ + if (p == in->packet) { + in->packet = nullptr; + in->sample_pos = 0; + + samples = RT_MIN(AudioMixBufFree(mixer_buf), + IN_SAMPLES - in->sample_pos); + } else + samples -= samples_written; + + /* current packet is completely processed */ + p->invalidate(); + p->mark_as_recorded(); + stream.increment_position(); + + /* next packet */ + p = stream.next(p); + } + + return read_samples; +} + +static uint32_t write_samples(GenodeVoiceOut *out, + PPDMAUDIOMIXBUF const mixer_buf) +{ + Audio_out::Connection * const c = &*out->audio[0]; + + /* samples in byte available for sending out */ + uint32_t samples = RT_MIN(AudioMixBufAvail(mixer_buf), + OUT_SAMPLES - out->sample_pos); + uint32_t written_samples = 0; + + while (samples) + { + if (c->stream()->queued() > OUT_PACKET_NUM) + return written_samples; + + if (out->packet == nullptr) { + + try { + out->packet = c->stream()->alloc(); + } catch (Audio_out::Stream::Alloc_failed) { + LogWarn(("stream alloc failed\n")); + return written_samples; + } + } + + /* assign the channels to audio streams */ + Audio_out::Packet *p[VBOX_CHANNELS] = { out->packet, nullptr }; + uint32_t const ppos = out->audio[0]->stream()->packet_position(p[0]); + p[1] = out->audio[1]->stream()->get(ppos); + + static_assert(VBOX_CHANNELS == 2, "Channel count does not match"); + + /* copy */ + float * const left = p[0]->content(); + float * const right = p[1]->content(); + + /* setup convert buffer */ + uint32_t const bytes = AUDIOMIXBUF_S2B(mixer_buf, samples); + int16_t buf[bytes / sizeof(int16_t)]; + + /* read mixer data in */ + uint32_t samples_processed = 0; + int rc = AudioMixBufReadCirc(mixer_buf, buf, bytes, &samples_processed); + + Assert(samples >= samples_processed); + written_samples += samples_processed; + + if (RT_FAILURE(rc)) + LogWarn(("AudioMixBufReadCirc failed rc=%Rrc\n", rc)); + + for (unsigned i = 0; i < samples_processed; i++, out->sample_pos++) { + left [out->sample_pos] = (float)(buf[i * VBOX_CHANNELS + 0]) + / 32768.0f; + right[out->sample_pos] = (float)(buf[i * VBOX_CHANNELS + 1]) + / 32768.0f; + } + + Assert(out->sample_pos <= OUT_SAMPLES); + + /* submit package if enough samples are in */ + if (out->sample_pos == OUT_SAMPLES) { + /* submit */ + for (unsigned i = 0; i < VBOX_CHANNELS; i++) + out->audio[i]->submit(p[i]); + + /* reset packet information */ + out->sample_pos = 0; + out->packet = nullptr; + } + + /* check for additional samples */ + samples = RT_MIN(AudioMixBufAvail(mixer_buf), + OUT_SAMPLES - out->sample_pos); + } + + return written_samples; +} + + +static DECLCALLBACK(int) drvHostOSSAudioControlIn(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMIN data, + PDMAUDIOSTREAMCMD cmd) +{ + LogFlowFuncEnter(); + + AssertPtrReturn(data, VERR_INVALID_POINTER); + GenodeVoiceIn * const in = reinterpret_cast(data); + + switch (cmd) { + case PDMAUDIOSTREAMCMD_ENABLE: + in->audio->start(); + return VINF_SUCCESS; + case PDMAUDIOSTREAMCMD_DISABLE: + in->audio->stop(); + return VINF_SUCCESS; + case PDMAUDIOSTREAMCMD_RESUME: + case PDMAUDIOSTREAMCMD_PAUSE: + default: + AssertMsgFailed(("Invalid command %ld\n", cmd)); + return VERR_INVALID_PARAMETER; + } +} + +static DECLCALLBACK(int) drvHostOSSAudioControlOut(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMOUT data, + PDMAUDIOSTREAMCMD cmd) +{ + LogFlowFuncEnter(); + + AssertPtrReturn(data, VERR_INVALID_POINTER); + + GenodeVoiceOut * const out = reinterpret_cast(data); + + switch (cmd) { + case PDMAUDIOSTREAMCMD_ENABLE: + for (unsigned i = 0; i < VBOX_CHANNELS; i++) + out->audio[i]->start(); + return VINF_SUCCESS; + + case PDMAUDIOSTREAMCMD_DISABLE: + /* fill up unfinished packets with empty samples and push to stream */ + if (out->packet) { + Audio_out::Packet *p[VBOX_CHANNELS] = { out->packet, nullptr }; + uint32_t const ppos = out->audio[0]->stream()->packet_position(out->packet); + p[1] = out->audio[1]->stream()->get(ppos); + + float * const left = p[0]->content(); + float * const right = p[1]->content(); + + memset(left , 0, (OUT_SAMPLES - out->sample_pos) + * Audio_out::SAMPLE_SIZE); + memset(right, 0, (OUT_SAMPLES - out->sample_pos) + * Audio_out::SAMPLE_SIZE); + + for (unsigned i = 0; i < VBOX_CHANNELS; i++) + out->audio[i]->submit(p[i]); + + /* reset packet information */ + out->sample_pos = 0; + out->packet = nullptr; + } + + /* stop further processing */ + for (unsigned i = 0; i < VBOX_CHANNELS; i++) { + out->audio[i]->stop(); + out->audio[i]->stream()->invalidate_all(); + } + return VINF_SUCCESS; + + case PDMAUDIOSTREAMCMD_RESUME: + case PDMAUDIOSTREAMCMD_PAUSE: + default: + AssertMsgFailed(("Invalid command %ld\n", cmd)); + return VERR_INVALID_PARAMETER; + } +} + +static DECLCALLBACK(int) drvHostOSSAudioInit(PPDMIHOSTAUDIO) +{ + LogFlowFuncEnter(); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioCaptureIn(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMIN data, + uint32_t * const samples) +{ + AssertPtrReturn(data, VERR_INVALID_POINTER); + GenodeVoiceIn * const in = reinterpret_cast(data); + + uint32_t const total_samples = read_samples(in, &data->MixBuf); + + if (total_samples) { + int rc = AudioMixBufMixToParent(&data->MixBuf, total_samples, nullptr); + if (RT_FAILURE(rc)) + LogWarn(("AudioMixBufMixToParent failed rc=%Rrc\n", rc)); + } + + if (samples) + *samples = total_samples; + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioFiniIn(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMIN data) +{ + LogFlowFuncEnter(); + AssertPtrReturn(data, VERR_INVALID_POINTER); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioFiniOut(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMOUT data) +{ + LogFlowFuncEnter(); + AssertPtrReturn(data, VERR_INVALID_POINTER); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioGetConf(PPDMIHOSTAUDIO, + PPDMAUDIOBACKENDCFG cfg) +{ + cfg->cbStreamOut = sizeof(GenodeVoiceOut); + cfg->cbStreamIn = sizeof(GenodeVoiceIn); + cfg->cMaxHstStrmsOut = 1; + cfg->cMaxHstStrmsIn = 1; + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioInitIn(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMIN const data, + PPDMAUDIOSTREAMCFG const cfg, + PPDMAUDIOSTREAMCFG, + PDMAUDIORECSOURCE, + uint32_t *samples) +{ + LogFlowFuncEnter(); + + AssertPtrReturn(data, VERR_INVALID_POINTER); + AssertPtrReturn(cfg, VERR_INVALID_POINTER); + + GenodeVoiceIn * in = reinterpret_cast(data); + + try { + in->audio.construct(genode_env(), "left"); + } catch (...) { + Genode::error("could not establish Audio_in connection"); + return VERR_GENERAL_FAILURE; + } + + if (samples) + *samples = IN_SAMPLES; + + Genode::log("--- using Audio_in session ---"); + Genode::log("freq: ", cfg->uHz); + Genode::log("channels: ", cfg->cChannels); + Genode::log("format: ", (int)cfg->enmFormat); + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) drvHostOSSAudioInitOut(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMOUT const data, + PPDMAUDIOSTREAMCFG const cfg, + PPDMAUDIOSTREAMCFG, + uint32_t *samples) +{ + LogFlowFuncEnter(); + + AssertPtrReturn(data, VERR_INVALID_POINTER); + AssertPtrReturn(cfg, VERR_INVALID_POINTER); + + GenodeVoiceOut * out = reinterpret_cast(data); + + if (cfg->cChannels != VBOX_CHANNELS) { + Genode::error("only ", (int)VBOX_CHANNELS, " channels supported ", + "( ", cfg->cChannels, " were requested)"); + return VERR_GENERAL_FAILURE; + } + + if (cfg->uHz != Audio_out::SAMPLE_RATE) { + Genode::error("only ", (int)Audio_out::SAMPLE_RATE, + " frequency supported (", cfg->uHz, " was requested)"); + return VERR_GENERAL_FAILURE; + } + + for (int i = 0; i < VBOX_CHANNELS; i++) { + try { + out->audio[i].construct(genode_env(), channel_names[i]); + } catch (...) { + Genode::error("could not establish Audio_out connection"); + while (i > 0) { + out->audio[--i].destruct(); + } + return VERR_GENERAL_FAILURE; + } + } + + if (samples) + *samples = OUT_SAMPLES; + + Genode::log("--- using Audio_out session ---"); + Genode::log("freq: ", cfg->uHz); + Genode::log("channels: ", cfg->cChannels); + Genode::log("format: ", (int)cfg->enmFormat); + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +static DECLCALLBACK(bool) drvHostOSSAudioIsEnabled(PPDMIHOSTAUDIO, PDMAUDIODIR) +{ + return true; +} + +static DECLCALLBACK(int) drvHostOSSAudioPlayOut(PPDMIHOSTAUDIO, + PPDMAUDIOHSTSTRMOUT const data, + uint32_t *samples) +{ + AssertPtrReturn(data, VERR_INVALID_POINTER); + GenodeVoiceOut * const out = reinterpret_cast(data); + + uint32_t const total_samples = write_samples(out, &data->MixBuf); + if (total_samples) + AudioMixBufFinish(&data->MixBuf, total_samples); + + if (samples) + *samples = total_samples; + + return VINF_SUCCESS; +} + +static DECLCALLBACK(void) drvHostOSSAudioShutdown(PPDMIHOSTAUDIO) +{ + LogFlowFuncEnter(); +} + +static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, + const char *pszIID) +{ + LogFlowFuncEnter(); + + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + +static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, + PCFGMNODE, uint32_t) +{ + PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); + LogFlowFuncEnter(); + + pThis->pDrvIns = pDrvIns; + pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface; + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostOSSAudio); + + return VINF_SUCCESS; +} + + +const PDMDRVREG g_DrvHostOSSAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "OSSAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "OSS audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTOSSAUDIO), + /* pfnConstruct */ + drvHostOSSAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; diff --git a/repos/ports/src/virtualbox5/drivers.cc b/repos/ports/src/virtualbox5/drivers.cc index 610ba02b29..768dc51021 100644 --- a/repos/ports/src/virtualbox5/drivers.cc +++ b/repos/ports/src/virtualbox5/drivers.cc @@ -20,17 +20,15 @@ extern "C" int VBoxDriversRegister(PCPDMDRVREGCB pCallbacks, uint32_t u32Version PDMDRVREG const *drvs[] = { &g_DrvKeyboardQueue, &g_DrvMouseQueue, -// &g_DrvBlock, -// &g_DrvMediaISO, &g_DrvACPI, &g_DrvChar, -// &g_DrvRawImage, &g_DrvRawFile, &g_DrvHostSerial, &g_DrvVD, &g_DrvHostInterface, &g_DrvVUSBRootHub, &g_DrvAUDIO, + &g_DrvHostOSSAudio, &g_DrvHostNullAudio, 0 };