summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Audio/DrvHostOSSAudio.cpp')
-rw-r--r--src/VBox/Devices/Audio/DrvHostOSSAudio.cpp1169
1 files changed, 1169 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp
new file mode 100644
index 00000000..4b1d7905
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp
@@ -0,0 +1,1169 @@
+/* $Id: DrvHostOSSAudio.cpp $ */
+/** @file
+ * OSS (Open Sound System) host audio backend.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/soundcard.h>
+#include <unistd.h>
+
+#include <iprt/alloc.h>
+#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+
+#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO)))
+/* OSS > 3.6 has a new syscall available for querying a bit more detailed information
+ * about OSS' audio capabilities. This is handy for e.g. Solaris. */
+# define VBOX_WITH_AUDIO_OSS_SYSINFO 1
+#endif
+
+/** Makes DRVHOSTOSSAUDIO out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTOSSAUDIO(pInterface) \
+ ( (PDRVHOSTOSSAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTOSSAUDIO, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+
+/**
+ * OSS host audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTOSSAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Error count for not flooding the release log.
+ * UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+} DRVHOSTOSSAUDIO, *PDRVHOSTOSSAUDIO;
+
+typedef struct OSSAUDIOSTREAMCFG
+{
+ PDMAUDIOPCMPROPS Props;
+ uint16_t cFragments;
+ uint32_t cbFragmentSize;
+} OSSAUDIOSTREAMCFG, *POSSAUDIOSTREAMCFG;
+
+typedef struct OSSAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Buffer alignment. */
+ uint8_t uAlign;
+ union
+ {
+ struct
+ {
+
+ } In;
+ struct
+ {
+#ifndef RT_OS_L4
+ /** Whether we use a memory mapped file instead of our
+ * own allocated PCM buffer below. */
+ /** @todo The memory mapped code seems to be utterly broken.
+ * Needs investigation! */
+ bool fMMIO;
+#endif
+ } Out;
+ };
+ int hFile;
+ int cFragments;
+ int cbFragmentSize;
+ /** Own PCM buffer. */
+ void *pvBuf;
+ /** Size (in bytes) of own PCM buffer. */
+ size_t cbBuf;
+ int old_optr;
+} OSSAUDIOSTREAM, *POSSAUDIOSTREAM;
+
+typedef struct OSSAUDIOCFG
+{
+#ifndef RT_OS_L4
+ bool try_mmap;
+#endif
+ int nfrags;
+ int fragsize;
+ const char *devpath_out;
+ const char *devpath_in;
+ int debug;
+} OSSAUDIOCFG, *POSSAUDIOCFG;
+
+static OSSAUDIOCFG s_OSSConf =
+{
+#ifndef RT_OS_L4
+ false,
+#endif
+ 4,
+ 4096,
+ "/dev/dsp",
+ "/dev/dsp",
+ 0
+};
+
+
+/* http://www.df.lth.se/~john_e/gems/gem002d.html */
+static uint32_t popcount(uint32_t u)
+{
+ u = ((u&0x55555555) + ((u>>1)&0x55555555));
+ u = ((u&0x33333333) + ((u>>2)&0x33333333));
+ u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f));
+ u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff));
+ u = ( u&0x0000ffff) + (u>>16);
+ return u;
+}
+
+
+static uint32_t lsbindex(uint32_t u)
+{
+ return popcount ((u&-u)-1);
+}
+
+
+static int ossOSSToAudioProps(int fmt, PPDMAUDIOPCMPROPS pProps)
+{
+ RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS));
+
+ switch (fmt)
+ {
+ case AFMT_S8:
+ pProps->cBytes = 1;
+ pProps->fSigned = true;
+ break;
+
+ case AFMT_U8:
+ pProps->cBytes = 1;
+ pProps->fSigned = false;
+ break;
+
+ case AFMT_S16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ break;
+
+ case AFMT_U16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+ break;
+
+ case AFMT_S16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ case AFMT_U16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Format %ld not supported\n", fmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossStreamClose(int *phFile)
+{
+ if (!phFile || !*phFile || *phFile == -1)
+ return VINF_SUCCESS;
+
+ int rc;
+ if (close(*phFile))
+ {
+ LogRel(("OSS: Closing stream failed: %s\n", strerror(errno)));
+ rc = VERR_GENERAL_FAILURE; /** @todo */
+ }
+ else
+ {
+ *phFile = -1;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+static int ossStreamOpen(const char *pszDev, int fOpen, POSSAUDIOSTREAMCFG pOSSReq, POSSAUDIOSTREAMCFG pOSSAcq, int *phFile)
+{
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ int hFile = -1;
+ do
+ {
+ hFile = open(pszDev, fOpen);
+ if (hFile == -1)
+ {
+ LogRel(("OSS: Failed to open %s: %s (%d)\n", pszDev, strerror(errno), errno));
+ break;
+ }
+
+ int iFormat;
+ switch (pOSSReq->Props.cBytes)
+ {
+ case 1:
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8;
+ break;
+
+ case 2:
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE;
+ break;
+
+ default:
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat))
+ {
+ LogRel(("OSS: Failed to set audio format to %ld: %s (%d)\n", iFormat, strerror(errno), errno));
+ break;
+ }
+
+ int cChannels = pOSSReq->Props.cChannels;
+ if (ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels))
+ {
+ LogRel(("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n",
+ pOSSReq->Props.cChannels, strerror(errno), errno));
+ break;
+ }
+
+ int freq = pOSSReq->Props.uHz;
+ if (ioctl(hFile, SNDCTL_DSP_SPEED, &freq))
+ {
+ LogRel(("OSS: Failed to set audio frequency (%dHZ): %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno));
+ break;
+ }
+
+ /* Obsolete on Solaris (using O_NONBLOCK is sufficient). */
+#if !(defined(VBOX) && defined(RT_OS_SOLARIS))
+ if (ioctl(hFile, SNDCTL_DSP_NONBLOCK))
+ {
+ LogRel(("OSS: Failed to set non-blocking mode: %s (%d)\n", strerror(errno), errno));
+ break;
+ }
+#endif
+
+ /* Check access mode (input or output). */
+ bool fIn = ((fOpen & O_ACCMODE) == O_RDONLY);
+
+ LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSReq->cFragments, fIn ? "input" : "output", pOSSReq->cbFragmentSize));
+
+ int mmmmssss = (pOSSReq->cFragments << 16) | lsbindex(pOSSReq->cbFragmentSize);
+ if (ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss))
+ {
+ LogRel(("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n",
+ pOSSReq->cFragments, pOSSReq->cbFragmentSize, strerror(errno), errno));
+ break;
+ }
+
+ audio_buf_info abinfo;
+ if (ioctl(hFile, fIn ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo))
+ {
+ LogRel(("OSS: Failed to retrieve %s buffer length: %s (%d)\n", fIn ? "input" : "output", strerror(errno), errno));
+ break;
+ }
+
+ rc = ossOSSToAudioProps(iFormat, &pOSSAcq->Props);
+ if (RT_SUCCESS(rc))
+ {
+ pOSSAcq->Props.cChannels = cChannels;
+ pOSSAcq->Props.uHz = freq;
+ pOSSAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pOSSAcq->Props.cBytes, pOSSAcq->Props.cChannels);
+
+ pOSSAcq->cFragments = abinfo.fragstotal;
+ pOSSAcq->cbFragmentSize = abinfo.fragsize;
+
+ LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSAcq->cFragments, fIn ? "input" : "output", pOSSAcq->cbFragmentSize));
+
+ *phFile = hFile;
+ }
+ }
+ while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int ossControlStreamIn(/*PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd*/ void)
+{
+ /** @todo Nothing to do here right now!? */
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossControlStreamOut(PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ DrvAudioHlpClearBuf(&pStreamOSS->pCfg->Props, pStreamOSS->pvBuf, pStreamOSS->cbBuf,
+ PDMAUDIOPCMPROPS_B2F(&pStreamOSS->pCfg->Props, pStreamOSS->cbBuf));
+
+ int mask = PCM_ENABLE_OUTPUT;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to enable output stream: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ int mask = 0;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to disable output stream: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbToRead = RT_MIN(pStreamOSS->cbBuf, cxBuf);
+
+ LogFlowFunc(("cbToRead=%zi\n", cbToRead));
+
+ uint32_t cbReadTotal = 0;
+ uint32_t cbTemp;
+ ssize_t cbRead;
+ size_t offWrite = 0;
+
+ while (cbToRead)
+ {
+ cbTemp = RT_MIN(cbToRead, pStreamOSS->cbBuf);
+ AssertBreakStmt(cbTemp, rc = VERR_NO_DATA);
+ cbRead = read(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf, cbTemp);
+
+ LogFlowFunc(("cbRead=%zi, cbTemp=%RU32, cbToRead=%zu\n", cbRead, cbTemp, cbToRead));
+
+ if (cbRead < 0)
+ {
+ switch (errno)
+ {
+ case 0:
+ {
+ LogFunc(("Failed to read %z frames\n", cbRead));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ case EINTR:
+ case EAGAIN:
+ rc = VERR_NO_DATA;
+ break;
+
+ default:
+ LogFlowFunc(("Failed to read %zu input frames, rc=%Rrc\n", cbTemp, rc));
+ rc = VERR_GENERAL_FAILURE; /** @todo Fix this. */
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (cbRead)
+ {
+ memcpy((uint8_t *)pvBuf + offWrite, pStreamOSS->pvBuf, cbRead);
+
+ Assert((ssize_t)cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ offWrite += cbRead;
+ cbReadTotal += cbRead;
+ }
+ else /* No more data, try next round. */
+ break;
+ }
+
+ if (rc == VERR_NO_DATA)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+
+static int ossDestroyStreamIn(PPDMAUDIOBACKENDSTREAM pStream)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ LogFlowFuncEnter();
+
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ RTMemFree(pStreamOSS->pvBuf);
+ pStreamOSS->pvBuf = NULL;
+ }
+
+ pStreamOSS->cbBuf = 0;
+
+ ossStreamClose(&pStreamOSS->hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossDestroyStreamOut(PPDMAUDIOBACKENDSTREAM pStream)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+#ifndef RT_OS_L4
+ if (pStreamOSS->Out.fMMIO)
+ {
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ int rc2 = munmap(pStreamOSS->pvBuf, pStreamOSS->cbBuf);
+ if (rc2 == 0)
+ {
+ pStreamOSS->pvBuf = NULL;
+ pStreamOSS->cbBuf = 0;
+
+ pStreamOSS->Out.fMMIO = false;
+ }
+ else
+ LogRel(("OSS: Failed to memory unmap playback buffer on close: %s\n", strerror(errno)));
+ }
+ }
+ else
+ {
+#endif
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ RTMemFree(pStreamOSS->pvBuf);
+ pStreamOSS->pvBuf = NULL;
+ }
+
+ pStreamOSS->cbBuf = 0;
+#ifndef RT_OS_L4
+ }
+#endif
+
+ ossStreamClose(&pStreamOSS->hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS audio driver");
+
+ pBackendCfg->cbStreamIn = sizeof(OSSAUDIOSTREAM);
+ pBackendCfg->cbStreamOut = sizeof(OSSAUDIOSTREAM);
+
+ int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0);
+ if (hFile == -1)
+ {
+ /* Try opening the mixing device instead. */
+ hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0);
+ }
+
+ int ossVer = -1;
+
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ oss_sysinfo ossInfo;
+ RT_ZERO(ossInfo);
+#endif
+
+ if (hFile != -1)
+ {
+ int err = ioctl(hFile, OSS_GETVERSION, &ossVer);
+ if (err == 0)
+ {
+ LogRel2(("OSS: Using version: %d\n", ossVer));
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ err = ioctl(hFile, OSS_SYSINFO, &ossInfo);
+ if (err == 0)
+ {
+ LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios));
+ LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers));
+
+ int cDev = ossInfo.nummixers;
+ if (!cDev)
+ cDev = ossInfo.numaudios;
+
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+ }
+ else
+ {
+#endif
+ /* Since we cannot query anything, assume that we have at least
+ * one input and one output if we found "/dev/dsp" or "/dev/mixer". */
+
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ }
+#endif
+ }
+ else
+ LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err));
+ }
+ else
+ LogRel(("OSS: No devices found, audio is not available\n"));
+
+ if (hFile != -1)
+ close(hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossCreateStreamIn(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ int rc;
+
+ int hFile = -1;
+
+ do
+ {
+ OSSAUDIOSTREAMCFG ossReq;
+ memcpy(&ossReq.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
+
+ ossReq.cFragments = s_OSSConf.nfrags;
+ ossReq.cbFragmentSize = s_OSSConf.fragsize;
+
+ OSSAUDIOSTREAMCFG ossAcq;
+ RT_ZERO(ossAcq);
+
+ rc = ossStreamOpen(s_OSSConf.devpath_in, O_RDONLY | O_NONBLOCK, &ossReq, &ossAcq, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pCfgAcq->Props, &ossAcq.Props, sizeof(PDMAUDIOPCMPROPS));
+
+ if (ossAcq.cFragments * ossAcq.cbFragmentSize & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Warning: Misaligned capturing buffer: Size = %zu, Alignment = %u\n",
+ ossAcq.cFragments * ossAcq.cbFragmentSize, pStreamOSS->uAlign + 1));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, ossAcq.cFragments * ossAcq.cbFragmentSize);
+ void *pvBuf = RTMemAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ LogRel(("OSS: Failed allocating capturing buffer with (%zu bytes)\n", cbBuf));
+ rc = VERR_NO_MEMORY;
+ }
+
+ pStreamOSS->hFile = hFile;
+ pStreamOSS->pvBuf = pvBuf;
+ pStreamOSS->cbBuf = cbBuf;
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, ossAcq.cbFragmentSize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering". */
+ /** @todo Pre-buffering required? */
+ }
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int ossCreateStreamOut(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ int rc;
+ int hFile = -1;
+
+ do
+ {
+ OSSAUDIOSTREAMCFG reqStream;
+ RT_ZERO(reqStream);
+
+ OSSAUDIOSTREAMCFG obtStream;
+ RT_ZERO(obtStream);
+
+ memcpy(&reqStream.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
+
+ reqStream.cFragments = s_OSSConf.nfrags;
+ reqStream.cbFragmentSize = s_OSSConf.fragsize;
+
+ rc = ossStreamOpen(s_OSSConf.devpath_out, O_WRONLY, &reqStream, &obtStream, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pCfgAcq->Props, &obtStream.Props, sizeof(PDMAUDIOPCMPROPS));
+
+ if (obtStream.cFragments * obtStream.cbFragmentSize & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n",
+ obtStream.cFragments * obtStream.cbFragmentSize, pStreamOSS->uAlign + 1));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->Out.fMMIO = false;
+
+ size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, obtStream.cFragments * obtStream.cbFragmentSize);
+ Assert(cbBuf);
+
+#ifndef RT_OS_L4
+ if (s_OSSConf.try_mmap)
+ {
+ pStreamOSS->pvBuf = mmap(0, cbBuf, PROT_READ | PROT_WRITE, MAP_SHARED, hFile, 0);
+ if (pStreamOSS->pvBuf == MAP_FAILED)
+ {
+ LogRel(("OSS: Failed to memory map %zu bytes of playback buffer: %s\n", cbBuf, strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+ else
+ {
+ int mask = 0;
+ if (ioctl(hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to retrieve initial trigger mask for playback buffer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ /* Note: No break here, need to unmap file first! */
+ }
+ else
+ {
+ mask = PCM_ENABLE_OUTPUT;
+ if (ioctl (hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to retrieve PCM_ENABLE_OUTPUT mask: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ /* Note: No break here, need to unmap file first! */
+ }
+ else
+ {
+ pStreamOSS->Out.fMMIO = true;
+ LogRel(("OSS: Using MMIO\n"));
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = munmap(pStreamOSS->pvBuf, cbBuf);
+ if (rc2)
+ LogRel(("OSS: Failed to memory unmap playback buffer: %s\n", strerror(errno)));
+ break;
+ }
+ }
+ }
+#endif /* !RT_OS_L4 */
+
+ /* Memory mapping failed above? Try allocating an own buffer. */
+#ifndef RT_OS_L4
+ if (!pStreamOSS->Out.fMMIO)
+ {
+#endif
+ void *pvBuf = RTMemAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ LogRel(("OSS: Failed allocating playback buffer with %zu bytes\n", cbBuf));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pStreamOSS->hFile = hFile;
+ pStreamOSS->pvBuf = pvBuf;
+ pStreamOSS->cbBuf = cbBuf;
+#ifndef RT_OS_L4
+ }
+#endif
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, obtStream.cbFragmentSize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering" */
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+ uint32_t cbWrittenTotal = 0;
+
+#ifndef RT_OS_L4
+ count_info cntinfo;
+#endif
+
+ do
+ {
+ uint32_t cbAvail = cxBuf;
+ uint32_t cbToWrite;
+
+#ifndef RT_OS_L4
+ if (pStreamOSS->Out.fMMIO)
+ {
+ /* Get current playback pointer. */
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOPTR, &cntinfo);
+ if (!rc2)
+ {
+ LogRel(("OSS: Failed to retrieve current playback pointer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ /* Nothing to play? */
+ if (cntinfo.ptr == pStreamOSS->old_optr)
+ break;
+
+ int cbData;
+ if (cntinfo.ptr > pStreamOSS->old_optr)
+ cbData = cntinfo.ptr - pStreamOSS->old_optr;
+ else
+ cbData = cxBuf + cntinfo.ptr - pStreamOSS->old_optr;
+ Assert(cbData >= 0);
+
+ cbToWrite = RT_MIN((unsigned)cbData, cbAvail);
+ }
+ else
+ {
+#endif
+ audio_buf_info abinfo;
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &abinfo);
+ if (rc2 < 0)
+ {
+ LogRel(("OSS: Failed to retrieve current playback buffer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ if ((size_t)abinfo.bytes > cxBuf)
+ {
+ LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", abinfo.bytes, cxBuf, cxBuf));
+ abinfo.bytes = cxBuf;
+ /* Keep going. */
+ }
+
+ if (abinfo.bytes < 0)
+ {
+ LogRel2(("OSS: Warning: Invalid available size (%d vs. %RU32)\n", abinfo.bytes, cxBuf));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ cbToWrite = RT_MIN(unsigned(abinfo.fragments * abinfo.fragsize), cbAvail);
+#ifndef RT_OS_L4
+ }
+#endif
+ cbToWrite = RT_MIN(cbToWrite, pStreamOSS->cbBuf);
+
+ while (cbToWrite)
+ {
+ uint32_t cbWritten = cbToWrite;
+
+ memcpy(pStreamOSS->pvBuf, pvBuf, cbWritten);
+
+ uint32_t cbChunk = cbWritten;
+ uint32_t cbChunkOff = 0;
+ while (cbChunk)
+ {
+ ssize_t cbChunkWritten = write(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf + cbChunkOff,
+ RT_MIN(cbChunk, (unsigned)s_OSSConf.fragsize));
+ if (cbChunkWritten < 0)
+ {
+ LogRel(("OSS: Failed writing output data: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ if (cbChunkWritten & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Misaligned write (written %z, expected %RU32)\n", cbChunkWritten, cbChunk));
+ break;
+ }
+
+ cbChunkOff += (uint32_t)cbChunkWritten;
+ Assert(cbChunkOff <= cbWritten);
+ Assert(cbChunk >= (uint32_t)cbChunkWritten);
+ cbChunk -= (uint32_t)cbChunkWritten;
+ }
+
+ Assert(cbToWrite >= cbWritten);
+ cbToWrite -= cbWritten;
+ cbWrittenTotal += cbWritten;
+ }
+
+#ifndef RT_OS_L4
+ /* Update read pointer. */
+ if (pStreamOSS->Out.fMMIO)
+ pStreamOSS->old_optr = cntinfo.ptr;
+#endif
+
+ } while(0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostOSSAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostOSSAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+ RT_NOREF(enmDir);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = ossCreateStreamIn (pStreamOSS, pCfgReq, pCfgAcq);
+ else
+ rc = ossCreateStreamOut(pStreamOSS, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamOSS->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = ossDestroyStreamIn(pStream);
+ else
+ rc = ossDestroyStreamOut(pStream);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamOSS->pCfg);
+ pStreamOSS->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = ossControlStreamIn(/*pInterface, pStream, enmStreamCmd*/);
+ else
+ rc = ossControlStreamOut(pStreamOSS, enmStreamCmd);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do here for OSS. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostOSSAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED
+ | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ return strmSts;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ 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;
+}
+
+/**
+ * Constructs an OSS audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
+ LogRel(("Audio: Initializing OSS driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostOSSAudio);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Char driver registration record.
+ */
+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
+};
+