Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FAudio.c
3010 lines | 78.7 KiB | text/x-c | CLexer
/* FAudio - XAudio Reimplementation for FNA
*
* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
*
*/
#include "FAudio_internal.h"
#define MAKE_SUBFORMAT_GUID(guid, fmt) \
FAudioGUID DATAFORMAT_SUBTYPE_##guid = \
{ \
(uint16_t) (fmt), \
0x0000, \
0x0010, \
{ \
0x80, \
0x00, \
0x00, \
0xAA, \
0x00, \
0x38, \
0x9B, \
0x71 \
} \
}
MAKE_SUBFORMAT_GUID(PCM, 1);
MAKE_SUBFORMAT_GUID(ADPCM, 2);
MAKE_SUBFORMAT_GUID(IEEE_FLOAT, 3);
MAKE_SUBFORMAT_GUID(XMAUDIO2, FAUDIO_FORMAT_XMAUDIO2);
MAKE_SUBFORMAT_GUID(WMAUDIO2, FAUDIO_FORMAT_WMAUDIO2);
MAKE_SUBFORMAT_GUID(WMAUDIO3, FAUDIO_FORMAT_WMAUDIO3);
MAKE_SUBFORMAT_GUID(WMAUDIO_LOSSLESS, FAUDIO_FORMAT_WMAUDIO_LOSSLESS);
#undef MAKE_SUBFORMAT_GUID
#ifdef FAUDIO_DUMP_VOICES
static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice);
static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice);
static void FAudio_DUMPVOICE_WriteBuffer(
const FAudioSourceVoice *voice,
const FAudioBuffer *pBuffer,
const FAudioBufferWMA *pBufferWMA,
const uint32_t playBegin,
const uint32_t playLength
);
#endif /* FAUDIO_DUMP_VOICES */
/* FAudio Version */
uint32_t FAudioLinkedVersion(void)
{
return FAUDIO_COMPILED_VERSION;
}
/* FAudio Interface */
uint32_t FAudioCreate(
FAudio **ppFAudio,
uint32_t Flags,
FAudioProcessor XAudio2Processor
) {
FAudioCOMConstructEXT(ppFAudio, FAUDIO_TARGET_VERSION);
FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor);
return 0;
}
uint32_t FAudioCOMConstructEXT(FAudio **ppFAudio, uint8_t version)
{
return FAudioCOMConstructWithCustomAllocatorEXT(
ppFAudio,
version,
FAudio_malloc,
FAudio_free,
FAudio_realloc
);
}
uint32_t FAudioCreateWithCustomAllocatorEXT(
FAudio **ppFAudio,
uint32_t Flags,
FAudioProcessor XAudio2Processor,
FAudioMallocFunc customMalloc,
FAudioFreeFunc customFree,
FAudioReallocFunc customRealloc
) {
FAudioCOMConstructWithCustomAllocatorEXT(
ppFAudio,
FAUDIO_TARGET_VERSION,
customMalloc,
customFree,
customRealloc
);
FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor);
return 0;
}
uint32_t FAudioCOMConstructWithCustomAllocatorEXT(
FAudio **ppFAudio,
uint8_t version,
FAudioMallocFunc customMalloc,
FAudioFreeFunc customFree,
FAudioReallocFunc customRealloc
) {
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
FAudioDebugConfiguration debugInit = {0};
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
FAudio_PlatformAddRef();
*ppFAudio = (FAudio*) customMalloc(sizeof(FAudio));
FAudio_zero(*ppFAudio, sizeof(FAudio));
(*ppFAudio)->version = version;
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
FAudio_SetDebugConfiguration(*ppFAudio, &debugInit, NULL);
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
(*ppFAudio)->sourceLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->sourceLock)
(*ppFAudio)->submixLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->submixLock)
(*ppFAudio)->callbackLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->callbackLock)
(*ppFAudio)->operationLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->operationLock)
(*ppFAudio)->pMalloc = customMalloc;
(*ppFAudio)->pFree = customFree;
(*ppFAudio)->pRealloc = customRealloc;
(*ppFAudio)->refcount = 1;
return 0;
}
uint32_t FAudio_AddRef(FAudio *audio)
{
LOG_API_ENTER(audio)
audio->refcount += 1;
LOG_API_EXIT(audio)
return audio->refcount;
}
uint32_t FAudio_Release(FAudio *audio)
{
uint32_t refcount;
LOG_API_ENTER(audio)
audio->refcount -= 1;
refcount = audio->refcount;
if (audio->refcount == 0)
{
FAudio_OPERATIONSET_ClearAll(audio);
FAudio_StopEngine(audio);
audio->pFree(audio->decodeCache);
audio->pFree(audio->resampleCache);
audio->pFree(audio->effectChainCache);
LOG_MUTEX_DESTROY(audio, audio->sourceLock)
FAudio_PlatformDestroyMutex(audio->sourceLock);
LOG_MUTEX_DESTROY(audio, audio->submixLock)
FAudio_PlatformDestroyMutex(audio->submixLock);
LOG_MUTEX_DESTROY(audio, audio->callbackLock)
FAudio_PlatformDestroyMutex(audio->callbackLock);
LOG_MUTEX_DESTROY(audio, audio->operationLock)
FAudio_PlatformDestroyMutex(audio->operationLock);
audio->pFree(audio);
FAudio_PlatformRelease();
}
else
{
LOG_API_EXIT(audio)
}
return refcount;
}
uint32_t FAudio_GetDeviceCount(FAudio *audio, uint32_t *pCount)
{
LOG_API_ENTER(audio)
*pCount = FAudio_PlatformGetDeviceCount();
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_GetDeviceDetails(
FAudio *audio,
uint32_t Index,
FAudioDeviceDetails *pDeviceDetails
) {
uint32_t result;
LOG_API_ENTER(audio)
result = FAudio_PlatformGetDeviceDetails(Index, pDeviceDetails);
LOG_API_EXIT(audio)
return result;
}
uint32_t FAudio_Initialize(
FAudio *audio,
uint32_t Flags,
FAudioProcessor XAudio2Processor
) {
LOG_API_ENTER(audio)
FAudio_assert(Flags == 0);
FAudio_assert(XAudio2Processor == FAUDIO_DEFAULT_PROCESSOR);
audio->initFlags = Flags;
/* FIXME: This is lazy... */
audio->decodeCache = (float*) audio->pMalloc(sizeof(float));
audio->resampleCache = (float*) audio->pMalloc(sizeof(float));
audio->decodeSamples = 1;
audio->resampleSamples = 1;
FAudio_StartEngine(audio);
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_RegisterForCallbacks(
FAudio *audio,
FAudioEngineCallback *pCallback
) {
LOG_API_ENTER(audio)
LinkedList_AddEntry(
&audio->callbacks,
pCallback,
audio->callbackLock,
audio->pMalloc
);
LOG_API_EXIT(audio)
return 0;
}
void FAudio_UnregisterForCallbacks(
FAudio *audio,
FAudioEngineCallback *pCallback
) {
LOG_API_ENTER(audio)
LinkedList_RemoveEntry(
&audio->callbacks,
pCallback,
audio->callbackLock,
audio->pFree
);
LOG_API_EXIT(audio)
}
uint32_t FAudio_CreateSourceVoice(
FAudio *audio,
FAudioSourceVoice **ppSourceVoice,
const FAudioWaveFormatEx *pSourceFormat,
uint32_t Flags,
float MaxFrequencyRatio,
FAudioVoiceCallback *pCallback,
const FAudioVoiceSends *pSendList,
const FAudioEffectChain *pEffectChain
) {
uint32_t i;
LOG_API_ENTER(audio)
LOG_FORMAT(audio, pSourceFormat)
*ppSourceVoice = (FAudioSourceVoice*) audio->pMalloc(sizeof(FAudioVoice));
FAudio_zero(*ppSourceVoice, sizeof(FAudioSourceVoice));
(*ppSourceVoice)->audio = audio;
(*ppSourceVoice)->type = FAUDIO_VOICE_SOURCE;
(*ppSourceVoice)->flags = Flags;
(*ppSourceVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE;
(*ppSourceVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
(*ppSourceVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
(*ppSourceVoice)->sendLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->sendLock)
(*ppSourceVoice)->effectLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->effectLock)
(*ppSourceVoice)->filterLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->filterLock)
(*ppSourceVoice)->volumeLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->volumeLock)
/* Source Properties */
FAudio_assert(MaxFrequencyRatio <= FAUDIO_MAX_FREQ_RATIO);
(*ppSourceVoice)->src.maxFreqRatio = MaxFrequencyRatio;
if ( pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM ||
pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT ||
pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2 ||
pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 )
{
FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) audio->pMalloc(
sizeof(FAudioWaveFormatExtensible)
);
/* convert PCM to EXTENSIBLE */
fmtex->Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE;
fmtex->Format.nChannels = pSourceFormat->nChannels;
fmtex->Format.nSamplesPerSec = pSourceFormat->nSamplesPerSec;
fmtex->Format.nAvgBytesPerSec = pSourceFormat->nAvgBytesPerSec;
fmtex->Format.nBlockAlign = pSourceFormat->nBlockAlign;
fmtex->Format.wBitsPerSample = pSourceFormat->wBitsPerSample;
fmtex->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);
fmtex->Samples.wValidBitsPerSample = pSourceFormat->wBitsPerSample;
fmtex->dwChannelMask = 0;
if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM)
{
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_PCM, sizeof(FAudioGUID));
}
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT)
{
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID));
}
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2)
{
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_XMAUDIO2, sizeof(FAudioGUID));
}
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2)
{
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO2, sizeof(FAudioGUID));
}
(*ppSourceVoice)->src.format = &fmtex->Format;
}
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_MSADPCM)
{
FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) audio->pMalloc(
sizeof(FAudioADPCMWaveFormat)
);
/* Copy what we can, ideally the sizes match! */
size_t cbSize = sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize;
FAudio_memcpy(
fmtex,
pSourceFormat,
FAudio_min(cbSize, sizeof(FAudioADPCMWaveFormat))
);
if (cbSize < sizeof(FAudioADPCMWaveFormat))
{
FAudio_zero(
((uint8_t*) fmtex) + cbSize,
sizeof(FAudioADPCMWaveFormat) - cbSize
);
}
/* XAudio2 does not validate this input! */
fmtex->wfx.cbSize = sizeof(FAudioADPCMWaveFormat) - sizeof(FAudioWaveFormatEx);
fmtex->wSamplesPerBlock = ((
fmtex->wfx.nBlockAlign / fmtex->wfx.nChannels
) - 6) * 2;
(*ppSourceVoice)->src.format = &fmtex->wfx;
}
else
{
/* direct copy anything else */
(*ppSourceVoice)->src.format = (FAudioWaveFormatEx*) audio->pMalloc(
sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize
);
FAudio_memcpy(
(*ppSourceVoice)->src.format,
pSourceFormat,
sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize
);
}
(*ppSourceVoice)->src.callback = pCallback;
(*ppSourceVoice)->src.active = 0;
(*ppSourceVoice)->src.freqRatio = 1.0f;
(*ppSourceVoice)->src.totalSamples = 0;
(*ppSourceVoice)->src.bufferList = NULL;
(*ppSourceVoice)->src.flushList = NULL;
(*ppSourceVoice)->src.bufferLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->src.bufferLock)
if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE)
{
FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) (*ppSourceVoice)->src.format;
#define COMPARE_GUID(type) \
(FAudio_memcmp( \
&fmtex->SubFormat, \
&DATAFORMAT_SUBTYPE_##type, \
sizeof(FAudioGUID) \
) == 0)
if (COMPARE_GUID(PCM))
{
#define DECODER(bit) \
if (fmtex->Format.wBitsPerSample == bit) \
{ \
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM##bit; \
}
DECODER(16)
else DECODER(8)
else DECODER(24)
else DECODER(32)
else
{
LOG_ERROR(
audio,
"Unrecognized wBitsPerSample: %d",
fmtex->Format.wBitsPerSample
)
FAudio_assert(0 && "Unrecognized wBitsPerSample!");
}
#undef DECODER
}
else if (COMPARE_GUID(IEEE_FLOAT))
{
/* FIXME: Weird behavior!
* Prototype creates a source with the IEEE_FLOAT tag,
* but it's actually PCM16. It seems to prioritize
* wBitsPerSample over the format tag. Not sure if we
* should fold this section into the section above...?
* -flibit
*/
if (fmtex->Format.wBitsPerSample == 16)
{
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM16;
}
else
{
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM32F;
}
}
else if ( COMPARE_GUID(WMAUDIO2) ||
COMPARE_GUID(WMAUDIO3) ||
COMPARE_GUID(WMAUDIO_LOSSLESS) ||
COMPARE_GUID(XMAUDIO2) )
{
#ifdef HAVE_FFMPEG
if (FAudio_FFMPEG_init(*ppSourceVoice, fmtex->SubFormat.Data1) != 0)
{
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR;
}
#else
FAudio_assert(0 && "xWMA is not supported!");
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR;
#endif /* HAVE_FFMPEG */
}
else
{
FAudio_assert(0 && "Unsupported WAVEFORMATEXTENSIBLE subtype!");
}
#undef COMPARE_GUID
}
else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
{
(*ppSourceVoice)->src.decode = ((*ppSourceVoice)->src.format->nChannels == 2) ?
FAudio_INTERNAL_DecodeStereoMSADPCM :
FAudio_INTERNAL_DecodeMonoMSADPCM;
}
else
{
FAudio_assert(0 && "Unsupported format tag!");
}
if ((*ppSourceVoice)->src.format->nChannels == 1)
{
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleMono;
}
else if ((*ppSourceVoice)->src.format->nChannels == 2)
{
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleStereo;
}
else
{
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleGeneric;
}
(*ppSourceVoice)->src.curBufferOffset = 0;
/* Sends/Effects */
FAudio_INTERNAL_VoiceOutputFrequency(*ppSourceVoice, pSendList);
FAudioVoice_SetEffectChain(*ppSourceVoice, pEffectChain);
FAudioVoice_SetOutputVoices(*ppSourceVoice, pSendList);
/* Default Levels */
(*ppSourceVoice)->volume = 1.0f;
(*ppSourceVoice)->channelVolume = (float*) audio->pMalloc(
sizeof(float) * (*ppSourceVoice)->outputChannels
);
for (i = 0; i < (*ppSourceVoice)->outputChannels; i += 1)
{
(*ppSourceVoice)->channelVolume[i] = 1.0f;
}
/* Filters */
if (Flags & FAUDIO_VOICE_USEFILTER)
{
(*ppSourceVoice)->filterState = (FAudioFilterState*) audio->pMalloc(
sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels
);
FAudio_zero(
(*ppSourceVoice)->filterState,
sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels
);
}
/* Sample Storage */
(*ppSourceVoice)->src.decodeSamples = (uint32_t) (FAudio_ceil(
(double) audio->updateSize *
(double) MaxFrequencyRatio *
(double) (*ppSourceVoice)->src.format->nSamplesPerSec /
(double) audio->master->master.inputSampleRate
)) + EXTRA_DECODE_PADDING * (*ppSourceVoice)->src.format->nChannels;
FAudio_INTERNAL_ResizeDecodeCache(
audio,
((*ppSourceVoice)->src.decodeSamples + EXTRA_DECODE_PADDING) * (*ppSourceVoice)->src.format->nChannels
);
LOG_INFO(audio, "-> %p", (void*) (*ppSourceVoice))
/* Add to list, finally. */
LinkedList_PrependEntry(
&audio->sources,
*ppSourceVoice,
audio->sourceLock,
audio->pMalloc
);
FAudio_AddRef(audio);
#ifdef FAUDIO_DUMP_VOICES
FAudio_DUMPVOICE_Init(*ppSourceVoice);
#endif /* FAUDIO_DUMP_VOICES */
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_CreateSubmixVoice(
FAudio *audio,
FAudioSubmixVoice **ppSubmixVoice,
uint32_t InputChannels,
uint32_t InputSampleRate,
uint32_t Flags,
uint32_t ProcessingStage,
const FAudioVoiceSends *pSendList,
const FAudioEffectChain *pEffectChain
) {
uint32_t i;
LOG_API_ENTER(audio)
*ppSubmixVoice = (FAudioSubmixVoice*) audio->pMalloc(sizeof(FAudioVoice));
FAudio_zero(*ppSubmixVoice, sizeof(FAudioSubmixVoice));
(*ppSubmixVoice)->audio = audio;
(*ppSubmixVoice)->type = FAUDIO_VOICE_SUBMIX;
(*ppSubmixVoice)->flags = Flags;
(*ppSubmixVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE;
(*ppSubmixVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
(*ppSubmixVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
(*ppSubmixVoice)->sendLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->sendLock)
(*ppSubmixVoice)->effectLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->effectLock)
(*ppSubmixVoice)->filterLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->filterLock)
(*ppSubmixVoice)->volumeLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->volumeLock)
/* Submix Properties */
(*ppSubmixVoice)->mix.inputChannels = InputChannels;
(*ppSubmixVoice)->mix.inputSampleRate = InputSampleRate;
(*ppSubmixVoice)->mix.processingStage = ProcessingStage;
/* Resampler */
if (InputChannels == 1)
{
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleMono;
}
else if (InputChannels == 2)
{
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleStereo;
}
else
{
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleGeneric;
}
/* Sample Storage */
(*ppSubmixVoice)->mix.inputSamples = ((uint32_t) FAudio_ceil(
audio->updateSize *
(double) InputSampleRate /
(double) audio->master->master.inputSampleRate
) + EXTRA_DECODE_PADDING) * InputChannels;
(*ppSubmixVoice)->mix.inputCache = (float*) audio->pMalloc(
sizeof(float) * (*ppSubmixVoice)->mix.inputSamples
);
FAudio_zero( /* Zero this now, for the first update */
(*ppSubmixVoice)->mix.inputCache,
sizeof(float) * (*ppSubmixVoice)->mix.inputSamples
);
/* Sends/Effects */
FAudio_INTERNAL_VoiceOutputFrequency(*ppSubmixVoice, pSendList);
FAudioVoice_SetEffectChain(*ppSubmixVoice, pEffectChain);
FAudioVoice_SetOutputVoices(*ppSubmixVoice, pSendList);
/* Default Levels */
(*ppSubmixVoice)->volume = 1.0f;
(*ppSubmixVoice)->channelVolume = (float*) audio->pMalloc(
sizeof(float) * (*ppSubmixVoice)->outputChannels
);
for (i = 0; i < (*ppSubmixVoice)->outputChannels; i += 1)
{
(*ppSubmixVoice)->channelVolume[i] = 1.0f;
}
/* Filters */
if (Flags & FAUDIO_VOICE_USEFILTER)
{
(*ppSubmixVoice)->filterState = (FAudioFilterState*) audio->pMalloc(
sizeof(FAudioFilterState) * InputChannels
);
FAudio_zero(
(*ppSubmixVoice)->filterState,
sizeof(FAudioFilterState) * InputChannels
);
}
/* Add to list, finally. */
FAudio_INTERNAL_InsertSubmixSorted(
&audio->submixes,
*ppSubmixVoice,
audio->submixLock,
audio->pMalloc
);
FAudio_AddRef(audio);
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_CreateMasteringVoice(
FAudio *audio,
FAudioMasteringVoice **ppMasteringVoice,
uint32_t InputChannels,
uint32_t InputSampleRate,
uint32_t Flags,
uint32_t DeviceIndex,
const FAudioEffectChain *pEffectChain
) {
FAudioDeviceDetails details;
LOG_API_ENTER(audio)
/* For now we only support one allocated master voice at a time */
FAudio_assert(audio->master == NULL);
if (FAudio_GetDeviceDetails(audio, DeviceIndex, &details) != 0)
{
return FAUDIO_E_INVALID_CALL;
}
*ppMasteringVoice = (FAudioMasteringVoice*) audio->pMalloc(sizeof(FAudioVoice));
FAudio_zero(*ppMasteringVoice, sizeof(FAudioMasteringVoice));
(*ppMasteringVoice)->audio = audio;
(*ppMasteringVoice)->type = FAUDIO_VOICE_MASTER;
(*ppMasteringVoice)->flags = Flags;
(*ppMasteringVoice)->effectLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->effectLock)
(*ppMasteringVoice)->volumeLock = FAudio_PlatformCreateMutex();
LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->volumeLock)
/* Default Levels */
(*ppMasteringVoice)->volume = 1.0f;
/* Master Properties */
(*ppMasteringVoice)->master.inputChannels = (InputChannels == FAUDIO_DEFAULT_CHANNELS) ?
details.OutputFormat.Format.nChannels :
InputChannels;
(*ppMasteringVoice)->master.inputSampleRate = (InputSampleRate == FAUDIO_DEFAULT_SAMPLERATE) ?
details.OutputFormat.Format.nSamplesPerSec :
InputSampleRate;
/* Sends/Effects */
FAudio_zero(&(*ppMasteringVoice)->sends, sizeof(FAudioVoiceSends));
FAudioVoice_SetEffectChain(*ppMasteringVoice, pEffectChain);
/* This is now safe enough to assign */
audio->master = *ppMasteringVoice;
/* Build the device format.
* The most unintuitive part of this is the use of outputChannels
* instead of master.inputChannels. Bizarrely, the effect chain can
* dictate the _actual_ output channel count, and when the channel count
* mismatches, we have to add a staging buffer for effects to process on
* before ultimately copying the final result to the device. ARGH.
*/
WriteWaveFormatExtensible(
&audio->mixFormat,
audio->master->outputChannels,
audio->master->master.inputSampleRate
);
/* Platform Device */
FAudio_AddRef(audio);
FAudio_PlatformInit(
audio,
audio->initFlags,
DeviceIndex,
&audio->mixFormat,
&audio->updateSize,
&audio->platform
);
if (audio->platform == NULL)
{
FAudioVoice_DestroyVoice(*ppMasteringVoice);
*ppMasteringVoice = NULL;
/* Not the best code, but it's probably true? */
return FAUDIO_E_DEVICE_INVALIDATED;
}
audio->master->outputChannels = audio->mixFormat.Format.nChannels;
audio->master->master.inputSampleRate = audio->mixFormat.Format.nSamplesPerSec;
/* Effect Chain Cache */
if ((*ppMasteringVoice)->master.inputChannels != (*ppMasteringVoice)->outputChannels)
{
(*ppMasteringVoice)->master.effectCache = (float*) audio->pMalloc(
sizeof(float) *
audio->updateSize *
(*ppMasteringVoice)->master.inputChannels
);
}
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_CreateMasteringVoice8(
FAudio *audio,
FAudioMasteringVoice **ppMasteringVoice,
uint32_t InputChannels,
uint32_t InputSampleRate,
uint32_t Flags,
uint16_t *szDeviceId,
const FAudioEffectChain *pEffectChain,
FAudioStreamCategory StreamCategory
) {
uint32_t DeviceIndex, retval;
LOG_API_ENTER(audio)
/* Eventually, we'll want the old CreateMastering to call the new one.
* That will depend on us being able to use DeviceID though.
* For now, use our little ID hack to turn szDeviceId into DeviceIndex.
* -flibit
*/
if (szDeviceId == NULL || szDeviceId[0] == 0)
{
DeviceIndex = 0;
}
else
{
DeviceIndex = szDeviceId[0] - L'0';
if (DeviceIndex > FAudio_PlatformGetDeviceCount())
{
DeviceIndex = 0;
}
}
/* Note that StreamCategory is ignored! */
retval = FAudio_CreateMasteringVoice(
audio,
ppMasteringVoice,
InputChannels,
InputSampleRate,
Flags,
DeviceIndex,
pEffectChain
);
LOG_API_EXIT(audio)
return retval;
}
void FAudio_SetEngineProcedureEXT(
FAudio *audio,
FAudioEngineProcedureEXT clientEngineProc,
void *user
) {
LOG_API_ENTER(audio)
audio->pClientEngineProc = clientEngineProc;
audio->clientEngineUser = user;
LOG_API_EXIT(audio)
}
uint32_t FAudio_StartEngine(FAudio *audio)
{
LOG_API_ENTER(audio)
audio->active = 1;
LOG_API_EXIT(audio)
return 0;
}
void FAudio_StopEngine(FAudio *audio)
{
LOG_API_ENTER(audio)
audio->active = 0;
FAudio_OPERATIONSET_CommitAll(audio);
FAudio_OPERATIONSET_Execute(audio);
LOG_API_EXIT(audio)
}
uint32_t FAudio_CommitOperationSet(FAudio *audio, uint32_t OperationSet)
{
LOG_API_ENTER(audio)
if (OperationSet == FAUDIO_COMMIT_ALL)
{
FAudio_OPERATIONSET_CommitAll(audio);
}
else
{
FAudio_OPERATIONSET_Commit(audio, OperationSet);
}
LOG_API_EXIT(audio)
return 0;
}
uint32_t FAudio_CommitChanges(FAudio *audio)
{
FAudio_Log(
"IF YOU CAN READ THIS, YOUR PROGRAM IS ABOUT TO BREAK!"
"\n\nEither you or somebody else is using FAudio_CommitChanges,"
"\nwhen they should be using FAudio_CommitOperationSet instead."
"\n\nIf your program calls this, move to CommitOperationSet."
"\n\nIf somebody else is calling this, find out who it is and"
"\nfile a bug report with them ASAP."
);
/* Seriously, this is like the worst possible thing short of no-oping.
* For the love-a Pete, just migrate, do it, what is wrong with you
*/
return FAudio_CommitOperationSet(audio, FAUDIO_COMMIT_ALL);
}
void FAudio_GetPerformanceData(
FAudio *audio,
FAudioPerformanceData *pPerfData
) {
LinkedList *list;
FAudioSourceVoice *source;
LOG_API_ENTER(audio)
FAudio_zero(pPerfData, sizeof(FAudioPerformanceData));
FAudio_PlatformLockMutex(audio->sourceLock);
LOG_MUTEX_LOCK(audio, audio->sourceLock)
list = audio->sources;
while (list != NULL)
{
source = (FAudioSourceVoice*) list->entry;
pPerfData->TotalSourceVoiceCount += 1;
if (source->src.active)
{
pPerfData->ActiveSourceVoiceCount += 1;
}
list = list->next;
}
FAudio_PlatformUnlockMutex(audio->sourceLock);
LOG_MUTEX_UNLOCK(audio, audio->sourceLock)
FAudio_PlatformLockMutex(audio->submixLock);
LOG_MUTEX_LOCK(audio, audio->submixLock)
list = audio->submixes;
while (list != NULL)
{
pPerfData->ActiveSubmixVoiceCount += 1;
list = list->next;
}
FAudio_PlatformUnlockMutex(audio->submixLock);
LOG_MUTEX_UNLOCK(audio, audio->submixLock)
if (audio->master != NULL)
{
/* estimate, should use real latency from platform */
pPerfData->CurrentLatencyInSamples = 2 * audio->updateSize;
}
LOG_API_EXIT(audio)
}
void FAudio_SetDebugConfiguration(
FAudio *audio,
FAudioDebugConfiguration *pDebugConfiguration,
void* pReserved
) {
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
char *env;
LOG_API_ENTER(audio)
FAudio_memcpy(
&audio->debug,
pDebugConfiguration,
sizeof(FAudioDebugConfiguration)
);
env = FAudio_getenv("FAUDIO_LOG_EVERYTHING");
if (env != NULL && *env == '1')
{
audio->debug.TraceMask = (
FAUDIO_LOG_ERRORS |
FAUDIO_LOG_WARNINGS |
FAUDIO_LOG_INFO |
FAUDIO_LOG_DETAIL |
FAUDIO_LOG_API_CALLS |
FAUDIO_LOG_FUNC_CALLS |
FAUDIO_LOG_TIMING |
FAUDIO_LOG_LOCKS |
FAUDIO_LOG_MEMORY |
FAUDIO_LOG_STREAMING
);
audio->debug.LogThreadID = 1;
audio->debug.LogFunctionName = 1;
audio->debug.LogTiming = 1;
}
#define CHECK_ENV(type) \
env = FAudio_getenv("FAUDIO_LOG_" #type); \
if (env != NULL) \
{ \
if (*env == '1') \
{ \
audio->debug.TraceMask |= FAUDIO_LOG_##type; \
} \
else \
{ \
audio->debug.TraceMask &= ~FAUDIO_LOG_##type; \
} \
}
CHECK_ENV(ERRORS)
CHECK_ENV(WARNINGS)
CHECK_ENV(INFO)
CHECK_ENV(DETAIL)
CHECK_ENV(API_CALLS)
CHECK_ENV(FUNC_CALLS)
CHECK_ENV(TIMING)
CHECK_ENV(LOCKS)
CHECK_ENV(MEMORY)
CHECK_ENV(STREAMING)
#undef CHECK_ENV
#define CHECK_ENV(envvar, boolvar) \
env = FAudio_getenv("FAUDIO_LOG_LOG" #envvar); \
if (env != NULL) \
{ \
audio->debug.Log##boolvar = (*env == '1'); \
}
CHECK_ENV(THREADID, ThreadID)
CHECK_ENV(FILELINE, Fileline)
CHECK_ENV(FUNCTIONNAME, FunctionName)
CHECK_ENV(TIMING, Timing)
#undef CHECK_ENV
LOG_API_EXIT(audio)
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
}
void FAudio_GetProcessingQuantum(
FAudio *audio,
uint32_t *quantumNumerator,
uint32_t *quantumDenominator
) {
FAudio_assert(audio->master != NULL);
if (quantumNumerator != NULL)
{
*quantumNumerator = audio->updateSize;
}
if (quantumDenominator != NULL)
{
*quantumDenominator = audio->master->master.inputSampleRate;
}
}
/* FAudioVoice Interface */
void FAudioVoice_GetVoiceDetails(
FAudioVoice *voice,
FAudioVoiceDetails *pVoiceDetails
) {
LOG_API_ENTER(voice->audio)
pVoiceDetails->CreationFlags = voice->flags;
pVoiceDetails->ActiveFlags = voice->flags;
if (voice->type == FAUDIO_VOICE_SOURCE)
{
pVoiceDetails->InputChannels = voice->src.format->nChannels;
pVoiceDetails->InputSampleRate = voice->src.format->nSamplesPerSec;
}
else if (voice->type == FAUDIO_VOICE_SUBMIX)
{
pVoiceDetails->InputChannels = voice->mix.inputChannels;
pVoiceDetails->InputSampleRate = voice->mix.inputSampleRate;
}
else if (voice->type == FAUDIO_VOICE_MASTER)
{
pVoiceDetails->InputChannels = voice->master.inputChannels;
pVoiceDetails->InputSampleRate = voice->master.inputSampleRate;
}
else
{
FAudio_assert(0 && "Unknown voice type!");
}
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetOutputVoices(
FAudioVoice *voice,
const FAudioVoiceSends *pSendList
) {
uint32_t i;
uint32_t outChannels;
FAudioVoiceSends defaultSends;
FAudioSendDescriptor defaultSend;
LOG_API_ENTER(voice->audio)
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
if (FAudio_INTERNAL_VoiceOutputFrequency(voice, pSendList) != 0)
{
LOG_ERROR(
voice->audio,
"%s",
"Changing the sample rate while an effect chain is attached is invalid!"
)
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
/* FIXME: This is lazy... */
for (i = 0; i < voice->sends.SendCount; i += 1)
{
voice->audio->pFree(voice->sendCoefficients[i]);
}
if (voice->sendCoefficients != NULL)
{
voice->audio->pFree(voice->sendCoefficients);
}
if (voice->sendMix != NULL)
{
voice->audio->pFree(voice->sendMix);
}
if (voice->sendFilter != NULL)
{
voice->audio->pFree(voice->sendFilter);
voice->sendFilter = NULL;
}
if (voice->sendFilterState != NULL)
{
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (voice->sendFilterState[i] != NULL)
{
voice->audio->pFree(voice->sendFilterState[i]);
}
}
voice->audio->pFree(voice->sendFilterState);
voice->sendFilterState = NULL;
}
if (voice->sends.pSends != NULL)
{
voice->audio->pFree(voice->sends.pSends);
}
if (pSendList == NULL)
{
/* Default to the mastering voice as output */
defaultSend.Flags = 0;
defaultSend.pOutputVoice = voice->audio->master;
defaultSends.SendCount = 1;
defaultSends.pSends = &defaultSend;
pSendList = &defaultSends;
}
else if (pSendList->SendCount == 0)
{
/* No sends? Nothing to do... */
voice->sendCoefficients = NULL;
voice->sendMix = NULL;
FAudio_zero(&voice->sends, sizeof(FAudioVoiceSends));
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
/* Copy send list */
voice->sends.SendCount = pSendList->SendCount;
voice->sends.pSends = (FAudioSendDescriptor*) voice->audio->pMalloc(
pSendList->SendCount * sizeof(FAudioSendDescriptor)
);
FAudio_memcpy(
voice->sends.pSends,
pSendList->pSends,
pSendList->SendCount * sizeof(FAudioSendDescriptor)
);
/* Allocate/Reset default output matrix, mixer function, filters */
voice->sendCoefficients = (float**) voice->audio->pMalloc(
sizeof(float*) * pSendList->SendCount
);
voice->sendMix = (FAudioMixCallback*) voice->audio->pMalloc(
sizeof(FAudioMixCallback) * pSendList->SendCount
);
for (i = 0; i < pSendList->SendCount; i += 1)
{
if (pSendList->pSends[i].pOutputVoice->type == FAUDIO_VOICE_MASTER)
{
outChannels = pSendList->pSends[i].pOutputVoice->master.inputChannels;
}
else
{
outChannels = pSendList->pSends[i].pOutputVoice->mix.inputChannels;
}
voice->sendCoefficients[i] = (float*) voice->audio->pMalloc(
sizeof(float) * voice->outputChannels * outChannels
);
FAudio_assert(voice->outputChannels > 0 && voice->outputChannels < 9);
FAudio_assert(outChannels > 0 && outChannels < 9);
FAudio_memcpy(
voice->sendCoefficients[i],
FAUDIO_INTERNAL_MATRIX_DEFAULTS[voice->outputChannels - 1][outChannels - 1],
voice->outputChannels * outChannels * sizeof(float)
);
if (voice->outputChannels == 1)
{
if (outChannels == 1)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_1out_Scalar;
}
else if (outChannels == 2)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_2out_Scalar;
}
else if (outChannels == 6)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_6out_Scalar;
}
else if (outChannels == 8)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_8out_Scalar;
}
else
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
}
}
else if (voice->outputChannels == 2)
{
if (outChannels == 1)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_1out_Scalar;
}
else if (outChannels == 2)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_2out_Scalar;
}
else if (outChannels == 6)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_6out_Scalar;
}
else if (outChannels == 8)
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_8out_Scalar;
}
else
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
}
}
else
{
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
}
if (pSendList->pSends[i].Flags & FAUDIO_SEND_USEFILTER)
{
/* Allocate the whole send filter array if needed... */
if (voice->sendFilter == NULL)
{
voice->sendFilter = (FAudioFilterParameters*) voice->audio->pMalloc(
sizeof(FAudioFilterParameters) * pSendList->SendCount
);
}
if (voice->sendFilterState == NULL)
{
voice->sendFilterState = (FAudioFilterState**) voice->audio->pMalloc(
sizeof(FAudioFilterState*) * pSendList->SendCount
);
FAudio_zero(
voice->sendFilterState,
sizeof(FAudioFilterState*) * pSendList->SendCount
);
}
/* ... then fill in this send's filter data */
voice->sendFilter[i].Type = FAUDIO_DEFAULT_FILTER_TYPE;
voice->sendFilter[i].Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
voice->sendFilter[i].OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
voice->sendFilterState[i] = (FAudioFilterState*) voice->audio->pMalloc(
sizeof(FAudioFilterState) * outChannels
);
FAudio_zero(
voice->sendFilterState[i],
sizeof(FAudioFilterState) * outChannels
);
}
}
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioVoice_SetEffectChain(
FAudioVoice *voice,
const FAudioEffectChain *pEffectChain
) {
uint32_t i;
FAPO *fapo;
uint32_t channelCount;
FAudioVoiceDetails voiceDetails;
FAPORegistrationProperties *pProps;
FAudioWaveFormatExtensible srcFmt, dstFmt;
FAPOLockForProcessBufferParameters srcLockParams, dstLockParams;
LOG_API_ENTER(voice->audio)
FAudioVoice_GetVoiceDetails(voice, &voiceDetails);
/* SetEffectChain must not change the number of output channels once the voice has been created */
if (pEffectChain == NULL && voice->outputChannels != 0)
{
/* cannot remove an effect chain that changes the number of channels */
if (voice->outputChannels != voiceDetails.InputChannels)
{
LOG_ERROR(
voice->audio,
"%s",
"Cannot remove effect chain that changes the number of channels"
)
FAudio_assert(0 && "Cannot remove effect chain that changes the number of channels");
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
}
if (pEffectChain != NULL && voice->outputChannels != 0)
{
uint32_t lst = pEffectChain->EffectCount - 1;
/* new effect chain must have same number of output channels */
if (voice->outputChannels != pEffectChain->pEffectDescriptors[lst].OutputChannels)
{
LOG_ERROR(
voice->audio,
"%s",
"New effect chain must have same number of output channels as the old chain"
)
FAudio_assert(0 && "New effect chain must have same number of output channels as the old chain");
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
}
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
if (pEffectChain == NULL)
{
FAudio_INTERNAL_FreeEffectChain(voice);
FAudio_zero(&voice->effects, sizeof(voice->effects));
voice->outputChannels = voiceDetails.InputChannels;
}
else
{
/* Validate incoming chain before changing the current chain */
/* These are always the same, so just write them now. */
srcLockParams.pFormat = &srcFmt.Format;
dstLockParams.pFormat = &dstFmt.Format;
if (voice->type == FAUDIO_VOICE_SOURCE)
{
srcLockParams.MaxFrameCount = voice->src.resampleSamples;
dstLockParams.MaxFrameCount = voice->src.resampleSamples;
}
else if (voice->type == FAUDIO_VOICE_SUBMIX)
{
srcLockParams.MaxFrameCount = voice->mix.outputSamples;
dstLockParams.MaxFrameCount = voice->mix.outputSamples;
}
else if (voice->type == FAUDIO_VOICE_MASTER)
{
srcLockParams.MaxFrameCount = voice->audio->updateSize;
dstLockParams.MaxFrameCount = voice->audio->updateSize;
}
/* The first source is the voice input data... */
srcFmt.Format.wBitsPerSample = 32;
srcFmt.Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE;
srcFmt.Format.nChannels = voiceDetails.InputChannels;
srcFmt.Format.nSamplesPerSec = voiceDetails.InputSampleRate;
srcFmt.Format.nBlockAlign = srcFmt.Format.nChannels * (srcFmt.Format.wBitsPerSample / 8);
srcFmt.Format.nAvgBytesPerSec = srcFmt.Format.nSamplesPerSec * srcFmt.Format.nBlockAlign;
srcFmt.Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);
srcFmt.Samples.wValidBitsPerSample = srcFmt.Format.wBitsPerSample;
srcFmt.dwChannelMask = 0;
FAudio_memcpy(&srcFmt.SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID));
FAudio_memcpy(&dstFmt, &srcFmt, sizeof(srcFmt));
for (i = 0; i < pEffectChain->EffectCount; i += 1)
{
fapo = pEffectChain->pEffectDescriptors[i].pEffect;
/* ... then we get this effect's format... */
dstFmt.Format.nChannels = pEffectChain->pEffectDescriptors[i].OutputChannels;
dstFmt.Format.nBlockAlign = dstFmt.Format.nChannels * (dstFmt.Format.wBitsPerSample / 8);
dstFmt.Format.nAvgBytesPerSec = dstFmt.Format.nSamplesPerSec * dstFmt.Format.nBlockAlign;
if (fapo->LockForProcess(fapo, 1, &srcLockParams, 1, &dstLockParams))
{
LOG_ERROR(
voice->audio,
"%s",
"Effect output format not supported"
)
FAudio_assert(0 && "Effect output format not supported");
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return FAUDIO_E_UNSUPPORTED_FORMAT;
}
/* Okay, now this effect is the source and the next
* effect will be the destination. Repeat until no
* effects left.
*/
FAudio_memcpy(&srcFmt, &dstFmt, sizeof(srcFmt));
}
FAudio_INTERNAL_FreeEffectChain(voice);
FAudio_INTERNAL_AllocEffectChain(
voice,
pEffectChain
);
/* check if in-place processing is supported */
channelCount = voiceDetails.InputChannels;
for (i = 0; i < voice->effects.count; i += 1)
{
fapo = voice->effects.desc[i].pEffect;
if (fapo->GetRegistrationProperties(fapo, &pProps) == 0)
{
voice->effects.inPlaceProcessing[i] = (pProps->Flags & FAPO_FLAG_INPLACE_SUPPORTED) == FAPO_FLAG_INPLACE_SUPPORTED;
voice->effects.inPlaceProcessing[i] &= (channelCount == voice->effects.desc[i].OutputChannels);
channelCount = voice->effects.desc[i].OutputChannels;
/* Fails if in-place processing is mandatory and
* the chain forces us to do otherwise...
*/
FAudio_assert(
!(pProps->Flags & FAPO_FLAG_INPLACE_REQUIRED) ||
voice->effects.inPlaceProcessing[i]
);
voice->audio->pFree(pProps);
}
}
voice->outputChannels = channelCount;
}
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioVoice_EnableEffect(
FAudioVoice *voice,
uint32_t EffectIndex,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueEnableEffect(
voice,
EffectIndex,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
voice->effects.desc[EffectIndex].InitialState = 1;
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioVoice_DisableEffect(
FAudioVoice *voice,
uint32_t EffectIndex,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueDisableEffect(
voice,
EffectIndex,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
voice->effects.desc[EffectIndex].InitialState = 0;
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetEffectState(
FAudioVoice *voice,
uint32_t EffectIndex,
int32_t *pEnabled
) {
LOG_API_ENTER(voice->audio)
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
*pEnabled = voice->effects.desc[EffectIndex].InitialState;
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetEffectParameters(
FAudioVoice *voice,
uint32_t EffectIndex,
const void *pParameters,
uint32_t ParametersByteSize,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetEffectParameters(
voice,
EffectIndex,
pParameters,
ParametersByteSize,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
if (voice->effects.parameters[EffectIndex] == NULL)
{
voice->effects.parameters[EffectIndex] = voice->audio->pMalloc(
ParametersByteSize
);
voice->effects.parameterSizes[EffectIndex] = ParametersByteSize;
}
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
if (voice->effects.parameterSizes[EffectIndex] < ParametersByteSize)
{
voice->effects.parameters[EffectIndex] = voice->audio->pRealloc(
voice->effects.parameters[EffectIndex],
ParametersByteSize
);
voice->effects.parameterSizes[EffectIndex] = ParametersByteSize;
}
FAudio_memcpy(
voice->effects.parameters[EffectIndex],
pParameters,
ParametersByteSize
);
voice->effects.parameterUpdates[EffectIndex] = 1;
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioVoice_GetEffectParameters(
FAudioVoice *voice,
uint32_t EffectIndex,
void *pParameters,
uint32_t ParametersByteSize
) {
FAPO *fapo;
LOG_API_ENTER(voice->audio)
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
fapo = voice->effects.desc[EffectIndex].pEffect;
fapo->GetParameters(fapo, pParameters, ParametersByteSize);
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioVoice_SetFilterParameters(
FAudioVoice *voice,
const FAudioFilterParameters *pParameters,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetFilterParameters(
voice,
pParameters,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
/* MSDN: "This method is usable only on source and submix voices and
* has no effect on mastering voices."
*/
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return 0;
}
if (!(voice->flags & FAUDIO_VOICE_USEFILTER))
{
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_PlatformLockMutex(voice->filterLock);
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
FAudio_memcpy(
&voice->filter,
pParameters,
sizeof(FAudioFilterParameters)
);
FAudio_PlatformUnlockMutex(voice->filterLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetFilterParameters(
FAudioVoice *voice,
FAudioFilterParameters *pParameters
) {
LOG_API_ENTER(voice->audio)
/* MSDN: "This method is usable only on source and submix voices and
* has no effect on mastering voices."
*/
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return;
}
if (!(voice->flags & FAUDIO_VOICE_USEFILTER))
{
LOG_API_EXIT(voice->audio)
return;
}
FAudio_PlatformLockMutex(voice->filterLock);
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
FAudio_memcpy(
pParameters,
&voice->filter,
sizeof(FAudioFilterParameters)
);
FAudio_PlatformUnlockMutex(voice->filterLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetOutputFilterParameters(
FAudioVoice *voice,
FAudioVoice *pDestinationVoice,
const FAudioFilterParameters *pParameters,
uint32_t OperationSet
) {
uint32_t i;
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetOutputFilterParameters(
voice,
pDestinationVoice,
pParameters,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
/* MSDN: "This method is usable only on source and submix voices and
* has no effect on mastering voices."
*/
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
/* Find the send index */
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
{
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
}
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
{
break;
}
}
if (i >= voice->sends.SendCount)
{
LOG_ERROR(
voice->audio,
"Destination not attached to source: %p %p",
(void*) voice,
(void*) pDestinationVoice
)
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER))
{
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
/* Set the filter parameters, finally. */
FAudio_memcpy(
&voice->sendFilter[i],
pParameters,
sizeof(FAudioFilterParameters)
);
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetOutputFilterParameters(
FAudioVoice *voice,
FAudioVoice *pDestinationVoice,
FAudioFilterParameters *pParameters
) {
uint32_t i;
LOG_API_ENTER(voice->audio)
/* MSDN: "This method is usable only on source and submix voices and
* has no effect on mastering voices."
*/
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return;
}
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
/* Find the send index */
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
{
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
}
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
{
break;
}
}
if (i >= voice->sends.SendCount)
{
LOG_ERROR(
voice->audio,
"Destination not attached to source: %p %p",
(void*) voice,
(void*) pDestinationVoice
)
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return;
}
if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER))
{
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return;
}
/* Set the filter parameters, finally. */
FAudio_memcpy(
pParameters,
&voice->sendFilter[i],
sizeof(FAudioFilterParameters)
);
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetVolume(
FAudioVoice *voice,
float Volume,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetVolume(
voice,
Volume,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
voice->volume = FAudio_clamp(
Volume,
-FAUDIO_MAX_VOLUME_LEVEL,
FAUDIO_MAX_VOLUME_LEVEL
);
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetVolume(
FAudioVoice *voice,
float *pVolume
) {
LOG_API_ENTER(voice->audio)
*pVolume = voice->volume;
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetChannelVolumes(
FAudioVoice *voice,
uint32_t Channels,
const float *pVolumes,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetChannelVolumes(
voice,
Channels,
pVolumes,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
if (pVolumes == NULL)
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
if (voice->type == FAUDIO_VOICE_MASTER)
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
if (voice->audio->version > 7 && Channels != voice->outputChannels)
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
FAudio_PlatformLockMutex(voice->volumeLock);
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
FAudio_memcpy(
voice->channelVolume,
pVolumes,
sizeof(float) * Channels
);
FAudio_PlatformUnlockMutex(voice->volumeLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetChannelVolumes(
FAudioVoice *voice,
uint32_t Channels,
float *pVolumes
) {
LOG_API_ENTER(voice->audio)
FAudio_PlatformLockMutex(voice->volumeLock);
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
FAudio_memcpy(
pVolumes,
voice->channelVolume,
sizeof(float) * Channels
);
FAudio_PlatformUnlockMutex(voice->volumeLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioVoice_SetOutputMatrix(
FAudioVoice *voice,
FAudioVoice *pDestinationVoice,
uint32_t SourceChannels,
uint32_t DestinationChannels,
const float *pLevelMatrix,
uint32_t OperationSet
) {
uint32_t i;
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetOutputMatrix(
voice,
pDestinationVoice,
SourceChannels,
DestinationChannels,
pLevelMatrix,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
/* Find the send index */
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
{
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
}
FAudio_assert(pDestinationVoice != NULL);
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
{
break;
}
}
if (i >= voice->sends.SendCount)
{
LOG_ERROR(
voice->audio,
"Destination not attached to source: %p %p",
(void*) voice,
(void*) pDestinationVoice
)
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
/* Verify the Source/Destination channel count */
FAudio_assert(SourceChannels == voice->outputChannels);
if (pDestinationVoice->type == FAUDIO_VOICE_MASTER)
{
FAudio_assert(DestinationChannels == pDestinationVoice->master.inputChannels);
}
else
{
FAudio_assert(DestinationChannels == pDestinationVoice->mix.inputChannels);
}
/* Set the matrix values, finally */
FAudio_memcpy(
voice->sendCoefficients[i],
pLevelMatrix,
sizeof(float) * SourceChannels * DestinationChannels
);
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioVoice_GetOutputMatrix(
FAudioVoice *voice,
FAudioVoice *pDestinationVoice,
uint32_t SourceChannels,
uint32_t DestinationChannels,
float *pLevelMatrix
) {
uint32_t i;
LOG_API_ENTER(voice->audio)
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
/* Find the send index */
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
{
break;
}
}
if (i >= voice->sends.SendCount)
{
LOG_ERROR(
voice->audio,
"Destination not attached to source: %p %p",
(void*) voice,
(void*) pDestinationVoice
)
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return;
}
/* Verify the Source/Destination channel count */
if (voice->type == FAUDIO_VOICE_SOURCE)
{
FAudio_assert(SourceChannels == voice->src.format->nChannels);
}
else
{
FAudio_assert(SourceChannels == voice->mix.inputChannels);
}
if (pDestinationVoice->type == FAUDIO_VOICE_MASTER)
{
FAudio_assert(DestinationChannels == pDestinationVoice->master.inputChannels);
}
else
{
FAudio_assert(DestinationChannels == pDestinationVoice->mix.inputChannels);
}
/* Get the matrix values, finally */
FAudio_memcpy(
pLevelMatrix,
voice->sendCoefficients[i],
sizeof(float) * SourceChannels * DestinationChannels
);
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
}
void FAudioVoice_DestroyVoice(FAudioVoice *voice)
{
uint32_t i;
LOG_API_ENTER(voice->audio)
/* TODO: Check for dependencies and remove from audio graph first! */
FAudio_OPERATIONSET_ClearAllForVoice(voice);
if (voice->type == FAUDIO_VOICE_SOURCE)
{
FAudioBufferEntry *entry, *next;
#ifdef FAUDIO_DUMP_VOICES
FAudio_DUMPVOICE_Finalize((FAudioSourceVoice*) voice);
#endif /* FAUDIO_DUMP_VOICES */
FAudio_PlatformLockMutex(voice->audio->sourceLock);
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
while (voice == voice->audio->processingSource)
{
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
FAudio_PlatformLockMutex(voice->audio->sourceLock);
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
}
LinkedList_RemoveEntry(
&voice->audio->sources,
voice,
voice->audio->sourceLock,
voice->audio->pFree
);
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
entry = voice->src.bufferList;
while (entry != NULL)
{
next = entry->next;
voice->audio->pFree(entry);
entry = next;
}
entry = voice->src.flushList;
while (entry != NULL)
{
next = entry->next;
voice->audio->pFree(entry);
entry = next;
}
voice->audio->pFree(voice->src.format);
LOG_MUTEX_DESTROY(voice->audio, voice->src.bufferLock)
FAudio_PlatformDestroyMutex(voice->src.bufferLock);
#ifdef HAVE_FFMPEG
if (voice->src.ffmpeg)
{
FAudio_FFMPEG_free(voice);
}
#endif /* HAVE_FFMPEG */
}
else if (voice->type == FAUDIO_VOICE_SUBMIX)
{
/* Remove submix from list */
LinkedList_RemoveEntry(
&voice->audio->submixes,
voice,
voice->audio->submixLock,
voice->audio->pFree
);
/* Delete submix data */
voice->audio->pFree(voice->mix.inputCache);
}
else if (voice->type == FAUDIO_VOICE_MASTER)
{
if (voice->audio->platform != NULL)
{
FAudio_PlatformQuit(voice->audio->platform);
voice->audio->platform = NULL;
}
if (voice->master.effectCache != NULL)
{
voice->audio->pFree(voice->master.effectCache);
}
voice->audio->master = NULL;
}
if (voice->sendLock != NULL)
{
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
for (i = 0; i < voice->sends.SendCount; i += 1)
{
voice->audio->pFree(voice->sendCoefficients[i]);
}
if (voice->sendCoefficients != NULL)
{
voice->audio->pFree(voice->sendCoefficients);
}
if (voice->sendMix != NULL)
{
voice->audio->pFree(voice->sendMix);
}
if (voice->sendFilter != NULL)
{
voice->audio->pFree(voice->sendFilter);
}
if (voice->sendFilterState != NULL)
{
for (i = 0; i < voice->sends.SendCount; i += 1)
{
if (voice->sendFilterState[i] != NULL)
{
voice->audio->pFree(voice->sendFilterState[i]);
}
}
voice->audio->pFree(voice->sendFilterState);
}
if (voice->sends.pSends != NULL)
{
voice->audio->pFree(voice->sends.pSends);
}
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_MUTEX_DESTROY(voice->audio, voice->sendLock)
FAudio_PlatformDestroyMutex(voice->sendLock);
}
if (voice->effectLock != NULL)
{
FAudio_PlatformLockMutex(voice->effectLock);
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
FAudio_INTERNAL_FreeEffectChain(voice);
FAudio_PlatformUnlockMutex(voice->effectLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
LOG_MUTEX_DESTROY(voice->audio, voice->effectLock)
FAudio_PlatformDestroyMutex(voice->effectLock);
}
if (voice->filterLock != NULL)
{
FAudio_PlatformLockMutex(voice->filterLock);
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
if (voice->filterState != NULL)
{
voice->audio->pFree(voice->filterState);
}
FAudio_PlatformUnlockMutex(voice->filterLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
LOG_MUTEX_DESTROY(voice->audio, voice->filterLock)
FAudio_PlatformDestroyMutex(voice->filterLock);
}
if (voice->volumeLock != NULL)
{
FAudio_PlatformLockMutex(voice->volumeLock);
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
if (voice->channelVolume != NULL)
{
voice->audio->pFree(voice->channelVolume);
}
FAudio_PlatformUnlockMutex(voice->volumeLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
LOG_MUTEX_DESTROY(voice->audio, voice->volumeLock)
FAudio_PlatformDestroyMutex(voice->volumeLock);
}
LOG_API_EXIT(voice->audio)
FAudio_Release(voice->audio);
voice->audio->pFree(voice);
}
/* FAudioSourceVoice Interface */
uint32_t FAudioSourceVoice_Start(
FAudioSourceVoice *voice,
uint32_t Flags,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueStart(
voice,
Flags,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_assert(Flags == 0);
voice->src.active = 1;
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioSourceVoice_Stop(
FAudioSourceVoice *voice,
uint32_t Flags,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueStop(
voice,
Flags,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
if (Flags & FAUDIO_PLAY_TAILS)
{
voice->src.active = 2;
}
else
{
voice->src.active = 0;
}
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioSourceVoice_SubmitSourceBuffer(
FAudioSourceVoice *voice,
const FAudioBuffer *pBuffer,
const FAudioBufferWMA *pBufferWMA
) {
uint32_t adpcmMask, *adpcmByteCount;
uint32_t playBegin, playLength, loopBegin, loopLength;
FAudioBufferEntry *entry, *list;
LOG_API_ENTER(voice->audio)
LOG_INFO(
voice->audio,
"%p: {Flags: 0x%x, AudioBytes: %u, pAudioData: %p, Play: %u + %u, Loop: %u + %u x %u}",
(void*) voice,
pBuffer->Flags,
pBuffer->AudioBytes,
(const void*) pBuffer->pAudioData,
pBuffer->PlayBegin,
pBuffer->PlayLength,
pBuffer->LoopBegin,
pBuffer->LoopLength,
pBuffer->LoopCount
)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
#ifdef HAVE_FFMPEG
FAudio_assert( (voice->src.ffmpeg != NULL && pBufferWMA != NULL) ||
(voice->src.ffmpeg == NULL && pBufferWMA == NULL) );
#endif /* HAVE_FFMPEG */
/* Start off with whatever they just sent us... */
playBegin = pBuffer->PlayBegin;
playLength = pBuffer->PlayLength;
loopBegin = pBuffer->LoopBegin;
loopLength = pBuffer->LoopLength;
/* "LoopBegin/LoopLength must be zero if LoopCount is 0" */
if (pBuffer->LoopCount == 0 && (loopBegin > 0 || loopLength > 0))
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
/* PlayLength Default */
if (playLength == 0)
{
if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
{
FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) voice->src.format;
playLength = (
pBuffer->AudioBytes /
fmtex->wfx.nBlockAlign *
fmtex->wSamplesPerBlock
) - playBegin;
}
else if (pBufferWMA != NULL)
{
playLength = (
pBufferWMA->pDecodedPacketCumulativeBytes[pBufferWMA->PacketCount - 1] /
(voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8)
) - playBegin;
}
else
{
playLength = (
pBuffer->AudioBytes /
voice->src.format->nBlockAlign
) - playBegin;
}
}
if (pBuffer->LoopCount > 0)
{
/* "The value of LoopBegin must be less than PlayBegin + PlayLength" */
if (loopBegin >= (playBegin + playLength))
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
/* LoopLength Default */
if (loopLength == 0)
{
loopLength = playBegin + playLength - loopBegin;
}
/* "The value of LoopBegin + LoopLength must be greater than PlayBegin
* and less than PlayBegin + PlayLength"
*/
if ( voice->audio->version > 7 && (
(loopBegin + loopLength) <= playBegin ||
(loopBegin + loopLength) > (playBegin + playLength)) )
{
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
}
/* For ADPCM, round down to the nearest sample block size */
if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
{
adpcmMask = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock;
playBegin -= playBegin % adpcmMask;
playLength -= playLength % adpcmMask;
loopBegin -= loopBegin % adpcmMask;
loopLength -= loopLength % adpcmMask;
/* This is basically a const_cast... */
adpcmByteCount = (uint32_t*) &pBuffer->AudioBytes;
*adpcmByteCount = (
pBuffer->AudioBytes / voice->src.format->nBlockAlign
) * voice->src.format->nBlockAlign;
}
/* Allocate, now that we have valid input */
entry = (FAudioBufferEntry*) voice->audio->pMalloc(sizeof(FAudioBufferEntry));
FAudio_memcpy(&entry->buffer, pBuffer, sizeof(FAudioBuffer));
entry->buffer.PlayBegin = playBegin;
entry->buffer.PlayLength = playLength;
entry->buffer.LoopBegin = loopBegin;
entry->buffer.LoopLength = loopLength;
if (pBufferWMA != NULL)
{
FAudio_memcpy(&entry->bufferWMA, pBufferWMA, sizeof(FAudioBufferWMA));
}
entry->next = NULL;
if ( voice->audio->version <= 7 && (
entry->buffer.LoopCount > 0 &&
entry->buffer.LoopBegin + entry->buffer.LoopLength <= entry->buffer.PlayBegin))
{
entry->buffer.LoopCount = 0;
}
#ifdef FAUDIO_DUMP_VOICES
/* dumping current buffer, append into "data" section */
if (pBuffer->pAudioData != NULL && playLength > 0)
{
FAudio_DUMPVOICE_WriteBuffer(voice, pBuffer, pBufferWMA, playBegin, playLength);
}
#endif /* FAUDIO_DUMP_VOICES */
/* Submit! */
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
if (voice->src.bufferList == NULL)
{
voice->src.bufferList = entry;
voice->src.curBufferOffset = entry->buffer.PlayBegin;
voice->src.newBuffer = 1;
}
else
{
list = voice->src.bufferList;
while (list->next != NULL)
{
list = list->next;
}
list->next = entry;
/* For some bizarre reason we get scenarios where a buffer is freed, only to
* have the allocator give us the exact same address and somehow get a single
* buffer referencing itself. I don't even know.
*/
FAudio_assert(list != entry);
}
LOG_INFO(
voice->audio,
"%p: appended buffer %p",
(void*) voice,
(void*) &entry->buffer
)
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioSourceVoice_FlushSourceBuffers(
FAudioSourceVoice *voice
) {
FAudioBufferEntry *entry, *latest;
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
/* If the source is playing, don't flush the active buffer */
entry = voice->src.bufferList;
if ((voice->src.active == 1) && entry != NULL && !voice->src.newBuffer)
{
entry = entry->next;
voice->src.bufferList->next = NULL;
}
else
{
voice->src.curBufferOffset = 0;
voice->src.bufferList = NULL;
voice->src.newBuffer = 0;
}
/* Move them to the pending flush list */
if (entry != NULL)
{
if (voice->src.flushList == NULL)
{
voice->src.flushList = entry;
}
else
{
latest = voice->src.flushList;
while (latest->next != NULL)
{
latest = latest->next;
}
latest->next = entry;
}
}
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioSourceVoice_Discontinuity(
FAudioSourceVoice *voice
) {
FAudioBufferEntry *buf;
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
if (voice->src.bufferList != NULL)
{
for (buf = voice->src.bufferList; buf->next != NULL; buf = buf->next);
buf->buffer.Flags |= FAUDIO_END_OF_STREAM;
}
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
return 0;
}
uint32_t FAudioSourceVoice_ExitLoop(
FAudioSourceVoice *voice,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueExitLoop(
voice,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
if (voice->src.bufferList != NULL)
{
voice->src.bufferList->buffer.LoopCount = 0;
}
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioSourceVoice_GetState(
FAudioSourceVoice *voice,
FAudioVoiceState *pVoiceState,
uint32_t Flags
) {
FAudioBufferEntry *entry;
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
if (!(Flags & FAUDIO_VOICE_NOSAMPLESPLAYED))
{
pVoiceState->SamplesPlayed = voice->src.totalSamples;
}
pVoiceState->BuffersQueued = 0;
pVoiceState->pCurrentBufferContext = NULL;
if (voice->src.bufferList != NULL)
{
entry = voice->src.bufferList;
if (!voice->src.newBuffer)
{
pVoiceState->pCurrentBufferContext = entry->buffer.pContext;
}
do
{
pVoiceState->BuffersQueued += 1;
entry = entry->next;
} while (entry != NULL);
}
/* Pending flushed buffers also count */
entry = voice->src.flushList;
while (entry != NULL)
{
pVoiceState->BuffersQueued += 1;
entry = entry->next;
}
LOG_INFO(
voice->audio,
"-> {pCurrentBufferContext: %p, BuffersQueued: %u, SamplesPlayed: %"FAudio_PRIu64"}",
pVoiceState->pCurrentBufferContext, pVoiceState->BuffersQueued,
pVoiceState->SamplesPlayed
)
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioSourceVoice_SetFrequencyRatio(
FAudioSourceVoice *voice,
float Ratio,
uint32_t OperationSet
) {
LOG_API_ENTER(voice->audio)
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
{
FAudio_OPERATIONSET_QueueSetFrequencyRatio(
voice,
Ratio,
OperationSet
);
LOG_API_EXIT(voice->audio)
return 0;
}
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
if (voice->flags & FAUDIO_VOICE_NOPITCH)
{
LOG_API_EXIT(voice->audio)
return 0;
}
voice->src.freqRatio = FAudio_clamp(
Ratio,
FAUDIO_MIN_FREQ_RATIO,
voice->src.maxFreqRatio
);
LOG_API_EXIT(voice->audio)
return 0;
}
void FAudioSourceVoice_GetFrequencyRatio(
FAudioSourceVoice *voice,
float *pRatio
) {
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
*pRatio = voice->src.freqRatio;
LOG_API_EXIT(voice->audio)
}
uint32_t FAudioSourceVoice_SetSourceSampleRate(
FAudioSourceVoice *voice,
uint32_t NewSourceSampleRate
) {
uint32_t outSampleRate;
uint32_t newDecodeSamples, newResampleSamples;
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
FAudio_assert( NewSourceSampleRate >= FAUDIO_MIN_SAMPLE_RATE &&
NewSourceSampleRate <= FAUDIO_MAX_SAMPLE_RATE );
FAudio_PlatformLockMutex(voice->src.bufferLock);
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
if ( voice->audio->version > 7 &&
voice->src.bufferList != NULL )
{
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
LOG_API_EXIT(voice->audio)
return FAUDIO_E_INVALID_CALL;
}
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
voice->src.format->nSamplesPerSec = NewSourceSampleRate;
/* Resize decode cache */
newDecodeSamples = (uint32_t) FAudio_ceil(
voice->audio->updateSize *
(double) voice->src.maxFreqRatio *
(double) NewSourceSampleRate /
(double) voice->audio->master->master.inputSampleRate
) + EXTRA_DECODE_PADDING * voice->src.format->nChannels;
FAudio_INTERNAL_ResizeDecodeCache(
voice->audio,
(newDecodeSamples + EXTRA_DECODE_PADDING) * voice->src.format->nChannels
);
voice->src.decodeSamples = newDecodeSamples;
FAudio_PlatformLockMutex(voice->sendLock);
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
if (voice->sends.SendCount == 0)
{
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
LOG_API_EXIT(voice->audio)
return 0;
}
outSampleRate = voice->sends.pSends[0].pOutputVoice->type == FAUDIO_VOICE_MASTER ?
voice->sends.pSends[0].pOutputVoice->master.inputSampleRate :
voice->sends.pSends[0].pOutputVoice->mix.inputSampleRate;
FAudio_PlatformUnlockMutex(voice->sendLock);
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
/* Resize resample cache */
newResampleSamples = (uint32_t) (FAudio_ceil(
(double) voice->audio->updateSize *
(double) outSampleRate /
(double) voice->audio->master->master.inputSampleRate
));
FAudio_INTERNAL_ResizeResampleCache(
voice->audio,
newResampleSamples * voice->src.format->nChannels
);
voice->src.resampleSamples = newResampleSamples;
LOG_API_EXIT(voice->audio)
return 0;
}
/* FAudioMasteringVoice Interface */
FAUDIOAPI uint32_t FAudioMasteringVoice_GetChannelMask(
FAudioMasteringVoice *voice,
uint32_t *pChannelMask
) {
LOG_API_ENTER(voice->audio)
FAudio_assert(voice->type == FAUDIO_VOICE_MASTER);
FAudio_assert(pChannelMask != NULL);
*pChannelMask = voice->audio->mixFormat.dwChannelMask;
LOG_API_EXIT(voice->audio)
return 0;
}
#ifdef FAUDIO_DUMP_VOICES
static inline FAudioIOStreamOut *DumpVoices_fopen(
const FAudioSourceVoice *voice,
const FAudioWaveFormatEx *format,
const char *mode,
const char *ext
) {
char loc[64];
uint16_t format_tag = format->wFormatTag;
uint16_t format_ex_tag = 0;
if (format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE)
{
/* get the GUID of the extended subformat */
const FAudioWaveFormatExtensible *format_ex =
(const FAudioWaveFormatExtensible*) format;
format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1);
}
FAudio_snprintf(
loc,
sizeof(loc),
"FA_fmt_0x%04X_0x%04X_0x%016lX%s.wav",
format_tag,
format_ex_tag,
(uint64_t) voice,
ext
);
FAudioIOStreamOut *fileOut = FAudio_fopen_out(loc, mode);
return fileOut;
}
static inline void DumpVoices_finalize_section(
const FAudioSourceVoice *voice,
const FAudioWaveFormatEx *format,
const char *section /* one of "data" or "dpds" */
) {
/* data file only contains the real data bytes */
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "rb", section);
if (!io_data)
{
return;
}
FAudio_PlatformLockMutex((FAudioMutex) io_data->lock);
size_t file_size_data = io_data->size(io_data->data);
if (file_size_data == 0)
{
/* nothing to do */
/* close data file */
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
FAudio_close_out(io_data);
return;
}
/* we got some data: append data section to main file */
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "ab", "");
if (!io)
{
/* close data file */
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
FAudio_close_out(io_data);
return;
}
/* data sub-chunk - 8 bytes + data */
/* SubChunk2ID - 4 --> "data" or "dpds" */
io->write(io->data, section, 4, 1);
/* Subchunk2Size - 4 */
uint32_t chunk_size = (uint32_t)file_size_data;
io->write(io->data, &chunk_size, 4, 1);
/* data */
/* fill in data bytes */
uint8_t buffer[1024*1024];
size_t count;
while((count = io_data->read(io_data->data, (void*) buffer, 1, 1024*1024)) > 0)
{
io->write(io->data, (void*) buffer, 1, count);
}
/* close data file */
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
FAudio_close_out(io_data);
/* close main file */
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
FAudio_close_out(io);
}
static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice)
{
const FAudioWaveFormatEx *format = voice->src.format;
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "wb", "");
if (!io)
{
return;
}
FAudio_PlatformLockMutex((FAudioMutex) io->lock);
/* another GREAT ressource
* https://wiki.multimedia.cx/index.php/Microsoft_xWMA
*/
/* wave file format taken from
* http://soundfile.sapp.org/doc/WaveFormat
* https://sites.google.com/site/musicgapi/technical-documents/wav-file-format
* |52 49|46 46|52 4A|02 00|
* |c1 sz|af|nc|sp rt|bt rt|
* |ba|bs|da ta|c2 sz|
* | R I F F |chunk size |W A V E |f m t |
* 19026
* | 52 49 46 46 52 4A 02 00 57 41 56 45 66 6D 74 20 | RIFFRJ..WAVEfmt
* | subchnk size|fmt |nChan |samplerate |byte rate |
* | 50 | 2 |2 |11025 |11289 |
* | 32 00 00 00 02 00 02 00 11 2B 00 00 19 2C 00 00 | 2........+...,..
* |blkaln|bps |efmt |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
* | 512 |4 |32 |500 |7 |256 |0 |512 |
* | 512 |4 |32 |459252 |256 |
* | 00 02|04 00 20 00 F4 01 07 00 00 01 00 00 00 02 | .... .ô.........
* | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
* |
* | 00 FF 00 00 00 00 C0 00 40 00 F0 00 00 00 CC 01 | .ÿ....À.@.ð...Ì.
* | XXXXXXXXXXXXXXXXXX|d a t a |chunk size |XXXXX |
* | | |18944 | |
* | 30 FF 88 01 18 FF 64 61 74 61 00 4A 02 00 00 00 | 0ÿ...ÿdata.J....
*/
uint16_t cbSize = format->cbSize;
const char *formatFourcc = "WAVE";
uint16_t wFormatTag = format->wFormatTag;
/* special handling for WMAUDIO2 */
if (wFormatTag == FAUDIO_FORMAT_EXTENSIBLE && cbSize >= 22)
{
const FAudioWaveFormatExtensible *format_ex =
(const FAudioWaveFormatExtensible*) format;
uint16_t format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1);
if (format_ex_tag == FAUDIO_FORMAT_WMAUDIO2)
{
cbSize = 0;
formatFourcc = "XWMA";
wFormatTag = FAUDIO_FORMAT_WMAUDIO2;
}
}
{ /* RIFF chunk descriptor - 12 byte */
/* ChunkID - 4 */
io->write(io->data, "RIFF", 4, 1);
/* ChunkSize - 4 */
uint32_t filesize = 0; /* the real file size is written in finalize step */
io->write(io->data, &filesize, 4, 1);
/* Format - 4 */
io->write(io->data, formatFourcc, 4, 1);
}
{ /* fmt sub-chunk 24 */
/* Subchunk1ID - 4 */
io->write(io->data, "fmt ", 4, 1);
/* Subchunk1Size - 4 */
/* 18 byte for WAVEFORMATEX and cbSize for WAVEFORMATEXTENDED */
uint32_t chunk_data_size = 18 + (uint32_t) cbSize;
io->write(io->data, &chunk_data_size, 4, 1);
/* AudioFormat - 2 */
io->write(io->data, &wFormatTag, 2, 1);
/* NumChannels - 2 */
io->write(io->data, &format->nChannels, 2, 1);
/* SampleRate - 4 */
io->write(io->data, &format->nSamplesPerSec, 4, 1);
/* ByteRate - 4 */
/* SampleRate * NumChannels * BitsPerSample/8 */
io->write(io->data, &format->nAvgBytesPerSec, 4, 1);
/* BlockAlign - 2 */
/* NumChannels * BitsPerSample/8 */
io->write(io->data, &format->nBlockAlign, 2, 1);
/* BitsPerSample - 2 */
io->write(io->data, &format->wBitsPerSample, 2, 1);
}
/* in case of extensible audio format write the additional data to the file */
{
/* always write the cbSize */
io->write(io->data, &cbSize, 2, 1);
if (cbSize >= 22)
{
/* we have a WAVEFORMATEXTENSIBLE struct to write */
const FAudioWaveFormatExtensible *format_ex =
(const FAudioWaveFormatExtensible*) format;
io->write(io->data, &format_ex->Samples.wValidBitsPerSample, 2, 1);
io->write(io->data, &format_ex->dwChannelMask, 4, 1);
/* write FAudioGUID */
io->write(io->data, &format_ex->SubFormat.Data1, 4, 1);
io->write(io->data, &format_ex->SubFormat.Data2, 2, 1);
io->write(io->data, &format_ex->SubFormat.Data3, 2, 1);
io->write(io->data, &format_ex->SubFormat.Data4, 1, 8);
}
if (format->cbSize > 22)
{
/* fill up the remaining cbSize bytes with zeros */
uint8_t zero = 0;
for (uint16_t i=23; i<=format->cbSize; i++)
{
io->write(io->data, &zero, 1, 1);
}
}
}
{ /* dpds sub-chunk - optional - 8 bytes + bufferWMA uint32_t samples */
/* create file to hold the bufferWMA samples */
FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, format, "wb", "dpds");
FAudio_close_out(io_dpds);
/* io_dpds file will be filled by SubmitBuffer */
}
{ /* data sub-chunk - 8 bytes + data */
/* create file to hold the data samples */
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "wb", "data");
FAudio_close_out(io_data);
/* io_data file will be filled by SubmitBuffer */
}
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
FAudio_close_out(io);
}
static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice)
{
const FAudioWaveFormatEx *format = voice->src.format;
/* add dpds subchunk - optional */
DumpVoices_finalize_section(voice, format, "dpds");
/* add data subchunk */
DumpVoices_finalize_section(voice, format, "data");
/* open main file to update filesize */
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "r+b", "");
if (!io)
{
return;
}
FAudio_PlatformLockMutex((FAudioMutex) io->lock);
size_t file_size = io->size(io->data);
if (file_size >= 44)
{
/* update filesize */
uint32_t chunk_size = (uint32_t)(file_size - 8);
io->seek(io->data, 4, FAUDIO_SEEK_SET);
io->write(io->data, &chunk_size, 4, 1);
}
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
FAudio_close_out(io);
}
static void FAudio_DUMPVOICE_WriteBuffer(
const FAudioSourceVoice *voice,
const FAudioBuffer *pBuffer,
const FAudioBufferWMA *pBufferWMA,
const uint32_t playBegin,
const uint32_t playLength
) {
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, voice->src.format, "ab", "data");
if (io_data == NULL)
{
return;
}
FAudio_PlatformLockMutex((FAudioMutex) io_data->lock);
if (pBufferWMA != NULL)
{
/* dump encoded buffer contents */
if (pBufferWMA->PacketCount > 0)
{
FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, voice->src.format, "ab", "dpds");
if (io_dpds)
{
FAudio_PlatformLockMutex((FAudioMutex) io_dpds->lock);
/* write to dpds file */
io_dpds->write(io_dpds->data, pBufferWMA->pDecodedPacketCumulativeBytes, sizeof(uint32_t), pBufferWMA->PacketCount);
FAudio_PlatformUnlockMutex((FAudioMutex) io_dpds->lock);
FAudio_close_out(io_dpds);
}
/* write buffer contents to data file */
io_data->write(io_data->data, pBuffer->pAudioData, sizeof(uint8_t), pBuffer->AudioBytes);
}
}
else
{
/* dump unencoded buffer contents */
uint16_t bytesPerFrame = (voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8);
FAudio_assert(bytesPerFrame > 0);
const void *pAudioDataBegin = pBuffer->pAudioData + playBegin*bytesPerFrame;
io_data->write(io_data->data, pAudioDataBegin, bytesPerFrame, playLength);
}
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
FAudio_close_out(io_data);
}
#endif /* FAUDIO_DUMP_VOICES */
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */