AudioMixBuffer.cpp revision b2cb7a03a79ad2e40f4e470acbc87881781e798e
/* $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.
*/
#include <iprt/asm-math.h>
#ifdef DEBUG_andy
#endif
#ifdef TESTCASE
#endif
#include "AudioMixBuffer.h"
#ifdef LOG_GROUP
#endif
#if 0
# define AUDMIXBUF_LOG(x) LogFlowFunc(x)
# define LOG_GROUP LOG_GROUP_DEV_AUDIO
#else
# define AUDMIXBUF_LOG(x) do {} while (0)
#endif
/*
* 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
static int audioMixBufInitCommon(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMPCMPROPS pProps);
typedef uint32_t (AUDMIXBUF_FN_CONVFROM) (PPDMAUDIOSAMPLE paDst, const void *pvSrc, size_t cbSrc, uint32_t cSamples);
typedef AUDMIXBUF_FN_CONVFROM *PAUDMIXBUF_FN_CONVFROM;
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
/**
* Note: Currently does not handle any endianess 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, size_t cbSrc, uint32_t cSamples) \
{ \
{ \
paDst->u64LSample = (ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(*pSrc++), 0x5F000000 /* Volume */) >> 31); \
paDst->u64RSample = (ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(*pSrc++), 0x5F000000 /* Volume */) >> 31); \
paDst++; \
} \
\
return cSamples; \
} \
\
AUDMIXBUF_MACRO_FN uint32_t audioMixBufConvFrom##_aName##Mono(PPDMAUDIOSAMPLE paDst, const void *pvSrc, size_t cbSrc, uint32_t cSamples) \
{ \
{ \
paDst->u64LSample = ASMMult2xS32RetS64(audioMixBufClipFrom##_aName(pSrc[0]), 0x5F000000 /* Volume */); \
pSrc++; \
paDst++; \
} \
\
return cSamples; \
} \
\
/* Note: Does no buffer size checking, so be careful what to do! */ \
AUDMIXBUF_MACRO_FN void audioMixBufConvTo##_aName##Stereo(void *pvDst, const PPDMAUDIOSAMPLE paSrc, uint32_t cSamples) \
{ \
_aType l, r; \
while (cSamples--) \
{ \
AUDMIXBUF_MACRO_LOG(("\t-> l=%RI16, r=%RI16\n", l, r)); \
*pDst++ = l; \
*pDst++ = r; \
pSrc++; \
} \
} \
\
/* Note: Does no buffer size checking, so be careful what to do! */ \
AUDMIXBUF_MACRO_FN void audioMixBufConvTo##_aName##Mono(void *pvDst, const PPDMAUDIOSAMPLE paSrc, uint32_t cSamples) \
{ \
while (cSamples--) \
{ \
pSrc++; \
} \
}
/* audioMixBufConvToS8: 8 bit, signed. */
AUDMIXBUF_CONVERT(S8 /* Name */, int8_t, INT8_MIN /* Min */, INT8_MAX /* Max */, true /* fSigned */, 8 /* cShift */)
/* audioMixBufConvToU8: 8 bit, unsigned. */
AUDMIXBUF_CONVERT(U8 /* Name */, uint8_t, 0 /* Min */, UINT8_MAX /* Max */, false /* fSigned */, 8 /* cShift */)
/* audioMixBufConvToS16: 16 bit, signed. */
AUDMIXBUF_CONVERT(S16 /* Name */, int16_t, INT16_MIN /* Min */, INT16_MAX /* Max */, true /* fSigned */, 16 /* cShift */)
/* audioMixBufConvToU16: 16 bit, unsigned. */
AUDMIXBUF_CONVERT(U16 /* Name */, uint16_t, 0 /* Min */, UINT16_MAX /* Max */, false /* fSigned */, 16 /* cShift */)
/* audioMixBufConvToS32: 32 bit, signed. */
AUDMIXBUF_CONVERT(S32 /* Name */, int32_t, INT32_MIN /* Min */, INT32_MAX /* Max */, true /* fSigned */, 32 /* cShift */)
/* audioMixBufConvToU32: 32 bit, unsigned. */
AUDMIXBUF_CONVERT(U32 /* Name */, uint32_t, 0 /* Min */, UINT32_MAX /* Max */, false /* fSigned */, 32 /* cShift */)
{ \
AUDMIXBUF_MACRO_LOG(("pRate=%p: srcOffset=%RU32, dstOffset=%RU64, dstInc=%RU64\n", \
\
{ \
{ \
} \
\
if (pcDstWritten) \
*pcDstWritten = cSamples; \
if (pcSrcRead) \
return; \
} \
\
uint64_t l = 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; \
\
{ \
AUDMIXBUF_MACRO_LOG(("foo1\n")); \
l++; \
break; \
AUDMIXBUF_MACRO_LOG(("foo1.1\n")); \
} \
AUDMIXBUF_MACRO_LOG(("foo2\n")); \
\
\
/* Interpolate. */ \
\
samOut.u64LSample = (samLast.u64LSample * ((int64_t) UINT32_MAX - t) + samCur.u64LSample * t) >> 32; \
samOut.u64RSample = (samLast.u64RSample * ((int64_t) UINT32_MAX - t) + samCur.u64RSample * t) >> 32; \
\
paDst++; \
\
AUDMIXBUF_MACRO_LOG(("\tt=%RI64, l=%RI64, r=%RI64 (cur l=%RI64, r=%RI64)\n", t, \
\
\
} \
\
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(("l=%RU64, pRate->srcSampleLast l=%RI64, r=%RI64\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. */
{
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;
}
{
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;
}
{
/* pcbWritten is optional. */
int rc;
if (pConv)
{
if (pcWritten)
rc = VINF_SUCCESS;
}
else
return rc;
}
/**
* TODO
*
* Note: This routine does not do any bounds checking for speed
* reasons, so be careful what you do with it.
*
* @return IPRT status code.
* @param pvDst
* @param paSrc
* @param cSamples
* @param pCfg
*/
{
int rc;
if (pConv)
{
rc = VINF_SUCCESS;
}
else
return rc;
}
{
if (pMixBuf)
{
{
}
}
}
{
pMixBuf->offReadWrite = 0;
pMixBuf->cProcessed = 0;
/* 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;
}
/** @todo Only handles ADC (and not DAC) conversion for now. If you
* want DAC conversion, link the buffers the other way around. */
/* Calculate the frequency ratio. */
if (!cSamples)
int rc = VINF_SUCCESS;
{
AUDMIXBUF_LOG(("%s: Reallocating samples %RU32 -> %RU32\n",
cSamples * sizeof(PDMAUDIOSAMPLE));
rc = VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
}
if (RT_SUCCESS(rc))
{
/* Create rate conversion. */
return VERR_NO_MEMORY;
AUDMIXBUF_LOG(("uThisHz=%RU32, uParentHz=%RU32, iFreqRatio=%RI64, uRateInc=%RU64, cSamples=%RU32 (%RU32 parent)\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(("\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)
{
cToProcess, enmFmt);
}
else
rc = VINF_SUCCESS;
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;
}
/*
* 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))
{
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. */
}
{
}
pMixBuf->iFreqRatio = 0;
{
}
}
{
}
/**
* 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;
int rc;
#if 0
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;
}
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. */
}
/* Anything to do at all? */
if (cLenDst1)
/* Second part present? */
&& cLenDst2)
{
}
#if 0
#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;
}