From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Devices/Audio/AudioMixBuffer.cpp | 2124 +++++++++++++++++++++++++++++ 1 file changed, 2124 insertions(+) create mode 100644 src/VBox/Devices/Audio/AudioMixBuffer.cpp (limited to 'src/VBox/Devices/Audio/AudioMixBuffer.cpp') diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.cpp b/src/VBox/Devices/Audio/AudioMixBuffer.cpp new file mode 100644 index 00000000..7ce89088 --- /dev/null +++ b/src/VBox/Devices/Audio/AudioMixBuffer.cpp @@ -0,0 +1,2124 @@ +/* $Id: AudioMixBuffer.cpp $ */ +/** @file + * Audio mixing buffer for converting reading/writing audio data. + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_audio_mixing_buffers Audio Mixer Buffer + * + * @section sec_audio_mixing_buffers_volume Soft Volume Control + * + * The external code supplies an 8-bit volume (attenuation) value in the + * 0 .. 255 range. This represents 0 to -96dB attenuation where an input + * value of 0 corresponds to -96dB and 255 corresponds to 0dB (unchanged). + * + * Each step thus corresponds to 96 / 256 or 0.375dB. Every 6dB (16 steps) + * represents doubling the sample value. + * + * For internal use, the volume control needs to be converted to a 16-bit + * (sort of) exponential value between 1 and 65536. This is used with fixed + * point arithmetic such that 65536 means 1.0 and 1 means 1/65536. + * + * For actual volume calculation, 33.31 fixed point is used. Maximum (or + * unattenuated) volume is represented as 0x40000000; conveniently, this + * value fits into a uint32_t. + * + * To enable fast processing, the maximum volume must be a power of two + * and must not have a sign when converted to int32_t. While 0x80000000 + * violates these constraints, 0x40000000 does not. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_AUDIO_MIXER_BUFFER +#if defined(VBOX_AUDIO_MIX_BUFFER_TESTCASE) && !defined(RT_STRICT) +# define RT_STRICT /* Run the testcase with assertions because the main functions doesn't return on invalid input. */ +#endif +#include + +#if 0 +/* + * AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA enables dumping the raw PCM data + * to a file on the host. Be sure to adjust AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH + * to your needs before using this! + */ +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA +# ifdef RT_OS_WINDOWS +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "c:\\temp\\" +# else +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "/tmp/" +# endif +/* Warning: Enabling this will generate *huge* logs! */ +//# define AUDIOMIXBUF_DEBUG_MACROS +#endif + +#include +#include +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA +# include +#endif +#include +#include /* For RT_BZERO. */ + +#ifdef VBOX_AUDIO_TESTCASE +# define LOG_ENABLED +# include +#endif +#include +#include + +#include "AudioMixBuffer.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef VBOX_AUDIO_TESTCASE +# ifdef DEBUG +# define AUDMIXBUF_LOG(x) LogFlowFunc(x) +# define AUDMIXBUF_LOG_ENABLED +# else +# define AUDMIXBUF_LOG(x) do {} while (0) +# endif +#else /* VBOX_AUDIO_TESTCASE */ +# define AUDMIXBUF_LOG(x) RTPrintf x +# define AUDMIXBUF_LOG_ENABLED +#endif + + +/** Bit shift for fixed point conversion. + * @sa @ref sec_audio_mixing_buffers_volume */ +#define AUDIOMIXBUF_VOL_SHIFT 30 + +/** Internal representation of 0dB volume (1.0 in fixed point). + * @sa @ref sec_audio_mixing_buffers_volume */ +#define AUDIOMIXBUF_VOL_0DB (1 << AUDIOMIXBUF_VOL_SHIFT) +AssertCompile(AUDIOMIXBUF_VOL_0DB <= 0x40000000); /* Must always hold. */ +AssertCompile(AUDIOMIXBUF_VOL_0DB == 0x40000000); /* For now -- when only attenuation is used. */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Logarithmic/exponential volume conversion table. + * @sa @ref sec_audio_mixing_buffers_volume + */ +static uint32_t const s_aVolumeConv[256] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ + 1, 2, 2, 2, 2, 2, 2, 2, /* 15 */ + 2, 2, 2, 2, 2, 3, 3, 3, /* 23 */ + 3, 3, 3, 3, 4, 4, 4, 4, /* 31 */ + 4, 4, 5, 5, 5, 5, 5, 6, /* 39 */ + 6, 6, 6, 7, 7, 7, 8, 8, /* 47 */ + 8, 9, 9, 10, 10, 10, 11, 11, /* 55 */ + 12, 12, 13, 13, 14, 15, 15, 16, /* 63 */ + 17, 17, 18, 19, 20, 21, 22, 23, /* 71 */ + 24, 25, 26, 27, 28, 29, 31, 32, /* 79 */ + 33, 35, 36, 38, 40, 41, 43, 45, /* 87 */ + 47, 49, 52, 54, 56, 59, 61, 64, /* 95 */ + 67, 70, 73, 76, 79, 83, 87, 91, /* 103 */ + 95, 99, 103, 108, 112, 117, 123, 128, /* 111 */ + 134, 140, 146, 152, 159, 166, 173, 181, /* 119 */ + 189, 197, 206, 215, 225, 235, 245, 256, /* 127 */ + 267, 279, 292, 304, 318, 332, 347, 362, /* 135 */ + 378, 395, 412, 431, 450, 470, 490, 512, /* 143 */ + 535, 558, 583, 609, 636, 664, 693, 724, /* 151 */ + 756, 790, 825, 861, 899, 939, 981, 1024, /* 159 */ + 1069, 1117, 1166, 1218, 1272, 1328, 1387, 1448, /* 167 */ + 1512, 1579, 1649, 1722, 1798, 1878, 1961, 2048, /* 175 */ + 2139, 2233, 2332, 2435, 2543, 2656, 2774, 2896, /* 183 */ + 3025, 3158, 3298, 3444, 3597, 3756, 3922, 4096, /* 191 */ + 4277, 4467, 4664, 4871, 5087, 5312, 5547, 5793, /* 199 */ + 6049, 6317, 6597, 6889, 7194, 7512, 7845, 8192, /* 207 */ + 8555, 8933, 9329, 9742, 10173, 10624, 11094, 11585, /* 215 */ + 12098, 12634, 13193, 13777, 14387, 15024, 15689, 16384, /* 223 */ + 17109, 17867, 18658, 19484, 20347, 21247, 22188, 23170, /* 231 */ + 24196, 25268, 26386, 27554, 28774, 30048, 31379, 32768, /* 239 */ + 34219, 35734, 37316, 38968, 40693, 42495, 44376, 46341, /* 247 */ + 48393, 50535, 52773, 55109, 57549, 60097, 62757, 65536, /* 255 */ +}; + + + +#ifdef VBOX_STRICT +# ifdef UNUSED + +/** + * Prints a single mixing buffer. + * Internal helper function for debugging. Do not use directly. + * + * @returns VBox status code. + * @param pMixBuf Mixing buffer to print. + * @param pszFunc Function name to log this for. + * @param uIdtLvl Indention level to use. + */ +static void audioMixBufDbgPrintSingle(PAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl) +{ + Log(("%s: %*s %s: offRead=%RU32, offWrite=%RU32 -> %RU32/%RU32\n", + pszFunc, uIdtLvl * 4, "", + pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cUsed, pMixBuf->cFrames)); +} + +static void audioMixBufDbgPrintInternal(PAUDIOMIXBUF pMixBuf, const char *pszFunc) +{ + audioMixBufDbgPrintSingle(pMixBuf, pszFunc, 0 /* iIdtLevel */); +} + +/** + * Validates a single mixing buffer. + * + * @return @true if the buffer state is valid or @false if not. + * @param pMixBuf Mixing buffer to validate. + */ +static bool audioMixBufDbgValidate(PAUDIOMIXBUF pMixBuf) +{ + //const uint32_t offReadEnd = (pMixBuf->offRead + pMixBuf->cUsed) % pMixBuf->cFrames; + //const uint32_t offWriteEnd = (pMixBuf->offWrite + (pMixBuf->cFrames - pMixBuf->cUsed)) % pMixBuf->cFrames; + + bool fValid = true; + + AssertStmt(pMixBuf->offRead <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->offWrite <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->cUsed <= pMixBuf->cFrames, fValid = false); + + if (pMixBuf->offWrite > pMixBuf->offRead) + { + if (pMixBuf->offWrite - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; + } + else if (pMixBuf->offWrite < pMixBuf->offRead) + { + if (pMixBuf->offWrite + pMixBuf->cFrames - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; + } + + if (!fValid) + { + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); + AssertFailed(); + } + + return fValid; +} + +# endif /* UNUSED */ +#endif /* VBOX_STRICT */ + + +/** + * Merges @a i32Src into the value stored at @a pi32Dst. + * + * @param pi32Dst The value to merge @a i32Src into. + * @param i32Src The new value to add. + */ +DECL_FORCE_INLINE(void) audioMixBufBlendSample(int32_t *pi32Dst, int32_t i32Src) +{ + if (i32Src) + { + int64_t const i32Dst = *pi32Dst; + if (!i32Dst) + *pi32Dst = i32Src; + else + *pi32Dst = (int32_t)(((int64_t)i32Dst + i32Src) / 2); + } +} + + +/** + * Variant of audioMixBufBlendSample that returns the result rather than storing it. + * + * This is used for stereo -> mono. + */ +DECL_FORCE_INLINE(int32_t) audioMixBufBlendSampleRet(int32_t i32Sample1, int32_t i32Sample2) +{ + if (!i32Sample1) + return i32Sample2; + if (!i32Sample2) + return i32Sample1; + return (int32_t)(((int64_t)i32Sample1 + i32Sample2) / 2); +} + + +/** + * Blends (merges) the source buffer into the destination buffer. + * + * We're taking a very simple approach here, working sample by sample: + * - if one is silent, use the other one. + * - otherwise sum and divide by two. + * + * @param pi32Dst The destination stream buffer (input and output). + * @param pi32Src The source stream buffer. + * @param cFrames Number of frames to process. + * @param cChannels Number of channels. + */ +static void audioMixBufBlendBuffer(int32_t *pi32Dst, int32_t const *pi32Src, uint32_t cFrames, uint8_t cChannels) +{ + switch (cChannels) + { + case 2: + while (cFrames-- > 0) + { + audioMixBufBlendSample(&pi32Dst[0], pi32Src[0]); + audioMixBufBlendSample(&pi32Dst[1], pi32Src[1]); + pi32Dst += 2; + pi32Src += 2; + } + break; + + default: + cFrames *= cChannels; + RT_FALL_THROUGH(); + case 1: + while (cFrames-- > 0) + { + audioMixBufBlendSample(pi32Dst, pi32Src[0]); + pi32Dst++; + pi32Src++; + } + break; + } +} + + +#ifdef AUDIOMIXBUF_DEBUG_MACROS +# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x) +#elif defined(VBOX_AUDIO_TESTCASE_VERBOSE) /* Warning: VBOX_AUDIO_TESTCASE_VERBOSE will generate huge logs! */ +# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x +#else +# define AUDMIXBUF_MACRO_LOG(x) do {} while (0) +#endif + +/* + * Instantiate format conversion (in and out of the mixer buffer.) + */ +/** @todo Currently does not handle any endianness conversion yet! */ + +/* audioMixBufConvXXXS8: 8-bit, signed. */ +#define a_Name S8 +#define a_Type int8_t +#define a_Min INT8_MIN +#define a_Max INT8_MAX +#define a_fSigned 1 +#define a_cShift 8 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU8: 8-bit, unsigned. */ +#define a_Name U8 +#define a_Type uint8_t +#define a_Min 0 +#define a_Max UINT8_MAX +#define a_fSigned 0 +#define a_cShift 8 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXS16: 16-bit, signed. */ +#define a_Name S16 +#define a_Type int16_t +#define a_Min INT16_MIN +#define a_Max INT16_MAX +#define a_fSigned 1 +#define a_cShift 16 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU16: 16-bit, unsigned. */ +#define a_Name U16 +#define a_Type uint16_t +#define a_Min 0 +#define a_Max UINT16_MAX +#define a_fSigned 0 +#define a_cShift 16 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXS32: 32-bit, signed. */ +#define a_Name S32 +#define a_Type int32_t +#define a_Min INT32_MIN +#define a_Max INT32_MAX +#define a_fSigned 1 +#define a_cShift 32 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU32: 32-bit, unsigned. */ +#define a_Name U32 +#define a_Type uint32_t +#define a_Min 0 +#define a_Max UINT32_MAX +#define a_fSigned 0 +#define a_cShift 32 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXRaw: 32-bit stored as 64-bit, signed. */ +#define a_Name Raw +#define a_Type int64_t +#define a_Min INT64_MIN +#define a_Max INT64_MAX +#define a_fSigned 1 +#define a_cShift 32 /* Yes, 32! */ +#include "AudioMixBuffer-Convert.cpp.h" + +#undef AUDMIXBUF_CONVERT +#undef AUDMIXBUF_MACRO_LOG + + +/* + * Resampling core. + */ +/** @todo Separate down- and up-sampling, borrow filter code from RDP. */ +#define COPY_LAST_FRAME_1CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + } while (0) +#define COPY_LAST_FRAME_2CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + } while (0) +#define COPY_LAST_FRAME_3CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + } while (0) +#define COPY_LAST_FRAME_4CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + } while (0) +#define COPY_LAST_FRAME_5CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + } while (0) +#define COPY_LAST_FRAME_6CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + } while (0) +#define COPY_LAST_FRAME_7CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + } while (0) +#define COPY_LAST_FRAME_8CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + } while (0) +#define COPY_LAST_FRAME_9CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + } while (0) +#define COPY_LAST_FRAME_10CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + } while (0) +#define COPY_LAST_FRAME_11CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + (a_pi32Dst)[10] = (a_pi32Src)[10]; \ + } while (0) +#define COPY_LAST_FRAME_12CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + (a_pi32Dst)[10] = (a_pi32Src)[10]; \ + (a_pi32Dst)[11] = (a_pi32Src)[11]; \ + } while (0) + +#define INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_iCh) \ + (a_pi32Dst)[a_iCh] = ((a_pi32Last)[a_iCh] * a_i64FactorLast + (a_pi32Src)[a_iCh] * a_i64FactorCur) >> 32 +#define INTERPOLATE_1CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + } while (0) +#define INTERPOLATE_2CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + } while (0) +#define INTERPOLATE_3CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + } while (0) +#define INTERPOLATE_4CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + } while (0) +#define INTERPOLATE_5CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + } while (0) +#define INTERPOLATE_6CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + } while (0) +#define INTERPOLATE_7CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + } while (0) +#define INTERPOLATE_8CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + } while (0) +#define INTERPOLATE_9CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + } while (0) +#define INTERPOLATE_10CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + } while (0) +#define INTERPOLATE_11CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \ + } while (0) +#define INTERPOLATE_12CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 11); \ + } while (0) + +#define AUDIOMIXBUF_RESAMPLE(a_cChannels, a_Suffix) \ + /** @returns Number of destination frames written. */ \ + static DECLCALLBACK(uint32_t) \ + audioMixBufResample##a_cChannels##Ch##a_Suffix(int32_t *pi32Dst, uint32_t cDstFrames, \ + int32_t const *pi32Src, uint32_t cSrcFrames, uint32_t *pcSrcFramesRead, \ + PAUDIOSTREAMRATE pRate) \ + { \ + Log5(("Src: %RU32 L %RU32; Dst: %RU32 L%RU32; uDstInc=%#RX64\n", \ + pRate->offSrc, cSrcFrames, RT_HI_U32(pRate->offDst), cDstFrames, pRate->uDstInc)); \ + int32_t * const pi32DstStart = pi32Dst; \ + int32_t const * const pi32SrcStart = pi32Src; \ + \ + int32_t ai32LastFrame[a_cChannels]; \ + COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, pRate->SrcLast.ai32Samples, a_cChannels); \ + \ + while (cDstFrames > 0 && cSrcFrames > 0) \ + { \ + int32_t const cSrcNeeded = RT_HI_U32(pRate->offDst) - pRate->offSrc + 1; \ + if (cSrcNeeded > 0) \ + { \ + if ((uint32_t)cSrcNeeded + 1 < cSrcFrames) \ + { \ + pRate->offSrc += (uint32_t)cSrcNeeded; \ + cSrcFrames -= (uint32_t)cSrcNeeded; \ + pi32Src += (uint32_t)cSrcNeeded * a_cChannels; \ + COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, &pi32Src[-a_cChannels], a_cChannels); \ + } \ + else \ + { \ + pi32Src += cSrcFrames * a_cChannels; \ + pRate->offSrc += cSrcFrames; \ + COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, &pi32Src[-a_cChannels], a_cChannels); \ + *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \ + return (pi32Dst - pi32DstStart) / a_cChannels; \ + } \ + } \ + \ + /* Interpolate. */ \ + int64_t const offFactorCur = pRate->offDst & UINT32_MAX; \ + int64_t const offFactorLast = (int64_t)_4G - offFactorCur; \ + INTERPOLATE_##a_cChannels##CH(pi32Dst, pi32Src, ai32LastFrame, offFactorCur, offFactorLast, a_cChannels); \ + \ + /* Advance. */ \ + pRate->offDst += pRate->uDstInc; \ + pi32Dst += a_cChannels; \ + cDstFrames -= 1; \ + } \ + \ + COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, ai32LastFrame, a_cChannels); \ + *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \ + return (pi32Dst - pi32DstStart) / a_cChannels; \ + } + +AUDIOMIXBUF_RESAMPLE(1,Generic) +AUDIOMIXBUF_RESAMPLE(2,Generic) +AUDIOMIXBUF_RESAMPLE(3,Generic) +AUDIOMIXBUF_RESAMPLE(4,Generic) +AUDIOMIXBUF_RESAMPLE(5,Generic) +AUDIOMIXBUF_RESAMPLE(6,Generic) +AUDIOMIXBUF_RESAMPLE(7,Generic) +AUDIOMIXBUF_RESAMPLE(8,Generic) +AUDIOMIXBUF_RESAMPLE(9,Generic) +AUDIOMIXBUF_RESAMPLE(10,Generic) +AUDIOMIXBUF_RESAMPLE(11,Generic) +AUDIOMIXBUF_RESAMPLE(12,Generic) + + +/** + * Resets the resampling state unconditionally. + * + * @param pRate The state to reset. + */ +static void audioMixBufRateResetAlways(PAUDIOSTREAMRATE pRate) +{ + pRate->offDst = 0; + pRate->offSrc = 0; + for (uintptr_t i = 0; i < RT_ELEMENTS(pRate->SrcLast.ai32Samples); i++) + pRate->SrcLast.ai32Samples[0] = 0; +} + + +/** + * Resets the resampling state. + * + * @param pRate The state to reset. + */ +DECLINLINE(void) audioMixBufRateReset(PAUDIOSTREAMRATE pRate) +{ + if (pRate->offDst == 0) + { /* likely */ } + else + { + Assert(!pRate->fNoConversionNeeded); + audioMixBufRateResetAlways(pRate); + } +} + + +/** + * Initializes the frame rate converter state. + * + * @returns VBox status code. + * @param pRate The state to initialize. + * @param uSrcHz The source frame rate. + * @param uDstHz The destination frame rate. + * @param cChannels The number of channels in a frame. + */ +DECLINLINE(int) audioMixBufRateInit(PAUDIOSTREAMRATE pRate, uint32_t uSrcHz, uint32_t uDstHz, uint8_t cChannels) +{ + /* + * Do we need to set up frequency conversion? + * + * Some examples to get an idea of what uDstInc holds: + * 44100 to 44100 -> (44100<<32) / 44100 = 0x01'00000000 (4294967296) + * 22050 to 44100 -> (22050<<32) / 44100 = 0x00'80000000 (2147483648) + * 44100 to 22050 -> (44100<<32) / 22050 = 0x02'00000000 (8589934592) + * 44100 to 48000 -> (44100<<32) / 48000 = 0x00'EB333333 (3946001203.2) + * 48000 to 44100 -> (48000<<32) / 44100 = 0x01'16A3B35F (4674794335.7823129251700680272109) + */ + audioMixBufRateResetAlways(pRate); + if (uSrcHz == uDstHz) + { + pRate->fNoConversionNeeded = true; + pRate->uDstInc = RT_BIT_64(32); + pRate->pfnResample = NULL; + } + else + { + pRate->fNoConversionNeeded = false; + pRate->uDstInc = ((uint64_t)uSrcHz << 32) / uDstHz; + AssertReturn(uSrcHz != 0, VERR_INVALID_PARAMETER); + switch (cChannels) + { + case 1: pRate->pfnResample = audioMixBufResample1ChGeneric; break; + case 2: pRate->pfnResample = audioMixBufResample2ChGeneric; break; + case 3: pRate->pfnResample = audioMixBufResample3ChGeneric; break; + case 4: pRate->pfnResample = audioMixBufResample4ChGeneric; break; + case 5: pRate->pfnResample = audioMixBufResample5ChGeneric; break; + case 6: pRate->pfnResample = audioMixBufResample6ChGeneric; break; + case 7: pRate->pfnResample = audioMixBufResample7ChGeneric; break; + case 8: pRate->pfnResample = audioMixBufResample8ChGeneric; break; + case 9: pRate->pfnResample = audioMixBufResample9ChGeneric; break; + case 10: pRate->pfnResample = audioMixBufResample10ChGeneric; break; + case 11: pRate->pfnResample = audioMixBufResample11ChGeneric; break; + case 12: pRate->pfnResample = audioMixBufResample12ChGeneric; break; + default: + AssertMsgFailedReturn(("resampling %u changes is not implemented yet\n", cChannels), VERR_OUT_OF_RANGE); + } + } + return VINF_SUCCESS; +} + + +/** + * Initializes a mixing buffer. + * + * @returns VBox status code. + * @param pMixBuf Mixing buffer to initialize. + * @param pszName Name of mixing buffer for easier identification. Optional. + * @param pProps PCM audio properties to use for the mixing buffer. + * @param cFrames Maximum number of audio frames the mixing buffer can hold. + */ +int AudioMixBufInit(PAUDIOMIXBUF pMixBuf, const char *pszName, PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + Assert(PDMAudioPropsAreValid(pProps)); + + /* + * Initialize all members, setting the volume to max (0dB). + */ + pMixBuf->cFrames = 0; + pMixBuf->pi32Samples = NULL; + pMixBuf->cChannels = 0; + pMixBuf->cbFrame = 0; + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cUsed = 0; + pMixBuf->Props = *pProps; + pMixBuf->Volume.fMuted = false; + pMixBuf->Volume.fAllMax = true; + for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++) + pMixBuf->Volume.auChannels[i] = AUDIOMIXBUF_VOL_0DB; + + int rc; + uint8_t const cChannels = PDMAudioPropsChannels(pProps); + if (cChannels >= 1 && cChannels <= PDMAUDIO_MAX_CHANNELS) + { + pMixBuf->pszName = RTStrDup(pszName); + if (pMixBuf->pszName) + { + pMixBuf->pi32Samples = (int32_t *)RTMemAllocZ(cFrames * cChannels * sizeof(pMixBuf->pi32Samples[0])); + if (pMixBuf->pi32Samples) + { + pMixBuf->cFrames = cFrames; + pMixBuf->cChannels = cChannels; + pMixBuf->cbFrame = cChannels * sizeof(pMixBuf->pi32Samples[0]); + pMixBuf->uMagic = AUDIOMIXBUF_MAGIC; +#ifdef AUDMIXBUF_LOG_ENABLED + char szTmp[PDMAUDIOPROPSTOSTRING_MAX]; + AUDMIXBUF_LOG(("%s: %s - cFrames=%#x (%d)\n", + pMixBuf->pszName, PDMAudioPropsToString(pProps, szTmp, sizeof(szTmp)), cFrames, cFrames)); +#endif + return VINF_SUCCESS; + } + RTStrFree(pMixBuf->pszName); + pMixBuf->pszName = NULL; + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_STR_MEMORY; + } + else + { + LogRelMaxFunc(64, ("cChannels=%d pszName=%s\n", cChannels, pszName)); + rc = VERR_OUT_OF_RANGE; + } + pMixBuf->uMagic = AUDIOMIXBUF_MAGIC_DEAD; + return rc; +} + +/** + * Terminates (uninitializes) a mixing buffer. + * + * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be + * quietly ignored. As will NULL. + */ +void AudioMixBufTerm(PAUDIOMIXBUF pMixBuf) +{ + if (!pMixBuf) + return; + + /* Ignore calls for an uninitialized (zeroed) or already destroyed instance. Happens a lot. */ + if ( pMixBuf->uMagic == 0 + || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD) + { + Assert(!pMixBuf->pszName); + Assert(!pMixBuf->pi32Samples); + Assert(!pMixBuf->cFrames); + return; + } + + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + pMixBuf->uMagic = ~AUDIOMIXBUF_MAGIC; + + if (pMixBuf->pszName) + { + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + RTStrFree(pMixBuf->pszName); + pMixBuf->pszName = NULL; + } + + if (pMixBuf->pi32Samples) + { + Assert(pMixBuf->cFrames); + RTMemFree(pMixBuf->pi32Samples); + pMixBuf->pi32Samples = NULL; + } + + pMixBuf->cFrames = 0; + pMixBuf->cChannels = 0; +} + + +/** + * Drops all the frames in the given mixing buffer + * + * This will reset the read and write offsets to zero. + * + * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be + * quietly ignored. + */ +void AudioMixBufDrop(PAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturnVoid(pMixBuf); + + /* Ignore uninitialized (zeroed) mixer sink buffers (happens with AC'97 during VM construction). */ + if ( pMixBuf->uMagic == 0 + || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD) + return; + + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cUsed = 0; +} + + +/** + * Gets the maximum number of audio frames this buffer can hold. + * + * @returns Number of frames. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufSize(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->cFrames; +} + + +/** + * Gets the maximum number of bytes this buffer can hold. + * + * @returns Number of bytes. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufSizeBytes(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + AssertReturn(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC, 0); + return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames); +} + + +/** + * Worker for AudioMixBufUsed and AudioMixBufUsedBytes. + */ +DECLINLINE(uint32_t) audioMixBufUsedInternal(PCAUDIOMIXBUF pMixBuf) +{ + uint32_t const cFrames = pMixBuf->cFrames; + uint32_t cUsed = pMixBuf->cUsed; + AssertStmt(cUsed <= cFrames, cUsed = cFrames); + return cUsed; +} + + +/** + * Get the number of used (readable) frames in the buffer. + * + * @returns Number of frames. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufUsed(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return audioMixBufUsedInternal(pMixBuf); +} + + +/** + * Get the number of (readable) bytes in the buffer. + * + * @returns Number of bytes. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufUsedBytes(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufUsedInternal(pMixBuf)); +} + + +/** + * Worker for AudioMixBufFree and AudioMixBufFreeBytes. + */ +DECLINLINE(uint32_t) audioMixBufFreeInternal(PCAUDIOMIXBUF pMixBuf) +{ + uint32_t const cFrames = pMixBuf->cFrames; + uint32_t cUsed = pMixBuf->cUsed; + AssertStmt(cUsed <= cFrames, cUsed = cFrames); + uint32_t const cFramesFree = cFrames - cUsed; + + AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames)); + return cFramesFree; +} + + +/** + * Gets the free buffer space in frames. + * + * @return Number of frames. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufFree(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return audioMixBufFreeInternal(pMixBuf); +} + + +/** + * Gets the free buffer space in bytes. + * + * @return Number of bytes. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufFreeBytes(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufFreeInternal(pMixBuf)); +} + + +/** + * Checks if the buffer is empty. + * + * @retval true if empty buffer. + * @retval false if not empty and there are frames to be processed. + * @param pMixBuf The mixing buffer. + */ +bool AudioMixBufIsEmpty(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, true); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->cUsed == 0; +} + + +/** + * Get the current read position. + * + * This is for the testcase. + * + * @returns Frame number. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufReadPos(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->offRead; +} + + +/** + * Gets the current write position. + * + * This is for the testcase. + * + * @returns Frame number. + * @param pMixBuf The mixing buffer. + */ +uint32_t AudioMixBufWritePos(PCAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->offWrite; +} + + +/** + * Creates a mapping between desination channels and source source channels. + * + * @param paidxChannelMap Where to store the mapping. Indexed by + * destination channel. Entry is either source + * channel index or -1 for zero and -2 for silence. + * @param pSrcProps The source properties. + * @param pDstProps The desination properties. + */ +static void audioMixBufInitChannelMap(int8_t paidxChannelMap[PDMAUDIO_MAX_CHANNELS], + PCPDMAUDIOPCMPROPS pSrcProps, PCPDMAUDIOPCMPROPS pDstProps) +{ + uintptr_t const cDstChannels = PDMAudioPropsChannels(pDstProps); + uintptr_t const cSrcChannels = PDMAudioPropsChannels(pSrcProps); + uintptr_t idxDst; + for (idxDst = 0; idxDst < cDstChannels; idxDst++) + { + uint8_t const idDstCh = pDstProps->aidChannels[idxDst]; + if (idDstCh >= PDMAUDIOCHANNELID_FRONT_LEFT && idDstCh < PDMAUDIOCHANNELID_END) + { + uintptr_t idxSrc; + for (idxSrc = 0; idxSrc < cSrcChannels; idxSrc++) + if (idDstCh == pSrcProps->aidChannels[idxSrc]) + { + paidxChannelMap[idxDst] = idxSrc; + break; + } + if (idxSrc >= cSrcChannels) + { + /** @todo deal with mono. */ + paidxChannelMap[idxDst] = -2; + } + } + else if (idDstCh == PDMAUDIOCHANNELID_UNKNOWN) + { + /** @todo What to do here? Pick unused source channels in order? */ + paidxChannelMap[idxDst] = -2; + } + else + { + AssertMsg(idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE || idDstCh == PDMAUDIOCHANNELID_UNUSED_ZERO, + ("idxDst=%u idDstCh=%u\n", idxDst, idDstCh)); + paidxChannelMap[idxDst] = idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE ? -2 : -1; + } + } + + /* Set the remainder to -1 just to be sure their are safe. */ + for (; idxDst < PDMAUDIO_MAX_CHANNELS; idxDst++) + paidxChannelMap[idxDst] = -1; +} + + +/** + * Initializes the peek state, setting up encoder and (if necessary) resampling. + * + * @returns VBox status code. + */ +int AudioMixBufInitPeekState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFPEEKSTATE pState, PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtr(pMixBuf); + AssertPtr(pState); + AssertPtr(pProps); + + /* + * Pick the encoding function first. + */ + uint8_t const cbSample = PDMAudioPropsSampleSize(pProps); + uint8_t const cSrcCh = PDMAudioPropsChannels(&pMixBuf->Props); + uint8_t const cDstCh = PDMAudioPropsChannels(pProps); + pState->cSrcChannels = cSrcCh; + pState->cDstChannels = cDstCh; + pState->cbDstFrame = PDMAudioPropsFrameSize(pProps); + audioMixBufInitChannelMap(pState->aidxChannelMap, &pMixBuf->Props, pProps); + AssertReturn(cDstCh > 0 && cDstCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE); + AssertReturn(cSrcCh > 0 && cSrcCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE); + + if (PDMAudioPropsIsSigned(pProps)) + { + /* Assign generic encoder first. */ + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncodeGenericS8; break; + case 2: pState->pfnEncode = audioMixBufEncodeGenericS16; break; + case 4: pState->pfnEncode = audioMixBufEncodeGenericS32; break; + case 8: + AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT); + pState->pfnEncode = audioMixBufEncodeGenericRaw; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } + + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode1ChTo1ChRaw; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode2ChTo1ChRaw; break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode1ChTo2ChRaw; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode2ChTo2ChRaw; break; + } + break; + } + } + else + { + /* Assign generic encoder first. */ + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncodeGenericU8; break; + case 2: pState->pfnEncode = audioMixBufEncodeGenericU16; break; + case 4: pState->pfnEncode = audioMixBufEncodeGenericU32; break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } + + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChU32; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChU32; break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChU32; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChU32; break; + } + break; + } + } + + int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(&pMixBuf->Props), PDMAudioPropsHz(pProps), cSrcCh); + AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(&pMixBuf->Props), + PDMAudioPropsHz(pProps), pState->Rate.uDstInc)); + return rc; +} + + +/** + * Initializes the write/blend state, setting up decoders and (if necessary) + * resampling. + * + * @returns VBox status code. + */ +int AudioMixBufInitWriteState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtr(pMixBuf); + AssertPtr(pState); + AssertPtr(pProps); + + /* + * Pick the encoding function first. + */ + uint8_t const cbSample = PDMAudioPropsSampleSize(pProps); + uint8_t const cSrcCh = PDMAudioPropsChannels(pProps); + uint8_t const cDstCh = PDMAudioPropsChannels(&pMixBuf->Props); + pState->cSrcChannels = cSrcCh; + pState->cDstChannels = cDstCh; + pState->cbSrcFrame = PDMAudioPropsFrameSize(pProps); + audioMixBufInitChannelMap(pState->aidxChannelMap, pProps, &pMixBuf->Props); + + if (PDMAudioPropsIsSigned(pProps)) + { + /* Assign generic decoders first. */ + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecodeGenericS8; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecodeGenericS16; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecodeGenericS32; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS32Blend; + break; + case 8: + AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT); + pState->pfnDecode = audioMixBufDecodeGenericRaw; + pState->pfnDecodeBlend = audioMixBufDecodeGenericRawBlend; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } + + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode1ChTo1ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChRawBlend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode2ChTo1ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChRawBlend; + break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode1ChTo2ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChRawBlend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode2ChTo2ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChRawBlend; + break; + } + break; + } + } + else + { + /* Assign generic decoders first. */ + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecodeGenericU8; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecodeGenericU16; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecodeGenericU32; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU32Blend; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } + + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU32Blend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU32Blend; + break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU32Blend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU32Blend; + break; + } + break; + } + } + + int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(pProps), PDMAudioPropsHz(&pMixBuf->Props), cDstCh); + AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(pProps), + PDMAudioPropsHz(&pMixBuf->Props), pState->Rate.uDstInc)); + return rc; +} + + +/** + * Worker for AudioMixBufPeek that handles the rate conversion case. + */ +DECL_NO_INLINE(static, void) +audioMixBufPeekResampling(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked, + PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked) +{ + *pcSrcFramesPeeked = 0; + *pcbDstPeeked = 0; + while (cMaxSrcFrames > 0 && cbDst >= pState->cbDstFrame) + { + /* Rate conversion into temporary buffer. */ + int32_t ai32DstRate[1024]; + uint32_t cSrcFrames = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames); + uint32_t cDstMaxFrames = RT_MIN(RT_ELEMENTS(ai32DstRate) / pState->cSrcChannels, cbDst / pState->cbDstFrame); + uint32_t const cDstFrames = pState->Rate.pfnResample(ai32DstRate, cDstMaxFrames, + &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + *pcSrcFramesPeeked += cSrcFrames; + cMaxSrcFrames -= cSrcFrames; + offSrcFrame = (offSrcFrame + cSrcFrames) % pMixBuf->cFrames; + + /* Encode the converted frames. */ + uint32_t const cbDstEncoded = cDstFrames * pState->cbDstFrame; + pState->pfnEncode(pvDst, ai32DstRate, cDstFrames, pState); + *pcbDstPeeked += cbDstEncoded; + cbDst -= cbDstEncoded; + pvDst = (uint8_t *)pvDst + cbDstEncoded; + } +} + + +/** + * Copies data out of the mixing buffer, converting it if needed, but leaves the + * read offset untouched. + * + * @param pMixBuf The mixing buffer. + * @param offSrcFrame The offset to start reading at relative to + * current read position (offRead). The caller has + * made sure there is at least this number of + * frames available in the buffer before calling. + * @param cMaxSrcFrames Maximum number of frames to read. + * @param pcSrcFramesPeeked Where to return the actual number of frames read + * from the mixing buffer. + * @param pState Output configuration & conversion state. + * @param pvDst The destination buffer. + * @param cbDst The size of the destination buffer in bytes. + * @param pcbDstPeeked Where to put the actual number of bytes + * returned. + */ +void AudioMixBufPeek(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked, + PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked) +{ + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnEncode); + Assert(pState->cSrcChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cMaxSrcFrames > 0); + Assert(cMaxSrcFrames <= pMixBuf->cFrames); + Assert(offSrcFrame <= pMixBuf->cFrames); + Assert(offSrcFrame + cMaxSrcFrames <= pMixBuf->cUsed); + AssertPtr(pcSrcFramesPeeked); + AssertPtr(pvDst); + Assert(cbDst >= pState->cbDstFrame); + AssertPtr(pcbDstPeeked); + + /* + * Make start frame absolute. + */ + offSrcFrame = (pMixBuf->offRead + offSrcFrame) % pMixBuf->cFrames; + + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) + { + /* Figure out how much we should convert. */ + cMaxSrcFrames = RT_MIN(cMaxSrcFrames, cbDst / pState->cbDstFrame); + *pcSrcFramesPeeked = cMaxSrcFrames; + *pcbDstPeeked = cMaxSrcFrames * pState->cbDstFrame; + + /* First chunk. */ + uint32_t const cSrcFrames1 = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames); + pState->pfnEncode(pvDst, &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels], cSrcFrames1, pState); + + /* Another chunk from the start of the mixing buffer? */ + if (cMaxSrcFrames > cSrcFrames1) + pState->pfnEncode((uint8_t *)pvDst + cSrcFrames1 * pState->cbDstFrame, + &pMixBuf->pi32Samples[0], cMaxSrcFrames - cSrcFrames1, pState); + + //Log9Func(("*pcbDstPeeked=%#x\n%32.*Rhxd\n", *pcbDstPeeked, *pcbDstPeeked, pvDst)); + } + else + audioMixBufPeekResampling(pMixBuf, offSrcFrame, cMaxSrcFrames, pcSrcFramesPeeked, pState, pvDst, cbDst, pcbDstPeeked); +} + + +/** + * Worker for AudioMixBufWrite that handles the rate conversion case. + */ +DECL_NO_INLINE(static, void) +audioMixBufWriteResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten) +{ + *pcDstFramesWritten = 0; + while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame) + { + /* Decode into temporary buffer. */ + int32_t ai32Decoded[1024]; + uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame); + pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState); + cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame; + pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame; + + /* Rate convert that into the mixer. */ + uint32_t iFrameDecoded = 0; + while (iFrameDecoded < cFramesDecoded) + { + uint32_t cDstMaxFramesNow = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded; + uint32_t const cDstFrames = pState->Rate.pfnResample(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], + cDstMaxFramesNow, + &ai32Decoded[iFrameDecoded * pState->cDstChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + + iFrameDecoded += cSrcFrames; + *pcDstFramesWritten += cDstFrames; + offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames; + } + } + + /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */ +} + + +/** + * Writes @a cbSrcBuf bytes to the mixer buffer starting at @a offDstFrame, + * converting it as needed, leaving the write offset untouched. + * + * @param pMixBuf The mixing buffer. + * @param pState Source configuration & conversion state. + * @param pvSrcBuf The source frames. + * @param cbSrcBuf Number of bytes of source frames. This will be + * convered in full. + * @param offDstFrame Mixing buffer offset relative to the write + * position. + * @param cDstMaxFrames Max number of frames to write. + * @param pcDstFramesWritten Where to return the number of frames actually + * written. + * + * @note Does not advance the write position, please call AudioMixBufCommit() + * to do that. + */ +void AudioMixBufWrite(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten) +{ + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cDstMaxFrames > 0); + Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed); + Assert(offDstFrame <= pMixBuf->cFrames); + AssertPtr(pvSrcBuf); + Assert(!(cbSrcBuf % pState->cbSrcFrame)); + AssertPtr(pcDstFramesWritten); + + /* + * Make start frame absolute. + */ + offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames; + + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) + { + /* Figure out how much we should convert. */ + Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame); + cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame); + *pcDstFramesWritten = cDstMaxFrames; + + //Log10Func(("cbSrc=%#x\n%32.*Rhxd\n", pState->cbSrcFrame * cDstMaxFrames, pState->cbSrcFrame * cDstMaxFrames, pvSrcBuf)); + + /* First chunk. */ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + pState->pfnDecode(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState); + //Log8Func(("offDstFrame=%#x cDstFrames1=%#x\n%32.*Rhxd\n", offDstFrame, cDstFrames1, + // cDstFrames1 * pMixBuf->cbFrame, &pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels])); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstMaxFrames > cDstFrames1) + { + pState->pfnDecode(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame, + cDstMaxFrames - cDstFrames1, pState); + //Log8Func(("cDstFrames2=%#x\n%32.*Rhxd\n", cDstMaxFrames - cDstFrames1, + // (cDstMaxFrames - cDstFrames1) * pMixBuf->cbFrame, &pMixBuf->pi32Samples[0])); + } + } + else + audioMixBufWriteResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesWritten); +} + + +/** + * Worker for AudioMixBufBlend that handles the rate conversion case. + */ +DECL_NO_INLINE(static, void) +audioMixBufBlendResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended) +{ + *pcDstFramesBlended = 0; + while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame) + { + /* Decode into temporary buffer. This then has the destination channel count. */ + int32_t ai32Decoded[1024]; + uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame); + pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState); + cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame; + pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame; + + /* Rate convert that into another temporary buffer and then blend that into the mixer. */ + uint32_t iFrameDecoded = 0; + while (iFrameDecoded < cFramesDecoded) + { + int32_t ai32Rate[1024]; + uint32_t cDstMaxFramesNow = RT_MIN(RT_ELEMENTS(ai32Rate) / pState->cDstChannels, cDstMaxFrames); + uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded; + uint32_t const cDstFrames = pState->Rate.pfnResample(&ai32Rate[0], cDstMaxFramesNow, + &ai32Decoded[iFrameDecoded * pState->cDstChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + + /* First chunk.*/ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstFrames); + audioMixBufBlendBuffer(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], + ai32Rate, cDstFrames1, pState->cDstChannels); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstFrames > cDstFrames1) + audioMixBufBlendBuffer(&pMixBuf->pi32Samples[0], &ai32Rate[cDstFrames1 * pState->cDstChannels], + cDstFrames - cDstFrames1, pState->cDstChannels); + + /* Advance */ + iFrameDecoded += cSrcFrames; + *pcDstFramesBlended += cDstFrames; + offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames; + } + } + + /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */ +} + + +/** + * @todo not sure if 'blend' is the appropriate term here, but you know what + * we mean. + */ +void AudioMixBufBlend(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended) +{ + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cDstMaxFrames > 0); + Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed); + Assert(offDstFrame <= pMixBuf->cFrames); + AssertPtr(pvSrcBuf); + Assert(!(cbSrcBuf % pState->cbSrcFrame)); + AssertPtr(pcDstFramesBlended); + + /* + * Make start frame absolute. + */ + offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames; + + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) + { + /* Figure out how much we should convert. */ + Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame); + cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame); + *pcDstFramesBlended = cDstMaxFrames; + + /* First chunk. */ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + pState->pfnDecodeBlend(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstMaxFrames > cDstFrames1) + pState->pfnDecodeBlend(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame, + cDstMaxFrames - cDstFrames1, pState); + } + else + audioMixBufBlendResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesBlended); +} + + +/** + * Writes @a cFrames of silence at @a offFrame relative to current write pos. + * + * This will also adjust the resampling state. + * + * @param pMixBuf The mixing buffer. + * @param pState The write state. + * @param offFrame Where to start writing silence relative to the current + * write position. + * @param cFrames Number of frames of silence. + * @sa AudioMixBufWrite + * + * @note Does not advance the write position, please call AudioMixBufCommit() + * to do that. + */ +void AudioMixBufSilence(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t offFrame, uint32_t cFrames) +{ + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cFrames > 0); +#ifdef VBOX_STRICT + uint32_t const cMixBufFree = pMixBuf->cFrames - pMixBuf->cUsed; +#endif + Assert(cFrames <= cMixBufFree); + Assert(offFrame < cMixBufFree); + Assert(offFrame + cFrames <= cMixBufFree); + + /* + * Make start frame absolute. + */ + offFrame = (pMixBuf->offWrite + offFrame) % pMixBuf->cFrames; + + /* + * First chunk. + */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFrame, cFrames); + RT_BZERO(&pMixBuf->pi32Samples[offFrame * pMixBuf->cChannels], cFramesChunk1 * pMixBuf->cbFrame); + + /* + * Second chunk, if needed. + */ + if (cFrames > cFramesChunk1) + { + cFrames -= cFramesChunk1; + AssertStmt(cFrames <= pMixBuf->cFrames, cFrames = pMixBuf->cFrames); + RT_BZERO(&pMixBuf->pi32Samples[0], cFrames * pMixBuf->cbFrame); + } + + /* + * Reset the resampling state. + */ + audioMixBufRateReset(&pState->Rate); +} + + +/** + * Records a blending gap (silence) of @a cFrames. + * + * This is used to adjust or reset the resampling state so we start from a + * silence state the next time we need to blend or write using @a pState. + * + * @param pMixBuf The mixing buffer. + * @param pState The write state. + * @param cFrames Number of frames of silence. + * @sa AudioMixBufSilence + */ +void AudioMixBufBlendGap(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t cFrames) +{ + /* + * For now we'll just reset the resampling state regardless of how many + * frames of silence there is. + */ + audioMixBufRateReset(&pState->Rate); + RT_NOREF(pMixBuf, cFrames); +} + + +/** + * Advances the read position of the buffer. + * + * For use after done peeking with AudioMixBufPeek(). + * + * @param pMixBuf The mixing buffer. + * @param cFrames Number of frames to advance. + * @sa AudioMixBufCommit + */ +void AudioMixBufAdvance(PAUDIOMIXBUF pMixBuf, uint32_t cFrames) +{ + AssertPtrReturnVoid(pMixBuf); + AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + + AssertStmt(cFrames <= pMixBuf->cUsed, cFrames = pMixBuf->cUsed); + pMixBuf->cUsed -= cFrames; + pMixBuf->offRead = (pMixBuf->offRead + cFrames) % pMixBuf->cFrames; + LogFlowFunc(("%s: Advanced %u frames: offRead=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offRead, pMixBuf->cUsed)); +} + + +/** + * Worker for audioMixAdjustVolume that adjust one contiguous chunk. + */ +static void audioMixAdjustVolumeWorker(PAUDIOMIXBUF pMixBuf, uint32_t off, uint32_t cFrames) +{ + int32_t *pi32Samples = &pMixBuf->pi32Samples[off * pMixBuf->cChannels]; + switch (pMixBuf->cChannels) + { + case 1: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + while (cFrames-- > 0) + { + *pi32Samples = (int32_t)(ASMMult2xS32RetS64(*pi32Samples, uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples++; + } + break; + } + + case 2: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 2; + } + break; + } + + case 3: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 3; + } + break; + } + + case 4: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 4; + } + break; + } + + case 5: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 5; + } + break; + } + + case 6: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4]; + uint32_t const uFactorCh5 = pMixBuf->Volume.auChannels[5]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[5] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[5], uFactorCh5) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 6; + } + break; + } + + default: + while (cFrames-- > 0) + for (uint32_t iCh = 0; iCh < pMixBuf->cChannels; iCh++, pi32Samples++) + *pi32Samples = ASMMult2xS32RetS64(*pi32Samples, pMixBuf->Volume.auChannels[iCh]) >> AUDIOMIXBUF_VOL_SHIFT; + break; + } +} + + +/** + * Does volume adjustments for the given stretch of the buffer. + * + * @param pMixBuf The mixing buffer. + * @param offFirst Where to start (validated). + * @param cFrames How many frames (validated). + */ +static void audioMixAdjustVolume(PAUDIOMIXBUF pMixBuf, uint32_t offFirst, uint32_t cFrames) +{ + /* Caller has already validated these, so we don't need to repeat that in non-strict builds. */ + Assert(offFirst < pMixBuf->cFrames); + Assert(cFrames <= pMixBuf->cFrames); + + /* + * Muted? + */ + if (pMixBuf->Volume.fMuted) + { + /* first chunk */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames); + RT_BZERO(&pMixBuf->pi32Samples[offFirst * pMixBuf->cChannels], pMixBuf->cbFrame * cFramesChunk1); + + /* second chunk */ + if (cFramesChunk1 < cFrames) + RT_BZERO(&pMixBuf->pi32Samples[0], pMixBuf->cbFrame * (cFrames - cFramesChunk1)); + } + /* + * Less than max volume? + */ + else if (!pMixBuf->Volume.fAllMax) + { + /* first chunk */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames); + audioMixAdjustVolumeWorker(pMixBuf, offFirst, cFramesChunk1); + + /* second chunk */ + if (cFramesChunk1 < cFrames) + audioMixAdjustVolumeWorker(pMixBuf, 0, cFrames - cFramesChunk1); + } +} + + +/** + * Adjust for volume settings and advances the write position of the buffer. + * + * For use after done peeking with AudioMixBufWrite(), AudioMixBufSilence(), + * AudioMixBufBlend() and AudioMixBufBlendGap(). + * + * @param pMixBuf The mixing buffer. + * @param cFrames Number of frames to advance. + * @sa AudioMixBufAdvance, AudioMixBufSetVolume + */ +void AudioMixBufCommit(PAUDIOMIXBUF pMixBuf, uint32_t cFrames) +{ + AssertPtrReturnVoid(pMixBuf); + AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + + AssertStmt(cFrames <= pMixBuf->cFrames - pMixBuf->cUsed, cFrames = pMixBuf->cFrames - pMixBuf->cUsed); + + audioMixAdjustVolume(pMixBuf, pMixBuf->offWrite, cFrames); + + pMixBuf->cUsed += cFrames; + pMixBuf->offWrite = (pMixBuf->offWrite + cFrames) % pMixBuf->cFrames; + LogFlowFunc(("%s: Advanced %u frames: offWrite=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offWrite, pMixBuf->cUsed)); +} + + +/** + * Sets the volume. + * + * The volume adjustments are applied by AudioMixBufCommit(). + * + * @param pMixBuf Mixing buffer to set volume for. + * @param pVol Pointer to volume structure to set. + */ +void AudioMixBufSetVolume(PAUDIOMIXBUF pMixBuf, PCPDMAUDIOVOLUME pVol) +{ + AssertPtrReturnVoid(pMixBuf); + AssertPtrReturnVoid(pVol); + + LogFlowFunc(("%s: fMuted=%RTbool auChannels=%.*Rhxs\n", + pMixBuf->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels)); + + /* + * Convert PDM audio volume to the internal format. + */ + if (!pVol->fMuted) + { + pMixBuf->Volume.fMuted = false; + + AssertCompileSize(pVol->auChannels[0], sizeof(uint8_t)); + for (uintptr_t i = 0; i < pMixBuf->cChannels; i++) + pMixBuf->Volume.auChannels[i] = s_aVolumeConv[pVol->auChannels[i]] * (AUDIOMIXBUF_VOL_0DB >> 16); + + pMixBuf->Volume.fAllMax = true; + for (uintptr_t i = 0; i < pMixBuf->cChannels; i++) + if (pMixBuf->Volume.auChannels[i] != AUDIOMIXBUF_VOL_0DB) + { + pMixBuf->Volume.fAllMax = false; + break; + } + } + else + { + pMixBuf->Volume.fMuted = true; + pMixBuf->Volume.fAllMax = false; + for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++) + pMixBuf->Volume.auChannels[i] = 0; + } +} + -- cgit v1.2.3