csgo-2018-source/engine/audio/private/snd_dev_xaudio.cpp
2021-07-24 21:11:47 -07:00

1068 lines
30 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: X360 XAudio Version
//
//=====================================================================================//
#include "audio_pch.h"
#include "snd_dev_xaudio.h"
#include "utllinkedlist.h"
#include "server.h"
#include "client.h"
#include "matchmaking/imatchframework.h"
#include "tier2/tier2.h"
#include "smartptr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// The outer code mixes in PAINTBUFFER_SIZE (# of samples) chunks (see MIX_PaintChannels), we will never need more than
// that many samples in a buffer. This ends up being about 20ms per buffer
#define XAUDIO2_BUFFER_SAMPLES PAINTBUFFER_SIZE
// buffer return has a latency, so need a decent pool
#define MAX_XAUDIO2_BUFFERS 32
#define SURROUND_HEADPHONES 0
#define SURROUND_STEREO 2
#define SURROUND_DIGITAL5DOT1 5
// 5.1 means there are a max of 6 channels
#define MAX_DEVICE_CHANNELS 6
ConVar snd_xaudio_spew_buffers( "snd_xaudio_spew_buffers", "0", 0, "Spew XAudio buffer delivery" );
extern IVEngineClient *engineClient;
//-----------------------------------------------------------------------------
// Implementation of XAudio
//-----------------------------------------------------------------------------
class CAudioXAudio : public CAudioDeviceBase
{
public:
CAudioXAudio() : m_pXAudio2(NULL), m_pMasteringVoice(NULL), m_pSourceVoice(NULL), m_pOutputBuffer(NULL)
{
memset( m_Buffers, 0, sizeof(m_Buffers) ); // COMPILE_TIME_ASSERT( sizeof(m_Buffers) == sizeof(XAUDIO2_BUFFER)*MAX_XAUDIO2_BUFFERS );
m_pName = "XAudio2 Device";
m_nChannels = 2;
m_nSampleBits = 16;
m_nSampleRate = 44100;
m_bIsActive = true;
}
~CAudioXAudio( void );
bool IsActive( void ) { return true; }
bool Init( void );
void Shutdown( void );
void Pause( void );
void UnPause( void );
int64 PaintBegin( float mixAheadTime, int64 soundtime, int64 paintedtime );
int GetOutputPosition( void );
void ClearBuffer( void );
void TransferSamples( int64 end );
const char *DeviceName( void );
int DeviceDmaSpeed( void ) { return m_nSampleRate; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
void XAudio2BufferCallback( int hCompletedBuffer );
static CAudioXAudio *m_pSingleton;
CXboxVoice *GetVoiceData( void ) { return &m_VoiceData; }
IXAudio2 *GetXAudio2( void ) { return m_pXAudio2; }
private:
int TransferStereo( const portable_samplepair_t *pFront, int paintedTime, int endTime, char *pOutptuBuffer );
int TransferSurroundInterleaved( const portable_samplepair_t *pFront, const portable_samplepair_t *pRear, const portable_samplepair_t *pCenter, int paintedTime, int endTime, char *pOutputBuffer );
int m_deviceSampleCount; // count of mono samples in output buffer
int m_clockDivider;
IXAudio2 *m_pXAudio2;
IXAudio2MasteringVoice *m_pMasteringVoice;
IXAudio2SourceVoice *m_pSourceVoice;
XAUDIO2_BUFFER m_Buffers[MAX_XAUDIO2_BUFFERS];
BYTE *m_pOutputBuffer;
int m_bufferSizeBytes; // size of a single hardware output buffer, in bytes
CInterlockedUInt m_BufferTail;
CInterlockedUInt m_BufferHead;
CXboxVoice m_VoiceData;
};
CAudioXAudio *CAudioXAudio::m_pSingleton = NULL;
class XAudio2VoiceCallback : public IXAudio2VoiceCallback
{
public:
XAudio2VoiceCallback() {}
~XAudio2VoiceCallback() {}
void OnStreamEnd() {}
void OnVoiceProcessingPassEnd() {}
void OnVoiceProcessingPassStart( UINT32 SamplesRequired ) {}
void OnBufferEnd( void *pBufferContext )
{
CAudioXAudio::m_pSingleton->XAudio2BufferCallback( (int)pBufferContext );
}
void OnBufferStart( void *pBufferContext ) {}
void OnLoopEnd( void *pBufferContext ) {}
void OnVoiceError( void *pBufferContext, HRESULT Error ) {}
};
XAudio2VoiceCallback s_XAudio2VoiceCallback;
//-----------------------------------------------------------------------------
// Create XAudio Device
//-----------------------------------------------------------------------------
IAudioDevice *Audio_CreateXAudioDevice( bool bInitVoice )
{
MEM_ALLOC_CREDIT();
if ( CommandLine()->CheckParm( "-nosound" ) )
{
// respect forced lack of audio
return NULL;
}
if ( !CAudioXAudio::m_pSingleton )
{
CAudioXAudio::m_pSingleton = new CAudioXAudio;
if ( !CAudioXAudio::m_pSingleton->Init() )
{
AssertMsg( false, "Failed to init CAudioXAudio\n" );
delete CAudioXAudio::m_pSingleton;
CAudioXAudio::m_pSingleton = NULL;
}
}
// need to support early init of XAudio (for bink startup video) without the voice
// voice requires matchmaking which is not available at this early point
// this defers the voice init to a later engine init mark
if ( bInitVoice && CAudioXAudio::m_pSingleton )
{
CAudioXAudio::m_pSingleton->GetVoiceData()->VoiceInit();
}
return CAudioXAudio::m_pSingleton;
}
CXboxVoice *Audio_GetXVoice( void )
{
if ( CAudioXAudio::m_pSingleton )
{
return CAudioXAudio::m_pSingleton->GetVoiceData();
}
return NULL;
}
IXAudio2 *Audio_GetXAudio2( void )
{
if ( CAudioXAudio::m_pSingleton )
{
return CAudioXAudio::m_pSingleton->GetXAudio2();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
CAudioXAudio::~CAudioXAudio( void )
{
m_pSingleton = NULL;
}
//-----------------------------------------------------------------------------
// Initialize XAudio
//-----------------------------------------------------------------------------
bool CAudioXAudio::Init( void )
{
XAUDIOSPEAKERCONFIG xAudioConfig = 0;
XGetSpeakerConfig( &xAudioConfig );
snd_surround.SetValue( ( xAudioConfig & XAUDIOSPEAKERCONFIG_DIGITAL_DOLBYDIGITAL ) ? SURROUND_DIGITAL5DOT1 : SURROUND_STEREO );
m_bHeadphone = false;
m_bSurround = false;
m_bSurroundCenter = false;
switch ( snd_surround.GetInt() )
{
case SURROUND_HEADPHONES:
m_bHeadphone = true;
m_nChannels = 2;
break;
default:
case SURROUND_STEREO:
m_nChannels = 2;
break;
case SURROUND_DIGITAL5DOT1:
m_bSurround = true;
m_bSurroundCenter = true;
m_nChannels = 6;
break;
}
Assert( m_nChannels <= MAX_DEVICE_CHANNELS );
m_nSampleBits = 16;
m_nSampleRate = SOUND_DMA_SPEED;
// initialize the XAudio Engine
// Both threads on core 2
m_pXAudio2 = NULL;
HRESULT hr = XAudio2Create( &m_pXAudio2, 0, XboxThread5 );
if ( FAILED( hr ) )
return false;
// create the mastering voice, this will upsample to the devices target hw output rate
m_pMasteringVoice = NULL;
hr = m_pXAudio2->CreateMasteringVoice( &m_pMasteringVoice );
if ( FAILED( hr ) )
return false;
// 16 bit PCM
WAVEFORMATEX waveFormatEx = { 0 };
waveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
waveFormatEx.nChannels = m_nChannels;
waveFormatEx.nSamplesPerSec = m_nSampleRate;
waveFormatEx.wBitsPerSample = 16;
waveFormatEx.nBlockAlign = ( waveFormatEx.nChannels * waveFormatEx.wBitsPerSample ) / 8;
waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * waveFormatEx.nBlockAlign;
waveFormatEx.cbSize = 0;
m_pSourceVoice = NULL;
hr = m_pXAudio2->CreateSourceVoice(
&m_pSourceVoice,
&waveFormatEx,
0,
XAUDIO2_DEFAULT_FREQ_RATIO,
&s_XAudio2VoiceCallback,
NULL,
NULL );
if ( FAILED( hr ) )
return false;
float volumes[MAX_DEVICE_CHANNELS];
for ( int i = 0; i < MAX_DEVICE_CHANNELS; i++ )
{
if ( !m_bSurround && i >= 2 )
{
volumes[i] = 0;
}
else
{
volumes[i] = 1.0;
}
}
m_pSourceVoice->SetChannelVolumes( m_nChannels, volumes );
m_bufferSizeBytes = XAUDIO2_BUFFER_SAMPLES * (m_nSampleBits/8) * m_nChannels;
m_pOutputBuffer = new BYTE[MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes];
ClearBuffer();
V_memset( m_Buffers, 0, MAX_XAUDIO2_BUFFERS * sizeof( XAUDIO2_BUFFER ) );
for ( int i = 0; i < MAX_XAUDIO2_BUFFERS; i++ )
{
m_Buffers[i].pAudioData = m_pOutputBuffer + i*m_bufferSizeBytes;
m_Buffers[i].pContext = (LPVOID)i;
}
m_BufferHead = 0;
m_BufferTail = 0;
// number of mono samples output buffer may hold
m_deviceSampleCount = MAX_XAUDIO2_BUFFERS * (m_bufferSizeBytes/(DeviceSampleBytes()));
// NOTE: This really shouldn't be tied to the # of bufferable samples.
// This just needs to be large enough so that it doesn't fake out the sampling in
// GetSoundTime(). Basically GetSoundTime() assumes a cyclical time stamp and finds wraparound cases
// but that means it needs to get called much more often than once per cycle. So this number should be
// much larger than the framerate in terms of output time
m_clockDivider = m_deviceSampleCount / ChannelCount();
// not really part of XAudio2, but mixer xma lacks one-time init, so doing it here
XMAPlaybackInitialize();
hr = m_pSourceVoice->Start( 0 );
if ( FAILED( hr ) )
return false;
DevMsg( "XAudio Device Initialized:\n" );
DevMsg( " %s\n"
" %d channel(s)\n"
" %d bits/sample\n"
" %d samples/sec\n", DeviceName(), ChannelCount(), BitsPerSample(), DeviceDmaSpeed() );
// success
return true;
}
//-----------------------------------------------------------------------------
// Shutdown XAudio
//-----------------------------------------------------------------------------
void CAudioXAudio::Shutdown( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Stop( 0 );
m_pSourceVoice->DestroyVoice();
m_pSourceVoice = NULL;
delete[] m_pOutputBuffer;
}
if ( m_pMasteringVoice )
{
m_pMasteringVoice->DestroyVoice();
m_pMasteringVoice = NULL;
}
// need to release ref to XAudio2
m_VoiceData.VoiceShutdown();
if ( m_pXAudio2 )
{
m_pXAudio2->Release();
m_pXAudio2 = NULL;
}
if ( this == CAudioXAudio::m_pSingleton )
{
CAudioXAudio::m_pSingleton = NULL;
}
}
//-----------------------------------------------------------------------------
// XAudio has completed a buffer. Assuming these are sequential
//-----------------------------------------------------------------------------
void CAudioXAudio::XAudio2BufferCallback( int hCompletedBuffer )
{
// buffer completion expected to be sequential
Assert( hCompletedBuffer == (int)( m_BufferTail % MAX_XAUDIO2_BUFFERS ) );
m_BufferTail++;
if ( snd_xaudio_spew_buffers.GetBool() )
{
if ( m_BufferTail == m_BufferHead )
{
Warning( "XAudio: Starved\n" );
}
else
{
Msg( "XAudio: Buffer Callback, Submit: %2d, Free: %2d\n", m_BufferHead - m_BufferTail, MAX_XAUDIO2_BUFFERS - ( m_BufferHead - m_BufferTail ) );
}
}
if ( m_BufferTail == m_BufferHead )
{
// very bad, out of buffers, xaudio is starving
// mix thread didn't keep up with audio clock and submit buffers
// submit a silent buffer to keep stream playing and audio clock running
int head = m_BufferHead++;
XAUDIO2_BUFFER *pBuffer = &m_Buffers[head % MAX_XAUDIO2_BUFFERS];
V_memset( (void *)pBuffer->pAudioData, 0, m_bufferSizeBytes );
pBuffer->AudioBytes = m_bufferSizeBytes;
m_pSourceVoice->SubmitSourceBuffer( pBuffer );
}
}
//-----------------------------------------------------------------------------
// Return the "write" cursor. Used to clock the audio mixing.
// The actual hw write cursor and the number of samples it fetches is unknown.
//-----------------------------------------------------------------------------
int CAudioXAudio::GetOutputPosition( void )
{
XAUDIO2_VOICE_STATE state;
state.SamplesPlayed = 0;
m_pSourceVoice->GetState( &state );
return ( state.SamplesPlayed % m_clockDivider );
}
//-----------------------------------------------------------------------------
// Pause playback
//-----------------------------------------------------------------------------
void CAudioXAudio::Pause( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Stop( 0 );
}
}
//-----------------------------------------------------------------------------
// Resume playback
//-----------------------------------------------------------------------------
void CAudioXAudio::UnPause( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Start( 0 );
}
}
//-----------------------------------------------------------------------------
// Calc the paint position
//-----------------------------------------------------------------------------
int64 CAudioXAudio::PaintBegin( float mixAheadTime, int64 soundtime, int64 paintedtime )
{
// soundtime = total full samples that have been played out to hardware at dmaspeed
// paintedtime = total full samples that have been mixed at speed
// endtime = target for full samples in mixahead buffer at speed
int mixaheadtime = mixAheadTime * DeviceDmaSpeed();
int64 endtime = soundtime + mixaheadtime;
if ( endtime <= paintedtime )
{
return endtime;
}
int fullsamps = MAX_XAUDIO2_BUFFERS * (m_bufferSizeBytes /( DeviceSampleBytes() * ChannelCount() ));
if ( ( endtime - soundtime ) > fullsamps )
{
endtime = soundtime + fullsamps;
}
if ( ( endtime - paintedtime ) & 0x03 )
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= ( endtime - paintedtime ) & 0x03;
}
return endtime;
}
//-----------------------------------------------------------------------------
// Fill the output buffers with silence
//-----------------------------------------------------------------------------
void CAudioXAudio::ClearBuffer( void )
{
V_memset( m_pOutputBuffer, 0, MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes );
}
//-----------------------------------------------------------------------------
// Fill the output buffer with L/R samples
//-----------------------------------------------------------------------------
int CAudioXAudio::TransferStereo( const portable_samplepair_t *pFrontBuffer, int paintedTime, int endTime, char *pOutputBuffer )
{
int linearCount;
int i;
int val;
int volumeFactor = S_GetMasterVolume() * 256;
int *pFront = (int *)pFrontBuffer;
short *pOutput = (short *)pOutputBuffer;
// get size of output buffer in full samples (LR pairs)
// number of sequential sample pairs that can be wrriten
linearCount = m_bufferSizeBytes/( DeviceSampleBytes() * ChannelCount() );
// clamp output count to requested number of samples
if ( linearCount > endTime - paintedTime )
{
linearCount = endTime - paintedTime;
}
// linearCount is now number of mono 16 bit samples (L and R) to xfer.
linearCount <<= 1;
// transfer mono 16bit samples multiplying each sample by volume.
for ( i=0; i<linearCount; i+=2 )
{
// L Channel
val = ( pFront[i] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
// R Channel
val = ( pFront[i+1] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
}
return linearCount * DeviceSampleBytes();
}
//-----------------------------------------------------------------------------
// Fill the output buffer with interleaved surround samples
//-----------------------------------------------------------------------------
int CAudioXAudio::TransferSurroundInterleaved( const portable_samplepair_t *pFrontBuffer, const portable_samplepair_t *pRearBuffer, const portable_samplepair_t *pCenterBuffer, int paintedTime, int endTime, char *pOutputBuffer )
{
int linearCount;
int i, j;
int val;
int volumeFactor = S_GetMasterVolume() * 256;
int *pFront = (int *)pFrontBuffer;
int *pRear = (int *)pRearBuffer;
int *pCenter = (int *)pCenterBuffer;
short *pOutput = (short *)pOutputBuffer;
// number of mono samples per channel
// number of sequential samples that can be wrriten
linearCount = m_bufferSizeBytes/( DeviceSampleBytes() * ChannelCount() );
// clamp output count to requested number of samples
if ( linearCount > endTime - paintedTime )
{
linearCount = endTime - paintedTime;
}
for ( i = 0, j = 0; i < linearCount; i++, j += 2 )
{
// FL
val = ( pFront[j] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
// FR
val = ( pFront[j+1] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
// Center
val = ( pCenter[j] * volumeFactor) >> 8;
*pOutput++ = iclip( val );
// Let the hardware mix the sub from the main channels since
// we don't have any sub-specific sounds, or direct sub-addressing
*pOutput++ = 0;
// RL
val = ( pRear[j] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
// RR
val = ( pRear[j+1] * volumeFactor ) >> 8;
*pOutput++ = iclip( val );
}
return linearCount * DeviceSampleBytes() * ChannelCount();
}
//-----------------------------------------------------------------------------
// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of samples out to the xaudio buffer(s).
//-----------------------------------------------------------------------------
void CAudioXAudio::TransferSamples( int64 endTime )
{
XAUDIO2_BUFFER *pBuffer;
if ( m_BufferHead - m_BufferTail >= MAX_XAUDIO2_BUFFERS )
{
DevWarning( "XAudio: No Free Buffers!\n" );
return;
}
int sampleCount = endTime - g_paintedtime;
if ( sampleCount > XAUDIO2_BUFFER_SAMPLES )
{
DevWarning( "XAudio: Overflowed mix buffer!\n" );
endTime = g_paintedtime + XAUDIO2_BUFFER_SAMPLES;
}
unsigned int nBuffer = m_BufferHead++;
pBuffer = &m_Buffers[nBuffer % MAX_XAUDIO2_BUFFERS];
if ( !m_bSurround )
{
pBuffer->AudioBytes = TransferStereo( PAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData );
}
else
{
pBuffer->AudioBytes = TransferSurroundInterleaved( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData );
}
// submit buffer
m_pSourceVoice->SubmitSourceBuffer( pBuffer );
}
//-----------------------------------------------------------------------------
// Get our device name
//-----------------------------------------------------------------------------
const char *CAudioXAudio::DeviceName( void )
{
if ( m_bSurround )
{
return "XAudio: 5.1 Channel Surround";
}
return "XAudio: Stereo";
}
CXboxVoice::CXboxVoice()
{
m_pXHVEngine = NULL;
}
//-----------------------------------------------------------------------------
// Initialize Voice
//-----------------------------------------------------------------------------
void CXboxVoice::VoiceInit( void )
{
if ( !m_pXHVEngine )
{
// Set the processing modes
XHV_PROCESSING_MODE rgMode = XHV_VOICECHAT_MODE;
// Set up parameters for the voice chat engine
XHV_INIT_PARAMS xhvParams = {0};
xhvParams.dwMaxRemoteTalkers = g_pMatchFramework->GetMatchTitle()->GetTotalNumPlayersSupported();
xhvParams.dwMaxLocalTalkers = XUSER_MAX_COUNT;
xhvParams.localTalkerEnabledModes = &rgMode;
xhvParams.remoteTalkerEnabledModes = &rgMode;
xhvParams.dwNumLocalTalkerEnabledModes = 1;
xhvParams.dwNumRemoteTalkerEnabledModes = 1;
xhvParams.pXAudio2 = CAudioXAudio::m_pSingleton->GetXAudio2();
// Create the engine
HRESULT hr = XHV2CreateEngine( &xhvParams, NULL, &m_pXHVEngine );
if ( hr != S_OK )
{
Warning( "Couldn't load XHV engine!\n" );
}
}
memset( m_bUserRegistered, 0 ,sizeof( m_bUserRegistered ) );
// Reset voice data for all ctrlrs
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
VoiceResetLocalData( k );
}
}
void CXboxVoice::VoiceShutdown( void )
{
if ( !m_pXHVEngine )
return;
m_pXHVEngine->Release();
m_pXHVEngine = NULL;
}
void CXboxVoice::AddPlayerToVoiceList( XUID xPlayer, int iController, uint64 uiFlags )
{
#ifdef _X360
XHV_PROCESSING_MODE local_proc_mode = XHV_VOICECHAT_MODE;
if ( !xPlayer && iController >= 0 && iController < XUSER_MAX_COUNT )
{
// If was registered from previous time, then unregister
if( m_bUserRegistered[ iController ] )
{
m_pXHVEngine->UnregisterLocalTalker( iController );
m_bUserRegistered[ iController ] = false;
}
// Now register local talker
if ( m_pXHVEngine->RegisterLocalTalker( iController ) == S_OK )
{
m_bUserRegistered[ iController ] = true;
m_pXHVEngine->StartLocalProcessingModes( iController, &local_proc_mode, 1 );
}
}
else if ( xPlayer )
{
HRESULT res = m_pXHVEngine->RegisterRemoteTalker( xPlayer, NULL, NULL, NULL );
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CXboxVoice::AddPlayerToVoiceList: Registering %llx returned 0x%08x\n", xPlayer, res );
}
if ( res == S_OK )
{
m_pXHVEngine->StartRemoteProcessingModes( xPlayer, &local_proc_mode, 1 );
}
}
else
{
Assert( 0 );
}
#endif
}
void CXboxVoice::RemovePlayerFromVoiceList( XUID xPlayer, int iController )
{
if ( !xPlayer && iController >= 0 && iController < XUSER_MAX_COUNT )
{
if( m_bUserRegistered[ iController ] )
{
m_pXHVEngine->UnregisterLocalTalker( iController );
m_bUserRegistered[ iController ] = false;
}
}
else if ( xPlayer )
{
m_pXHVEngine->UnregisterRemoteTalker( xPlayer );
}
else
{
Assert( 0 );
}
}
#ifdef _X360
ConVar voice_xplay_debug( "voice_xplay_debug", "0" );
#endif
void CXboxVoice::PlayIncomingVoiceData( XUID xuid, const byte *pbData, unsigned int dwDataSize, const bool *bAudiblePlayers )
{
ConVarRef voice_verbose( "voice_verbose" );
#ifdef _X360
static double s_flLastCheckedTime = 0.0;
static const double s_flCheckFreq = 0.5;
double flTime = Plat_FloatTime();
if ( flTime - s_flLastCheckedTime > s_flCheckFreq )
{
s_flLastCheckedTime = flTime;
// Register all idle signed in players
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
bool bSignedIn = !( XUserGetSigninState( k ) == eXUserSigninState_NotSignedIn );
if ( m_bUserRegistered[k] == bSignedIn )
continue;
XHV_PROCESSING_MODE local_proc_mode = XHV_VOICECHAT_MODE;
if ( bSignedIn )
{
if ( m_pXHVEngine->RegisterLocalTalker( k ) == S_OK )
{
m_bUserRegistered[ k ] = true;
m_pXHVEngine->StartLocalProcessingModes( k, &local_proc_mode, 1 );
}
}
else
{
m_pXHVEngine->UnregisterLocalTalker( k );
m_bUserRegistered[ k ] = false;
}
// Notify matchmaking that local talkers have changed
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSysMuteListChanged" ) );
}
}
#endif
for ( DWORD dwSlot = 0; dwSlot < XBX_GetNumGameUsers(); ++ dwSlot )
{
int iCtrlr = XBX_GetUserId( dwSlot );
IPlayerLocal *pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iCtrlr );
if ( pPlayer && pPlayer->GetXUID() == xuid )
{
//Hack: Don't play stuff that comes from ourselves.
if ( voice_verbose.GetBool() )
{
Msg( "* CXboxVoice::PlayIncomingVoiceData: dropping voice data from self with dwBytes %u\n", dwDataSize );
}
return;
}
}
if ( g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( xuid ) )
{
// Set the playback priority for talkers that we can't hear due to game rules.
for ( DWORD dwSlot = 0; dwSlot < XBX_GetNumGameUsers(); ++dwSlot )
{
bool bCanHearPlayer = !bAudiblePlayers || bAudiblePlayers[dwSlot];
// Check if the client thinks the player is audible
if ( !bAudiblePlayers )
{
for ( int iClient = 0; iClient < GetBaseLocalClient().m_nMaxClients; iClient++ )
{
int iIndex = iClient + 1;
player_info_t infoClient;
if ( engineClient->GetPlayerInfo( iIndex, &infoClient ) )
{
if ( xuid == infoClient.xuid )
{
bCanHearPlayer = g_pSoundServices->GetPlayerAudible( iIndex );
break;
}
}
}
}
if ( voice_xplay_debug.GetBool() )
{
DevMsg( "XVoiceDebug: %llx -> %d = %s\n", xuid, XBX_GetUserId( dwSlot ), bCanHearPlayer ? "yes" : "no" );
}
m_pXHVEngine->SetPlaybackPriority( xuid, XBX_GetUserId( dwSlot ), bCanHearPlayer ? XHV_PLAYBACK_PRIORITY_MAX : XHV_PLAYBACK_PRIORITY_NEVER );
}
// For idle users who are simply signed in on the console, but do not participate in the game
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
if ( m_bUserRegistered[k] && ( XBX_GetSlotByUserId( k ) < 0 ) )
{
if ( voice_xplay_debug.GetBool() )
{
DevMsg( "XVoiceDebug: %llx -> %d = idle\n", xuid, k );
}
m_pXHVEngine->SetPlaybackPriority( xuid, k, XHV_PLAYBACK_PRIORITY_NEVER );
}
}
}
else
{
// Cannot submit voice for the player that we are not allowed to playback!
if ( voice_xplay_debug.GetBool() )
{
DevMsg( "XVoiceDebug: %llx muted\n", xuid );
}
return;
}
DWORD dwApiSize = dwDataSize;
HRESULT hrSubmit = m_pXHVEngine->SubmitIncomingChatData( xuid, pbData, &dwApiSize );
if ( voice_verbose.GetBool() )
{
Msg( "* CXboxVoice::PlayIncomingVoiceData: SubmitIncomingChatData with %u bytes returned 0x%08x and copied dwBytes %u\n", dwDataSize, hrSubmit, dwApiSize );
}
}
void CXboxVoice::UpdateHUDVoiceStatus( void )
{
for ( int iClient = 0; iClient < GetBaseLocalClient().m_nMaxClients; iClient++ )
{
// Information about local client if it's a local client speaking
bool bLocalClient = false;
int iSsSlot = -1;
int iCtrlr = -1;
// Detection if it's a local client
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
CClientState &cs = GetLocalClient( k );
if ( cs.m_nPlayerSlot == iClient )
{
bLocalClient = true;
iSsSlot = k;
iCtrlr = XBX_GetUserId( iSsSlot );
}
}
// Convert into index and XUID
int iIndex = iClient + 1;
XUID xid = NULL;
player_info_t infoClient;
if ( engineClient->GetPlayerInfo( iIndex, &infoClient ) )
{
xid = infoClient.xuid;
}
// [jason] skip any bots - we don't want to override their voice status from the chatter
if ( infoClient.fakeplayer )
continue;
if ( !xid )
// No XUID means no VOIP
{
g_pSoundServices->OnChangeVoiceStatus( iIndex, -1, false );
if ( bLocalClient )
g_pSoundServices->OnChangeVoiceStatus( iIndex, iSsSlot, false );
continue;
}
// Determine talking status
bool bTalking = false;
if ( bLocalClient )
{
//Make sure the player's own "remote" label is not on.
g_pSoundServices->OnChangeVoiceStatus( iIndex, -1, false );
iIndex = -1; // issue notification as ent=-1
bTalking = IsLocalPlayerTalking( iCtrlr );
}
else
{
bTalking = IsPlayerTalking( xid );
}
g_pSoundServices->OnChangeVoiceStatus( iIndex, iSsSlot, bTalking );
}
}
bool CXboxVoice::VoiceUpdateData( int iCtrlr )
{
DWORD dwNumPackets = 0;
DWORD dwBytes = 0;
WORD wVoiceBytes = 0;
bool bShouldSend = false;
DWORD dwVoiceFlags = m_pXHVEngine->GetDataReadyFlags();
//Update UI stuff.
UpdateHUDVoiceStatus();
while ( 1 )
{
int i = iCtrlr;
if ( IsHeadsetPresent( i ) == false )
break;
if ( !(dwVoiceFlags & ( 1 << i )) )
break;
dwBytes = m_ChatBufferSize - m_wLocalDataSize[i];
if( dwBytes < XHV_VOICECHAT_MODE_PACKET_SIZE )
{
bShouldSend = true;
}
else
{
HRESULT hr = m_pXHVEngine->GetLocalChatData( i, m_ChatBuffer[i] + m_wLocalDataSize[i], &dwBytes, &dwNumPackets );
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CXboxVoice::VoiceUpdateData: GetLocalChatData for user %d returned 0x%08x with dwBytes %u and dwNumPackets %u\n", i, hr, dwBytes, dwNumPackets );
}
m_wLocalDataSize[i] += ((WORD)dwBytes) & MAXWORD;
if( m_wLocalDataSize[i] > ( ( m_ChatBufferSize * 7 ) / 10 ) )
{
bShouldSend = true;
}
}
wVoiceBytes += m_wLocalDataSize[i] & MAXWORD;
break;
}
return bShouldSend ||
( wVoiceBytes &&
( GetTickCount() - m_dwLastVoiceSend[ iCtrlr ] ) > MAX_VOICE_BUFFER_TIME );
}
void CXboxVoice::SetPlaybackPriority( XUID remoteTalker, int iController, int iAllowPlayback )
{
XHV_PLAYBACK_PRIORITY playbackPriority = iAllowPlayback ? XHV_PLAYBACK_PRIORITY_MAX : XHV_PLAYBACK_PRIORITY_NEVER;
// DevMsg( "[XAUDIO] SetPlaybackPriority %I64x %d %08X\n", remoteTalker, dwUserIndex, playbackPriority );
m_pXHVEngine->SetPlaybackPriority( remoteTalker, iController, playbackPriority );
#ifdef _X360
// When setting NEVER playback priority, make sure it is set for all controllers
if ( playbackPriority == XHV_PLAYBACK_PRIORITY_NEVER )
{
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
m_pXHVEngine->SetPlaybackPriority( remoteTalker, k, playbackPriority );
}
}
#endif
}
void CXboxVoice::GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers )
{
m_pXHVEngine->GetRemoteTalkers( (DWORD*)pNumTalkers, pRemoteTalkers );
}
void CXboxVoice::GetVoiceData( int iCtrlr, CCLCMsg_VoiceData_t *pMessage )
{
IPlayerLocal *pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iCtrlr );
pMessage->set_xuid( pPlayer ? pPlayer->GetXUID() : 0ull );
if ( !pMessage->xuid() )
return;
if ( !m_wLocalDataSize[ iCtrlr ] )
return;
pMessage->set_data( m_ChatBuffer[ iCtrlr ], m_wLocalDataSize[ iCtrlr ] );
}
void CXboxVoice::GetVoiceData( int iController, const byte **ppvVoiceDataBuffer, unsigned int *pnumVoiceDataBytes )
{
*pnumVoiceDataBytes = m_wLocalDataSize[ iController ];
*ppvVoiceDataBuffer = m_ChatBuffer[ iController ];
}
void CXboxVoice::VoiceSendData( int iCtrlr, INetChannel *pChannel )
{
CCLCMsg_VoiceData_t voiceMsg;
GetVoiceData( iCtrlr, &voiceMsg );
if ( pChannel )
{
pChannel->SendNetMsg( voiceMsg, false, true );
VoiceResetLocalData( iCtrlr );
}
}
void CXboxVoice::VoiceResetLocalData( int iCtrlr )
{
m_dwLastVoiceSend[ iCtrlr ] = GetTickCount();
Q_memset( m_ChatBuffer[ iCtrlr ], 0, m_ChatBufferSize );
m_wLocalDataSize[ iCtrlr ] = 0;
}
#pragma warning(push) // warning C4800 is meaningless or worse
#pragma warning( disable: 4800 )
bool CXboxVoice::IsLocalPlayerTalking( int controllerID )
{
return m_pXHVEngine->IsLocalTalking( controllerID ) != 0;
}
bool CXboxVoice::IsPlayerTalking( XUID uid )
{
#ifdef _X360
if ( !g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( uid ) )
return false;
return m_pXHVEngine->IsRemoteTalking( uid ) != 0;
#else
return false;
#endif
}
bool CXboxVoice::IsHeadsetPresent( int id )
{
return m_pXHVEngine->IsHeadsetPresent( id ) != 0;
}
#pragma warning(pop)
void CXboxVoice::RemoveAllTalkers()
{
#ifdef _X360
int numRemoteTalkers;
CArrayAutoPtr< XUID > remoteTalkers( new XUID[ g_pMatchFramework->GetMatchTitle()->GetTotalNumPlayersSupported() ] );
GetRemoteTalkers( &numRemoteTalkers, remoteTalkers.Get() );
for ( int iRemote = 0; iRemote < numRemoteTalkers; iRemote++ )
{
m_pXHVEngine->UnregisterRemoteTalker( remoteTalkers[iRemote] );
}
for ( int i = 0; i < XUSER_MAX_COUNT; ++i )
{
if( !m_bUserRegistered[ i ] )
continue;
m_pXHVEngine->UnregisterLocalTalker( i );
m_bUserRegistered[ i ] = false;
}
#endif
}
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CXboxVoice, IEngineVoice,
IENGINEVOICE_INTERFACE_VERSION, *Audio_GetXVoice() );