//========= 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> 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() );