Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2023 lines
69 KiB

//========= 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 "vjobs/sndupsampler_shared.h"
// from PS3
#include <sysutil/sysutil_sysparam.h>
#include <cell/audio.h>
#include <sys/event.h>
#include <cell/sysmodule.h>
#include <cell/mic.h>
#include <cell/voice.h>
#include <vjobs_interface.h>
#include <vjobs/root.h>
#include <ps3/vjobutils.h>
#include <vjobs/sndupsampler_shared.h>
#include <ps3/vjobutils_shared.h>
#include "engine/IPS3FrontPanelLED.h"
#include <tier0/microprofiler.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#include "sys/tty.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
#define ENABLE_SOUND_DEBUG 0
#define SOUND_PC_CELP_SUPPORTED
extern IVEngineClient *engineClient;
extern IVJobs * g_pVJobs;
ConVar snd_ps3_back_channel_multiplier( "snd_ps3_back_channel_multiplier", "0" );
//-----------------------------------------------------------------------------
// An ugly workaround: our sound mixing library outputs at 44.1khz; PS3 inputs
// at 48khz. We'll probably need to replace the mixer wholesale, but for now,
// add an upsampling stage to the pipeline.
//-----------------------------------------------------------------------------
class CAudioIckyUpsampler // a non-virtual class
{
public:
// local constants
enum ConstEnum_t
{
INPUTFREQ = job_sndupsampler::INPUTFREQ,
OUTPUTFREQ = job_sndupsampler::OUTPUTFREQ,
BUFFERSIZE = job_sndupsampler::BUFFERSIZE, // < input samples, should be a power of two
};
CThreadMutex m_mutex;
CAudioIckyUpsampler();
~CAudioIckyUpsampler(); // NON-VIRTUAL .. mark virtual if you inherit from here
// initialize to stereo or surround. Can be called again to reinitialize.
void Init( bool surround );
// take in some number of input samples. Returns the number actually consumed (the buffer might be full)
int IngestStereo( portable_samplepair_t *pInputSamples, int numSamples ) RESTRICT;
int IngestSurround( portable_samplepair_t *pInputStereo, portable_samplepair_t *pInputSurround, portable_samplepair_t *pInputCenter, int numSamples ) RESTRICT;
// upsample and emit the left and right channels into an interleaved float stream.
// scalar gets multiplied onto each sample as it's written (ie to rescale -32767..32767 -> -1..1
// returns number of samples actually emitted.
// assumes PS3 interleaving (
/*
// you specify a pointer to the left channel, an offset (in words) between the left and right
// channel, and the stride between successive samples. Returns the number of samples actually
// emitted.
// scalar gets multiplied onto each sample as it's written (ie to rescale -32767..32767 -> -1..1
int Excrete( uint nSamplesRequested, float *pOutSamples, int nLeftToRightOffset, int nstride, float scalar );
*/
// We output stereo in a surround channel.
int ExcreteStereo( uint nSamplesRequested, libaudio_sample_surround_t *pOutSamples, float scalar ) RESTRICT;
int ExcreteSurround( uint nSamplesRequested, libaudio_sample_surround_t *pOutSamples, float scalar ) RESTRICT;
/// dump all data
void Flush();
// the number of INPUT samples already in the buffer
inline int Count();
inline int SpaceRemaining() { return BUFFERSIZE - Count(); }
int OutputSamplesAvailable();
inline bool IsSurround() { return m_bSurround; }
public:
/// ring buffers: one interleaved pair for stereo, or five channels for surround.
/// I compress them back to signed shorts to conserve memory bandwidth.
typedef job_sndupsampler::stereosample_t stereosample_t;
typedef job_sndupsampler::surroundsample_t surroundsample_t;
union // the pointer to the input buffer of samples at 44k
{
stereosample_t *m_p44kStereoIn;
surroundsample_t *m_p44kSurroundIn;
uintp m_ea44kSamplesIn;
};
// size in bytes of an input sample for the current channel mode
inline int InputSampleSizeof( ) { return m_bSurround ? sizeof(surroundsample_t) : sizeof(stereosample_t); }
CInterlockedInt m_ringHead; ///< "head" of the buffer, the first sample Excrete() will look at
CInterlockedInt m_ringCount; ///< number of samples in the buffer
float m_fractionalSamples; ///< we will have some fractional number of samples still waiting to be sent. ie 0.2f means we still have temporally 80% of the HEAD sample to emit.
bool m_bSurround; // set to surround sound mode
};
CAudioIckyUpsampler::CAudioIckyUpsampler()
: m_ringHead( 0 )
, m_ringCount( 0 )
, m_fractionalSamples( 0 )
, m_p44kStereoIn( NULL )
, m_bSurround( false )
{
}
CAudioIckyUpsampler::~CAudioIckyUpsampler()
{
if ( IsSurround() )
MemAlloc_FreeAligned( m_p44kSurroundIn );
else
MemAlloc_FreeAligned( m_p44kStereoIn );
m_p44kStereoIn = NULL;
}
void CAudioIckyUpsampler::Init( bool surround )
{
// there's a buffer already, toss it. (in certain cases could just recycle it, but it's unlikely that this is ever actually called twice)
if ( m_p44kStereoIn != NULL )
{
MemAlloc_FreeAligned( m_p44kStereoIn );
m_p44kStereoIn = NULL;
}
// Note : we need to allocate aligned memory so that when DMA'ing ring buffer that wraps around,
// there's no hole at the wrap point (so the end and start of the buffer must be aligned
if ( m_bSurround = surround )
{ // we are in surround mode
m_p44kSurroundIn = ( surroundsample_t* ) MemAlloc_AllocAligned( sizeof( surroundsample_t ) * BUFFERSIZE, 16 );
memset( m_p44kSurroundIn, 0, sizeof(surroundsample_t)*BUFFERSIZE );
}
else
{ // stereo mode
m_p44kStereoIn = ( stereosample_t* )MemAlloc_AllocAligned( sizeof( stereosample_t ) * BUFFERSIZE, 16 );
memset( m_p44kStereoIn, 0, sizeof(stereosample_t)*BUFFERSIZE );
}
}
void CAudioIckyUpsampler::Flush()
{
AUTO_LOCK( m_mutex ); // TODO: get rid of this lock, use head-tail counters instead of head+count
m_fractionalSamples = 0;
m_ringHead = m_ringCount = 0;
}
inline int CAudioIckyUpsampler::Count()
{
return m_ringCount;
}
int CAudioIckyUpsampler::IngestStereo( portable_samplepair_t * RESTRICT pInputSamples, int numSamples ) RESTRICT
{
if ( IsSurround() )
{
Error( "Audio: Tried to ingest stereo data into a surround upsampler!\n" );
}
const int nSamplesToConsume = MIN( numSamples, SpaceRemaining() );
int i,o;
for ( i = 0, o = (m_ringHead + m_ringCount)%BUFFERSIZE ;
i < nSamplesToConsume ;
++i, o = (o + 1)%BUFFERSIZE ) // power-of-two mods become ands
{
m_p44kStereoIn[ o ].left = pInputSamples[i].left;
m_p44kStereoIn[ o ].right = pInputSamples[i].right;
}
m_ringCount += nSamplesToConsume;
return nSamplesToConsume;
// ------------------------------------------
// PURELY FOR EXAMPLE: a basic sine wave test
if ( false ) {
int q = 0 ;
while ( q++ < numSamples && m_ringCount < BUFFERSIZE )
{
int i = (m_ringHead + m_ringCount)%BUFFERSIZE;
float f = sin( ( i & 0x100 ? (2.0/256.0) : (2.0/128.0)) * M_PI * i ) * 20000;
m_p44kStereoIn[i].left = f;
m_p44kStereoIn[i].right = f;
++m_ringCount;
}
return numSamples;
}
}
int CAudioIckyUpsampler::IngestSurround( portable_samplepair_t * RESTRICT pInputStereo, portable_samplepair_t * RESTRICT pInputSurround, portable_samplepair_t * RESTRICT pInputCenterAsPairs, int numSamples ) RESTRICT
{
if ( !IsSurround() )
{
Error( "Audio: Tried to ingest stereo data into a surround upsampler!\n" );
}
const int nSamplesToConsume = MIN( numSamples, SpaceRemaining() );
int i,o;
for ( i = 0, o = (m_ringHead + m_ringCount)%BUFFERSIZE ;
i < nSamplesToConsume ;
++i, o = (o + 1)%BUFFERSIZE ) // power-of-two mods become ands
{
// hopefully the compiler knows how to pipeline these intelligently ( should be >1 IPC )
m_p44kSurroundIn[ o ].left = pInputStereo[i].left;
m_p44kSurroundIn[ o ].right = pInputStereo[i].right;
m_p44kSurroundIn[ o ].center = pInputCenterAsPairs[i].left; // Center is stored as left / right, but only left contains the necessary information
m_p44kSurroundIn[ o ].surleft = pInputSurround[i].left;
m_p44kSurroundIn[ o ].surright = pInputSurround[i].right;
}
m_ringCount += nSamplesToConsume;
return nSamplesToConsume;
}
#pragma message("TODO: obvious optimization opportunities")
// TODO: move to SPU -- many obvious optimization opportunities
float g_flSumStereo;
int CAudioIckyUpsampler::ExcreteStereo( uint nSamplesRequested, libaudio_sample_surround_t * RESTRICT pOutSamples, float scalar ) RESTRICT
{
Assert( !IsSurround() );
// do a braindead linear interpolation
float t = m_fractionalSamples;
float tstep = ((float)INPUTFREQ)/((float) OUTPUTFREQ);
const int nSamplesToEmit = MIN( OutputSamplesAvailable(), nSamplesRequested );
int curSamp = m_ringHead;
int nextSamp = (curSamp + 1) & (BUFFERSIZE - 1);
int n44kSamplesConsumed = 0;
// iterator
for ( int iRemain = nSamplesToEmit ;
iRemain > 0 ;
t += tstep, --iRemain )
{
// if we've passed a sample boundary, go onto the next sample
if ( t >= 1.0f ) // ugh, fcmp, it's just a hacky prototype
{
t -= 1.0f;
curSamp = nextSamp;
nextSamp = (curSamp + 1) & (BUFFERSIZE - 1);
++n44kSamplesConsumed;
}
pOutSamples->left = Lerp( t, m_p44kStereoIn[curSamp].left, m_p44kStereoIn[nextSamp].left ) * scalar; // left
pOutSamples->right = Lerp( t, m_p44kStereoIn[curSamp].right, m_p44kStereoIn[nextSamp].right ) * scalar; // right
// Fill the remaining with blank
pOutSamples->center = 0;
pOutSamples->subwoofer = 0;
pOutSamples->leftsurround = 0;
pOutSamples->rightsurround = 0;
pOutSamples->leftextend = 0;
pOutSamples->rightextend = 0;
g_flSumStereo += pOutSamples->left * pOutSamples->left + pOutSamples->right * pOutSamples->right;
pOutSamples++;
}
// Make sure to update correctly the variables after the last sample (after all, we did t += tstep).
if ( t >= 1.0f )
{
t -= 1.0f;
curSamp = nextSamp;
++n44kSamplesConsumed;
}
m_fractionalSamples = t;
m_ringHead = curSamp;
m_ringCount -= n44kSamplesConsumed;
return nSamplesToEmit;
}
int CAudioIckyUpsampler::ExcreteSurround( uint nSamplesRequested, libaudio_sample_surround_t *pOutSamples, float scalar ) RESTRICT
{
Assert( IsSurround() );
// do a braindead linear interpolation
float t = m_fractionalSamples;
float tstep = ((float)INPUTFREQ)/((float) OUTPUTFREQ);
const int nSamplesToEmit = MIN( OutputSamplesAvailable(), nSamplesRequested );
int curSamp = m_ringHead;
int nextSamp = (curSamp + 1) & (BUFFERSIZE - 1);
int n44kSamplesConsumed = 0;
const float flBackChannelMultipler = snd_ps3_back_channel_multiplier.GetFloat();
// iterator
for ( int iRemain = nSamplesToEmit ;
iRemain > 0 ;
t += tstep, --iRemain )
{
// if we've passed a sample boundary, go onto the next sample
if ( t >= 1.0f ) // ugh, fcmp, it's just a hacky prototype
{
t -= 1.0f;
curSamp = nextSamp;
nextSamp = (curSamp + 1) & (BUFFERSIZE - 1);
++n44kSamplesConsumed;
}
pOutSamples->left = Lerp( t, m_p44kSurroundIn[curSamp].left, m_p44kSurroundIn[nextSamp].left ) * scalar; // left
pOutSamples->right = Lerp( t, m_p44kSurroundIn[curSamp].right, m_p44kSurroundIn[nextSamp].right ) * scalar; // right
pOutSamples->center = Lerp( t, m_p44kSurroundIn[curSamp].center, m_p44kSurroundIn[nextSamp].center ) * scalar; // left
pOutSamples->subwoofer = 0; // As on X360, let the HW mix the sub from the main channel since we don't have any sub-specific sounds, or direct sub-addressing
float fLeftSurround = Lerp( t, m_p44kSurroundIn[curSamp].surleft, m_p44kSurroundIn[nextSamp].surleft ) * scalar; // left
float fRightSurround = Lerp( t, m_p44kSurroundIn[curSamp].surright, m_p44kSurroundIn[nextSamp].surright ) * scalar; // right
pOutSamples->leftsurround = fLeftSurround;
pOutSamples->rightsurround = fRightSurround;
pOutSamples->leftextend = fLeftSurround * flBackChannelMultipler;
pOutSamples->rightextend = fRightSurround * flBackChannelMultipler;
pOutSamples++;
}
// Make sure to update correctly the variables after the last sample (after all, we did t += tstep).
if ( t >= 1.0f )
{
t -= 1.0f;
curSamp = nextSamp;
++n44kSamplesConsumed;
}
m_fractionalSamples = t;
m_ringHead = curSamp;
m_ringCount -= n44kSamplesConsumed;
return nSamplesToEmit;
}
int CAudioIckyUpsampler::OutputSamplesAvailable()
{
// returns the number of output samples we can generate (notice this rounds down, ie we may leave some fractional samples for next time)
return ( ( m_ringCount - 1 ) * OUTPUTFREQ) / INPUTFREQ;
}
//-----------------------------------------------------------------------------
// Implementation of an audio device on top of PS3's libaudio
//-----------------------------------------------------------------------------
class CAudioPS3LibAudio: public CAudioDeviceBase, public VJobInstance
{
public:
~CAudioPS3LibAudio( void );
CAudioPS3LibAudio( 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 );
void PaintEnd( void );
int GetOutputPosition( void );
void ClearBuffer( void );
void TransferSamples( int64 end ) ;
virtual const char *DeviceName( void );
virtual int DeviceChannels( void );
virtual int DeviceSampleBits( void );
virtual int DeviceSampleBytes( void );
virtual int DeviceDmaSpeed( void );
virtual int DeviceSampleCount( void );
CEngineVoicePs3 *GetVoiceData( void ) { return &m_VoiceData; }
static CAudioPS3LibAudio *m_pSingleton;
static void QuerySystemAudioConfiguration( bool bForceRefreshCache = false ); // retrieves information from the OS about what output devices are connected. Caches result.
virtual void OnVjobsInit(){ s_LibAudioThread.OnVjobsInit( m_pRoot ); }
virtual void OnVjobsShutdown() { s_LibAudioThread.OnVjobsShutdown(); }
protected:
inline float *GetAddressForBlockIdx( unsigned nBlock );
inline uint GetPortBufferSize()const;
inline float *GetNextReadBlockAddress();
int UpdateBufferCount( void ); // update the count of how many filled buffers remain to be read. return m_nFilledBuffers to avoid a LHS
#if 0 // These functions were deprecated by the move to a DMA thread:
// blit the audio data to the hardware. returns the number of samples written.
int TransferStereo( const portable_samplepair_t *pFrontBuffer, int nSamplesToWrite, int nStartBlock );
int TransferSurroundInterleaved( const portable_samplepair_t *pFrontBuffer, const portable_samplepair_t *pRearBuffer, const int *pCenterBuffer,
int nSamplesToWrite, int nStartBlock );
#endif
int m_deviceChannels; // channels per ring port ( either 2 for stereo or 8 for surround -- PS3's libaudio has no concept of 5.1 )
// information from the OS.
// right now this is all a little redundant because we are only using the "primary" sound device,
// which always has exactly one logical device even if there are multiple things attached. But it
// will be significant if we eventually create another class to handle "secondary" outputs, in which
// case the members below should not be static, and QuerySystemAudioConfiguration() will need to take
// a flag specifying CELL_AUDIO_OUT_PRIMARY or CELL_AUDIO_OUT_SECONDARY. You'll also need to
// touch the init/shutdown functions so that you only intialize the OS's audio lib once.
static bool s_bCellAudioStateInitialized;
static CUtlVectorConservative<CellAudioOutState> s_vCellAudioOutputDeviceStates;
uint32_t m_nPortNum; // the "port number" given us by Cell
sys_addr_t m_pPortBufferAddr; // base address of the audio port's ring buffer
float * m_pDebugBuffer;
volatile uint64_t* m_pReadIndexAddr; // pointer to an int containing the index of next block in the buffer that the hardware will read
char m_nPhysicalOutChannels;
CInterlockedInt m_nTotalBlocksPlayed;
/// ----- THE FOLLOWING ARE CONTROLLED BY ONE MUTEX ----------
CAudioIckyUpsampler m_upsampler;
/// ----------------------------------------------------------
class CAudioPS3DMAThread : public CThread
{
public:
CAudioPS3DMAThread( );
bool Setup( CAudioPS3LibAudio *pOwner );
CInterlockedInt m_eHalt; // owner can signal halt with this by setting it to TRUE
job_sndupsampler::JobDescriptor_t * m_pSndUpsamplerJobDescriptor;
job_sndupsampler::JobOutput_t * GetSndUpsamplerJobOutput() { return ( job_sndupsampler::JobOutput_t * )( m_pSndUpsamplerJobDescriptor + 1 ); }
job_sndupsampler::JobParams_t * GetSndUpsamplerJobParams() { return job_sndupsampler::GetJobParams( m_pSndUpsamplerJobDescriptor ); }
void OnVjobsInit( VJobsRoot* pRoot );
void OnVjobsShutdown();
protected:
virtual int Run();
virtual void OnExit(); // clean up
int OutputData( int nCurrentAudioBlockReadIndex ) RESTRICT; // dump sounds from the upsampler into the DAC. return number of blocks written.
int m_nFilledBuffers; // the number of block that are full of data but haven't been played yet.
int m_nPreviouslySeenNextToUpdateBufferIndex;
// the event queue (for counting how many blocks get played -- we could also use this for async sound writing)
sys_event_queue_t m_AudioEventQueue;
sys_ipc_key_t m_AudioIPCKey;
CAudioPS3LibAudio *m_pOwner;
enum { STACKSIZE=4095 };
};
// a tiny thread whose only job in existence is to feed libaudio from our upsampling buffer.
// it has to be this way because the system only caches two events from libaudio, and so
// we can't know how many blocks have been played if we happen to be polled less than once
// every ten milliseconds.
static CAudioPS3DMAThread s_LibAudioThread;
friend class CAudioPS3DMAThread;
CEngineVoicePs3 m_VoiceData;
};
CAudioPS3LibAudio *CAudioPS3LibAudio::m_pSingleton = NULL;
CAudioPS3LibAudio::CAudioPS3DMAThread::CAudioPS3DMAThread()
: m_nFilledBuffers(0)
, m_eHalt(false)
, m_pSndUpsamplerJobDescriptor( NULL )
{
}
// [Main] called from the context of main thread
//
void CAudioPS3LibAudio::CAudioPS3DMAThread::OnVjobsInit( VJobsRoot* pRoot )
{
Assert( !m_pSndUpsamplerJobDescriptor );
m_pSndUpsamplerJobDescriptor = ( job_sndupsampler::JobDescriptor_t * )MemAlloc_AllocAligned( sizeof( job_sndupsampler::JobDescriptor_t ) + sizeof( job_sndupsampler::JobOutput_t ), 128 );
m_pSndUpsamplerJobDescriptor->header = *pRoot->m_pJobSndUpsampler;
m_pSndUpsamplerJobDescriptor->header.useInOutBuffer = 0;
job_sndupsampler::JobParams_t * pJobParams = job_sndupsampler::GetJobParams( m_pSndUpsamplerJobDescriptor );
pJobParams->m_flMaxSumStereo = 500;
pJobParams->m_nDebuggerBreak = 0;
pJobParams->m_deviceChannels = m_pOwner->m_deviceChannels;
Assert( pJobParams->m_deviceChannels == m_pOwner->m_deviceChannels );
pJobParams->m_bIsSurround = m_pOwner->IsSurround();
pJobParams->m_nCellAudioBlockSamplesLog2 = COMPILE_TIME_LOG2( CELL_AUDIO_BLOCK_SAMPLES );
cellAudioCreateNotifyEventQueue( &m_AudioEventQueue, &m_AudioIPCKey );
cellAudioSetNotifyEventQueue( m_AudioIPCKey );
Start(STACKSIZE);
}
// [Main] called from the context of main thread
//
void CAudioPS3LibAudio::CAudioPS3DMAThread::OnVjobsShutdown()
{
m_eHalt = true ;
Join();
cellAudioRemoveNotifyEventQueue( m_AudioIPCKey );
sys_event_queue_destroy( m_AudioEventQueue, SYS_EVENT_QUEUE_DESTROY_FORCE );
Assert( m_pSndUpsamplerJobDescriptor );
if( m_pSndUpsamplerJobDescriptor )
{
m_pSndUpsamplerJobDescriptor = NULL;
job_sndupsampler::JobOutput_t * pJobOutput = GetSndUpsamplerJobOutput();
MemAlloc_FreeAligned( m_pSndUpsamplerJobDescriptor );
m_pSndUpsamplerJobDescriptor = NULL;
}
}
bool CAudioPS3LibAudio::CAudioPS3DMAThread::Setup( CAudioPS3LibAudio *pOwner )
{
if ( m_pOwner )
{
AssertMsg( false, "Tried to make two CAudioPS3LibAudio s\n");
return false;
}
SetName("CAudioPS3DMAThread");
m_pOwner = pOwner;
return true;
}
void CAudioPS3LibAudio::CAudioPS3DMAThread::OnExit()
{
// NOTE: please don't destruct resources created in the context of main thread here; Setup() is called in main thread; OnExit is called from the audio thread.
// destroy those resources where you join the thread , the same way they are created before the thread Start() is called
// NOTE: we need to shutdown AFTER exiting the thread, to avoid accessing thread structure during/after shutdown
Assert( m_pSndUpsamplerJobDescriptor );
}
int CAudioPS3LibAudio::CAudioPS3DMAThread::Run()
{
// this needs to be a high priority thread since it is realtime
SetPriority( 5 );
m_nPreviouslySeenNextToUpdateBufferIndex = *(m_pOwner->m_pReadIndexAddr);
while ( !m_eHalt )
{
// wait for the audio engine to signal that it's mixed out a buffer
// and is ready for another
sys_event_t audioevent;
int waitresult = sys_event_queue_receive( m_AudioEventQueue, &audioevent, 5000000 );
if ( waitresult == ETIMEDOUT )
{
// warnings are commented out here because CUtlString::Format() allocates a 4096byte buffer and thereby blows
// the thread stack!
// Warning("Went five seconds without a libaudio event, wtf?\n");
continue;
}
else if ( waitresult != CELL_OK )
{
Error("Failed to wait on libaudio buffer, error %x\n", waitresult );
return -1;
}
// down here, we successfully received an event
// make sure we still haven't been halted
if ( m_eHalt )
break;
int nextBufferToBeRead = *(m_pOwner->m_pReadIndexAddr);
if ( nextBufferToBeRead != (m_nPreviouslySeenNextToUpdateBufferIndex + 1) % CELLAUDIO_PORT_BUFFER_BLOCKS )
{
// warnings are commented out here because CUtlString::Format() allocates a 4096byte buffer and thereby blows
// the thread stack!
// Warning( "Missed an audio buffer being sent through! previously seen was %d now is %d\n",
// m_nPreviouslySeenNextToUpdateBufferIndex, nextBufferToBeRead );
// flush everything and start over
m_nFilledBuffers = 0;
}
else
{
// one more buffer has been eaten
m_nFilledBuffers = m_nFilledBuffers <= 0 ? 0 : m_nFilledBuffers - 1;
}
// let's avoid a sys call in non-debug configurations
if ( !ENABLE_SOUND_DEBUG || ( CPS3FrontPanelLED::GetSwitches() & CPS3FrontPanelLED::kPS3SWITCH2 ) == 0 )
{
int bufsRead = OutputData( nextBufferToBeRead );
// Msg("Emited %d bufs\n", bufsRead);
m_nFilledBuffers += bufsRead;
}
else
{
// An example of a dead simple sine wave test:
m_pOwner->m_upsampler.Flush();
float * pOut = m_pOwner->GetAddressForBlockIdx( nextBufferToBeRead );
// brain-dead test, just a low note
for ( int n = 0 ; n < CELL_AUDIO_BLOCK_SAMPLES ; ++n )
{
float f = 0.4f * sin( (4.0 / (double)CELL_AUDIO_BLOCK_SAMPLES) * M_PI * n );
pOut[n*2 + 0] = f;
pOut[n*2 + 1] = f;
m_nFilledBuffers = 1;
}
/*
char buffer[200];
int len = V_snprintf( buffer, sizeof( buffer) , "Snd %x, @%p\n", CELL_AUDIO_BLOCK_SAMPLES * 2 * sizeof( float ), pOut );
uint wrote;
sys_tty_write( SYS_TTYP3, buffer, len, &wrote );
*/
}
m_nPreviouslySeenNextToUpdateBufferIndex = nextBufferToBeRead; // *(m_pOwner->m_pReadIndexAddr); // reread the volatile
// Update the voice buffer here too
Audio_GetXVoice()->PlayPortInterruptHandler();
}
return 0;
}
ConVar sound_on_spu( "sound_on_spu", "1" );
#define AssertRt(X) //while(!(X)){DebuggerBreak();break;}
uint g_nOutputDataBlocksWritten = 0;
ConVar sound_sleep_rt_thread( "sound_sleep_rt_thread", "30" );
int CAudioPS3LibAudio::CAudioPS3DMAThread::OutputData( int nCurrentAudioBlockReadIndex ) RESTRICT
{
Assert( m_pSndUpsamplerJobDescriptor ); // the SPU job code must be loaded/streamed in already
// grab the upsampler mutex
int availableInputSamples = m_pOwner->m_upsampler.OutputSamplesAvailable(); // at the 48khz rate
if( availableInputSamples <= 0 )
{
return 0;
}
int nextBlockIdxToWrite = ( nCurrentAudioBlockReadIndex + m_nFilledBuffers ) % CELLAUDIO_PORT_BUFFER_BLOCKS;
float volumeFactor = S_GetMasterVolume() / 32767.0f;
// flush as many samples out to the device as possible
// now output as many full audio blocks @48khz as we can
int emptyblocks = CELLAUDIO_PORT_BUFFER_BLOCKS - m_nFilledBuffers;
int availableInputBlocks = availableInputSamples / CELL_AUDIO_BLOCK_SAMPLES; // rounds down
// don't emit more blocks than the DAC has room for
availableInputBlocks = MIN( availableInputBlocks, emptyblocks );
if( availableInputBlocks <= 0 )
{
return 0;
}
uint blocksWrit = 0;
if( int nSoundOnSpu = sound_on_spu.GetInt() )
{
job_sndupsampler::JobOutput_t * pJobOutput = GetSndUpsamplerJobOutput();
job_sndupsampler::JobParams_t * pJobParams = job_sndupsampler::GetJobParams( m_pSndUpsamplerJobDescriptor );
float * pSpuResult = ENABLE_SOUND_DEBUG && nSoundOnSpu > 1 ? m_pOwner->m_pDebugBuffer : NULL;
uint n44kSamplesAvailable = m_pOwner->m_upsampler.m_ringCount;
{
MICRO_PROFILE( g_mpOutputData );
AssertRt( pJobParams->m_deviceChannels == m_pOwner->m_deviceChannels );
pJobParams->m_eaPortBufferAddr = ( pSpuResult ? (sys_addr_t)pSpuResult : m_pOwner->m_pPortBufferAddr ); // may change when user switches surround <-> stereo
pJobParams->m_availableInputBlocks = availableInputBlocks;
pJobParams->m_volumeFactor = volumeFactor;
pJobParams->m_nextBlockIdxToWrite = nextBlockIdxToWrite;
pJobParams->m_fractionalSamples = m_pOwner->m_upsampler.m_fractionalSamples;
pJobParams->m_flBackChannelMultipler = snd_ps3_back_channel_multiplier.GetFloat();
if( ENABLE_SOUND_DEBUG )
{
// let's avoid a sys call (sys_gpio_get) in non-debug configurations
if( ( CPS3FrontPanelLED::GetSwitches() & CPS3FrontPanelLED::kPS3SWITCH3 ) == 0 )
{
pJobParams->m_nDebuggerBreak &= 0x7F;
}
else
{
pJobParams->m_nDebuggerBreak |= 0x80;
}
}
uint nSampleSizeIn, nSampleSizeOutLog2;
// align the number of samples so that we don't have to deal with unaligned blocks
if( m_pOwner->m_upsampler.IsSurround() )
{
nSampleSizeOutLog2 = COMPILE_TIME_LOG2( sizeof( libaudio_sample_surround_t ) );
nSampleSizeIn = sizeof( job_sndupsampler::surroundsample_t );
}
else
{
nSampleSizeOutLog2 = COMPILE_TIME_LOG2( sizeof( libaudio_sample_surround_t ) );
nSampleSizeIn = sizeof( job_sndupsampler::stereosample_t );
}
CDmaListConstructor dmaConstructor( m_pSndUpsamplerJobDescriptor->workArea.dmaList );
int nRingHead = m_pOwner->m_upsampler.m_ringHead;
uintp ea44kSamplesIn = m_pOwner->m_upsampler.m_ea44kSamplesIn;
uint eaInputSamplesBegin = ea44kSamplesIn + ( nRingHead * nSampleSizeIn );
pJobParams->m_eaInputSamplesBegin0xF = uint8( eaInputSamplesBegin & 0xF );
pJobParams->m_ringHead = nRingHead;
pJobParams->m_ringCount = n44kSamplesAvailable;
if( nRingHead + n44kSamplesAvailable > CAudioIckyUpsampler::BUFFERSIZE )
{
// split into 2 transactions
dmaConstructor.AddInputDmaLargeRegion( (void*)( eaInputSamplesBegin & -16 ), (void*)( ea44kSamplesIn + ( CAudioIckyUpsampler::BUFFERSIZE * nSampleSizeIn ) ) );
// this is always aligned
dmaConstructor.AddInputDmaLarge( AlignValue( ( nRingHead + n44kSamplesAvailable - CAudioIckyUpsampler::BUFFERSIZE ) * nSampleSizeIn, 16 ), ( void * )ea44kSamplesIn );
}
else
{
dmaConstructor.AddInputDmaLargeUnalignedRegion( (void*)eaInputSamplesBegin, (void*)( eaInputSamplesBegin + ( n44kSamplesAvailable * nSampleSizeIn ) ) );
}
m_pSndUpsamplerJobDescriptor->header.sizeOut =
sizeof( job_sndupsampler::JobOutput_t ) // job output structure
+ ( ( 2 * CELL_AUDIO_BLOCK_SAMPLES ) << nSampleSizeOutLog2 ) // output samples
+ 16 // potential misalignment of output samples ( stereo samples are 8 bytes each )
;
dmaConstructor.FinishInBuffer( &m_pSndUpsamplerJobDescriptor->header, pJobParams );
pJobOutput->m_nBlockWritten = 0xFFFFFFFF; // invalid value
// NOTE: if we need to wait for the job, we need to push it with CELL_SPURS_JOBQUEUE_FLAG_SYNC_JOB flag!
if( int nError = m_pOwner->m_pRoot->m_queuePortSound.pushJob( &m_pSndUpsamplerJobDescriptor->header, sizeof( job_sndupsampler::JobDescriptor_t ), 0, 0 ) )
{
Warning( "Cannot start Sound job, error 0x%X\n", nError );
}
}
//m_pOwner->m_pRoot->m_queuePortSound.sync( 0 ); // NOTE! if we use sync() here, we need to push job with CELL_SPURS_JOBQUEUE_FLAG_SYNC_JOB flag!
sys_timer_usleep( sound_sleep_rt_thread.GetInt() );
while( *(volatile uint32*)( &pJobOutput->m_nBlockWritten ) == 0xFFFFFFFF ); // must be synchronized by now
{
sys_timer_usleep( 100 ); // wait for the job to complete; we aren't in a hurry
}
AssertRt( n44kSamplesAvailable > pJobOutput->m_n44kSamplesConsumed ); // we must never consume the last sample, because we won't be able to extrapolate the value
AssertRt( m_pOwner->m_upsampler.m_ringCount >= n44kSamplesAvailable );
AUTO_LOCK( m_pOwner->m_upsampler.m_mutex ); // TODO: get rid of this lock, use head-tail counters instead of head+count
if( !m_pOwner->m_upsampler.m_ringCount )
{
// if the ring count was flushed, don't do any checks - it's useless
blocksWrit = pJobOutput->m_nBlockWritten;
}
else if( ENABLE_SOUND_DEBUG && pSpuResult )
{
int nRingCountBegin = m_pOwner->m_upsampler.m_ringCount, nRingHeadBegin = m_pOwner->m_upsampler.m_ringHead;
// STEREO!
while ( availableInputBlocks>0 )
{
// all sorts of obvious ways to optimize this (explicit iterators, VMX, SPU, etc)
float * RESTRICT pWriteDest = (m_pOwner->GetAddressForBlockIdx( nextBlockIdxToWrite ));
float *buffer = new float[CELL_AUDIO_BLOCK_SAMPLES * sizeof( libaudio_sample_surround_t ) / sizeof( float )];
AssertRt( fabsf( m_pOwner->m_upsampler.m_fractionalSamples - pJobOutput->m_trace[blocksWrit].m_fractionalSamplesBefore ) < 1e-2f );
int nRingHeadBefore = m_pOwner->m_upsampler.m_ringHead, nRingCountBefore = m_pOwner->m_upsampler.m_ringCount;
float fractionalSamplesBefore = m_pOwner->m_upsampler.m_fractionalSamples;
if( ENABLE_SOUND_DEBUG )
{
m_pOwner->m_upsampler.m_ringHead = nRingHeadBefore;
m_pOwner->m_upsampler.m_ringCount = nRingCountBefore;
m_pOwner->m_upsampler.m_fractionalSamples = fractionalSamplesBefore;
}
int samplesWritten = m_pOwner->IsSurround() ?
m_pOwner->m_upsampler.ExcreteSurround( CELL_AUDIO_BLOCK_SAMPLES, reinterpret_cast<libaudio_sample_surround_t *>(buffer), volumeFactor ) :
m_pOwner->m_upsampler.ExcreteStereo( CELL_AUDIO_BLOCK_SAMPLES, reinterpret_cast<libaudio_sample_surround_t *>(buffer), volumeFactor ) ;
AssertRt( samplesWritten == CELL_AUDIO_BLOCK_SAMPLES );
int nSamplesConsumedSoFar = nRingCountBegin - m_pOwner->m_upsampler.m_ringCount;
AssertRt( nSamplesConsumedSoFar == pJobOutput->m_trace[blocksWrit].m_n44kSamplesConsumed );
float * pSpuResultBlock = pSpuResult + ( pWriteDest - ( float * )m_pOwner->m_pPortBufferAddr );
for( uint i = 0; i < CELL_AUDIO_BLOCK_SAMPLES * m_pOwner->m_deviceChannels; )
{
if( fabsf( buffer[i] - pSpuResultBlock[i] ) > 1e-6f )
{
DebuggerBreakIfDebugging();
pJobOutput->m_nBlockWritten = 0xFFFFFFFF; // invalid value
int nError = m_pOwner->m_pRoot->m_queuePortSound.pushJob( &m_pSndUpsamplerJobDescriptor->header, sizeof( job_sndupsampler::JobDescriptor_t ), 0, 0 );
while( pJobOutput->m_nBlockWritten == 0xFFFFFFFF )
continue;
continue;
}
++i;
}
--availableInputBlocks;
nextBlockIdxToWrite = (nextBlockIdxToWrite + 1)%CELLAUDIO_PORT_BUFFER_BLOCKS;
++blocksWrit;
delete []buffer;
}
AssertRt( blocksWrit == pJobOutput->m_nBlockWritten );
AssertRt( m_pOwner->m_upsampler.m_ringHead == pJobOutput->m_ringHead );
AssertRt( fabsf( m_pOwner->m_upsampler.m_fractionalSamples - pJobOutput->m_fractionalSamples ) < 1e-6f );
}
else
{
blocksWrit = pJobOutput->m_nBlockWritten;
m_pOwner->m_upsampler.m_ringCount -= pJobOutput->m_n44kSamplesConsumed;
m_pOwner->m_upsampler.m_ringHead = pJobOutput->m_ringHead;
m_pOwner->m_upsampler.m_fractionalSamples = pJobOutput->m_fractionalSamples;
}
}
else
{
// STEREO!
AUTO_LOCK( m_pOwner->m_upsampler.m_mutex ); // TODO: get rid of this lock, use head-tail counters instead of head+count
MICRO_PROFILE( g_mpOutputData );
g_flSumStereo = 0;
while ( availableInputBlocks>0 )
{
// all sorts of obvious ways to optimize this (explicit iterators, VMX, SPU, etc)
float * RESTRICT pWriteDest = (m_pOwner->GetAddressForBlockIdx( nextBlockIdxToWrite ));
int samplesWritten = m_pOwner->IsSurround() ?
m_pOwner->m_upsampler.ExcreteSurround( CELL_AUDIO_BLOCK_SAMPLES, reinterpret_cast<libaudio_sample_surround_t *>(pWriteDest), volumeFactor ) :
m_pOwner->m_upsampler.ExcreteStereo( CELL_AUDIO_BLOCK_SAMPLES, reinterpret_cast<libaudio_sample_surround_t *>(pWriteDest), volumeFactor ) ;
AssertRt( samplesWritten == CELL_AUDIO_BLOCK_SAMPLES );
--availableInputBlocks;
nextBlockIdxToWrite = (nextBlockIdxToWrite + 1)%CELLAUDIO_PORT_BUFFER_BLOCKS;
++blocksWrit;
}
if( ENABLE_SOUND_DEBUG && g_flSumStereo > GetSndUpsamplerJobParams()->m_flMaxSumStereo )
{
char buffer[200];
int len = V_snprintf( buffer, sizeof( buffer) , "PPU Stereo %g\n", g_flSumStereo );
uint wrote;
sys_tty_write( SYS_TTYP3, buffer, len, &wrote );
}
}
m_pOwner->m_nTotalBlocksPlayed += blocksWrit;
g_nOutputDataBlocksWritten += blocksWrit;
return blocksWrit;
}
CAudioPS3LibAudio::CAudioPS3DMAThread CAudioPS3LibAudio::s_LibAudioThread;
bool CAudioPS3LibAudio::s_bCellAudioStateInitialized = false;
CUtlVectorConservative<CellAudioOutState> CAudioPS3LibAudio::s_vCellAudioOutputDeviceStates(0,1);
// NULL out the pointers so that failures are more immediate, evident
CAudioPS3LibAudio::CAudioPS3LibAudio( void ) : m_deviceChannels( 0 )
, m_nPortNum( -1 )
, m_nPhysicalOutChannels( 0 )
, m_pPortBufferAddr( NULL )
, m_pReadIndexAddr( NULL )
, m_nTotalBlocksPlayed( 0 )
{
// AssertMsg( false, "Please implement CAudioPS3LibAudio\n" );
}
//-----------------------------------------------------------------------------
// Create XAudio Device
//-----------------------------------------------------------------------------
IAudioDevice *Audio_CreatePS3AudioDevice( bool bInitVoice )
{
MEM_ALLOC_CREDIT();
if ( CommandLine()->CheckParm( "-nosound" ) )
{
// respect forced lack of audio
return NULL;
}
if ( !CAudioPS3LibAudio::m_pSingleton )
{
CAudioPS3LibAudio::m_pSingleton = new CAudioPS3LibAudio;
if ( !CAudioPS3LibAudio::m_pSingleton->Init() )
{
AssertMsg( false, "Failed to init CAudioPS3LibAudio\n" );
delete CAudioPS3LibAudio::m_pSingleton;
CAudioPS3LibAudio::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 && CAudioPS3LibAudio::m_pSingleton )
{
CAudioPS3LibAudio::m_pSingleton->GetVoiceData()->VoiceInit();
}
return CAudioPS3LibAudio::m_pSingleton;
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
CAudioPS3LibAudio::~CAudioPS3LibAudio( void )
{
if ( m_deviceChannels > 0 )
{
AssertMsg( false, "CAudioPS3LibAudio() called without being Shutdown() first! Initiating emergency purge.\n" );
Shutdown();
}
g_pVJobs->Unregister( this );
m_pSingleton = NULL;
}
void CAudioPS3LibAudio::QuerySystemAudioConfiguration( bool bForceRefreshCache )
{
if ( !s_bCellAudioStateInitialized || bForceRefreshCache )
{
// flush the data we've got if necessary
s_vCellAudioOutputDeviceStates.RemoveAll();
const int numDevices = cellAudioOutGetNumberOfDevice( CELL_AUDIO_OUT_PRIMARY );
if ( numDevices < 0 )
{
// error!
s_vCellAudioOutputDeviceStates.RemoveAll();
AssertMsg1( false, "cellAudioOutGetNumberOfDevice failed: %x\n", numDevices );
return;
}
s_vCellAudioOutputDeviceStates.EnsureCount(numDevices); // set the count of output devices
for ( int d = 0 ; d < s_vCellAudioOutputDeviceStates.Count() ; ++d )
{
int suc = cellAudioOutGetState( CELL_AUDIO_OUT_PRIMARY, d, &s_vCellAudioOutputDeviceStates[d] );
AssertMsg2( suc == CELL_OK, "cellAudioOutGetState(%d) failed: %x\n", d, suc );
}
s_bCellAudioStateInitialized = true;
}
}
// This code is from Bink's examples - they are configuring the HW mixer
// This may resolve the issue with center being lost when we are in stereo.
// Note that we do not detect DTS. Maybe we should?
static int init_audio_hardware( int minimum_chans, int &nPhysicalChannels )
{
int ret;
int ch_pcm;
int ch_bit;
CellAudioOutConfiguration a_config;
memset( &a_config, 0, sizeof( CellAudioOutConfiguration ) );
// Note that we would probably need to do something smarter than what this function is doing.
// It seems we should test these cases in this order (highest to lowest quality):
// Do we have enough channels for:
// - LPCM
// - DTS
// - AC3
// If not, then we have to downmix:
// - If from 8 to 6 (type B downmixer)
// - Try LPCM encoder
// - Otherwise try DTS encoder - Is this necessary?
// - Then the AC3 encoder - Is this necessary?
// - If it does not work (or from 8 to 2, 6 to 2) - use Type A downmixer:
// - Try PCM encoder
// Current code is doing:
// - Do we have enough channels for LPCM
// If yes, set it, done.
// If not
// Do we have support for 6 channels?
// If yes, then set downmixer from 8 to 6 in LPCM mode.
// If not then we support only 2 channels.
// Do we have support for Dolby (any number of channels)?
// If yes, do we have enough support for requested channels?
// If yes, set it, done.
// If not, set the downmixer to 5.1, if successful, done.
// If not (or Dolby downmixer to 5.1 failed), downmix to 2 with LPCM.
// first lets see how many pcm output channels we have
ch_pcm = cellAudioOutGetSoundAvailability( CELL_AUDIO_OUT_PRIMARY,
CELL_AUDIO_OUT_CODING_TYPE_LPCM,
CELL_AUDIO_OUT_FS_48KHZ, 0 );
nPhysicalChannels = ch_pcm;
if ( ch_pcm >= minimum_chans )
{
a_config.channel = ch_pcm;
a_config.encoder = CELL_AUDIO_OUT_CODING_TYPE_LPCM;
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_NONE; /* No downmixer is used */
cellAudioOutConfigure( CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0 );
ret = ch_pcm;
}
else
{
switch ( ch_pcm )
{
case 6:
// this means we asked for 8 channels, but only 6 are available
// so, we'll turn on the 7.1 to 5.1 downmixer.
a_config.channel = 6;
a_config.encoder = CELL_AUDIO_OUT_CODING_TYPE_LPCM;
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_TYPE_B;
if ( cellAudioOutConfigure( CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0 ) != CELL_OK )
{
return 0; // error - the downmixer didn't init
}
ret = 8;
break;
case 2:
// ok, this means they asked for multi-channel out, but only stereo
// is supported. we'll try Dolby digital first and then the downmixer
// Support for DTS - disabled as we can't test it right now
#if 0
ch_bit = cellAudioOutGetSoundAvailability( CELL_AUDIO_OUT_PRIMARY,
CELL_AUDIO_OUT_CODING_TYPE_DTS,
CELL_AUDIO_OUT_FS_48KHZ, 0 );
if ( ch_bit > 0 )
{
a_config.channel = ch_bit;
a_config.encoder = CELL_AUDIO_OUT_CODING_TYPE_DTS;
if ( ch_bit >= minimum_chans )
{
// we have enough channels to support their minimum
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_NONE;
ret = ch_bit;
}
else
{
// we don't have enough channels to support their minimum, so use the downmixer
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_TYPE_B; // Downmix to 5.1 channels
ret = 8;
}
if ( cellAudioOutConfigure( CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0 ) == CELL_OK )
{
nPhysicalChannels = ch_bit;
break;
}
// if we got here the DTS encoder didn't init, so fall through to Dolby encoder
}
#endif
ch_bit = cellAudioOutGetSoundAvailability( CELL_AUDIO_OUT_PRIMARY,
CELL_AUDIO_OUT_CODING_TYPE_AC3,
CELL_AUDIO_OUT_FS_48KHZ, 0 );
if ( ch_bit > 0 )
{
a_config.channel = ch_bit;
a_config.encoder = CELL_AUDIO_OUT_CODING_TYPE_AC3;
if ( ch_bit >= minimum_chans )
{
// we have enough channels to support their minimum
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_NONE;
ret = ch_bit;
}
else
{
// we don't have enough channels to support their minimum, so use the downmixer
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_TYPE_B; // Downmix to 5.1 channels
ret = 8;
}
if ( cellAudioOutConfigure( CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0 ) == CELL_OK )
{
nPhysicalChannels = ch_bit;
break;
}
// if we got here the Dolby encoder didn't init, so fall through to downmixing to stereo
}
a_config.channel = 2;
a_config.encoder = CELL_AUDIO_OUT_CODING_TYPE_LPCM;
a_config.downMixer = CELL_AUDIO_OUT_DOWNMIXER_TYPE_A; // Downmix to 2 channels
if ( cellAudioOutConfigure( CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0 ) != CELL_OK )
{
return 0; // error - downmixer didn't work
}
ret = 7; // downmixer does 7.0 to 2.0 downmixing...
break;
default:
// some other weird case that we don't understand
return 0;
}
}
/*
// wait for the device to enable (not necessary anymore?)
{
CellAudioOutState a_state;
do
{
if ( cellAudioOutGetState( CELL_AUDIO_OUT_PRIMARY, 0, &a_state ) != CELL_OK )
{
return( ret ); // If that failed, we are still returning the expected value. This call is not critical.
}
sys_timer_usleep( 5000 );
}
while ( a_state.state != CELL_AUDIO_OUT_OUTPUT_STATE_ENABLED );
}
*/
// turn off copy protection stupidness
cellAudioOutSetCopyControl( CELL_AUDIO_OUT_PRIMARY,
CELL_AUDIO_OUT_COPY_CONTROL_COPY_FREE );
return( ret );
}
bool CAudioPS3LibAudio::Init( void )
{
if ( this == CAudioPS3LibAudio::m_pSingleton ) // guard against possible multiples of this thing
{
int success = cellAudioInit();
if ( success != CELL_OK )
{
Warning( "Could not initalize PS3 audio, error code: %x\n", success );
return false;
}
}
// get relevant info from the OS
QuerySystemAudioConfiguration( false );
// work out our surround sound mode
// assumption: we're only looking at CELL_AUDIO_OUT_PRIMARY
// also: we could query headphones properly from some kind of UI setting
m_bHeadphone = snd_surround.GetInt() == SURROUND_HEADPHONES; // in case we set this from the UI
// number of physical out channels connected
// (remember libaudio only logically supports 2 or 8 in the ring buffer)
// also this is an enum rather than a direct count
const int eOutputChannels = s_vCellAudioOutputDeviceStates[0].soundMode.channel;
switch ( eOutputChannels )
{
case CELL_AUDIO_OUT_CHNUM_2:
default:
m_nPhysicalOutChannels = 2;
break;
case CELL_AUDIO_OUT_CHNUM_4:
m_nPhysicalOutChannels = 4; // quad
break;
case CELL_AUDIO_OUT_CHNUM_6:
m_nPhysicalOutChannels = 6; // 5.1
break;
case CELL_AUDIO_OUT_CHNUM_8:
m_nPhysicalOutChannels = 8; // 7.1
break;
}
int nPhysicalChannels = m_nPhysicalOutChannels;
int nResult = init_audio_hardware( 6, nPhysicalChannels ); // Ask for 5.1, so it can down mix to stereo if necessary
// Then engine will support the right format regardless of what the down mixer can support
if ( nResult != 0 )
{
m_nPhysicalOutChannels = nPhysicalChannels; // We could initialize the down mixer, lets use the physical out channels.
}
CellAudioPortParam aparam;
float flBackChannelMultipler = 0.0f;
if ( m_nPhysicalOutChannels > 2 )
{
snd_surround.SetValue( SURROUND_DIGITAL7DOT1 ); // all internal mixing is 8 channels; downsampling occurs in hardware
m_bSurround = m_bSurroundCenter = true;
m_deviceChannels = 8; // you can only have stereo or 7.1
aparam.nChannel = CELL_AUDIO_PORT_8CH;
// If we are in 7.1, we want to fill the back channel with the same value as the side channels
// However, if we are in 5.1, we don't want to fill it in case the downmixer uses it and creates unwanted side-effects.
if ( m_nPhysicalOutChannels > 6 )
{
flBackChannelMultipler = 1.0f;
}
}
else
{
// Because we have the down mixer for Bink, we are going to output 7.1 even for stereo only
// That way Bink can output 5.1 normally, and the down mixer will change it to stereo.
// Same for the engine, however the engine is going to only output stereo values.
snd_surround.SetValue( m_bHeadphone ? SURROUND_HEADPHONES : SURROUND_STEREO );
m_bSurround = m_bSurroundCenter = false;
m_deviceChannels = 8;
aparam.nChannel = CELL_AUDIO_PORT_8CH;
}
snd_ps3_back_channel_multiplier.SetValue( flBackChannelMultipler );
// open the audio port
COMPILE_TIME_ASSERT( CELLAUDIO_PORT_BUFFER_BLOCKS == 8 || CELLAUDIO_PORT_BUFFER_BLOCKS == 16 ); // You're only allowed to have either 8 or 16 blocks in the ring buffer!
aparam.nBlock = CELLAUDIO_PORT_BUFFER_BLOCKS == 16 ? CELL_AUDIO_BLOCK_16 : CELL_AUDIO_BLOCK_8; // you can have 8 or 16 blocks in the ring buffer. 16 gives us about 80ms of buffer.
aparam.attr = 0; // no special attributes
aparam.level = 1; // this is actually ignored without setting CELL_AUDIO_PORTATTR_INITLEVEL to the attributes above.
int success = cellAudioPortOpen( &aparam, &m_nPortNum );
if ( success != CELL_OK )
{
Warning("Could not initialize libaudio, error code %x\n", success );
}
else
{
DevMsg( "PS3 libaudio device initialized:\n" );
DevMsg( " %d channel(s)\n"
" %d bits/sample\n"
" %d samples/sec\n", DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed() );
}
// get the pointers to the start and next-block-indicator of the ring buffer
CellAudioPortConfig configinfo;
cellAudioGetPortConfig( m_nPortNum, &configinfo );
m_pPortBufferAddr = configinfo.portAddr;
uint nPortBufferSize = GetPortBufferSize();
m_pDebugBuffer = ENABLE_SOUND_DEBUG ? (float*)MemAlloc_AllocAligned( nPortBufferSize, 16 ) : NULL ;
m_pReadIndexAddr = (uint64_t*)configinfo.readIndexAddr;
m_upsampler.Init( m_bSurround );
s_LibAudioThread.Setup( this );
g_pVJobs->Register( this );
cellAudioPortStart( m_nPortNum );
return success == CELL_OK;
}
int CAudioPS3LibAudio::DeviceChannels( void )
{
return m_deviceChannels;
}
int CAudioPS3LibAudio::DeviceSampleBits( void )
{
return sizeof(float)*8; // the only sound format is single precision floats. PERIOD.
}
int CAudioPS3LibAudio::DeviceSampleBytes( void )
{
return sizeof(float);
}
int CAudioPS3LibAudio::DeviceDmaSpeed( void )
{
return SOUND_DMA_SPEED; // the upsampler fakes 44.1
}
int CAudioPS3LibAudio::DeviceSampleCount( void )
{
return CELL_AUDIO_BLOCK_SAMPLES * CELLAUDIO_PORT_BUFFER_BLOCKS; // each block is 256 samples long. OR ELSE.
}
inline float *CAudioPS3LibAudio::GetAddressForBlockIdx( unsigned nBlock )
{
nBlock %= CELLAUDIO_PORT_BUFFER_BLOCKS;
return reinterpret_cast<float *>( m_pPortBufferAddr + ( nBlock * sizeof(float) * CELL_AUDIO_BLOCK_SAMPLES * m_deviceChannels ) );
}
uint CAudioPS3LibAudio::GetPortBufferSize()const
{
Assert( m_deviceChannels == 2 || m_deviceChannels == 8 );
return CELLAUDIO_PORT_BUFFER_BLOCKS * CELL_AUDIO_BLOCK_SAMPLES * sizeof( float ) * m_deviceChannels;
}
inline float *CAudioPS3LibAudio::GetNextReadBlockAddress()
{
return GetAddressForBlockIdx((uint)(*m_pReadIndexAddr));
}
void CAudioPS3LibAudio::Shutdown( void )
{
// need to release ref to voice library
m_VoiceData.VoiceShutdown();
Assert( s_LibAudioThread.m_pSndUpsamplerJobDescriptor );
// unregistering with Vjobs will cascade shutdown of all SPU-related and SPU-using resources (like the audio thread - it's using SPU resources)
// this'll happen synchronously
g_pVJobs->Unregister( this );
Assert( !s_LibAudioThread.m_pSndUpsamplerJobDescriptor ); // must be shut down already
int suc = cellAudioPortClose(m_nPortNum);
AssertMsg2( suc == CELL_OK, "Couldn't close libaudio port(%d): %x\n", m_nPortNum, suc );
m_nPortNum = -1;
m_deviceChannels = 0;
m_nPhysicalOutChannels = 0;
m_pPortBufferAddr = NULL;
m_pReadIndexAddr = NULL;
m_nTotalBlocksPlayed = 0;
if ( this == CAudioPS3LibAudio::m_pSingleton )
{
CAudioPS3LibAudio::m_pSingleton = NULL;
cellAudioQuit();
s_bCellAudioStateInitialized = false;
}
}
void CAudioPS3LibAudio::Pause( void )
{
cellAudioPortStop( m_nPortNum );
}
void CAudioPS3LibAudio::UnPause( void )
{
cellAudioPortStart( m_nPortNum );
}
//-----------------------------------------------------------------------------
// Fill the output buffers with silence
//-----------------------------------------------------------------------------
void CAudioPS3LibAudio::ClearBuffer( void )
{
memset( reinterpret_cast<void *>(m_pPortBufferAddr), 0, CELLAUDIO_PORT_BUFFER_BLOCKS * CELL_AUDIO_BLOCK_SAMPLES * m_deviceChannels * sizeof(float) );
m_upsampler.Flush();
}
//-----------------------------------------------------------------------------
// Calc the paint position
//-----------------------------------------------------------------------------
int64 CAudioPS3LibAudio::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 mixaheadsamples = mixAheadTime * DeviceDmaSpeed();
// the upsampler will consume as much as it can
int64 endtime = paintedtime + MIN(mixaheadsamples, m_upsampler.SpaceRemaining());
// only get samples in numbers divisible by four
endtime = endtime & ~0x3;
return endtime;
}
void CAudioPS3LibAudio::TransferSamples( int64 endTime )
{
AUTO_LOCK( m_upsampler.m_mutex );
// first, pull as many samples into the upsampler as we can
int sampleCountToWrite = endTime - g_paintedtime; // copied from xaudio version. a global? wtf?
Assert( sampleCountToWrite <= m_upsampler.SpaceRemaining() );
if ( IsSurround() != m_upsampler.IsSurround() )
{
AssertMsg2( false, "Tried to write %d channels onto a %d-channel audio port!\n", m_deviceChannels, m_upsampler.IsSurround() ? 8 : 2 );
// abort! abort!
m_upsampler.Flush();
return;
}
if ( IsSurround() )
m_upsampler.IngestSurround( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, sampleCountToWrite );
else
m_upsampler.IngestStereo( PAINTBUFFER, sampleCountToWrite );
}
void CAudioPS3LibAudio::PaintEnd( void )
{}
#if 0
#pragma message("TODO: obvious optimization opportunities")
// TODO: move to SPU -- many obvious optimization opportunities
int CAudioPS3LibAudio::TransferStereo( const portable_samplepair_t * RESTRICT pFrontBuffer, int nSamplesToWrite, int nStartBlock )
{
// Assert that the compiler hasn't failed to pack the structs properly
COMPILE_TIME_ASSERT( sizeof(libaudio_sample_stereo_t) == 8 );
COMPILE_TIME_ASSERT( sizeof(libaudio_sample_surround_t) == 32 );
AssertMsg1( m_deviceChannels == 2, "Transferred stereo onto a %d-channel audio port\n", m_deviceChannels );
float volumeFactor = S_GetMasterVolume() / 32767.0f;
uint blocksWrit = 0;
while ( nSamplesToWrite>0 )
{
// all sorts of obvious ways to optimize this (explicit iterators, VMX, SPU, etc)
libaudio_sample_stereo_t * RESTRICT pWriteDest = reinterpret_cast<libaudio_sample_stereo_t *>(GetAddressForBlockIdx( nStartBlock ));
const uint batchsize = MIN( nSamplesToWrite, CELL_AUDIO_BLOCK_SAMPLES );
for ( uint s = 0 ; s < batchsize ; ++s )
{
pWriteDest[s].left = pFrontBuffer[s].left * volumeFactor;
pWriteDest[s].right = pFrontBuffer[s].right * volumeFactor;
}
pFrontBuffer += batchsize;
nSamplesToWrite -= batchsize;
nStartBlock = (nStartBlock + 1)%CELLAUDIO_PORT_BUFFER_BLOCKS;
++blocksWrit;
}
return blocksWrit;
}
// TODO: move to SPU -- many obvious optimization opportunities
// TODO: make a LFE channel
int CAudioPS3LibAudio::TransferSurroundInterleaved( const portable_samplepair_t * RESTRICT pFrontBuffer,
const portable_samplepair_t * RESTRICT pRearBuffer,
const int * RESTRICT pCenterBuffer,
int nSamplesToWrite, int nStartBlock )
{
if ( m_deviceChannels != 8 ) //!!EMERGENCY! EMERGENCY! WILL CAUSE CRASH!
{
AssertMsg1( false, "Tried to write 7.1 surround onto a %d-channel audio port!\n", m_deviceChannels );
return TransferStereo( pFrontBuffer, nSamplesToWrite, nStartBlock ); // CRASH AVERTED!
}
int volumeFactor = S_GetMasterVolume() * 256; // a legacy WTF
uint blocksWrit = 0;
while ( nSamplesToWrite>0 )
{
// all sorts of obvious ways to optimize this (explicit iterators, VMX, SPU, etc)
libaudio_sample_surround_t * RESTRICT pWriteDest = reinterpret_cast<libaudio_sample_surround_t*>(GetAddressForBlockIdx( nStartBlock ));
const uint batchsize = MIN( nSamplesToWrite, CELL_AUDIO_BLOCK_SAMPLES );
for ( uint s = 0 ; s < batchsize ; ++s )
{
// perform the writes in order to be gentle on the write aggregator
// (really I should go back and use dcbz+vmx here)
pWriteDest[s].left = pFrontBuffer[s].left * volumeFactor;
pWriteDest[s].right = pFrontBuffer[s].right * volumeFactor;
pWriteDest[s].center = pCenterBuffer[s] * volumeFactor;
pWriteDest[s].subwoofer = 0;
pWriteDest[s].leftsurround = pRearBuffer[s].left * volumeFactor;
pWriteDest[s].rightsurround = pRearBuffer[s].right * volumeFactor;
pWriteDest[s].leftextend = 0;
pWriteDest[s].rightextend = 0;
}
pFrontBuffer += batchsize;
pRearBuffer += batchsize;
pCenterBuffer += batchsize;
nSamplesToWrite -= batchsize;
nStartBlock = (nStartBlock + 1)%CELLAUDIO_PORT_BUFFER_BLOCKS;
++blocksWrit;
}
return blocksWrit;
}
#endif
//-----------------------------------------------------------------------------
// Get our device name
//-----------------------------------------------------------------------------
const char *CAudioPS3LibAudio::DeviceName( void )
{
if ( m_bSurround )
return "PS3 libaudio: 7.1 Channel Surround";
else
return "PS3 libaudio: Stereo";
}
int CAudioPS3LibAudio::GetOutputPosition( void )
{
return m_nTotalBlocksPlayed * CELL_AUDIO_BLOCK_SAMPLES; // makes this effectively a 24-bit number, but the higher level code expects a count of samples, not sample blocks.
}
#ifdef PS3_SUPPORT_XVOICE
ConVar voice_xplay_enable( "voice_xplay_enable", "1" );
ConVar voice_xplay_debug( "voice_xplay_debug", "0" );
ConVar voice_xplay_bandwidth_debug( "voice_xplay_bandwidth_debug", "0" );
ConVar voice_xplay_echo( "voice_xplay_echo", "0" );
CEngineVoicePs3::CEngineVoicePs3() :
m_memContainer( SYS_MEMORY_CONTAINER_ID_INVALID )
{
}
//-----------------------------------------------------------------------------
// Initialize Voice
//-----------------------------------------------------------------------------
void CEngineVoicePs3::VoiceInit( void )
{
memset( m_bUserRegistered, kVoiceNotInitialized, sizeof( m_bUserRegistered ) );
// Reset voice data for all ctrlrs
for ( int k = 0; k < m_numVoiceUsers; ++ k )
{
VoiceResetLocalData( k );
}
}
void CEngineVoicePs3::VoiceShutdown( void )
{
RemoveAllTalkers(); // this should shutdown the voice engine on PS3
}
void CEngineVoicePs3::AddPlayerToVoiceList( XUID xPlayer, int iController, uint64 uiFlags )
{
if ( !voice_xplay_enable.GetBool() )
return;
// This should only happen if we are not initialized and matchmaking
// is in an online session
if ( XBX_GetNumGameUsers() != 1 )
return;
if ( !g_pMatchFramework )
return;
if ( !g_pMatchFramework->GetMatchSession() )
return;
if ( Q_stricmp( g_pMatchFramework->GetMatchSession()->GetSessionSettings()->GetString( "system/network" ), "LIVE" ) )
return;
// Any connecting player initializes the voice system
if ( m_bUserRegistered[ GetVoiceUserIndex( iController ) ] == kVoiceNotInitialized )
{
//
// Initialize syslib
//
int res = cellSysmoduleLoadModule( CELL_SYSMODULE_VOICE );
if ( res != CELL_OK )
{
Warning( "VOICE PS3: Failed to load libvoice! Error code %d\n", res );
return;
}
CellVoiceInitParam cvip;
Q_memset( &cvip, 0, sizeof( cvip ) );
cvip.appType = CELLVOICE_APPTYPE_GAME_1MB;
cvip.version = CELLVOICE_VERSION_100;
res = cellVoiceInitEx( &cvip );
if ( res != CELL_OK )
{
Warning( "VOICE PS3: Failed to cellVoiceInit! Error code %d\n", res );
cellSysmoduleUnloadModule( CELL_SYSMODULE_VOICE );
return;
}
// Otherwise we have successfully initialized syslib
m_bUserRegistered[ GetVoiceUserIndex( iController ) ] = kVoiceInit;
DevMsg( "PS3 Voice: microphone library loaded and started\n" );
// Create ports
res = CreateVoicePortsLocal( uiFlags );
if ( res < 0 )
{
Warning( "VOICE PS3: Failed to create voice ports! Error code %d\n", res );
cellVoiceEnd();
cellSysmoduleUnloadModule( CELL_SYSMODULE_VOICE );
m_bUserRegistered[ GetVoiceUserIndex( iController ) ] = kVoiceNotInitialized;
}
}
if ( xPlayer )
{
if ( m_bUserRegistered[ GetVoiceUserIndex( iController ) ] < kVoiceOpen )
{
DevWarning( "VOICE PS3: Cannot add remote talkers since voice system is not initialized (state %d)\n", m_bUserRegistered[ GetVoiceUserIndex( iController ) ] );
Assert( 0 );
return;
}
for ( int k = 0; k < m_arrRemoteTalkers.Count(); ++ k )
{
if ( m_arrRemoteTalkers[k].m_xuid == xPlayer )
return;
}
RemoteTalker_t rt = { xPlayer, 0, uiFlags, 0 };
int res = CreateVoicePortsRemote( rt );
if ( res < 0 )
{
DevWarning( "VOICE PS3: Cannot add remote talker %llx! Failed to create ports, error %d\n", xPlayer, res );
return;
}
m_arrRemoteTalkers.AddToTail( rt );
DevMsg( "VOICE PS3: added remote talker %llx\n", xPlayer );
}
}
int CEngineVoicePs3::CreateVoicePortsLocal( uint64 uiFlags )
{
int res = CELL_OK;
CellVoicePortParam pp;
Q_memset( &pp, 0, sizeof( pp ) );
pp.threshold = 100;
pp.volume = 1.0f;
// MIC > [IMic]
pp.portType = CELLVOICE_PORTTYPE_IN_MIC;
pp.device.playerId = 0;
res = cellVoiceCreatePort( &m_portIMic, &pp );
if ( res < 0 ) return res;
// MIC > [IMic] > [OVoice]
pp.portType = CELLVOICE_PORTTYPE_OUT_VOICE;
pp.voice.bitrate = CELLVOICE_BITRATE_7300;
res = cellVoiceCreatePort( &m_portOVoice, &pp );
if ( res < 0 ) return res;
res = cellVoiceConnectIPortToOPort( m_portIMic, m_portOVoice );
if ( res < 0 ) return res;
#ifndef SOUND_PC_CELP_SUPPORTED
// PCM output
pp.portType = CELLVOICE_PORTTYPE_OUT_PCMAUDIO;
pp.pcmaudio.bufSize = 8*1024;
pp.pcmaudio.format.numChannels = 1;
pp.pcmaudio.format.sampleAlignment = 1;
pp.pcmaudio.format.dataType = CELLVOICE_PCM_SHORT_LITTLE_ENDIAN;
pp.pcmaudio.format.sampleRate = CELLVOICE_SAMPLINGRATE_16000;
res = cellVoiceCreatePort( &m_portOPcm, &pp );
if ( res < 0 ) return res;
res = cellVoiceConnectIPortToOPort( m_portIMic, m_portOPcm );
if ( res < 0 ) return res;
#endif
// Remote > [OEarphone]
pp.portType = CELLVOICE_PORTTYPE_OUT_SECONDARY;
pp.device.playerId = 0;
res = cellVoiceCreatePort( &m_portOEarphone, &pp );
if ( res < 0 ) return res;
// IVoiceEcho
pp.portType = CELLVOICE_PORTTYPE_IN_VOICE;
pp.voice.bitrate = CELLVOICE_BITRATE_7300;
res = cellVoiceCreatePort( &m_portIVoiceEcho, &pp );
if ( res < 0 ) return res;
res = cellVoiceConnectIPortToOPort( m_portIVoiceEcho, m_portOEarphone );
if ( res < 0 ) return res;
// Start
res = sys_memory_container_create( &m_memContainer, 1024*1024 );
if ( res < 0 ) return res;
CellVoiceStartParam cvsp;
Q_memset( &cvsp, 0, sizeof( cvsp ) );
cvsp.container = m_memContainer;
res = cellVoiceStartEx( &cvsp );
if ( ( res < 0 ) && ( m_memContainer != SYS_MEMORY_CONTAINER_ID_INVALID ) )
{
sys_memory_container_destroy( m_memContainer );
m_memContainer = SYS_MEMORY_CONTAINER_ID_INVALID;
}
if ( res < 0 ) return res;
m_portOSendForRemote = m_portOVoice;
m_bUserRegistered[ GetVoiceUserIndex( XBX_GetPrimaryUserId() ) ] = kVoiceOpen;
return res;
}
int CEngineVoicePs3::CreateVoicePortsRemote( RemoteTalker_t &rt )
{
VoiceResetLocalData( XBX_GetPrimaryUserId() );
CellVoicePortParam pp;
Q_memset( &pp, 0, sizeof( pp ) );
pp.threshold = 100;
pp.volume = 1.0f;
#ifndef SOUND_PC_CELP_SUPPORTED
if ( !( rt.m_uiFlags & ENGINE_VOICE_FLAG_PS3 ) )
{
pp.portType = CELLVOICE_PORTTYPE_IN_PCMAUDIO;
pp.threshold = 0; // can't have PCM threshold (needs huge buffer)
pp.pcmaudio.bufSize = 8 * 1024;
pp.pcmaudio.format.numChannels = 1;
pp.pcmaudio.format.sampleAlignment = 1;
pp.pcmaudio.format.dataType = CELLVOICE_PCM_SHORT_LITTLE_ENDIAN;
pp.pcmaudio.format.sampleRate = CELLVOICE_SAMPLINGRATE_16000;
m_portOSendForRemote = m_portOPcm;
}
else
#endif
{
pp.portType = CELLVOICE_PORTTYPE_IN_VOICE;
pp.voice.bitrate = CELLVOICE_BITRATE_7300;
m_portOSendForRemote = m_portOVoice;
}
int res = cellVoiceCreatePort( &rt.m_portIRemoteVoice, &pp );
if ( res < 0 ) return res;
res = cellVoiceConnectIPortToOPort( rt.m_portIRemoteVoice, m_portOEarphone );
if ( res < 0 )
{
cellVoiceDeletePort( rt.m_portIRemoteVoice );
}
return res;
}
void CEngineVoicePs3::RemovePlayerFromVoiceList( XUID xPlayer, int iController )
{
if ( !m_bUserRegistered[ GetVoiceUserIndex( iController ) ] )
return;
if ( xPlayer )
{
// Find the remote player in our list
for ( int k = 0; k < m_arrRemoteTalkers.Count(); ++ k )
{
if ( m_arrRemoteTalkers[k].m_xuid == xPlayer )
{
cellVoiceDisconnectIPortFromOPort( m_arrRemoteTalkers[k].m_portIRemoteVoice, m_portOEarphone );
cellVoiceDeletePort( m_arrRemoteTalkers[k].m_portIRemoteVoice );
m_arrRemoteTalkers.Remove( k );
DevMsg( "VOICE PS3: removed remote talker %llx\n", xPlayer );
return;
}
}
DevWarning( "CEngineVoicePs3::RemovePlayerFromVoiceList for unregistered remote talker %llx!\n", xPlayer );
return;
}
// Local player removed shuts down the whole voice system
cellVoiceEnd();
cellSysmoduleUnloadModule( CELL_SYSMODULE_VOICE );
if ( m_memContainer != SYS_MEMORY_CONTAINER_ID_INVALID )
{
sys_memory_container_destroy( m_memContainer );
m_memContainer = SYS_MEMORY_CONTAINER_ID_INVALID;
}
m_bUserRegistered[ GetVoiceUserIndex( iController ) ] = kVoiceNotInitialized;
DevMsg( "PS3 Voice: microphone library stopped and unloaded\n" );
m_arrRemoteTalkers.Purge();
}
void CEngineVoicePs3::PlayIncomingVoiceData( XUID xuid, const byte *pbData, unsigned int dwDataSize, const bool *bAudiblePlayers )
{
if ( !voice_xplay_enable.GetBool() )
return;
if ( m_bUserRegistered[ GetVoiceUserIndex( XBX_GetPrimaryUserId() ) ] < kVoiceOpen )
return;
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_xplay_echo.GetBool() && ( m_portOSendForRemote == m_portOVoice ) )
{
cellVoiceWriteToIPort( m_portIVoiceEcho, pbData, &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];
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 );
}
}
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;
}
//
// Save incoming stream
//
for ( int k = 0; k < m_arrRemoteTalkers.Count(); ++ k )
{
if ( m_arrRemoteTalkers[k].m_xuid == xuid )
{
cellVoiceWriteToIPort( m_arrRemoteTalkers[k].m_portIRemoteVoice, pbData, &dwDataSize );
m_arrRemoteTalkers[k].m_flLastTalkTimestamp = Plat_FloatTime();
return;
}
}
}
void CEngineVoicePs3::PlayPortInterruptHandler()
{
}
void CEngineVoicePs3::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;
}
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 CEngineVoicePs3::VoiceUpdateData( int iCtrlr )
{
int32 dwBytes = 0;
int32 wVoiceBytes = 0;
bool bShouldSend = false;
//Update UI stuff.
UpdateHUDVoiceStatus();
if ( m_bUserRegistered[ GetVoiceUserIndex( iCtrlr ) ] < kVoiceOpen )
return false;
if ( 1 )
{
int i = GetVoiceUserIndex( iCtrlr );
dwBytes = m_ChatBufferSize - m_wLocalDataSize[i];
if( dwBytes < ( m_ChatBufferSize/10 ) )
{
bShouldSend = true;
}
else
{
int res = cellVoiceReadFromOPort( m_portOSendForRemote, m_ChatBuffer[i] + m_wLocalDataSize[i], (uint32*) &dwBytes );
if ( ( res >= 0 ) && ( dwBytes > 0 ) )
{
m_wLocalDataSize[i] += ( dwBytes ) & 0xFFFF;
wVoiceBytes += ( dwBytes ) & 0xFFFF;
if( m_wLocalDataSize[i] >= 64 ) // voice buffer enough size that it should get sent
{
bShouldSend = true;
}
}
}
}
return bShouldSend ||
( wVoiceBytes &&
( Plat_FloatTime() - m_dwLastVoiceSend[ GetVoiceUserIndex( iCtrlr ) ] ) > MAX_VOICE_BUFFER_TIME );
}
void CEngineVoicePs3::SetPlaybackPriority( XUID remoteTalker, int iController, int iAllowPlayback )
{
// No muting support in voice engine
}
void CEngineVoicePs3::GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers )
{
if ( pNumTalkers )
*pNumTalkers = m_arrRemoteTalkers.Count();
if ( pRemoteTalkers )
{
for ( int k = 0; k < m_arrRemoteTalkers.Count(); ++ k )
pRemoteTalkers[k] = m_arrRemoteTalkers[k].m_xuid;
}
}
void CEngineVoicePs3::GetVoiceData( int iController, const byte **ppvVoiceDataBuffer, unsigned int *pnumVoiceDataBytes )
{
iController = GetVoiceUserIndex( iController );
*pnumVoiceDataBytes = m_wLocalDataSize[ iController ];
*ppvVoiceDataBuffer = m_ChatBuffer[ iController ];
}
void CEngineVoicePs3::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;
int iController = GetVoiceUserIndex( iCtrlr );
if ( !m_wLocalDataSize[ iController ] )
return;
pMessage->set_data( m_ChatBuffer[ iController ], m_wLocalDataSize[ iController ] );
}
void CEngineVoicePs3::VoiceSendData( int iCtrlr, INetChannel *pChannel )
{
if ( !pChannel )
return;
CCLCMsg_VoiceData_t voiceMsg;
GetVoiceData( iCtrlr, &voiceMsg );
pChannel->SendNetMsg( voiceMsg, false, true );
VoiceResetLocalData( iCtrlr );
}
void CEngineVoicePs3::VoiceResetLocalData( int iCtrlr )
{
iCtrlr = GetVoiceUserIndex( iCtrlr );
if ( voice_xplay_bandwidth_debug.GetBool() && m_wLocalDataSize[iCtrlr] )
{
DevMsg( "PS3 Voice: microphone stream %0.2fKb\n", m_wLocalDataSize[iCtrlr]/1024.0f );
}
m_dwLastVoiceSend[ iCtrlr ] = Plat_FloatTime();
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 CEngineVoicePs3::IsLocalPlayerTalking( int controllerID )
{
controllerID = GetVoiceUserIndex( controllerID );
return ( m_wLocalDataSize[controllerID] > 0 ) || ( Plat_FloatTime() - m_dwLastVoiceSend[controllerID] <= MAX_VOICE_BUFFER_TIME );
}
bool CEngineVoicePs3::IsPlayerTalking( XUID uid )
{
if ( !g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( uid ) )
return false;
for ( int k = 0; k < m_arrRemoteTalkers.Count(); ++ k )
{
if ( m_arrRemoteTalkers[k].m_xuid == uid )
{
return ( ( Plat_FloatTime() - m_arrRemoteTalkers[k].m_flLastTalkTimestamp ) < 0.2 );
}
}
return false;
}
bool CEngineVoicePs3::IsHeadsetPresent( int id )
{
return ( m_bUserRegistered[ GetVoiceUserIndex( id ) ] >= kVoiceInit );
}
#pragma warning(pop)
void CEngineVoicePs3::RemoveAllTalkers()
{
RemovePlayerFromVoiceList( NULL, XBX_GetPrimaryUserId() ); // there's only one user possibly registered, unregister it
}
CEngineVoicePs3 *Audio_GetXVoice( void )
{
if ( CAudioPS3LibAudio::m_pSingleton )
{
return CAudioPS3LibAudio::m_pSingleton->GetVoiceData();
}
return NULL;
}
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineVoicePs3, IEngineVoice,
IENGINEVOICE_INTERFACE_VERSION, *Audio_GetXVoice() );
#endif