AudioMixBuffer.cpp revision a1d00479d8ed953cb02b08532465ac659d982b4a
/* $Id$ */
/** @file
* samples.
*/
/*
* Copyright (C) 2014-2015 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*/
/*
* DEBUG_DUMP_PCM_DATA enables dumping the raw PCM data
* to a file on the host. Be sure to adjust the dumping path
* to your needs before using this!
*/
#ifdef DEBUG
//# define DEBUG_DUMP_PCM_DATA
#endif
#include <iprt/asm-math.h>
#ifdef DEBUG_DUMP_PCM_DATA
#endif
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
#ifdef TESTCASE
# define LOG_ENABLED
#endif
#include "AudioMixBuffer.h"
#if 0
# define AUDMIXBUF_LOG(x) LogFlowFunc(x)
#else
# if defined(TESTCASE)
# define AUDMIXBUF_LOG(x) LogFunc(x)
# else
# define AUDMIXBUF_LOG(x) do {} while (0)
# endif
#endif
/**
* Structure for holding sample conversion parameters for
* the audioMixBufConvFromXXX / audioMixBufConvToXXX macros.
*/
typedef struct AUDMIXBUF_CONVOPTS
{
/** Number of audio samples to convert. */
/** Volume to apply during conversion. Pass 0
* to convert the original values. May not apply to
* all conversion functions. */
/*
* When running the audio testcases we want to verfiy
* the macro-generated routines separately, so unmark them as being
* inlined + static.
*/
#ifdef TESTCASE
# define AUDMIXBUF_MACRO_FN
#else
# define AUDMIXBUF_MACRO_FN static inline
#endif
#ifdef DEBUG
static uint64_t s_cSamplesMixedTotal = 0;
#endif
typedef uint32_t (AUDMIXBUF_FN_CONVFROM) (PPDMAUDIOSAMPLE paDst, const void *pvSrc, uint32_t cbSrc, const PAUDMIXBUF_CONVOPTS pOpts);
typedef AUDMIXBUF_FN_CONVFROM *PAUDMIXBUF_FN_CONVFROM;
typedef void (AUDMIXBUF_FN_CONVTO) (void *pvDst, const PPDMAUDIOSAMPLE paSrc, const PAUDMIXBUF_CONVOPTS pOpts);
typedef AUDMIXBUF_FN_CONVTO *PAUDMIXBUF_FN_CONVTO;
/* Can return VINF_TRY_AGAIN for getting next pointer at beginning (circular) */
{
int rc;
if (!cSamplesToRead)
{
*pcSamplesRead = 0;
return VINF_SUCCESS;
}
{
rc = VINF_TRY_AGAIN;
}
else
{
rc = VINF_SUCCESS;
}
return rc;
}
/**
* Clears (zeroes) the buffer by a certain amount of (processed) samples and
* keeps track to eventually assigned children buffers.
*
* @param pMixBuf
* @param cSamplesToClear
*/
{
AUDMIXBUF_LOG(("%s: offReadWrite=%RU32, cProcessed=%RU32\n",
{
AUDMIXBUF_LOG(("\t%s: cMixed=%RU32 -> %RU32\n",
pIter->offReadWrite = 0;
}
{
AUDMIXBUF_LOG(("Clearing1: %RU32 - %RU32\n",
offClear = 0;
}
else
if (cLeft)
{
AUDMIXBUF_LOG(("Clearing2: %RU32 - %RU32\n",
}
}
{
if (!pMixBuf)
return;
{
}
{
}
}
/** @todo Rename this function! Too confusing in combination with audioMixBufFreeBuf(). */
{
AssertPtrReturn(pMixBuf, 0);
{
/*
* As a linked child buffer we want to know how many samples
* already have been consumed by the parent.
*/
}
else
return cFree;
}
{
}
{
if (!cbSamples)
return VERR_INVALID_PARAMETER;
return VERR_NO_MEMORY;
return VINF_SUCCESS;
}
/** Note: Enabling this will generate huge logs! */
//#define DEBUG_MACROS
#ifdef DEBUG_MACROS
# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x)
# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x
#else
# define AUDMIXBUF_MACRO_LOG(x) do {} while (0)
#endif
/**
* thus don't do any bounds checking!
*
* Note: Currently does not handle any endianness conversion yet!
*/
/* Clips a specific output value to a single sample value. */ \
{ \
if (_aSigned) \
} \
\
/* Clips a single sample value to a specific output value. */ \
{ \
if (iVal >= 0x7f000000) \
return _aMax; \
else if (iVal < -2147483648LL) \
return _aMin; \
\
if (_aSigned) \
} \
\
AUDMIXBUF_MACRO_FN uint32_t audioMixBufConvFrom##_aName##Stereo(PPDMAUDIOSAMPLE paDst, const void *pvSrc, uint32_t cbSrc, \
const PAUDMIXBUF_CONVOPTS pOpts) \
{ \
AUDMIXBUF_MACRO_LOG(("cSamples=%RU32, sizeof(%zu), lVol=%RU32, rVol=%RU32\n", \
{ \
paDst->i64LSample = ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(*pSrc++), pOpts->Volume.uLeft ) >> 31; \
paDst->i64RSample = ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(*pSrc++), pOpts->Volume.uRight) >> 31; \
paDst++; \
} \
\
return cSamples; \
} \
\
AUDMIXBUF_MACRO_FN uint32_t audioMixBufConvFrom##_aName##Mono(PPDMAUDIOSAMPLE paDst, const void *pvSrc, uint32_t cbSrc, \
const PAUDMIXBUF_CONVOPTS pOpts) \
{ \
AUDMIXBUF_MACRO_LOG(("cSamples=%RU32, sizeof(%zu), lVol=%RU32, rVol=%RU32\n", \
{ \
paDst->i64LSample = ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(*pSrc++), pOpts->Volume.uLeft) >> 31; \
paDst++; \
} \
\
return cSamples; \
} \
\
AUDMIXBUF_MACRO_FN void audioMixBufConvTo##_aName##Stereo(void *pvDst, const PPDMAUDIOSAMPLE paSrc, \
const PAUDMIXBUF_CONVOPTS pOpts) \
{ \
_aType l, r; \
while (cSamples--) \
{ \
AUDMIXBUF_MACRO_LOG(("\t-> l=%RI16, r=%RI16\n", l, r)); \
*pDst++ = l; \
*pDst++ = r; \
pSrc++; \
} \
} \
\
const PAUDMIXBUF_CONVOPTS pOpts) \
{ \
while (cSamples--) \
{ \
pSrc++; \
} \
}
/* audioMixBufConvXXXS8: 8 bit, signed. */
AUDMIXBUF_CONVERT(S8 /* Name */, int8_t, INT8_MIN /* Min */, INT8_MAX /* Max */, true /* fSigned */, 8 /* cShift */)
/* audioMixBufConvXXXU8: 8 bit, unsigned. */
AUDMIXBUF_CONVERT(U8 /* Name */, uint8_t, 0 /* Min */, UINT8_MAX /* Max */, false /* fSigned */, 8 /* cShift */)
/* audioMixBufConvXXXS16: 16 bit, signed. */
AUDMIXBUF_CONVERT(S16 /* Name */, int16_t, INT16_MIN /* Min */, INT16_MAX /* Max */, true /* fSigned */, 16 /* cShift */)
/* audioMixBufConvXXXU16: 16 bit, unsigned. */
AUDMIXBUF_CONVERT(U16 /* Name */, uint16_t, 0 /* Min */, UINT16_MAX /* Max */, false /* fSigned */, 16 /* cShift */)
/* audioMixBufConvXXXS32: 32 bit, signed. */
AUDMIXBUF_CONVERT(S32 /* Name */, int32_t, INT32_MIN /* Min */, INT32_MAX /* Max */, true /* fSigned */, 32 /* cShift */)
/* audioMixBufConvXXXU32: 32 bit, unsigned. */
AUDMIXBUF_CONVERT(U32 /* Name */, uint32_t, 0 /* Min */, UINT32_MAX /* Max */, false /* fSigned */, 32 /* cShift */)
{ \
AUDMIXBUF_MACRO_LOG(("pRate=%p: srcOffset=0x%RX32 (%RU32), dstOffset=0x%RX32 (%RU32), dstInc=0x%RX64 (%RU64)\n", \
\
{ \
{ \
} \
\
if (pcDstWritten) \
*pcDstWritten = cSamples; \
if (pcSrcRead) \
return; \
} \
\
PDMAUDIOSAMPLE samCur = { 0 }; \
\
AUDMIXBUF_MACRO_LOG(("Start: paDstEnd=%p - paDstStart=%p -> %zu\n", paDstEnd, paDst, paDstEnd - paDstStart)); \
AUDMIXBUF_MACRO_LOG(("Start: paSrcEnd=%p - paSrcStart=%p -> %zu\n", paSrcEnd, paSrc, paSrcEnd - paSrcStart)); \
\
{ \
break; \
\
lDelta = 0; \
{ \
lDelta++; \
break; \
} \
\
break; \
\
\
/* Interpolate. */ \
\
samOut.i64LSample = (samLast.i64LSample * ((int64_t) UINT32_MAX - iDstOffInt) + samCur.i64LSample * iDstOffInt) >> 32; \
samOut.i64RSample = (samLast.i64RSample * ((int64_t) UINT32_MAX - iDstOffInt) + samCur.i64RSample * iDstOffInt) >> 32; \
\
paDst++; \
\
AUDMIXBUF_MACRO_LOG(("\tlDelta=0x%RX64 (%RU64), iDstOffInt=0x%RX64 (%RI64), l=%RI64, r=%RI64 (cur l=%RI64, r=%RI64)\n", \
\
\
AUDMIXBUF_MACRO_LOG(("\t\tpRate->dstOffset=0x%RX32 (%RU32)\n", pRate->dstOffset, pRate->dstOffset >> 32)); \
\
} \
\
AUDMIXBUF_MACRO_LOG(("End: paDst=%p - paDstStart=%p -> %zu\n", paDst, paDstStart, paDst - paDstStart)); \
AUDMIXBUF_MACRO_LOG(("End: paSrc=%p - paSrcStart=%p -> %zu\n", paSrc, paSrcStart, paSrc - paSrcStart)); \
\
\
AUDMIXBUF_MACRO_LOG(("pRate->srcSampleLast l=%RI64, r=%RI64, lDelta=0x%RX64 (%RU64)\n", \
\
if (pcDstWritten) \
if (pcSrcRead) \
}
/* audioMixBufOpAssign: Assigns values from source buffer to destination bufffer, overwriting the destination. */
/* audioMixBufOpBlend: Blends together the values from both, the source and the destination buffer. */
/**
*
** @todo Speed up the lookup by binding it to the actual stream state.
*
* @return IPRT status code.
* @return PAUDMIXBUF_FN_CONVFROM
* @param enmFmt
*/
{
if (AUDMIXBUF_FMT_SIGNED(enmFmt))
{
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvFromS8Stereo;
case 16: return audioMixBufConvFromS16Stereo;
case 32: return audioMixBufConvFromS32Stereo;
default: return NULL;
}
}
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvFromS8Mono;
case 16: return audioMixBufConvFromS16Mono;
case 32: return audioMixBufConvFromS32Mono;
default: return NULL;
}
}
}
else /* Unsigned */
{
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvFromU8Stereo;
case 16: return audioMixBufConvFromU16Stereo;
case 32: return audioMixBufConvFromU32Stereo;
default: return NULL;
}
}
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvFromU8Mono;
case 16: return audioMixBufConvFromU16Mono;
case 32: return audioMixBufConvFromU32Mono;
default: return NULL;
}
}
}
return NULL;
}
/**
*
** @todo Speed up the lookup by binding it to the actual stream state.
*
* @return PAUDMIXBUF_FN_CONVTO
* @param enmFmt
*/
{
if (AUDMIXBUF_FMT_SIGNED(enmFmt))
{
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvToS8Stereo;
case 16: return audioMixBufConvToS16Stereo;
case 32: return audioMixBufConvToS32Stereo;
default: return NULL;
}
}
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvToS8Mono;
case 16: return audioMixBufConvToS16Mono;
case 32: return audioMixBufConvToS32Mono;
default: return NULL;
}
}
}
else /* Unsigned */
{
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvToU8Stereo;
case 16: return audioMixBufConvToU16Stereo;
case 32: return audioMixBufConvToU32Stereo;
default: return NULL;
}
}
{
switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
{
case 8: return audioMixBufConvToU8Mono;
case 16: return audioMixBufConvToU16Mono;
case 32: return audioMixBufConvToU32Mono;
default: return NULL;
}
}
}
return NULL;
}
{
if (pMixBuf)
{
{
}
}
}
int audioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMPCMPROPS pProps, uint32_t cSamples)
{
pMixBuf->offReadWrite = 0;
pMixBuf->cProcessed = 0;
/* Set initial volume to max. */
/* Prevent division by zero.
* Do a 1:1 conversion according to AUDIOMIXBUF_S2B_RATIO. */
return VERR_NO_MEMORY;
AUDMIXBUF_LOG(("%s: uHz=%RU32, cChan=%RU8, cBits=%RU8, fSigned=%RTbool\n",
}
{
AssertPtrReturn(pMixBuf, true);
return (pMixBuf->cProcessed == 0);
}
{
("Parent sample frequency (Hz) not set\n"), VERR_INVALID_PARAMETER);
("Buffer sample frequency (Hz) not set\n"), VERR_INVALID_PARAMETER);
("Circular linking not allowed\n"), VERR_INVALID_PARAMETER);
{
AUDMIXBUF_LOG(("%s: Already linked to \"%s\"\n",
return VERR_ACCESS_DENIED;
}
/* Calculate the frequency ratio. */
if (!cSamples)
int rc = VINF_SUCCESS;
{
AUDMIXBUF_LOG(("%s: Reallocating samples %RU32 -> %RU32\n",
rc = VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
/* Make sure to zero the reallocated buffer so that it can be
* used properly when blending with another buffer later. */
}
}
if (RT_SUCCESS(rc))
{
{
/* Create rate conversion. */
return VERR_NO_MEMORY;
}
else
AUDMIXBUF_LOG(("uThisHz=%RU32, uParentHz=%RU32, iFreqRatio=0x%RX64 (%RI64), uRateInc=0x%RX64 (%RU64), cSamples=%RU32 (%RU32 parent)\n",
AUDMIXBUF_LOG(("%s (%RU32Hz) -> %s (%RU32Hz)\n",
}
return rc;
}
{
AssertPtrReturn(pMixBuf, 0);
("Buffer is not linked to a parent buffer\n"),
0);
}
static int audioMixBufMixTo(PPDMAUDIOMIXBUF pDst, PPDMAUDIOMIXBUF pSrc, uint32_t cSamples, uint32_t *pcProcessed)
{
/* pcProcessed is optional. */
/* Live samples indicate how many samples there are in the source buffer
* which have not been processed yet by the destination buffer. */
AUDMIXBUF_LOG(("Destination buffer \"%s\" full (%RU32 samples max), live samples = %RU32\n",
/* Dead samples are the number of samples in the destination buffer which
* will not be needed, that is, are not needed in order to process the live
* samples of the source buffer. */
uint32_t cReadTotal = 0;
uint32_t cWrittenTotal = 0;
AUDMIXBUF_LOG(("pSrc=%s (%RU32 samples), pDst=%s (%RU32 samples), cLive=%RU32, cDead=%RU32, cToReadTotal=%RU32, offWrite=%RU32\n",
pSrc->pszName, pSrc->cSamples, pDst->pszName, pDst->cSamples, cLive, cDead, cToReadTotal, offWrite));
while (cToReadTotal)
{
if (!cToWrite)
{
break;
}
AUDMIXBUF_LOG(("\t%RU32Hz -> %RU32Hz\n", AUDMIXBUF_FMT_SAMPLE_FREQ(pSrc->AudioFmt), AUDMIXBUF_FMT_SAMPLE_FREQ(pDst->AudioFmt)));
AUDMIXBUF_LOG(("\tcDead=%RU32, offWrite=%RU32, cToWrite=%RU32, offRead=%RU32, cToRead=%RU32\n",
cReadTotal += cRead;
cToReadTotal -= cRead;
}
#ifdef DEBUG
#endif
if (pcProcessed)
AUDMIXBUF_LOG(("cReadTotal=%RU32 (pcProcessed), cWrittenTotal=%RU32, cSrcMixed=%RU32, cDstProc=%RU32\n",
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
uint32_t cProcessedMax = 0;
{
if (RT_FAILURE(rc))
break;
}
if (pcProcessed)
return rc;
}
{
("Buffer is not linked to a parent buffer\n"),
}
{
#ifdef DEBUG_DISABLED
AUDMIXBUF_LOG(("********************************************\n"));
AUDMIXBUF_LOG(("%s: offReadWrite=%RU32, cProcessed=%RU32, cMixed=%RU32 (BpS=%RU32)\n",
{
AUDMIXBUF_LOG(("\t%s: offReadWrite=%RU32, cProcessed=%RU32, cMixed=%RU32 (BpS=%RU32)\n",
}
AUDMIXBUF_LOG(("********************************************\n"));
#endif
}
/**
* Returns the total number of samples processed.
*
* @return uint32_t
* @param pMixBuf
*/
{
AssertPtrReturn(pMixBuf, 0);
return pMixBuf->cProcessed;
}
{
}
{
/* pcbRead is optional. */
AUDMIXBUF_LOG(("%s: offSamples=%RU32, cLive=%RU32, cDead=%RU32, cToProcess=%RU32\n",
int rc;
if (cToProcess)
{
if (pConv)
{
rc = VINF_SUCCESS;
}
else
}
else
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
if (pcbRead)
}
return rc;
}
{
}
{
/* pcbRead is optional. */
if (!cbBuf)
return VINF_SUCCESS;
AUDMIXBUF_LOG(("%s: pvBuf=%p, cbBuf=%zu (%RU32 samples), cToRead=%RU32\n",
if (!cToRead)
{
if (pcRead)
*pcRead = 0;
return VINF_SUCCESS;
}
if (!pConv) /* Audio format not supported. */
return VERR_NOT_SUPPORTED;
/*
* Do we need to wrap around to read all requested data, that is,
* starting at the beginning of our circular buffer? This then will
* be the optional second part to do.
*/
{
/* Save new read offset. */
}
/* Anything to do at all? */
int rc = VINF_SUCCESS;
if (cLenSrc1)
{
}
/* Second part present? */
&& cLenSrc2)
{
}
if (RT_SUCCESS(rc))
{
#ifdef DEBUG_DUMP_PCM_DATA
if (RT_SUCCESS(rc))
{
}
#endif
if (pcRead)
}
AUDMIXBUF_LOG(("cRead=%RU32 (%zu bytes), rc=%Rrc\n",
return rc;
}
{
pMixBuf->offReadWrite = 0;
pMixBuf->cProcessed = 0;
}
{
}
{
}
/**
* Returns the maximum amount of bytes this buffer can hold.
*
* @return uint32_t
* @param pMixBuf
*/
{
}
{
return;
{
AUDMIXBUF_LOG(("%s: Unlinking from parent \"%s\"\n",
/* Make sure to reset the parent mixing buffer each time it gets linked
* to a new child. */
}
{
}
{
}
}
{
}
/**
* TODO
*
* @return IPRT status code.
* @return int
* @param pMixBuf
* @param enmFmt
* @param offSamples
* @param pvBuf
* @param cbBuf
* @param pcWritten Returns number of samples written. Optional.
*/
{
/* pcWritten is optional. */
AUDMIXBUF_LOG(("%s: offSamples=%RU32, cLive=%RU32, cDead=%RU32, cToProcess=%RU32\n",
return VERR_BUFFER_OVERFLOW;
if (!pConv)
return VERR_NOT_SUPPORTED;
int rc;
#ifdef DEBUG_DUMP_PCM_DATA
if (RT_SUCCESS(rc))
{
}
#endif
if (cToProcess)
{
}
else
{
cWritten = 0;
rc = VINF_SUCCESS;
}
if (RT_SUCCESS(rc))
{
if (pcWritten)
}
return rc;
}
{
}
{
/* pcbWritten is optional. */
if (!cbBuf)
{
if (pcWritten)
*pcWritten = 0;
return VINF_SUCCESS;
}
AUDMIXBUF_LOG(("%s: enmFmt=%ld, pBuf=%p, cbBuf=%zu, pParent=%p (%RU32)\n",
if ( pParent
{
if (pcWritten)
*pcWritten = 0;
AUDMIXBUF_LOG(("%s: Parent buffer %s is full\n",
return VINF_SUCCESS;
}
if (!pConv)
return VERR_NOT_SUPPORTED;
int rc = VINF_SUCCESS;
/*
* Do we need to wrap around to write all requested data, that is,
* starting at the beginning of our circular buffer? This then will
* be the optional second part to do.
*/
{
/* Save new read offset. */
}
uint32_t cWrittenTotal = 0;
/* Anything to do at all? */
if (cLenDst1)
{
}
/* Second part present? */
&& cLenDst2)
{
cWrittenTotal += pConv(pSamplesDst2, (uint8_t *)pvBuf + AUDIOMIXBUF_S2B(pMixBuf, cLenDst1), cbBuf, &convOpts);
}
#ifdef DEBUG_DUMP_PCM_DATA
#endif
AUDMIXBUF_LOG(("cLenDst1=%RU32, cLenDst2=%RU32, offWrite=%RU32\n",
if (RT_SUCCESS(rc))
{
if (pcWritten)
}
AUDMIXBUF_LOG(("cWritten=%RU32 (%zu bytes), rc=%Rrc\n",
return rc;
}