Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FAudio.c
3010 lines | 78.7 KiB | text/x-c | CLexer
3010 lines | 78.7 KiB | text/x-c | CLexer
r0 | /* 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: */ | ||||