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
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
|