mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2307 lines
82 KiB
C
2307 lines
82 KiB
C
/*H********************************************************************************/
|
|
/*!
|
|
\File voipheadsetpc.c
|
|
|
|
\Description
|
|
VoIP headset manager.
|
|
|
|
\Copyright
|
|
Copyright Electronic Arts 2004-2011.
|
|
|
|
\Notes
|
|
LPCM is supported only in loopback mode.
|
|
|
|
\Version 1.0 03/30/2004 (jbrookes) First Version
|
|
\Version 2.0 10/20/2011 (jbrookes) Major rewrite for cleanup and to fix I/O bugs
|
|
\Version 3.0 07/09/2012 (akirchner) Added functionality to play buffer
|
|
*/
|
|
/********************************************************************************H*/
|
|
|
|
/*** Include files ****************************************************************/
|
|
|
|
#pragma warning(push,0)
|
|
#include <windows.h>
|
|
#pragma warning(pop)
|
|
|
|
#include <string.h>
|
|
#include <mmsystem.h>
|
|
|
|
#include "DirtySDK/platform.h"
|
|
#include "DirtySDK/dirtysock.h"
|
|
#include "DirtySDK/dirtysock/dirtymem.h"
|
|
|
|
#include "DirtySDK/voip/voipdef.h"
|
|
#include "DirtySDK/voip/voiptranscribe.h"
|
|
#include "DirtySDK/voip/voipnarrate.h"
|
|
|
|
#include "voippriv.h"
|
|
#include "voipcommon.h"
|
|
#include "voipconnection.h"
|
|
#include "voippacket.h"
|
|
#include "voipmixer.h"
|
|
#include "voipconduit.h"
|
|
#include "DirtySDK/voip/voipcodec.h"
|
|
#include "voipheadset.h"
|
|
|
|
#include "voipdvi.h"
|
|
#include "voippcm.h"
|
|
|
|
/*** Defines **********************************************************************/
|
|
|
|
// Defines taken from mmddk.h - not including mmddk directly as that would
|
|
// add a dependancy on the Windows DDK
|
|
#define DRVM_MAPPER (0x2000)
|
|
#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER + 23)
|
|
#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER + 21)
|
|
#define DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY (0x00000001)
|
|
|
|
#define VOIP_HEADSET_SAMPLERATE (16000) //!< sample rate; 16khz audio
|
|
#define VOIP_HEADSET_SAMPLEWIDTH (2) //!< sample size; 16-bit samples
|
|
#define VOIP_HEADSET_FRAMEDURATION (20) //!< frame duration in milliseconds; 20ms
|
|
#define VOIP_HEADSET_FRAMESAMPLES ((VOIP_HEADSET_SAMPLERATE*VOIP_HEADSET_FRAMEDURATION)/1000) //!< samples per frame (20ms; 8khz=160, 11.025khz=220.5, 16khz=320)
|
|
#define VOIP_HEADSET_FRAMESIZE (VOIP_HEADSET_FRAMESAMPLES*VOIP_HEADSET_SAMPLEWIDTH) //!< frame size in bytes; 640
|
|
#define VOIP_HEADSET_NUMWAVEBUFFERS (5)
|
|
#define VOIP_HEADSET_MAXDEVICES (16)
|
|
#define VOIP_HEADSET_WAVEFORMAT (WAVE_FORMAT_1M16)
|
|
#define VOIP_HEADSET_PREBUFFERBLOCKS (4)
|
|
|
|
#if defined(_WIN64)
|
|
#define VOIP_HEADSET_WAVE_MAPPER ((uint64_t)-1)
|
|
#else
|
|
#define VOIP_HEADSET_WAVE_MAPPER (WAVE_MAPPER)
|
|
#endif
|
|
|
|
//! transmission interval in milliseconds
|
|
#define VOIP_THREAD_SLEEP_DURATION (20)
|
|
|
|
//! headset types (input, output)
|
|
typedef enum VoipHeadsetDeviceTypeE
|
|
{
|
|
VOIP_HEADSET_INPDEVICE,
|
|
VOIP_HEADSET_OUTDEVICE,
|
|
} VoipHeadsetDeviceTypeE;
|
|
|
|
/*** Macros ************************************************************************/
|
|
|
|
/*** Type Definitions **************************************************************/
|
|
//! playback data
|
|
typedef struct VoipHeadsetWaveDataT
|
|
{
|
|
WAVEHDR WaveHdr;
|
|
uint8_t FrameData[VOIP_HEADSET_FRAMESIZE];
|
|
} VoipHeadsetWaveDataT;
|
|
|
|
//! wave caps structure; this is a combination of the in and out caps, which are identical other than dwSupport (output only)
|
|
typedef struct VoipHeadsetWaveCapsT
|
|
{
|
|
WORD wMid; //!< manufacturer ID
|
|
WORD wPid; //!< product ID
|
|
MMVERSION vDriverVersion; //!< version of the driver
|
|
CHAR szPname[MAXPNAMELEN]; //!< product name (NULL terminated string)
|
|
DWORD dwFormats; //!< formats supported
|
|
WORD wChannels; //!< number of sources supported
|
|
WORD wReserved1; //!< packing
|
|
DWORD dwSupport; //!< functionality supported by driver
|
|
} VoipHeadsetWaveCapsT;
|
|
|
|
//! device info
|
|
typedef struct VoipHeadsetDeviceInfoT
|
|
{
|
|
VoipHeadsetDeviceTypeE eDevType; //!< device type
|
|
|
|
HANDLE hDevice; //!< handle to currently open device, or NULL if no device is open
|
|
int32_t iActiveDevice; //!< device index of active device
|
|
int32_t iDeviceToOpen; //!< device index of device we want to open, or -1 for any
|
|
|
|
int32_t iCurVolume; //!< playback volume (output devices only)
|
|
int32_t iNewVolume; //!< new volume to set (if different from iCurVolume)
|
|
|
|
VoipHeadsetWaveDataT WaveData[VOIP_HEADSET_NUMWAVEBUFFERS]; //!< audio input/output buffer
|
|
int32_t iCurWaveBuffer; //!< current input/output buffer
|
|
|
|
DWORD dwCheckFlag[VOIP_HEADSET_NUMWAVEBUFFERS]; //!< flag to check for empty buffer (output only)
|
|
|
|
VoipHeadsetWaveCapsT WaveDeviceCaps[VOIP_HEADSET_MAXDEVICES];
|
|
int32_t iNumDevices; //!< number of enumerated devices
|
|
|
|
uint8_t bActive; //!< device is active (recording/playing)
|
|
uint8_t bCloseDevice; //!< device close requested
|
|
uint8_t bChangeDevice; //!< device change requested
|
|
uint8_t _pad;
|
|
} VoipHeadsetDeviceInfoT;
|
|
|
|
//!< local user data space
|
|
typedef struct PCLocalVoipUserT
|
|
{
|
|
uint32_t uPlaybackFlags; //!< mute flag every remote user is 1 bit
|
|
} PCLocalVoipUserT;
|
|
|
|
//! remote user data space
|
|
typedef struct PCRemoteVoipUserT
|
|
{
|
|
VoipUserT User;
|
|
} PCRemoteVoipUserT;
|
|
|
|
//! VOIP module state data
|
|
struct VoipHeadsetRefT
|
|
{
|
|
int32_t iMemGroup; //!< mem group
|
|
void *pMemGroupUserData; //!< mem group user data
|
|
|
|
VoipHeadsetDeviceInfoT MicrInfo; //!< input device info
|
|
VoipHeadsetDeviceInfoT SpkrInfo; //!< output device info
|
|
|
|
// conduit info
|
|
int32_t iMaxConduits;
|
|
VoipConduitRefT *pConduitRef;
|
|
|
|
// mixer state
|
|
VoipMixerRefT *pMixerRef;
|
|
|
|
// accessibility support modules
|
|
VoipTranscribeRefT *pTranscribeRef;
|
|
VoipNarrateRefT *pNarrateRef;
|
|
|
|
// codec frame size in bytes
|
|
int32_t iCmpFrameSize;
|
|
|
|
// module debug level
|
|
int32_t iDebugLevel;
|
|
|
|
// which user 0-3 "owns" voip, VOIP_INVALID_LOCAL_USER_INDEX if no one
|
|
int32_t iParticipatingUserIndex;
|
|
|
|
// boolean control options
|
|
volatile uint8_t bMicOn; //!< TRUE if the mic is on, else FALSE
|
|
uint8_t bMuted; //!< TRUE if muted (e.g. push-to-talk), else FALSE
|
|
uint8_t bLoopback; //!< TRUE if loopback is enabled, else FALSE
|
|
uint8_t bTextChatAccessibility; //!< TRUE if text chat accessibility features are enabled
|
|
uint8_t bCrossplay; //!< TRUE if cross-play is enabled, else FALSE
|
|
|
|
uint8_t uSendSeq; //!< send sequence count
|
|
uint8_t _pad[2];
|
|
|
|
// user callback data
|
|
VoipHeadsetMicDataCbT *pMicDataCb;
|
|
VoipHeadsetTextDataCbT *pTextDataCb;
|
|
VoipHeadsetStatusCbT *pStatusCb;
|
|
void *pCbUserData;
|
|
|
|
// speaker callback data
|
|
VoipSpkrCallbackT *pSpkrDataCb;
|
|
void *pSpkrCbUserData;
|
|
|
|
// critical section for guarding access to device close/change flags
|
|
NetCritT DevChangeCrit;
|
|
|
|
// player
|
|
int32_t iPlayerActive;
|
|
int16_t *pPlayerBuffer;
|
|
uint32_t uPlayerBufferFrameCurrent;
|
|
uint32_t uPlayerBufferFrames;
|
|
uint32_t uPlayerFirstTime;
|
|
int32_t iPlayerFirstUse;
|
|
|
|
// STT
|
|
uint8_t bVoiceTranscriptionEnabled;
|
|
|
|
// TTS
|
|
uint8_t bNarrating;
|
|
int32_t iNarrateWritePos;
|
|
VoipNarrateGenderE eDefaultGender;
|
|
int8_t narrateBuffer[VOIP_HEADSET_FRAMESIZE];
|
|
|
|
PCLocalVoipUserT aLocalUsers[VOIP_MAXLOCALUSERS];
|
|
|
|
//! remote user list - must come last in ref as it is variable length
|
|
int32_t iMaxRemoteUsers;
|
|
PCRemoteVoipUserT aRemoteUsers[1];
|
|
};
|
|
|
|
/*** Function Prototypes **********************************************************/
|
|
|
|
/*** Variables ********************************************************************/
|
|
|
|
// Public Variables
|
|
|
|
// Private Variables
|
|
|
|
/*** Private Functions ************************************************************/
|
|
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveGetNumDevs
|
|
|
|
\Description
|
|
Wraps wave(In|Out)GetNumDevs()
|
|
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
UINT - wave(In|Out)GetNumDevs() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static UINT _VoipHeadsetWaveGetNumDevs(VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInGetNumDevs() : waveOutGetNumDevs());
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveGetDevCaps
|
|
|
|
\Description
|
|
Wraps wave(In|Out)GetDevCaps()
|
|
|
|
\Input uDeviceID - device to get capabilities of
|
|
\Input *pWaveCaps - wave capabilities (note this structure does double-duty for input and output)
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)GetNumDevs() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWaveGetDevCaps(UINT_PTR uDeviceID, VoipHeadsetWaveCapsT *pWaveCaps, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInGetDevCaps(uDeviceID, (LPWAVEINCAPSA)pWaveCaps, sizeof(WAVEINCAPSA)) : waveOutGetDevCaps(uDeviceID, (LPWAVEOUTCAPSA)pWaveCaps, sizeof(WAVEOUTCAPSA)));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveOpen
|
|
|
|
\Description
|
|
Wraps wave(In|Out)Open()
|
|
|
|
\Input *pHandle - [out] storage for handle to opened device
|
|
\Input uDeviceID - device to open
|
|
\Input *pWaveFormat - wave format we want
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)Open() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWaveOpen(HANDLE *pHandle, UINT uDeviceID, LPCWAVEFORMATEX pWaveFormat, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInOpen((LPHWAVEIN)pHandle, uDeviceID, pWaveFormat, 0, 0, 0) : waveOutOpen((LPHWAVEOUT)pHandle, uDeviceID, pWaveFormat, 0, 0, 0));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveClose
|
|
|
|
\Description
|
|
Wraps wave(In|Out)Close()
|
|
|
|
\Input hHandle - handle to device to close
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)Close() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWaveClose(HANDLE hHandle, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInClose((HWAVEIN)hHandle) : waveOutClose((HWAVEOUT)hHandle));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWavePrepareHeader
|
|
|
|
\Description
|
|
Wraps wave(In|Out)PrepareHeader()
|
|
|
|
\Input hHandle - handle to device to prepare wave header for
|
|
\Input pWaveHeader - wave header to prepare
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)PrepareHeader() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWavePrepareHeader(HANDLE hHandle, LPWAVEHDR pWaveHeader, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInPrepareHeader((HWAVEIN)hHandle, pWaveHeader, sizeof(WAVEHDR)) : waveOutPrepareHeader((HWAVEOUT)hHandle, pWaveHeader, sizeof(WAVEHDR)));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveUnprepareHeader
|
|
|
|
\Description
|
|
Wraps wave(In|Out)UnprepareHeader()
|
|
|
|
\Input hHandle - handle to device to prepare wave header for
|
|
\Input pWaveHeader - wave header to unprepare
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)UnprepareHeader() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWaveUnprepareHeader(HANDLE hHandle, LPWAVEHDR pWaveHeader, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInUnprepareHeader((HWAVEIN)hHandle, pWaveHeader, sizeof(WAVEHDR)) : waveOutUnprepareHeader((HWAVEOUT)hHandle, pWaveHeader, sizeof(WAVEHDR)));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetWaveReset
|
|
|
|
\Description
|
|
Wraps wave(In|Out)Reset()
|
|
|
|
\Input hHandle - handle to device to prepare wave header for
|
|
\Input eDevType - VOIP_HEADSET_INPDEVICE or VOIP_HEADSET_OUTDEVICE
|
|
|
|
\Output
|
|
MMRESULT - wave(In|Out)Reset() result
|
|
|
|
\Version 10/12/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static MMRESULT _VoipHeadsetWaveReset(HANDLE hHandle, VoipHeadsetDeviceTypeE eDevType)
|
|
{
|
|
return((eDevType == VOIP_HEADSET_INPDEVICE) ? waveInReset((HWAVEIN)hHandle) : waveOutReset((HWAVEOUT)hHandle));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetPrepareWaveHeaders
|
|
|
|
\Description
|
|
Prepare wave headers for use.
|
|
|
|
\Input *pDeviceInfo - device info
|
|
\Input iNumBuffers - number of buffers in array
|
|
\Input hDevice - handle to wave device
|
|
|
|
\Output
|
|
int32_t - negative=failure, zero=success
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetPrepareWaveHeaders(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iNumBuffers, HANDLE hDevice)
|
|
{
|
|
HRESULT hResult;
|
|
int32_t iPlayBuf;
|
|
|
|
for (iPlayBuf = 0; iPlayBuf < iNumBuffers; iPlayBuf++)
|
|
{
|
|
// set up the wave header
|
|
pDeviceInfo->WaveData[iPlayBuf].WaveHdr.lpData = (LPSTR)pDeviceInfo->WaveData[iPlayBuf].FrameData;
|
|
pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwBufferLength = sizeof(pDeviceInfo->WaveData[iPlayBuf].FrameData);
|
|
pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwFlags = 0L;
|
|
pDeviceInfo->WaveData[iPlayBuf].WaveHdr.dwLoops = 0L;
|
|
|
|
// prepare the header
|
|
if ((hResult = _VoipHeadsetWavePrepareHeader(hDevice, &pDeviceInfo->WaveData[iPlayBuf].WaveHdr, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d preparing %s buffer %d\n", hResult,
|
|
pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iPlayBuf));
|
|
return(-1);
|
|
}
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetUnprepareWaveHeaders
|
|
|
|
\Description
|
|
Unprepare wave headers, prior to releasing them.
|
|
|
|
\Input *pDeviceInfo - device info
|
|
\Input iNumBuffers - number of buffers in array
|
|
\Input hDevice - handle to wave device
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetUnprepareWaveHeaders(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iNumBuffers, HANDLE hDevice)
|
|
{
|
|
HRESULT hResult;
|
|
int32_t iPlayBuf;
|
|
|
|
for (iPlayBuf = 0; iPlayBuf < iNumBuffers; iPlayBuf++)
|
|
{
|
|
if ((hResult = _VoipHeadsetWaveUnprepareHeader(hDevice, &pDeviceInfo->WaveData[iPlayBuf].WaveHdr, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d unpreparing %s buffer %d\n", hResult,
|
|
pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iPlayBuf));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetOpenDevice
|
|
|
|
\Description
|
|
Open a wave device.
|
|
|
|
\Input *pDeviceInfo - device state
|
|
\Input iDevice - handle to wave device (may be WAVE_MAPPER)
|
|
\Input iNumBuffers - number of input buffers
|
|
|
|
\Output
|
|
int32_t - negative=error, zero=success
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetOpenDevice(VoipHeadsetDeviceInfoT *pDeviceInfo, int32_t iDevice, int32_t iNumBuffers)
|
|
{
|
|
WAVEFORMATEX WaveFormatEx = { WAVE_FORMAT_PCM, 1, VOIP_HEADSET_SAMPLERATE, VOIP_HEADSET_SAMPLERATE * VOIP_HEADSET_SAMPLEWIDTH, 2, 16, 0 };
|
|
HANDLE hDevice = NULL;
|
|
HRESULT hResult;
|
|
|
|
// open device
|
|
if ((hResult = _VoipHeadsetWaveOpen(&hDevice, iDevice, &WaveFormatEx, pDeviceInfo->eDevType)) != S_OK)
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d opening input device\n", hResult));
|
|
return(-1);
|
|
}
|
|
|
|
// set up input wave data
|
|
if (_VoipHeadsetPrepareWaveHeaders(pDeviceInfo, iNumBuffers, hDevice) >= 0)
|
|
{
|
|
// set volume to -1 so it will be reset
|
|
pDeviceInfo->iCurVolume = -1;
|
|
|
|
// set active device
|
|
pDeviceInfo->iActiveDevice = iDevice;
|
|
|
|
if (pDeviceInfo->eDevType == VOIP_HEADSET_OUTDEVICE)
|
|
{
|
|
int32_t iBuffer;
|
|
|
|
// mark as playing
|
|
pDeviceInfo->bActive = TRUE;
|
|
|
|
// set up check flag (we have to check for WHDR_PREPARED until we've written into the buffer)
|
|
for (iBuffer = 0; iBuffer < VOIP_HEADSET_NUMWAVEBUFFERS; iBuffer += 1)
|
|
{
|
|
pDeviceInfo->dwCheckFlag[iBuffer] = WHDR_PREPARED;
|
|
}
|
|
}
|
|
|
|
NetPrintf(("voipheadsetpc: opened %s device\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output"));
|
|
}
|
|
else
|
|
{
|
|
_VoipHeadsetWaveClose(hDevice, pDeviceInfo->eDevType);
|
|
hDevice = NULL;
|
|
}
|
|
|
|
pDeviceInfo->hDevice = hDevice;
|
|
return((hDevice == NULL) ? -2 : 0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetCloseDevice
|
|
|
|
\Description
|
|
Close an open device.
|
|
|
|
\Input *pHeadset - module state
|
|
\Input *pDeviceInfo - device info
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetCloseDevice(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo)
|
|
{
|
|
MMRESULT hResult;
|
|
int32_t iDevice;
|
|
|
|
// no open device, nothing to do
|
|
if (pDeviceInfo->hDevice == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// reset the device
|
|
if ((hResult = _VoipHeadsetWaveReset(pDeviceInfo->hDevice, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: failed to reset %s device (err=%d)\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "waveIn" : "waveOut", hResult));
|
|
}
|
|
|
|
// unprepare the wave headers
|
|
_VoipHeadsetUnprepareWaveHeaders(pDeviceInfo, 2, pDeviceInfo->hDevice);
|
|
|
|
// close the device
|
|
if ((hResult = _VoipHeadsetWaveClose(pDeviceInfo->hDevice, pDeviceInfo->eDevType)) != MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: failed to close %s device (err=%d)\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "waveIn" : "waveOut", hResult));
|
|
}
|
|
|
|
// reset device state
|
|
NetPrintf(("voipheadsetpc: closed %s device\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output"));
|
|
|
|
pDeviceInfo->hDevice = NULL;
|
|
pDeviceInfo->bActive = FALSE;
|
|
iDevice = pDeviceInfo->iActiveDevice;
|
|
pDeviceInfo->iActiveDevice = -1;
|
|
|
|
// trigger device inactive callback
|
|
// user index is always 0 because PC does not support MLU
|
|
pHeadset->pStatusCb(0, FALSE, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? VOIP_HEADSET_STATUS_INPUT : VOIP_HEADSET_STATUS_OUTPUT, pHeadset->pCbUserData);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetEnumerateDevices
|
|
|
|
\Description
|
|
Enumerate devices attached to system, and add those that are compatible
|
|
to device list.
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input *pDeviceInfo - device info
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetEnumerateDevices(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo)
|
|
{
|
|
int32_t iDevice, iNumDevices, iAddedDevices;
|
|
VoipHeadsetWaveCapsT *pDeviceCaps;
|
|
const char *pActiveDeviceName = NULL;
|
|
int32_t iActiveDevice = -1;
|
|
|
|
// get total number of eDevType devices
|
|
iNumDevices = _VoipHeadsetWaveGetNumDevs(pDeviceInfo->eDevType);
|
|
|
|
// get the active device name, if a device is active
|
|
if (pDeviceInfo->iActiveDevice != -1)
|
|
{
|
|
iActiveDevice = pDeviceInfo->iActiveDevice;
|
|
pActiveDeviceName = pDeviceInfo->WaveDeviceCaps[iActiveDevice].szPname;
|
|
}
|
|
|
|
// walk device list
|
|
for (iDevice = 0, iAddedDevices = 0; (iDevice < iNumDevices) && (iAddedDevices < VOIP_HEADSET_MAXDEVICES); iDevice++)
|
|
{
|
|
// get device capabilities
|
|
pDeviceCaps = &pDeviceInfo->WaveDeviceCaps[iAddedDevices];
|
|
_VoipHeadsetWaveGetDevCaps(iDevice, pDeviceCaps, pDeviceInfo->eDevType);
|
|
|
|
// print device info
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: querying %s device %d\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wMid=%d\n", pDeviceCaps->wMid));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wPid=%d\n", pDeviceCaps->wPid));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: vDriverVersion=%d.%d\n", (pDeviceCaps->vDriverVersion&0xff00) >> 8, pDeviceCaps->vDriverVersion&0xff));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: szPname=%s\n", pDeviceCaps->szPname));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: dwFormats=0x%08x\n", pDeviceCaps->dwFormats));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: wChannels=%d\n", pDeviceCaps->wChannels));
|
|
|
|
// is it compatible?
|
|
if (pDeviceCaps->dwFormats & VOIP_HEADSET_WAVEFORMAT)
|
|
{
|
|
// if this device is our active input device make sure to update our iActiveDevice value;
|
|
if ((iActiveDevice != -1) && (strcmp(pActiveDeviceName, pDeviceCaps->szPname) == 0))
|
|
{
|
|
// if the active input device index changed log the change
|
|
if (iActiveDevice != iAddedDevices)
|
|
{
|
|
pDeviceInfo->iActiveDevice = iAddedDevices;
|
|
NetPrintf(("voipheadsetpc: active %s device, %s, moved from index %d to %d\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output",
|
|
pActiveDeviceName, iActiveDevice, iAddedDevices));
|
|
}
|
|
|
|
// already matched don't compare names again
|
|
iActiveDevice = -1;
|
|
}
|
|
|
|
NetPrintf(("voipheadsetpc: %s device %d is compatible\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice));
|
|
iAddedDevices++;
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: %s device %d is not compatible\n", pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output", iDevice));
|
|
}
|
|
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc:\n"));
|
|
}
|
|
|
|
// set number of devices
|
|
pDeviceInfo->iNumDevices = iAddedDevices;
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetStop
|
|
|
|
\Description
|
|
Stop recording & playback, and close USB audio device.
|
|
|
|
\Notes
|
|
This function is safe to call regardless of device state.
|
|
|
|
\Version 11/03/2003 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetStop(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
_VoipHeadsetCloseDevice(pHeadset, &pHeadset->MicrInfo);
|
|
_VoipHeadsetCloseDevice(pHeadset, &pHeadset->SpkrInfo);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetEnumerate
|
|
|
|
\Description
|
|
Enumerate all connected audio devices.
|
|
|
|
\Input *pHeadset - headset module state
|
|
|
|
\Notes
|
|
Although we only support one headset, we will scan up to eight to try and
|
|
locate one that meets our needs for data format and sample rate.
|
|
|
|
\Version 07/27/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetEnumerate(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
NetPrintf(("voipheadsetpc: enumerating devices\n"));
|
|
|
|
// re-enumerating will kill our existing devices, so clean up appropriately.
|
|
_VoipHeadsetStop(pHeadset);
|
|
|
|
// enumerate input devices
|
|
_VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->MicrInfo);
|
|
|
|
// enumerate output devices
|
|
_VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->SpkrInfo);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetSetCodec
|
|
|
|
\Description
|
|
Sets the specified codec.
|
|
|
|
\Input *pHeadset - pointer to module state
|
|
\Input iCodecIdent - codec identifier to set
|
|
|
|
\Output
|
|
int32_t - zero=success, negative=failure
|
|
|
|
\Version 10/26/2011 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetSetCodec(VoipHeadsetRefT *pHeadset, int32_t iCodecIdent)
|
|
{
|
|
int32_t iResult;
|
|
|
|
// pass through codec creation request
|
|
if ((iResult = VoipCodecCreate(iCodecIdent, pHeadset->iMaxConduits)) < 0)
|
|
{
|
|
return(iResult);
|
|
}
|
|
|
|
// query codec output size
|
|
pHeadset->iCmpFrameSize = VoipCodecStatus(iCodecIdent, 'fsiz', VOIP_HEADSET_FRAMESAMPLES, NULL, 0);
|
|
|
|
// return result to caller
|
|
return(iResult);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetSendEncodedAudio
|
|
|
|
\Description
|
|
Send encoded audio on the wire, or into the conduit for local playback
|
|
in loopback mode.
|
|
|
|
\Input *pHeadset - module state
|
|
\Input *pPacket - compressed audio data
|
|
\Input iCompBytes - size of the compressed audio data
|
|
|
|
\Version 12/05/2018 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetSendEncodedAudio(VoipHeadsetRefT *pHeadset, VoipMicrPacketT *pPacket, int32_t iCompBytes)
|
|
{
|
|
if (!pHeadset->bLoopback)
|
|
{
|
|
pHeadset->pMicDataCb(&pPacket->aData, iCompBytes, NULL, 0, pHeadset->iParticipatingUserIndex, pHeadset->uSendSeq++, pHeadset->pCbUserData);
|
|
}
|
|
else
|
|
{
|
|
// set up a null user for loopback
|
|
VoipUserT VoipUser;
|
|
ds_memclr(&VoipUser, sizeof(VoipUser));
|
|
|
|
VoipConduitReceiveVoiceData(pHeadset->pConduitRef, &VoipUser, pPacket->aData, iCompBytes);
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetNarrateCb
|
|
|
|
\Description
|
|
VoipNarrate callback handler
|
|
|
|
\Input *pNarrateRef - narration module state
|
|
\Input iUserIndex - local user index of user who requested narration
|
|
\Input *pSamples - sample data, or NULL if no data
|
|
\Input iSize - size of sample data in bytes, or zero if no data
|
|
\Input *pUserData - callback user data (headset module ref)
|
|
|
|
\Version 12/05/2018 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetNarrateCb(VoipNarrateRefT *pNarrateRef, int32_t iUserIndex, const int16_t *pSamples, int32_t iSize, void *pUserData)
|
|
{
|
|
VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData;
|
|
const int8_t *pBuffer = (const int8_t *)pSamples;
|
|
VoipMicrPacketT PacketData;
|
|
int32_t iCompBytes;
|
|
|
|
if (iSize > 0)
|
|
{
|
|
int32_t iRemain = iSize;
|
|
while (iRemain != 0)
|
|
{
|
|
int32_t iCopySize = 0;
|
|
int8_t *pFrameBuffer = pHeadset->narrateBuffer + pHeadset->iNarrateWritePos;
|
|
|
|
if (pHeadset->iNarrateWritePos == 0)
|
|
{
|
|
ds_memclr(pFrameBuffer, VOIP_HEADSET_FRAMESIZE);
|
|
}
|
|
|
|
if (iRemain >= (VOIP_HEADSET_FRAMESIZE - pHeadset->iNarrateWritePos))
|
|
{
|
|
iCopySize = VOIP_HEADSET_FRAMESIZE - pHeadset->iNarrateWritePos;
|
|
}
|
|
else
|
|
{
|
|
iCopySize = iRemain;
|
|
}
|
|
|
|
ds_memcpy(pFrameBuffer, pBuffer + (iSize - iRemain), iCopySize);
|
|
iRemain -= iCopySize;
|
|
|
|
if ((iCopySize == VOIP_HEADSET_FRAMESIZE) || ((pHeadset->iNarrateWritePos + iCopySize) == VOIP_HEADSET_FRAMESIZE))
|
|
{
|
|
pHeadset->iNarrateWritePos = 0;
|
|
|
|
// compress the input data, and if there is compressed data send it to the appropriate function
|
|
if ((iCompBytes = VoipCodecEncode(PacketData.aData, (const int16_t*)(pHeadset->narrateBuffer), VOIP_HEADSET_FRAMESAMPLES, (!pHeadset->bTextChatAccessibility && pHeadset->bVoiceTranscriptionEnabled) ? pHeadset->pTranscribeRef : NULL)) > 0)
|
|
{
|
|
_VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pHeadset->iNarrateWritePos += iCopySize;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pHeadset->bNarrating = (iSize == VOIPNARRATE_STREAM_START) ? TRUE : FALSE;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetProcessPlay
|
|
|
|
\Description
|
|
Process recording of data from the buffer and sending it to the registered
|
|
mic data user callback.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
|
|
\Version 07/09/2012 (akirchner)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetProcessPlay(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
VoipMicrPacketT PacketData;
|
|
int32_t iCompBytes;
|
|
uint32_t uPlayerLastTime;
|
|
uint32_t uPackets;
|
|
uint32_t uPacket;
|
|
|
|
if (!pHeadset->iPlayerFirstUse)
|
|
{
|
|
pHeadset->iPlayerFirstUse = 1;
|
|
pHeadset->uPlayerFirstTime = NetTick();
|
|
}
|
|
|
|
uPlayerLastTime = NetTick();
|
|
uPackets = (uPlayerLastTime - pHeadset->uPlayerFirstTime) / VOIP_THREAD_SLEEP_DURATION;
|
|
|
|
for (uPacket = 0; uPacket < uPackets; uPacket++)
|
|
{
|
|
pHeadset->uPlayerBufferFrameCurrent += VOIP_HEADSET_SAMPLEWIDTH;
|
|
pHeadset->uPlayerBufferFrameCurrent = (pHeadset->uPlayerBufferFrameCurrent >= pHeadset->uPlayerBufferFrames) ? 0 : pHeadset->uPlayerBufferFrameCurrent;
|
|
|
|
// compress the buffer data, and if there is compressed data send it to the appropriate function
|
|
if ((iCompBytes = VoipCodecEncode(PacketData.aData, &pHeadset->pPlayerBuffer[VOIP_HEADSET_FRAMESAMPLES * pHeadset->uPlayerBufferFrameCurrent], VOIP_HEADSET_FRAMESAMPLES, pHeadset->pTranscribeRef)) > 0)
|
|
{
|
|
_VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes);
|
|
}
|
|
}
|
|
|
|
pHeadset->uPlayerFirstTime = uPlayerLastTime;
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetProcessRecord
|
|
|
|
\Description
|
|
Process recording of data from the mic and sending it to the registered
|
|
mic data user callback.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
|
|
\Version 04/01/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetProcessRecord(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
VoipHeadsetDeviceInfoT *pMicrInfo = &pHeadset->MicrInfo;
|
|
VoipMicrPacketT PacketData;
|
|
uint8_t FrameData[VOIP_HEADSET_FRAMESIZE]; //!< current recorded audio frame
|
|
int32_t iCompBytes;
|
|
HRESULT hResult;
|
|
|
|
// only record if we have a recording device open
|
|
if ((pMicrInfo->hDevice == NULL) || (pHeadset->iParticipatingUserIndex == VOIP_INVALID_LOCAL_USER_INDEX))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// read data from the mic
|
|
if (pMicrInfo->bActive == TRUE)
|
|
{
|
|
if (pHeadset->bMicOn == FALSE)
|
|
{
|
|
// tell hardware to stop recording
|
|
if ((hResult = waveInStop((HWAVEIN)pMicrInfo->hDevice)) == MMSYSERR_NOERROR)
|
|
{
|
|
// mark as not recording
|
|
NetPrintf(("voipheadsetpc: stop recording\n"));
|
|
pMicrInfo->bActive = FALSE;
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d trying to stop recording\n", hResult));
|
|
}
|
|
}
|
|
|
|
// pull in any waiting data
|
|
for (; pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].WaveHdr.dwFlags & WHDR_DONE; )
|
|
{
|
|
// copy audio data out of buffer
|
|
ds_memcpy_s(FrameData, sizeof(FrameData), &pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].FrameData, VOIP_HEADSET_FRAMESIZE);
|
|
|
|
// compress the input data, and if there is compressed data send it to the appropriate function
|
|
if (!pHeadset->bNarrating && (!pHeadset->bMuted || pHeadset->bLoopback))
|
|
{
|
|
if ((iCompBytes = VoipCodecEncode(PacketData.aData, (int16_t *)FrameData, VOIP_HEADSET_FRAMESAMPLES, pHeadset->bVoiceTranscriptionEnabled ? pHeadset->pTranscribeRef : NULL)) > 0)
|
|
{
|
|
if (pHeadset->bTextChatAccessibility == FALSE)
|
|
{
|
|
_VoipHeadsetSendEncodedAudio(pHeadset, &PacketData, iCompBytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
// re-queue buffer to read more data
|
|
if ((hResult = waveInAddBuffer((HWAVEIN)pMicrInfo->hDevice, &pMicrInfo->WaveData[pMicrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
|
|
{
|
|
if (hResult == MMSYSERR_NODRIVER)
|
|
{
|
|
NetPrintf(("voipheadsetpc: waveInAddBuffer returned MMSYSERR_NODRIVER. Headset removed? Closing headset\n"));
|
|
_VoipHeadsetStop(pHeadset);
|
|
break;
|
|
}
|
|
else if (hResult != WAVERR_STILLPLAYING)
|
|
{
|
|
NetPrintf(("voipheadsetpc: could not add buffer %d to record queue (err=%d)\n", pMicrInfo->iCurWaveBuffer, hResult));
|
|
}
|
|
}
|
|
|
|
// index to next read buffer
|
|
pMicrInfo->iCurWaveBuffer = (pMicrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS;
|
|
}
|
|
}
|
|
// if not recording and a mic-on request comes in, start recording
|
|
else if (pHeadset->bMicOn == TRUE)
|
|
{
|
|
int32_t iBuffer;
|
|
|
|
if ((hResult = waveInStart((HWAVEIN)pMicrInfo->hDevice)) == MMSYSERR_NOERROR)
|
|
{
|
|
// mark as recording
|
|
NetPrintf(("voipheadsetpc: recording...\n"));
|
|
pMicrInfo->bActive = TRUE;
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d trying to start recording\n", hResult));
|
|
}
|
|
|
|
// add buffers to start recording
|
|
for (iBuffer = 0; iBuffer < VOIP_HEADSET_NUMWAVEBUFFERS; iBuffer += 1)
|
|
{
|
|
if ((hResult = waveInAddBuffer((HWAVEIN)pMicrInfo->hDevice, &pMicrInfo->WaveData[iBuffer].WaveHdr, sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: waveInAddBuffer for buffer %d failed err=%d\n", iBuffer, hResult));
|
|
}
|
|
}
|
|
pMicrInfo->iCurWaveBuffer = 0;
|
|
|
|
// reset compression state
|
|
VoipCodecReset();
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetProcessPlayback
|
|
|
|
\Description
|
|
Process playback of data received from the network to the headset earpiece.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
|
|
\Version 04/01/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetProcessPlayback(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
VoipHeadsetDeviceInfoT *pSpkrInfo = &pHeadset->SpkrInfo;
|
|
uint8_t FrameData[VOIP_HEADSET_FRAMESIZE]; //!< current recorded audio frame
|
|
int32_t iSampBytes;
|
|
HRESULT hResult;
|
|
|
|
// only play if we have a playback device open
|
|
if ((pSpkrInfo->hDevice == NULL) || (pHeadset->iParticipatingUserIndex == VOIP_INVALID_LOCAL_USER_INDEX))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// write data to the audio output if it's available
|
|
if (pSpkrInfo->bActive == TRUE)
|
|
{
|
|
int32_t iLoop, iNumBufAvail;
|
|
|
|
// update play volume, if requested
|
|
if ((pSpkrInfo->iCurVolume != pSpkrInfo->iNewVolume) && (pSpkrInfo->iNewVolume != -1))
|
|
{
|
|
int32_t iVolume;
|
|
|
|
iVolume = (pSpkrInfo->iNewVolume * 65535) / 100;
|
|
iVolume |= iVolume << 16;
|
|
|
|
if ((hResult = waveOutSetVolume((HWAVEOUT)pSpkrInfo->hDevice, iVolume)) == MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintf(("voipheadsetpc: changed play volume to %d\n", pSpkrInfo->iNewVolume));
|
|
pSpkrInfo->iCurVolume = pSpkrInfo->iNewVolume;
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: error %d trying to change play volume\n", hResult));
|
|
}
|
|
}
|
|
|
|
// count number of buffers available to write into
|
|
for (iLoop = 0, iNumBufAvail = 0; iLoop < VOIP_HEADSET_NUMWAVEBUFFERS; iLoop += 1)
|
|
{
|
|
iNumBufAvail += (pSpkrInfo->WaveData[iLoop].WaveHdr.dwFlags & WHDR_DONE) ? 1 : 0;
|
|
}
|
|
|
|
// if we have space to write
|
|
for ( ; pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr.dwFlags & pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer]; )
|
|
{
|
|
// decode and mix buffered packet data
|
|
if ((iSampBytes = VoipMixerProcess(pHeadset->pMixerRef, FrameData)) == VOIP_HEADSET_FRAMESIZE)
|
|
{
|
|
// if there's nothing playing, we want to prebuffer with silence
|
|
if (iNumBufAvail == VOIP_HEADSET_NUMWAVEBUFFERS)
|
|
{
|
|
uint8_t FrameSilenceData[VOIP_HEADSET_FRAMESIZE];
|
|
int32_t iBlock;
|
|
|
|
ds_memclr(FrameSilenceData, sizeof(FrameSilenceData));
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: prebuffering %d blocks\n", VOIP_HEADSET_PREBUFFERBLOCKS));
|
|
for (iBlock = 0; iBlock < VOIP_HEADSET_PREBUFFERBLOCKS; iBlock += 1)
|
|
{
|
|
ds_memcpy_s(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData, sizeof(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData), FrameSilenceData, sizeof(FrameSilenceData));
|
|
if ((hResult = waveOutWrite((HWAVEOUT)pSpkrInfo->hDevice, &pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) == MMSYSERR_NOERROR)
|
|
{
|
|
pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer] = WHDR_DONE;
|
|
pSpkrInfo->iCurWaveBuffer = (pSpkrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS;
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: write failed (error=%d)\n", hResult));
|
|
}
|
|
}
|
|
iNumBufAvail = 0;
|
|
}
|
|
|
|
// forward data to speaker callback, if callback is specified
|
|
if (pHeadset->pSpkrDataCb != NULL)
|
|
{
|
|
pHeadset->pSpkrDataCb((int16_t *)FrameData, VOIP_HEADSET_FRAMESAMPLES, pHeadset->pSpkrCbUserData);
|
|
}
|
|
|
|
// copy data to prepared wave buffer
|
|
ds_memcpy_s(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData, sizeof(pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].FrameData), FrameData, VOIP_HEADSET_FRAMESIZE);
|
|
|
|
// write out wave buffer
|
|
if ((hResult = waveOutWrite((HWAVEOUT)pSpkrInfo->hDevice, &pSpkrInfo->WaveData[pSpkrInfo->iCurWaveBuffer].WaveHdr, sizeof(WAVEHDR))) == MMSYSERR_NOERROR)
|
|
{
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: [%2d] wrote %d samples\n", pSpkrInfo->iCurWaveBuffer, VOIP_HEADSET_FRAMESAMPLES));
|
|
pSpkrInfo->dwCheckFlag[pSpkrInfo->iCurWaveBuffer] = WHDR_DONE;
|
|
pSpkrInfo->iCurWaveBuffer = (pSpkrInfo->iCurWaveBuffer + 1) % VOIP_HEADSET_NUMWAVEBUFFERS;
|
|
}
|
|
else
|
|
{
|
|
if (hResult == MMSYSERR_NODRIVER)
|
|
{
|
|
NetPrintf(("voipheadsetpc: returned MMSYSERR_NODRIVER. Headset removed? Closing headset\n"));
|
|
_VoipHeadsetStop(pHeadset);
|
|
}
|
|
else if (hResult == WAVERR_STILLPLAYING)
|
|
{
|
|
NetPrintf(("voipheadsetpc: WAVERR_STILLPLAYING\n"));
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: write failed (error=%d)\n", hResult));
|
|
}
|
|
}
|
|
}
|
|
else if (iSampBytes > 0)
|
|
{
|
|
NetPrintf(("voipheadsetpc: error - got %d bytes from mixer when we were expecting %d\n", iSampBytes, VOIP_HEADSET_FRAMESIZE));
|
|
}
|
|
else
|
|
{
|
|
// no data waiting in the mixer, so break out
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetProcessDeviceChange
|
|
|
|
\Description
|
|
Process a device change request.
|
|
|
|
\Input *pHeadset - headset state
|
|
\Input *pDeviceInfo - device info
|
|
|
|
\Version 10/12/2011 (jbrookes) Split from VoipHeadsetProcess()
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetProcessDeviceChange(VoipHeadsetRefT *pHeadset, VoipHeadsetDeviceInfoT *pDeviceInfo)
|
|
{
|
|
// early out if no change is requested
|
|
if (!pDeviceInfo->bChangeDevice && !pDeviceInfo->bCloseDevice)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// device change requires sole access to headset critical section
|
|
NetCritEnter(&pHeadset->DevChangeCrit);
|
|
|
|
// close the device
|
|
_VoipHeadsetCloseDevice(pHeadset, pDeviceInfo);
|
|
pDeviceInfo->bCloseDevice = FALSE;
|
|
|
|
// process change input device request
|
|
if (pDeviceInfo->bChangeDevice)
|
|
{
|
|
if (_VoipHeadsetOpenDevice(pDeviceInfo, pDeviceInfo->iDeviceToOpen, VOIP_HEADSET_NUMWAVEBUFFERS) == 0)
|
|
{
|
|
// user index is always 0 because PC does not support MLU
|
|
pHeadset->pStatusCb(0, TRUE, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? VOIP_HEADSET_STATUS_INPUT : VOIP_HEADSET_STATUS_OUTPUT, pHeadset->pCbUserData);
|
|
}
|
|
|
|
pDeviceInfo->bChangeDevice = FALSE;
|
|
}
|
|
|
|
NetCritLeave(&pHeadset->DevChangeCrit);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetProcessTranscription
|
|
|
|
\Description
|
|
Process voice transcription
|
|
|
|
\Input pHeadset - headset ref
|
|
|
|
\Version 09/07/2018 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _VoipHeadsetProcessTranscription(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
char strTranscribeBuf[VOIPTRANSCRIBE_OUTPUT_MAX];
|
|
|
|
if (pHeadset->pTranscribeRef != NULL)
|
|
{
|
|
// process transcription
|
|
VoipTranscribeUpdate(pHeadset->pTranscribeRef);
|
|
|
|
// if a transcription is available, send it along
|
|
if (VoipTranscribeGet(pHeadset->pTranscribeRef, strTranscribeBuf, sizeof(strTranscribeBuf)) > 0)
|
|
{
|
|
if (pHeadset->iParticipatingUserIndex != VOIP_INVALID_LOCAL_USER_INDEX)
|
|
{
|
|
pHeadset->pTextDataCb(strTranscribeBuf, pHeadset->iParticipatingUserIndex, pHeadset->pCbUserData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetGetRemoteUserIndex
|
|
|
|
\Description
|
|
Returns the user index of the remote user
|
|
|
|
\Input *pHeadset - pHeadset ref
|
|
\Input *pRemoteUser - remote user
|
|
|
|
\Output
|
|
int32_t - remote user index, negative if user is not found
|
|
|
|
\Version 05/28/2019 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetGetRemoteUserIndex(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser)
|
|
{
|
|
int32_t iRemoteUserIndex;
|
|
|
|
for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex)
|
|
{
|
|
if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return((iRemoteUserIndex != pHeadset->iMaxRemoteUsers) ? iRemoteUserIndex : -1);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetPlaybackCallback
|
|
|
|
\Description
|
|
Determines if we should be submit audio to the given mixer
|
|
|
|
\Input *pMixer - mixer ref
|
|
\Input *pRemoteUser - remote user who sent the mic packets
|
|
\Input *pUserData - callback user data (VoipHeadsetRefT)
|
|
|
|
\Output
|
|
uint8_t - TRUE for playback, FALSE for not to playback
|
|
|
|
\Version 05/28/2019 (cvienneau)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static uint8_t _VoipHeadsetPlaybackCallback(VoipMixerRefT *pMixer, VoipUserT *pRemoteUser, void *pUserData)
|
|
{
|
|
uint8_t bPlayback = TRUE;
|
|
VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData;
|
|
|
|
int32_t iRemoteUserIndex;
|
|
int32_t iLocalUserIndex;
|
|
|
|
// find the remote user index
|
|
iRemoteUserIndex = _VoipHeadsetGetRemoteUserIndex(pHeadset, pRemoteUser);
|
|
|
|
if (iRemoteUserIndex < 0)
|
|
{
|
|
NetPrintf(("voipheadsetpc: _VoipHeadsetPlaybackCallback() cannot find the remote user iPersonaId: %lld", pRemoteUser->AccountInfo.iPersonaId));
|
|
return(TRUE);
|
|
}
|
|
|
|
// loop through all local users determine if ANY of them have this remote user index muted (we have a shared device on PC)
|
|
for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex)
|
|
{
|
|
// muting from +-pbk selectors
|
|
if ((pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags & (1 << iRemoteUserIndex)) == 0)
|
|
{
|
|
bPlayback = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return(bPlayback);
|
|
}
|
|
|
|
|
|
/*** Public functions *************************************************************/
|
|
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetCreate
|
|
|
|
\Description
|
|
Create the headset manager.
|
|
|
|
\Input iMaxConduits - max number of conduits
|
|
\Input *pMicDataCb - pointer to user callback to trigger when mic data is ready
|
|
\Input *pTextDataCb - pointer to user callback to trigger when transcribed text is ready
|
|
\Input *pOpaqueDataCb - pointer to user callback to trigger when opaque data is ready (ignored)
|
|
\Input *pStatusCb - pointer to user callback to trigger when headset status changes
|
|
\Input *pCbUserData - pointer to user callback data
|
|
\Input iData - platform-specific - unused for PC
|
|
|
|
\Output
|
|
VoipHeadsetRefT * - pointer to module state, or NULL if an error occured
|
|
|
|
\Version 03/30/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
VoipHeadsetRefT *VoipHeadsetCreate(int32_t iMaxConduits, VoipHeadsetMicDataCbT *pMicDataCb, VoipHeadsetTextDataCbT *pTextDataCb, VoipHeadsetOpaqueDataCbT *pOpaqueDataCb, VoipHeadsetStatusCbT *pStatusCb, void *pCbUserData, int32_t iData)
|
|
{
|
|
VoipHeadsetRefT *pHeadset;
|
|
int32_t iMemGroup, iSize, iLocalUserIndex;
|
|
void *pMemGroupUserData;
|
|
|
|
// Query mem group data
|
|
VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData);
|
|
|
|
// make sure we don't exceed maxconduits
|
|
if (iMaxConduits > VOIP_MAXCONNECT)
|
|
{
|
|
NetPrintf(("voipheadsetpc: request for %d conduits exceeds max\n", iMaxConduits));
|
|
return(NULL);
|
|
}
|
|
|
|
iSize = sizeof(*pHeadset) + (sizeof(PCRemoteVoipUserT) * (iMaxConduits + VOIP_MAX_LOW_LEVEL_CONNS - 1));
|
|
|
|
// allocate and clear module state
|
|
if ((pHeadset = (VoipHeadsetRefT *)DirtyMemAlloc(iSize, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
|
|
{
|
|
return(NULL);
|
|
}
|
|
ds_memclr(pHeadset, iSize);
|
|
|
|
// allocate mixer
|
|
if ((pHeadset->pMixerRef = VoipMixerCreate(16, VOIP_HEADSET_FRAMESAMPLES)) == NULL)
|
|
{
|
|
NetPrintf(("voipheadsetpc: unable to create mixer\n"));
|
|
DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData);
|
|
return(NULL);
|
|
}
|
|
|
|
// allocate conduit manager
|
|
if ((pHeadset->pConduitRef = VoipConduitCreate(iMaxConduits)) == NULL)
|
|
{
|
|
NetPrintf(("voipheadsetpc: unable to allocate conduit manager\n"));
|
|
VoipMixerDestroy(pHeadset->pMixerRef);
|
|
DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData);
|
|
return(NULL);
|
|
}
|
|
pHeadset->iMaxConduits = iMaxConduits;
|
|
pHeadset->iMaxRemoteUsers = iMaxConduits + VOIP_MAX_LOW_LEVEL_CONNS;
|
|
VoipConduitRegisterPlaybackCb(pHeadset->pConduitRef, _VoipHeadsetPlaybackCallback, pHeadset);
|
|
|
|
// initialize playback flags (default on for everyone)
|
|
for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex)
|
|
{
|
|
pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags = 0xFFFFFFFF;
|
|
}
|
|
|
|
// allocate narration module
|
|
if ((pHeadset->pNarrateRef = VoipNarrateCreate(_VoipHeadsetNarrateCb, pHeadset)) == NULL)
|
|
{
|
|
VoipTranscribeDestroy(pHeadset->pTranscribeRef);
|
|
VoipConduitDestroy(pHeadset->pConduitRef);
|
|
VoipMixerDestroy(pHeadset->pMixerRef);
|
|
DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData);
|
|
return(NULL);
|
|
}
|
|
|
|
// set no participating user at the start
|
|
pHeadset->iParticipatingUserIndex = VOIP_INVALID_LOCAL_USER_INDEX;
|
|
|
|
// set TTS default gender
|
|
pHeadset->eDefaultGender = VOIPNARRATE_GENDER_MALE;
|
|
|
|
// set mixer
|
|
VoipConduitMixerSet(pHeadset->pConduitRef, pHeadset->pMixerRef);
|
|
|
|
// register codecs
|
|
VoipCodecRegister('dvid', &VoipDVI_CodecDef);
|
|
VoipCodecRegister('lpcm', &VoipPCM_CodecDef);
|
|
|
|
// set up to use dvi codec by default
|
|
VoipHeadsetControl(pHeadset, 'cdec', 'dvid', 0, NULL);
|
|
|
|
// enable microphone
|
|
VoipHeadsetControl(pHeadset, 'micr', TRUE, 0, NULL);
|
|
|
|
// save mem info
|
|
pHeadset->iMemGroup = iMemGroup;
|
|
pHeadset->pMemGroupUserData = pMemGroupUserData;
|
|
|
|
// save info
|
|
pHeadset->pMicDataCb = pMicDataCb;
|
|
pHeadset->pTextDataCb = pTextDataCb;
|
|
pHeadset->pStatusCb = pStatusCb;
|
|
pHeadset->pCbUserData = pCbUserData;
|
|
|
|
// no currently active device
|
|
pHeadset->SpkrInfo.eDevType = VOIP_HEADSET_OUTDEVICE;
|
|
pHeadset->SpkrInfo.iActiveDevice = -1;
|
|
pHeadset->SpkrInfo.iDeviceToOpen = WAVE_MAPPER;
|
|
|
|
pHeadset->MicrInfo.eDevType = VOIP_HEADSET_INPDEVICE;
|
|
pHeadset->MicrInfo.iActiveDevice = -1;
|
|
pHeadset->MicrInfo.iDeviceToOpen = WAVE_MAPPER;
|
|
|
|
// play
|
|
pHeadset->iPlayerActive = 0;
|
|
|
|
// set initial volume
|
|
pHeadset->SpkrInfo.iNewVolume = -1;
|
|
|
|
// set default debuglevel
|
|
pHeadset->iDebugLevel = 1;
|
|
|
|
// enumerate headsets on startup
|
|
_VoipHeadsetEnumerate(pHeadset);
|
|
|
|
// init the critical section
|
|
NetCritInit(&pHeadset->DevChangeCrit, "voipheadsetpc-devchange");
|
|
|
|
// return module ref to caller
|
|
return(pHeadset);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetDestroy
|
|
|
|
\Description
|
|
Destroy the headset manager.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
|
|
\Version 03/31/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetDestroy(VoipHeadsetRefT *pHeadset)
|
|
{
|
|
int32_t iMemGroup;
|
|
void *pMemGroupUserData;
|
|
|
|
// stop headsets
|
|
_VoipHeadsetStop(pHeadset);
|
|
|
|
// destroy transcription ref
|
|
if (pHeadset->pTranscribeRef != NULL)
|
|
{
|
|
VoipTranscribeDestroy(pHeadset->pTranscribeRef);
|
|
}
|
|
|
|
// destroy narrate ref
|
|
VoipNarrateDestroy(pHeadset->pNarrateRef);
|
|
|
|
// free conduit manager
|
|
VoipConduitDestroy(pHeadset->pConduitRef);
|
|
|
|
// free mixer
|
|
VoipMixerDestroy(pHeadset->pMixerRef);
|
|
|
|
// free active codec
|
|
VoipCodecDestroy();
|
|
|
|
// kill the critical section
|
|
NetCritKill(&pHeadset->DevChangeCrit);
|
|
|
|
// dispose of module memory
|
|
VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData);
|
|
DirtyMemFree(pHeadset, VOIP_MEMID, iMemGroup, pMemGroupUserData);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetReceiveVoiceDataCb
|
|
|
|
\Description
|
|
Connectionlist callback to handle receiving a voice packet from a remote peer.
|
|
|
|
\Input *pRemoteUsers - user we're receiving the voice data from
|
|
\Input iRemoteUserSize - pRemoteUsers array size
|
|
\Input iConsoleId - generic identifier for the console to which the users belong
|
|
\Input *pMicrInfo - micr info from inbound packet
|
|
\Input *pPacketData - pointer to beginning of data in packet payload
|
|
\Input *pUserData - VoipHeadsetT ref
|
|
|
|
\Version 03/21/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetReceiveVoiceDataCb(VoipUserT *pRemoteUsers, int32_t iRemoteUserSize, int32_t iConsoleId, VoipMicrInfoT *pMicrInfo, uint8_t *pPacketData, void *pUserData)
|
|
{
|
|
VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData;
|
|
uint32_t uRemoteUserIndex = 0;
|
|
uint32_t uMicrPkt;
|
|
const BYTE *pSubPacket = pPacketData;
|
|
|
|
// if we're not playing, ignore it
|
|
if (pHeadset->SpkrInfo.bActive == FALSE)
|
|
{
|
|
#if DIRTYCODE_LOGGING
|
|
if ((pMicrInfo->uSeqn % 30) == 0)
|
|
{
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 2, "voipheadsetpc: playback disabled, discarding voice data (seqn=%d)\n", pMicrInfo->uSeqn));
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// validate subpacket size if we are dealing with a fixed-length scenario
|
|
if ((pMicrInfo->uSubPacketSize != 0xFF) && (pMicrInfo->uSubPacketSize != pHeadset->iCmpFrameSize))
|
|
{
|
|
NetPrintf(("voipheadsetpc: discarding voice packet with %d voice bundles and mismatched sub-packet size %d (expecting %d)\n",
|
|
pMicrInfo->uNumSubPackets, pMicrInfo->uSubPacketSize, pHeadset->iCmpFrameSize));
|
|
return;
|
|
}
|
|
|
|
// if this is the shared user index
|
|
if (pMicrInfo->uUserIndex == VOIP_SHARED_REMOTE_INDEX)
|
|
{
|
|
int32_t iIndex;
|
|
|
|
// find the first valid user to playback the audio
|
|
for (iIndex = 0; iIndex < iRemoteUserSize; iIndex += 1)
|
|
{
|
|
if (!VOIP_NullUser(&pRemoteUsers[iIndex]))
|
|
{
|
|
uRemoteUserIndex = iIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iIndex == iRemoteUserSize)
|
|
{
|
|
// didn't find a remote user to play back the shared audio
|
|
NetPrintf(("voipheadsetpc: discarding voice packet from shared user because we cannot find a remote user to play it back as!\n"));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uRemoteUserIndex = pMicrInfo->uUserIndex;
|
|
}
|
|
|
|
// submit voice sub-packets
|
|
for (uMicrPkt = 0; uMicrPkt < pMicrInfo->uNumSubPackets; uMicrPkt++)
|
|
{
|
|
// get the size of the subpacket based on variable or not
|
|
uint32_t uSubPacketSize = (pMicrInfo->uSubPacketSize != 0xFF) ? pMicrInfo->uSubPacketSize : *pSubPacket++;
|
|
|
|
// send it to conduit manager
|
|
VoipConduitReceiveVoiceData(pHeadset->pConduitRef, &pRemoteUsers[uRemoteUserIndex], pSubPacket, uSubPacketSize);
|
|
|
|
// move to next packet
|
|
pSubPacket += uSubPacketSize;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetSetRemoteUserVoicePlayback
|
|
|
|
\Description
|
|
Helper function to enable or disable playback of voice from a remote user
|
|
for a given local user.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
\Input *pRemoteUser - remote user
|
|
\Input iLocalUserIndex - local user index
|
|
\Input bEnablePlayback - TRUE to enable voice playback. FALSE to disable voice playback.
|
|
|
|
\Output
|
|
int32_t - negative=error, zero=success
|
|
|
|
\Version 06/09/2019 (cvienneau)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetSetRemoteUserVoicePlayback(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser, int32_t iLocalUserIndex, uint8_t bEnablePlayback)
|
|
{
|
|
int32_t iRemoteUserIndex;
|
|
|
|
// find the remote user index
|
|
iRemoteUserIndex = _VoipHeadsetGetRemoteUserIndex(pHeadset, pRemoteUser);
|
|
|
|
if (iRemoteUserIndex < 0)
|
|
{
|
|
NetPrintf(("voipheadsetpc: _VoipHeadsetSetRemoteUserVoicePlayback() cannot find the remote user iPersonaId: %lld", pRemoteUser->AccountInfo.iPersonaId));
|
|
return(-1);
|
|
}
|
|
|
|
if (bEnablePlayback)
|
|
{
|
|
pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags |= (1 << iRemoteUserIndex);
|
|
}
|
|
else
|
|
{
|
|
pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags &= ~(1 << iRemoteUserIndex);
|
|
}
|
|
NetPrintf(("voipheadsetpc: local user[%d] playback flags now: 0x%08x.\n", iLocalUserIndex, pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags));
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetAddRemoteTalker
|
|
|
|
\Description
|
|
Create the game chat participant for this remote user.
|
|
|
|
\Input *pHeadset - headset module
|
|
\Input *pRemoteUser - remote user
|
|
\Input uConsoleId - unique console identifier (local scope only, does not need to be the same on all hosts)
|
|
|
|
\Output
|
|
int32_t - negative=error, zero=success
|
|
|
|
\Version 06/09/2019 (cvienneau)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetAddRemoteTalker(VoipHeadsetRefT *pHeadset, VoipUserT *pRemoteUser, uint64_t uConsoleId)
|
|
{
|
|
int32_t iRemoteUserIndex;
|
|
PCRemoteVoipUserT *pPcRemoteUser = NULL;
|
|
|
|
// find a duplicate entry
|
|
for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex)
|
|
{
|
|
if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser))
|
|
{
|
|
pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// early return if we remote user already exists
|
|
if (pPcRemoteUser != NULL)
|
|
{
|
|
NetPrintf(("voipheadsetpc: adding remote talker %lld failed because it already exists\n", pRemoteUser->AccountInfo.iPersonaId));
|
|
return(-1);
|
|
}
|
|
|
|
// find a empty remote user entry
|
|
for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex)
|
|
{
|
|
if (pHeadset->aRemoteUsers[iRemoteUserIndex].User.AccountInfo.iPersonaId == 0)
|
|
{
|
|
ds_memclr(&pHeadset->aRemoteUsers[iRemoteUserIndex], sizeof(PCRemoteVoipUserT));
|
|
ds_memcpy(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pRemoteUser, sizeof(VoipUserT));
|
|
pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex];
|
|
|
|
// register the remote user with all the conduits
|
|
VoipConduitRegisterUser(pHeadset->pConduitRef, pRemoteUser, TRUE);
|
|
|
|
NetPrintf(("voipheadsetpc: registered remote talker %lld at remote user index %d\n", pRemoteUser->AccountInfo.iPersonaId, iRemoteUserIndex));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pPcRemoteUser == NULL)
|
|
{
|
|
NetPrintf(("voipheadsetpc: adding remote talker %lld failed because aRemoteUsers is full\n", pRemoteUser->AccountInfo.iPersonaId));
|
|
return(-2);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _VoipHeadsetRemoveRemoteTalker
|
|
|
|
\Description
|
|
Remove the specified remote user from the collection of users known by the chat manager.
|
|
|
|
\Input *pHeadset - headset module
|
|
\Input *pUser - user to be removed
|
|
|
|
\Output
|
|
int32_t - negative=error, zero=success
|
|
|
|
\Version 11/22/2018 (mclouatre)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int32_t _VoipHeadsetRemoveRemoteTalker(VoipHeadsetRefT *pHeadset, VoipUserT *pUser)
|
|
{
|
|
int32_t iRetCode = 0;
|
|
int32_t iRemoteUserIndex;
|
|
PCRemoteVoipUserT *pPcRemoteUser = NULL;
|
|
|
|
VoipConduitRegisterUser(pHeadset->pConduitRef, pUser, FALSE);
|
|
|
|
// find the remote user
|
|
for (iRemoteUserIndex = 0; iRemoteUserIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserIndex)
|
|
{
|
|
if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserIndex].User, pUser))
|
|
{
|
|
pPcRemoteUser = &pHeadset->aRemoteUsers[iRemoteUserIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pPcRemoteUser != NULL)
|
|
{
|
|
NetPrintf(("voipheadsetpc: unregistered remote talker %lld at remote user index %d\n", pPcRemoteUser->User.AccountInfo.iPersonaId, iRemoteUserIndex));
|
|
ds_memclr(pPcRemoteUser, sizeof(PCRemoteVoipUserT));
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: unregistered remote talker %lld failed because it does not exist\n", pUser->AccountInfo.iPersonaId));
|
|
iRetCode = -1;
|
|
}
|
|
|
|
return(iRetCode);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetRegisterUserCb
|
|
|
|
\Description
|
|
Connectionlist callback to register/unregister a new user with the
|
|
VoipConduit module.
|
|
|
|
\Input *pRemoteUser - user to register
|
|
\Input iConsoleId - generic identifier for the console to which the user belongs (ignored)
|
|
\Input bRegister - true=register, false=unregister
|
|
\Input *pUserData - voipheadset module ref
|
|
|
|
\Version 03/21/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetRegisterUserCb(VoipUserT *pRemoteUser, int32_t iConsoleId, uint32_t bRegister, void *pUserData)
|
|
{
|
|
VoipHeadsetRefT *pHeadset = (VoipHeadsetRefT *)pUserData;
|
|
|
|
// early exit if invalid remote talker
|
|
if (VOIP_NullUser(pRemoteUser))
|
|
{
|
|
NetPrintf(("voipheadsetpc: can't %s NULL remote talker\n", (bRegister ? "register" : "unregister")));
|
|
return;
|
|
}
|
|
|
|
if (bRegister)
|
|
{
|
|
_VoipHeadsetAddRemoteTalker(pHeadset, pRemoteUser, (uint64_t)iConsoleId);
|
|
}
|
|
else
|
|
{
|
|
_VoipHeadsetRemoveRemoteTalker(pHeadset, pRemoteUser);
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetProcess
|
|
|
|
\Description
|
|
Headset process function.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
\Input uFrameCount - process iteration counter
|
|
|
|
\Version 03/31/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetProcess(VoipHeadsetRefT *pHeadset, uint32_t uFrameCount)
|
|
{
|
|
if (pHeadset->iPlayerActive)
|
|
{
|
|
// process playing
|
|
_VoipHeadsetProcessPlay(pHeadset);
|
|
}
|
|
else
|
|
{
|
|
// process recording
|
|
_VoipHeadsetProcessRecord(pHeadset);
|
|
}
|
|
|
|
// process narration
|
|
VoipNarrateUpdate(pHeadset->pNarrateRef);
|
|
|
|
// process transcription
|
|
_VoipHeadsetProcessTranscription(pHeadset);
|
|
|
|
// process playback
|
|
_VoipHeadsetProcessPlayback(pHeadset);
|
|
|
|
// process device change requests
|
|
_VoipHeadsetProcessDeviceChange(pHeadset, &pHeadset->MicrInfo);
|
|
_VoipHeadsetProcessDeviceChange(pHeadset, &pHeadset->SpkrInfo);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetSetVolume
|
|
|
|
\Description
|
|
Sets play and record volume.
|
|
|
|
\Input *pHeadset - pointer to headset state
|
|
\Input iPlayVol - play volume to set
|
|
\Input iRecVol - record volume to set
|
|
|
|
\Notes
|
|
To not set a value, specify it as -1.
|
|
|
|
\Version 03/31/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetSetVolume(VoipHeadsetRefT *pHeadset, int32_t iPlayVol, uint32_t iRecVol)
|
|
{
|
|
if (iPlayVol != -1)
|
|
{
|
|
pHeadset->SpkrInfo.iNewVolume = iPlayVol;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetControl
|
|
|
|
\Description
|
|
Control function.
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input iControl - control selector
|
|
\Input iValue - control value
|
|
\Input iValue2 - control value
|
|
\Input *pValue - control value
|
|
|
|
\Output
|
|
int32_t - selector specific, or -1 if no such selector
|
|
|
|
\Notes
|
|
iControl can be one of the following:
|
|
|
|
\verbatim
|
|
'aloc' - promote user to 'participating' state, or demote user from 'participating' state
|
|
'cdec' - create new codec
|
|
'cide' - close voip input device
|
|
'code' - close voip output device
|
|
'cstm' - clear speech to text metrics in VoipSpeechToTextMetricsT
|
|
'ctsm' - clear text to speech metrics in VoipTextToSpeechMetricsT
|
|
'edev' - enumerate voip input/output devices
|
|
'idev' - select voip input device
|
|
'loop' - enable/disable loopback
|
|
'micr' - enable/disable recording
|
|
'mute' - enable/disable audio input muting
|
|
'odev' - select voip output device
|
|
'play' - enable/disable playing
|
|
'txta' - enable/disable text chat accessibility, this also controls loopback in this context
|
|
'tran' - enable/disable local generation of transcribed text for speech procuced by local users (speech-to-text component)
|
|
'svol' - changes speaker volume
|
|
'ttos' - send utf8 text in pValue as voice and initialize TTS voice
|
|
'xply' - enable/disable crossplay (no difference on PC)
|
|
|
|
\endverbatim
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t VoipHeadsetControl(VoipHeadsetRefT *pHeadset, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue)
|
|
{
|
|
if (iControl == 'aloc')
|
|
{
|
|
int32_t iLocalUserIndex;
|
|
|
|
if (iValue2 == 0)
|
|
{
|
|
pHeadset->iParticipatingUserIndex = VOIP_INVALID_LOCAL_USER_INDEX;
|
|
NetPrintf(("voipheadsetpc: no user participating\n", iValue));
|
|
}
|
|
else
|
|
{
|
|
pHeadset->iParticipatingUserIndex = iValue;
|
|
NetPrintf(("voipheadsetpc: user %d, entering participating state\n", iValue));
|
|
}
|
|
|
|
// whenever active user status changes all mute masks will be re-calculated so lets reset our state
|
|
for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS; ++iLocalUserIndex)
|
|
{
|
|
pHeadset->aLocalUsers[iLocalUserIndex].uPlaybackFlags = 0xFFFFFFFF;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
if (iControl == 'cdec')
|
|
{
|
|
return(_VoipHeadsetSetCodec(pHeadset, iValue));
|
|
}
|
|
if ((iControl == 'cide') || (iControl == 'code'))
|
|
{
|
|
VoipHeadsetDeviceInfoT *pDeviceInfo = (iControl == 'cide') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo;
|
|
NetCritEnter(&pHeadset->DevChangeCrit);
|
|
pDeviceInfo->bCloseDevice = TRUE;
|
|
NetCritLeave(&pHeadset->DevChangeCrit);
|
|
}
|
|
if (iControl == 'cstm')
|
|
{
|
|
if (pHeadset->pTranscribeRef != NULL)
|
|
{
|
|
return(VoipTranscribeControl(pHeadset->pTranscribeRef, iControl, iValue, 0, NULL));
|
|
}
|
|
return(-1);
|
|
}
|
|
if (iControl == 'ctsm')
|
|
{
|
|
return(VoipNarrateControl(pHeadset->pNarrateRef, iControl, 0, 0, NULL));
|
|
}
|
|
if (iControl == 'edev')
|
|
{
|
|
_VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->MicrInfo);
|
|
_VoipHeadsetEnumerateDevices(pHeadset, &pHeadset->SpkrInfo);
|
|
return(0);
|
|
}
|
|
if ((iControl == 'idev') || (iControl == 'odev'))
|
|
{
|
|
VoipHeadsetDeviceInfoT *pDeviceInfo = (iControl == 'idev') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo;
|
|
if (pDeviceInfo->iActiveDevice != iValue)
|
|
{
|
|
NetPrintf(("voipheadsetpc: '%C' selector used to change %s device from %d to %d\n", iControl, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output",
|
|
pDeviceInfo->iActiveDevice, iValue));
|
|
NetCritEnter(&pHeadset->DevChangeCrit);
|
|
pDeviceInfo->iDeviceToOpen = iValue;
|
|
pDeviceInfo->bChangeDevice = TRUE;
|
|
NetCritLeave(&pHeadset->DevChangeCrit);
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: '%C' selector ignored because %s device %d is already active\n", iControl, pDeviceInfo->eDevType == VOIP_HEADSET_INPDEVICE ? "input" : "output",
|
|
pDeviceInfo->iActiveDevice, iValue));
|
|
}
|
|
return(0);
|
|
}
|
|
if (iControl == 'loop')
|
|
{
|
|
pHeadset->bLoopback = iValue;
|
|
NetPrintf(("voipheadsetpc: loopback mode %s\n", (iValue ? "enabled" : "disabled")));
|
|
return(0);
|
|
}
|
|
if (iControl == 'micr')
|
|
{
|
|
pHeadset->bMicOn = (uint8_t)iValue;
|
|
NetPrintf(("voipheadsetpc: mic %s\n", (iValue ? "enabled" : "disabled")));
|
|
return(0);
|
|
}
|
|
if (iControl == 'mute')
|
|
{
|
|
uint8_t bMuted = iValue2 ? TRUE : FALSE;
|
|
if (pHeadset->bMuted != bMuted)
|
|
{
|
|
NetPrintfVerbose((pHeadset->iDebugLevel, 1, "voipheadsetpc: mute %s\n", (pHeadset->bMuted ? "enabled" : "disabled")));
|
|
pHeadset->bMuted = bMuted;
|
|
}
|
|
return(0);
|
|
}
|
|
if ((iControl == '-pbk') || (iControl == '+pbk'))
|
|
{
|
|
uint8_t bVoiceEnable = (iControl == '+pbk') ? TRUE : FALSE;
|
|
int32_t iRetCode = 0; // default to success
|
|
|
|
VoipUserT *pRemoteUser;
|
|
|
|
if ((pValue != NULL) && (iValue < VOIP_MAXLOCALUSERS_EXTENDED))
|
|
{
|
|
pRemoteUser = (VoipUserT *)pValue;
|
|
|
|
// make sure the local user and the remote user are not a shared user (shared user concept not supported on pc)
|
|
if ((iValue != VOIP_SHARED_USER_INDEX) && ((pRemoteUser->AccountInfo.iPersonaId & VOIP_SHARED_USER_MASK) != VOIP_SHARED_USER_VALUE))
|
|
{
|
|
_VoipHeadsetSetRemoteUserVoicePlayback(pHeadset, pRemoteUser, iValue, bVoiceEnable);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: VoipHeadsetControl('%C', %d) invalid arguments\n", iControl, iValue));
|
|
iRetCode = -2;
|
|
}
|
|
return(iRetCode);
|
|
}
|
|
#if DIRTYCODE_LOGGING
|
|
if (iControl == 'spam')
|
|
{
|
|
pHeadset->iDebugLevel = iValue;
|
|
NetPrintf(("voipheadsetpc: debuglevel=%d\n", pHeadset->iDebugLevel));
|
|
VoipConduitControl(pHeadset->pConduitRef, 'spam', iValue, pValue);
|
|
return(VoipCodecControl(VOIP_CODEC_ACTIVE, iControl, iValue, 0, NULL));
|
|
}
|
|
#endif
|
|
if (iControl == 'txta')
|
|
{
|
|
pHeadset->bTextChatAccessibility = pHeadset->bLoopback = iValue;
|
|
NetPrintf(("voipheadsetpc: text chat accessibility mode %s\n", (iValue ? "enabled" : "disabled")));
|
|
return(0);
|
|
}
|
|
if (iControl == 'play')
|
|
{
|
|
if (iValue)
|
|
{
|
|
pHeadset->pPlayerBuffer = (int16_t *) pValue;
|
|
pHeadset->uPlayerBufferFrameCurrent = 0;
|
|
pHeadset->uPlayerBufferFrames = iValue / (VOIP_HEADSET_SAMPLEWIDTH * VOIP_HEADSET_FRAMESAMPLES);
|
|
pHeadset->uPlayerFirstTime = 0;
|
|
|
|
pHeadset->iPlayerActive = 1;
|
|
}
|
|
else
|
|
{
|
|
pHeadset->iPlayerActive = 0;
|
|
}
|
|
|
|
NetPrintf(("voipheadsetpc: play %s\n", ((pHeadset->iPlayerActive) ? "enabled" : "disabled")));
|
|
|
|
return(0);
|
|
}
|
|
if (iControl == 'tran')
|
|
{
|
|
if (iValue != pHeadset->bVoiceTranscriptionEnabled)
|
|
{
|
|
pHeadset->bVoiceTranscriptionEnabled = iValue;
|
|
NetPrintf(("voipheadsetpc: %s voice transcription locally\n", pHeadset->bVoiceTranscriptionEnabled ? "enabling" : "disabling"));
|
|
|
|
// create the transcription module the first time we need it
|
|
if (pHeadset->bVoiceTranscriptionEnabled && (pHeadset->pTranscribeRef == NULL))
|
|
{
|
|
// allocate transcription module; give it 16k buffer to match ssl max frame size for efficient network transport
|
|
if ((pHeadset->pTranscribeRef = VoipTranscribeCreate(16 * 1024)) != NULL)
|
|
{
|
|
// since this is being created late, set the debug level to the current setting
|
|
#if DIRTYCODE_LOGGING
|
|
VoipTranscribeControl(pHeadset->pTranscribeRef, 'spam', pHeadset->iDebugLevel, 0, NULL);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("voipheadsetpc: unable to allocate transcription manager\n"));
|
|
return(-1);
|
|
}
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
if (iControl == 'svol')
|
|
{
|
|
VoipHeadsetSetVolume(pHeadset, iValue, 0);
|
|
return(0);
|
|
}
|
|
if (iControl == 'voic')
|
|
{
|
|
int iRet = 0;
|
|
const VoipSynthesizedSpeechCfgT *pCfg = (const VoipSynthesizedSpeechCfgT *)pValue;
|
|
pHeadset->eDefaultGender = (pCfg->iPersonaGender == 1) ? VOIPNARRATE_GENDER_FEMALE : VOIPNARRATE_GENDER_MALE;
|
|
|
|
if (pCfg->iLanguagePackCode != 0)
|
|
{
|
|
iRet = VoipNarrateControl(pHeadset->pNarrateRef, 'lang', pCfg->iLanguagePackCode, 0, NULL);
|
|
}
|
|
|
|
return(iRet);
|
|
}
|
|
if (iControl == 'ttos')
|
|
{
|
|
VoipNarrateGenderE eGender = ((iValue2 > VOIPNARRATE_GENDER_NONE) && (iValue2 < VOIPNARRATE_NUMGENDERS)) ? (VoipNarrateGenderE)iValue2 : pHeadset->eDefaultGender;
|
|
return(VoipNarrateInput(pHeadset->pNarrateRef, iValue, eGender, (const char *)pValue));
|
|
}
|
|
if (iControl == 'uvoc')
|
|
{
|
|
VoipNarrateControl(pHeadset->pNarrateRef, 'uvoc', 0, 0, NULL);
|
|
return(0);
|
|
}
|
|
if(iControl == 'xply')
|
|
{
|
|
uint8_t bCrossplay = iValue ? TRUE : FALSE;
|
|
|
|
if (pHeadset->bCrossplay != bCrossplay)
|
|
{
|
|
NetPrintf(("voipheadsetpc: changing crossplay mode to: %s", bCrossplay ? "crossplay" : "native"));
|
|
pHeadset->bCrossplay = bCrossplay;
|
|
}
|
|
return(0);
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetStatus
|
|
|
|
\Description
|
|
Status function.
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input iSelect - control selector
|
|
\Input iValue - selector specific
|
|
\Input *pBuf - buffer pointer
|
|
\Input iBufSize - buffer size
|
|
|
|
\Output
|
|
int32_t - selector specific, or -1 if no such selector
|
|
|
|
\Notes
|
|
iSelect can be one of the following:
|
|
|
|
\verbatim
|
|
'ndev' - zero
|
|
'idev' - get name of input device at index iValue (-1 returns number of input devices)
|
|
'odev' - get name of output device at index iValue (-1 returns number of input devices)
|
|
'idft' - get the default input device index (as specified in control panel)
|
|
'mute' - get muted status
|
|
'odft' - get the default output device index (as specified in control panel)
|
|
'ruvu' - return TRUE if the given remote user (pBuf) is registered with voipheadset, FALSE if not.
|
|
'sttm' - get the VoipSpeechToTextMetricsT via pBuf
|
|
'ttos' - returns TRUE if TTS is active, else FALSE
|
|
'ttsm' - get the VoipTextToSpeechMetricsT via pBuf
|
|
'xply' - return status on crossplay
|
|
|
|
\endverbatim
|
|
|
|
\Version 07/28/2004 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t VoipHeadsetStatus(VoipHeadsetRefT *pHeadset, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize)
|
|
{
|
|
if (iSelect == 'ndev')
|
|
{
|
|
return(0);
|
|
}
|
|
if ((iSelect == 'idev') || (iSelect == 'odev'))
|
|
{
|
|
VoipHeadsetDeviceInfoT *pDeviceInfo = (iSelect == 'idev') ? &pHeadset->MicrInfo : &pHeadset->SpkrInfo;
|
|
if (iValue == -1)
|
|
{
|
|
return(pDeviceInfo->iNumDevices);
|
|
}
|
|
else if ((iValue >= 0) && (iValue < pDeviceInfo->iNumDevices))
|
|
{
|
|
ds_memcpy(pBuf, pDeviceInfo->WaveDeviceCaps[iValue].szPname, sizeof(pDeviceInfo->WaveDeviceCaps[iValue].szPname));
|
|
return(iValue);
|
|
}
|
|
}
|
|
if (iSelect == 'idft')
|
|
{
|
|
DWORD dwPreferredDevice=0;
|
|
DWORD dwStatusFlags=0;
|
|
DWORD dwRetVal = 0;
|
|
|
|
dwRetVal = waveInMessage((HWAVEIN)VOIP_HEADSET_WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&dwPreferredDevice, (DWORD_PTR)&dwStatusFlags);
|
|
return((int32_t)dwPreferredDevice);
|
|
}
|
|
if (iSelect == 'mute')
|
|
{
|
|
return(pHeadset->bMuted);
|
|
}
|
|
if (iSelect == 'odft')
|
|
{
|
|
DWORD dwPreferredDevice=0;
|
|
DWORD dwStatusFlags;
|
|
DWORD dwRetVal;
|
|
uint32_t uMessage;
|
|
|
|
switch (iValue)
|
|
{
|
|
case VOIP_DEFAULTDEVICE_VOICECOM:
|
|
dwStatusFlags = 0;
|
|
uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET;
|
|
break;
|
|
|
|
case VOIP_DEFAULTDEVICE_VOICECOM_ONLY:
|
|
dwStatusFlags = DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY;
|
|
uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET;
|
|
break;
|
|
|
|
case VOIP_DEFAULTDEVICE_PREFERRED:
|
|
dwStatusFlags = 0;
|
|
uMessage = DRVM_MAPPER_PREFERRED_GET;
|
|
break;
|
|
|
|
case VOIP_DEFAULTDEVICE_PREFERRED_ONLY:
|
|
dwStatusFlags = DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY;
|
|
uMessage = DRVM_MAPPER_PREFERRED_GET;
|
|
break;
|
|
|
|
default:
|
|
NetPrintf(("voipheadsetpc: iValue=%d, which is not a VoipHeadsetPreferredDeviceTypeE, using default\n", iValue));
|
|
dwStatusFlags = 0;
|
|
uMessage = DRVM_MAPPER_CONSOLEVOICECOM_GET;
|
|
break;
|
|
}
|
|
|
|
dwRetVal = waveOutMessage((HWAVEOUT)VOIP_HEADSET_WAVE_MAPPER, uMessage, (DWORD_PTR)&dwPreferredDevice, (DWORD_PTR)&dwStatusFlags);
|
|
|
|
return((int32_t)dwPreferredDevice);
|
|
}
|
|
if (iSelect == 'ruvu')
|
|
{
|
|
int32_t iRemoteUserSpaceIndex = 0;
|
|
|
|
for (iRemoteUserSpaceIndex = 0; iRemoteUserSpaceIndex < pHeadset->iMaxRemoteUsers; ++iRemoteUserSpaceIndex)
|
|
{
|
|
if (VOIP_SameUser(&pHeadset->aRemoteUsers[iRemoteUserSpaceIndex].User, (VoipUserT *)pBuf))
|
|
{
|
|
// remote user found and it is registered with voipheadset
|
|
return (TRUE);
|
|
}
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
if (iSelect == 'sttm')
|
|
{
|
|
if ((pHeadset->pTranscribeRef != NULL) && (iValue == pHeadset->iParticipatingUserIndex))
|
|
{
|
|
return(VoipTranscribeStatus(pHeadset->pTranscribeRef, iSelect, iValue, pBuf, iBufSize));
|
|
}
|
|
return(-1);
|
|
}
|
|
if (iSelect == 'ttos')
|
|
{
|
|
return(pHeadset->bNarrating);
|
|
}
|
|
if (iSelect == 'ttsm')
|
|
{
|
|
if (iValue == pHeadset->iParticipatingUserIndex)
|
|
{
|
|
return(VoipNarrateStatus(pHeadset->pNarrateRef, iSelect, iValue, pBuf, iBufSize));
|
|
}
|
|
return(-1);
|
|
}
|
|
if (iSelect == 'xply')
|
|
{
|
|
return(pHeadset->bCrossplay);
|
|
}
|
|
// unhandled result, fallthrough to active codec
|
|
return(VoipCodecStatus(VOIP_CODEC_ACTIVE, iSelect, iValue, pBuf, iBufSize));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetSpkrCallback
|
|
|
|
\Description
|
|
Set speaker output callback.
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input *pCallback - what to call when output data is available
|
|
\Input *pUserData - user data for callback
|
|
|
|
\Version 12/12/2005 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetSpkrCallback(VoipHeadsetRefT *pHeadset, VoipSpkrCallbackT *pCallback, void *pUserData)
|
|
{
|
|
pHeadset->pSpkrDataCb = pCallback;
|
|
pHeadset->pSpkrCbUserData = pUserData;
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetConfigTranscription
|
|
|
|
\Description
|
|
Configure the transcribe module
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input uProfile - transcribe profile
|
|
\Input *pUrl - transcribe provider url
|
|
\Input *pKey - transcribe key
|
|
|
|
\Version 11/06/2018 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetConfigTranscription(VoipHeadsetRefT *pHeadset, uint32_t uProfile, const char *pUrl, const char *pKey)
|
|
{
|
|
VoipTranscribeConfig(uProfile, pUrl, pKey);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetConfigNarration
|
|
|
|
\Description
|
|
Configure the transcribe module
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input uProvider - narrate provider identifier
|
|
\Input *pUrl - narrate provider url
|
|
\Input *pKey - narrate key
|
|
|
|
\Version 1/07/2019 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetConfigNarration(VoipHeadsetRefT *pHeadset, uint32_t uProvider, const char *pUrl, const char *pKey)
|
|
{
|
|
VoipNarrateConfig((VoipNarrateProviderE)uProvider, pUrl, pKey);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function VoipHeadsetSetFirstPartyIdCallback
|
|
|
|
\Description
|
|
Callback to tell dirtysdk what the first party id is for this user
|
|
|
|
\Input *pHeadset - headset module state
|
|
\Input *pCallback - what to call when transcribed text is received from remote player
|
|
\Input *pUserData - user data for callback
|
|
|
|
\Version 04/28/2020 (eesponda)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void VoipHeadsetSetFirstPartyIdCallback(VoipHeadsetRefT *pHeadset, VoipFirstPartyIdCallbackCbT *pCallback, void *pUserData)
|
|
{
|
|
}
|