Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

565 lines
17 KiB
C

/*H********************************************************************************/
/*!
\File voipcodec.c
\Description
VoIP codec manager.
\Copyright
Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED.
\Version 1.0 08/04/2004 (jbrookes) First version
*/
/********************************************************************************H*/
/*** Include files ****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DirtySDK/platform.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/voip/voipdef.h"
#include "DirtySDK/voip/voiptranscribe.h"
#include "DirtySDK/voip/voipcodec.h"
/*** Defines **********************************************************************/
#define VOIPCODEC_MAXENTRIES (8)
#define VOIPCODEC_TIMER (DIRTYCODE_LOGGING && FALSE)
//! default VAD thresholds
#define VOIPCODEC_DEFAULT_VAD_AMPLITUDE_THRESHOLD (0.00015f)
#define VOIPCODEC_DEFAULT_VAD_FRAMES_THRESHOLD (32)
/*** Macros ***********************************************************************/
/*** Type Definitions *************************************************************/
typedef struct VoipCodecRegT
{
int32_t iCodecIdent;
const VoipCodecDefT *pCodecDef;
VoipCodecRefT *pCodecRef;
} VoipCodecRegT;
/*** Function Prototypes **********************************************************/
/*** Variables ********************************************************************/
//! codec reg table
static VoipCodecRegT _VoipCodec_RegTable[VOIPCODEC_MAXENTRIES];
//! number of registered codecs
static int32_t _VoipCodec_iNumCodecs = 0;
//! active codec
static int32_t _VoipCodec_iActiveCodec = -1;
/*** Private Functions ************************************************************/
/*F********************************************************************************/
/*!
\Function _VoipCodecGet
\Description
Get the specified codec.
\Input iCodecIdent - codec ident or VOIP_CODEC_ACTIVE
\Input *pIndex - [out, optional] storage for codec index
\Output
VoipCodecRegT * - pointer to specified codec, or NULL
\Version 10/10/2011 (jbrookes)
*/
/********************************************************************************F*/
static VoipCodecRegT *_VoipCodecGet(int32_t iCodecIdent, int32_t *pIndex)
{
VoipCodecRegT *pRegEntry = NULL;
int32_t iCodec;
// zero is reserved/invalid
if (iCodecIdent == 0)
{
return(NULL);
}
// search for codec ident
if (iCodecIdent != VOIP_CODEC_ACTIVE)
{
// look up codec by ident
for (iCodec = 0; iCodec < _VoipCodec_iNumCodecs; iCodec++)
{
if (_VoipCodec_RegTable[iCodec].iCodecIdent == iCodecIdent)
{
pRegEntry = &_VoipCodec_RegTable[iCodec];
if (pIndex != NULL)
{
*pIndex = iCodec;
}
break;
}
}
}
else if (_VoipCodec_iActiveCodec != -1)
{
pRegEntry = &_VoipCodec_RegTable[_VoipCodec_iActiveCodec];
if (pIndex != NULL)
{
*pIndex = _VoipCodec_iActiveCodec;
}
}
return(pRegEntry);
}
/*F*************************************************************************************************/
/*!
\Function _VoipCodecVadProcess16BitFrame
\Description
Use this function to pass next frame of 16-bit voice samples to the VAD engine.
\Input *pVoipCodecRef - codec reference pointer
\Input *pVoiceFrame - pointer to voice buffer
\Input iNumSamples - number of samples in voice frame
\Output
uint8_t - TRUE if voice is considered silent after processing that frame, FALSE otherwise
\Version 06/13/2012 (mclouatre)
*/
/*************************************************************************************************F*/
static uint8_t _VoipCodecVadProcess16BitFrame(VoipCodecRefT *pVoipCodecRef, const int16_t *pVoiceFrame, int32_t iNumSamples)
{
int32_t iCount;
float fAmp, fPower, fPowerSum;
fAmp = fPower = fPowerSum = 0.0f;
// walk the frame contents and calculate the amplitude of the signal
for (iCount = 0; iCount < iNumSamples; iCount += 1)
{
fAmp = (float)abs(pVoiceFrame[iCount]);
fPowerSum += (fAmp * fAmp);
}
fPower = (fPowerSum / (32768.0f * 32768.0f * (float)iNumSamples));
// save current power level for external visibility
pVoipCodecRef->fPowerLevel = fPower;
if (fPower < pVoipCodecRef->fVadPowerThreshold)
{
if (pVoipCodecRef->iVadCumulSilenceFrames > pVoipCodecRef->iVadSilenceFramesThreshold)
{
if (!pVoipCodecRef->bVadSilent)
{
NetPrintfVerbose((pVoipCodecRef->iDebugLevel, 1, "voipcodec: silence detected (fPowerThreshold=%f, fPower=%f)\n",
pVoipCodecRef->fVadPowerThreshold, fPower));
pVoipCodecRef->bVadSilent = TRUE;
}
}
else
{
pVoipCodecRef->iVadCumulSilenceFrames++;
}
}
else
{
if (pVoipCodecRef->bVadSilent)
{
NetPrintfVerbose((pVoipCodecRef->iDebugLevel, 1, "voipcodec: voice detected (fPowerThreshold=%f, fPower=%f)\n",
pVoipCodecRef->fVadPowerThreshold, fPower));
pVoipCodecRef->bVadSilent = FALSE;
}
pVoipCodecRef->iVadCumulSilenceFrames = 0;
}
return(pVoipCodecRef->bVadSilent);
}
/*** Public functions *************************************************************/
/*F********************************************************************************/
/*!
\Function VoipCodecRegister
\Description
Register a codec in the table.
\Input iCodecIdent - identifier with which the codec will be tagged
\Input *pCodecDef - pointer to codec definition table
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipCodecRegister(int32_t iCodecIdent, const VoipCodecDefT *pCodecDef)
{
int32_t iCodec;
for (iCodec = _VoipCodec_iNumCodecs; iCodec < VOIPCODEC_MAXENTRIES; iCodec++)
{
if (_VoipCodec_RegTable[iCodec].iCodecIdent == 0)
{
NetPrintf(("voipcodec: registering codec '%C'\n", iCodecIdent));
_VoipCodec_RegTable[iCodec].iCodecIdent = iCodecIdent;
_VoipCodec_RegTable[iCodec].pCodecDef = pCodecDef;
_VoipCodec_iNumCodecs++;
return;
}
}
NetPrintf(("voipcodec: unable to register codec '%C'\n", iCodecIdent));
}
/*F********************************************************************************/
/*!
\Function VoipCodecCreate
\Description
Create a codec module of the given type.
\Input iCodecIdent - identifier with which the codec will be tagged
\Input iDecodeChannels - number of decoder channels
\Output
int32_t - zero=success, negative=failure
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipCodecCreate(int32_t iCodecIdent, int32_t iDecodeChannels)
{
VoipCodecRegT *pRegEntry = NULL;
int32_t iCodec = 0;
// get codec reg entry
if ((pRegEntry = _VoipCodecGet(iCodecIdent, &iCodec)) == NULL)
{
NetPrintf(("voipcodec: codec '%C' is not registered\n", iCodecIdent));
return(-1);
}
// if there is an active codec, destroy it
VoipCodecDestroy();
// create it and return to caller
if ((pRegEntry->pCodecRef = pRegEntry->pCodecDef->pCreate(iDecodeChannels)) != NULL)
{
NetPrintf(("voipcodec: created codec '%C'\n", iCodecIdent));
_VoipCodec_iActiveCodec = iCodec;
}
else
{
NetPrintf(("voipcodec: unable to create codec '%C'\n", iCodecIdent));
return(-1);
}
// initialize default values
pRegEntry->pCodecRef->fVadPowerThreshold = VOIPCODEC_DEFAULT_VAD_AMPLITUDE_THRESHOLD;
pRegEntry->pCodecRef->iVadSilenceFramesThreshold = VOIPCODEC_DEFAULT_VAD_FRAMES_THRESHOLD;
return(0);
}
/*F********************************************************************************/
/*!
\Function VoipCodecControl
\Description
Control codec.
\Input iCodecIdent - codec identifier, or VOIP_CODEC_ACTIVE
\Input iControl - control selector
\Input iValue - selector specific
\Input iValue2 - selector specific
\Input *pValue - selector specific
\Output
int32_t - selector specific
\Notes
iControl can be one of the following:
\verbatim
'+vad' - enable/disable vad
'vada' - signal level threshold used to detect silence in a single frame (in millionths)
'vadf' - number of silence frames required for silence detection to kick in
\endverbatim
\Version 08/09/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipCodecControl(int32_t iCodecIdent, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue)
{
VoipCodecRegT *pRegEntry;
// make sure active codec is valid
if ((pRegEntry = _VoipCodecGet(iCodecIdent, NULL)) == NULL)
{
NetPrintf(("voipcodec: invalid codec, cannot process control message '%C'\n", iControl));
return(-1);
}
if (iControl == '+vad')
{
if (pRegEntry->pCodecRef->bVadEnabled != (iValue?TRUE:FALSE))
{
NetPrintf(("voipcodec: VAD mode changed from %s to %s\n", (pRegEntry->pCodecRef->bVadEnabled?"ON":"OFF"), (iValue?"ON":"OFF")));
pRegEntry->pCodecRef->bVadEnabled= (iValue?TRUE:FALSE);
}
return(0);
}
#if DIRTYCODE_LOGGING
if (iControl == 'spam')
{
NetPrintf(("voipcodec: debug verbosity level changed from %d to %d\n", pRegEntry->pCodecRef->iDebugLevel, iValue));
pRegEntry->pCodecRef->iDebugLevel = iValue;
// do not return here, we want to pass-through to codec-specific control funct.
}
#endif
if (iControl == 'vada')
{
float fNewThreshold;
fNewThreshold = iValue * 0.000001f;
NetPrintf(("voipcodec: VAD signal amplitude threshold (power of) changed from %f to %f\n", pRegEntry->pCodecRef->fVadPowerThreshold, fNewThreshold));
pRegEntry->pCodecRef->fVadPowerThreshold = fNewThreshold;
return(0);
}
if (iControl == 'vadf')
{
NetPrintf(("voipcodec: VAD frames threshold changed from %d to %d\n", pRegEntry->pCodecRef->iVadSilenceFramesThreshold, iValue));
pRegEntry->pCodecRef->iVadSilenceFramesThreshold = iValue;
return(0);
}
// then call the registered codec control function
return(pRegEntry->pCodecDef->pControl(pRegEntry->pCodecRef, iControl, iValue, iValue2, pValue));
}
/*F********************************************************************************/
/*!
\Function VoipCodecStatus
\Description
Get codec status
\Input iCodecIdent - codec identifier, or VOIP_CODEC_ACTIVE
\Input iSelect - status selector
\Input iValue - selector-specific
\Input *pBuffer - [out] selector-specific buffer space
\Input iBufSize - size of output buffer
\Output
int32_t - selector specific
\Notes
iControl can be one of the following:
\verbatim
'coco' - returns the number of register codecs
'plvl' - returns current voice power level (same calc as VAD threshold)
\endverbatim
\Version 08/09/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipCodecStatus(int32_t iCodecIdent, int32_t iSelect, int32_t iValue, void *pBuffer, int32_t iBufSize)
{
VoipCodecRegT *pRegEntry;
// returns the number of active codecs
if (iSelect == 'coco')
{
return(_VoipCodec_iNumCodecs);
}
// make sure active codec is valid
if ((pRegEntry = _VoipCodecGet(iCodecIdent, NULL)) == NULL)
{
NetPrintf(("voipcodec: invalid codec, cannot process status message '%C'\n", iSelect));
return(-1);
}
// returns current voice power level (same calculation as is used for VAD threshold)
if (iSelect == 'plvl')
{
return((int32_t)(pRegEntry->pCodecRef->fPowerLevel / 0.000001f));
}
// call the registered status function
return(pRegEntry->pCodecDef->pStatus(pRegEntry->pCodecRef, iSelect, iValue, pBuffer, iBufSize));
}
/*F********************************************************************************/
/*!
\Function VoipCodecDestroy
\Description
Destroy the active codec
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipCodecDestroy(void)
{
VoipCodecRegT *pRegEntry;
// get active codec
if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL)
{
return;
}
// call the registered destroy function
pRegEntry->pCodecRef->pCodecDef->pDestroy(pRegEntry->pCodecRef);
// clear codec ref state pointer
pRegEntry->pCodecRef = NULL;
// reset active codec index
_VoipCodec_iActiveCodec = -1;
}
/*F********************************************************************************/
/*!
\Function VoipCodecReset
\Description
Resets codec state of active codec.
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipCodecReset(void)
{
VoipCodecRegT *pRegEntry;
// get active codec
if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL)
{
return;
}
// call the registered reset function
pRegEntry->pCodecDef->pReset(pRegEntry->pCodecRef);
}
/*F********************************************************************************/
/*!
\Function VoipCodecEncode
\Description
Encode input data with the active codec.
\Input *pOutput - pointer to output buffer
\Input *pInput - pointer to input sample data
\Input iNumSamples - number of input samples
\Input *pTranscribeRef - transcription ref, if available
\Output
int32_t - size of output data
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipCodecEncode(uint8_t *pOutput, const int16_t *pInput, int32_t iNumSamples, VoipTranscribeRefT *pTranscribeRef)
{
VoipCodecRegT *pRegEntry;
int32_t iResult;
#if VOIPCODEC_TIMER
uint64_t uTimer;
#endif
// make sure active codec is valid
if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL)
{
NetPrintf(("voipcodec: no active codec, cannot encode\n"));
return(0);
}
// if VAD is enabled and we detect silence, skip the encode
if (pRegEntry->pCodecRef->bVadEnabled && (_VoipCodecVadProcess16BitFrame(pRegEntry->pCodecRef, pInput, iNumSamples)))
{
return(0);
}
#if VOIPCODEC_TIMER
uTimer = NetTickUsec();
#endif
// call the registered encoder
iResult = pRegEntry->pCodecDef->pEncode(pRegEntry->pCodecRef, pOutput, pInput, iNumSamples);
#if VOIPCODEC_TIMER
NetPrintf(("voipcodecencode: %dus\n", NetTickDiff(NetTickUsec(), uTimer)));
#endif
// submit voice data for transcription if we were passed a transcription ref
if ((pTranscribeRef != NULL) && (iResult > 0))
{
if (VoipTranscribeStatus(pTranscribeRef, 'cmpr', 0, NULL, 0))
{
// transcribing compressed audio
VoipTranscribeSubmit(pTranscribeRef, pOutput, iResult);
}
else
{
// transcribing uncompressed audio
VoipTranscribeSubmit(pTranscribeRef, (const uint8_t *)pInput, iNumSamples*sizeof(*pInput));
}
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function VoipCodecDecode
\Description
Decode input data with the active codec.
\Input *pOutput - pointer to output buffer
\Input *pInput - pointer to input compressed data
\Input iInputBytes - size in bytes of input data
\Input iChannel - the input channel whose data we are decoding
\Output
int32_t - number of output samples
\Version 08/04/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipCodecDecode(int32_t *pOutput, const uint8_t *pInput, int32_t iInputBytes, int32_t iChannel)
{
VoipCodecRegT *pRegEntry;
int32_t iResult;
#if VOIPCODEC_TIMER
uint64_t uTimer = NetTickUsec();
#endif
// make sure active codec is valid
if ((pRegEntry = _VoipCodecGet(VOIP_CODEC_ACTIVE, NULL)) == NULL)
{
NetPrintf(("voipcodec: no active codec, cannot decode\n"));
return(0);
}
// call the registered decoder
iResult = pRegEntry->pCodecDef->pDecode(pRegEntry->pCodecRef, pOutput, pInput, iInputBytes, iChannel);
#if VOIPCODEC_TIMER
NetPrintf(("voipcodecdecode: %dus\n", NetTickDiff(NetTickUsec(), uTimer)));
#endif
return(iResult);
}