📄 voice.cpp
字号:
/*==============================================================================
Copyright (c) Valve, LLC. 1999
All Rights Reserved. Proprietary.
--------------------------------------------------------------------------------
Author : DSpeyrer
Purpose:
--------------------------------------------------------------------------------
* $Log: $
*
* $NoKeywords:
==============================================================================*/
#include <windows.h>
#include <math.h>
#include <mmsystem.h>
#include <stdio.h>
#include "tier0/dbg.h"
#include "convar.h"
#include "CircularBuffer.h"
#include "voice.h"
#include "voice_wavefile.h"
#include "voice_sound_engine_interface.h"
#include "sound.h"
#include "r_efx.h"
#include "cdll_int.h"
#include "voice_gain.h"
#include "voice_mixer_controls.h"
#include "SoundService.h"
#include "ivoicerecord.h"
#include "ivoicecodec.h"
#include "filesystem.h"
#include "../../FileSystem_Engine.h"
void Voice_EndChannel( int iChannel );
// Special entity index used for tweak mode.
#define TWEAKMODE_ENTITYINDEX -500
// Special channel index passed to Voice_AddIncomingData when in tweak mode.
#define TWEAKMODE_CHANNELINDEX -100
// How long does the sign stay above someone's head when they talk?
#define SPARK_TIME 0.2
// How long a voice channel has to be inactive before we free it.
#define DIE_COUNTDOWN 0.5
#define VOICE_RECEIVE_BUFFER_SIZE (VOICE_OUTPUT_SAMPLE_RATE * BYTES_PER_SAMPLE)
#define TIME_PADDING 0.2f // Time between receiving the first voice packet and actually starting
// to play the sound. This accounts for frametime differences on the clients
// and the server.
#define LOCALPLAYERTALKING_TIMEOUT 0.2f // How long it takes for the client to decide the server isn't sending acks
// of voice data back.
// The format we sample voice in.
WAVEFORMATEX g_VoiceSampleFormat =
{
WAVE_FORMAT_PCM, // wFormatTag
1, // nChannels
8000, // nSamplesPerSec
16000, // nAvgBytesPerSec
2, // nBlockAlign
16, // wBitsPerSample
sizeof(WAVEFORMATEX) // cbSize
};
ConVar voice_dsound( "voice_dsound", "0" );
ConVar voice_avggain( "voice_avggain", "0.5" );
ConVar voice_maxgain( "voice_maxgain", "5" );
ConVar voice_scale( "voice_scale", "1", FCVAR_ARCHIVE );
ConVar voice_loopback( "voice_loopback", "0" );
ConVar voice_fadeouttime( "voice_fadeouttime", "0.1" ); // It fades to no sound at the tail end of your voice data when you release the key.
// Debugging cvars.
ConVar voice_profile( "voice_profile", "0" );
ConVar voice_showchannels( "voice_showchannels", "0" ); // 1 = list channels
// 2 = show timing info, etc
ConVar voice_showincoming( "voice_showincoming", "0" ); // show incoming voice data
ConVar voice_enable( "voice_enable", "1", FCVAR_ARCHIVE ); // Globally enable or disable voice.
// Have it force your mixer control settings so waveIn comes from the microphone.
// CD rippers change your waveIn to come from the CD drive
ConVar voice_forcemicrecord( "voice_forcemicrecord", "1", FCVAR_ARCHIVE );
int g_nVoiceFadeSamples = 1; // Calculated each frame from the cvar.
float g_VoiceFadeMul = 1; // 1 / (g_nVoiceFadeSamples - 1).
// While in tweak mode, you can't hear anything anyone else is saying, and your own voice data
// goes directly to the speakers.
bool g_bInTweakMode = false;
bool g_bVoiceAtLeastPartiallyInitted = false;
IMixerControls *g_pMixerControls = NULL;
// Timing info for each frame.
static double g_CompressTime = 0;
static double g_DecompressTime = 0;
static double g_GainTime = 0;
static double g_UpsampleTime = 0;
class CVoiceTimer
{
public:
inline void Start()
{
if( voice_profile.GetInt() )
m_StartTime = g_pSoundServices->GetRealTime();
}
inline void End(double *out)
{
if( voice_profile.GetInt() )
*out += g_pSoundServices->GetRealTime() - m_StartTime;
}
double m_StartTime;
};
static bool g_bLocalPlayerTalkingAck = false;
static float g_LocalPlayerTalkingTimeout = 0;
CSysModule *g_hVoiceCodecDLL = 0;
// Voice recorder. Can be waveIn, DSound, or whatever.
static IVoiceRecord *g_pVoiceRecord = NULL;
static IVoiceCodec *g_pEncodeCodec = NULL;
static bool g_bVoiceRecording = false; // Are we recording at the moment?
// Hacked functions to create the inputs and codecs..
extern IVoiceRecord* CreateVoiceRecord_DSound(int nSamplesPerSec);
//
// Used for storing incoming voice data from an entity.
//
class CVoiceChannel
{
public:
CVoiceChannel();
// Called when someone speaks and a new voice channel is setup to hold the data.
void Init(int nEntity);
public:
int m_iEntity; // Number of the entity speaking on this channel (index into cl_entities).
// This is -1 when the channel is unused.
CSizedCircularBuffer
<VOICE_RECEIVE_BUFFER_SIZE> m_Buffer; // Circular buffer containing the voice data.
// Used for upsampling..
double m_LastFraction; // Fraction between m_LastSample and the next sample.
short m_LastSample;
bool m_bStarved; // Set to true when the channel runs out of data for the mixer.
// The channel is killed at that point.
float m_TimePad; // Set to TIME_PADDING when the first voice packet comes in from a sender.
// We add time padding (for frametime differences)
// by waiting a certain amount of time before starting to output the sound.
IVoiceCodec *m_pVoiceCodec; // Each channel gets is own IVoiceCodec instance so the codec can maintain state.
CAutoGain m_AutoGain; // Gain we're applying to this channel
CVoiceChannel *m_pNext;
};
CVoiceChannel::CVoiceChannel()
{
m_iEntity = -1;
m_pVoiceCodec = NULL;
}
void CVoiceChannel::Init(int nEntity)
{
m_iEntity = nEntity;
m_bStarved = false;
m_Buffer.Flush();
m_TimePad = TIME_PADDING;
m_LastSample = 0;
m_LastFraction = 0.999;
m_AutoGain.Reset( 128, voice_maxgain.GetFloat(), voice_avggain.GetFloat(), voice_scale.GetFloat() );
}
// Incoming voice channels.
CVoiceChannel g_VoiceChannels[VOICE_NUM_CHANNELS];
// These are used for recording the wave data into files for debugging.
#define MAX_WAVEFILEDATA_LEN 1024*1024
char *g_pUncompressedFileData = NULL;
int g_nUncompressedDataBytes = 0;
const char *g_pUncompressedDataFilename = NULL;
char *g_pDecompressedFileData = NULL;
int g_nDecompressedDataBytes = 0;
const char *g_pDecompressedDataFilename = NULL;
char *g_pMicInputFileData = NULL;
int g_nMicInputFileBytes = 0;
int g_CurMicInputFileByte = 0;
double g_MicStartTime;
inline void ApplyFadeToSamples(short *pSamples, int nSamples, int fadeOffset, float fadeMul)
{
for(int i=0; i < nSamples; i++)
{
float percent = (i+fadeOffset) * fadeMul;
pSamples[i] = (short)(pSamples[i] * (1 - percent));
}
}
int Voice_GetOutputData(
const int iChannel, //! The voice channel it wants samples from.
char *copyBufBytes, //! The buffer to copy the samples into.
const int copyBufSize, //! Maximum size of copyBuf.
const int samplePosition, //! Which sample to start at.
const int sampleCount //! How many samples to get.
)
{
CVoiceChannel *pChannel = &g_VoiceChannels[iChannel];
short *pCopyBuf = (short*)copyBufBytes;
int maxOutSamples = copyBufSize / BYTES_PER_SAMPLE;
// Find out how much we want and get it from the received data channel.
CCircularBuffer *pBuffer = &pChannel->m_Buffer;
int nBytesToRead = pBuffer->GetReadAvailable();
nBytesToRead = min(min(nBytesToRead, (int)maxOutSamples), sampleCount * BYTES_PER_SAMPLE);
int nSamplesGotten = pBuffer->Read(pCopyBuf, nBytesToRead) / BYTES_PER_SAMPLE;
// Are we at the end of the buffer's data? If so, fade data to silence so it doesn't clip.
int readSamplesAvail = pBuffer->GetReadAvailable() / BYTES_PER_SAMPLE;
if(readSamplesAvail < g_nVoiceFadeSamples)
{
int bufferFadeOffset = max((readSamplesAvail + nSamplesGotten) - g_nVoiceFadeSamples, 0);
int globalFadeOffset = max(g_nVoiceFadeSamples - (readSamplesAvail + nSamplesGotten), 0);
ApplyFadeToSamples(
&pCopyBuf[bufferFadeOffset],
nSamplesGotten - bufferFadeOffset,
globalFadeOffset,
g_VoiceFadeMul);
}
// If there weren't enough bytes in the received data channel, pad it with zeros.
if(nSamplesGotten < sampleCount)
{
int wantedSampleCount = min(sampleCount, maxOutSamples);
memset(&pCopyBuf[nSamplesGotten], 0, (wantedSampleCount - nSamplesGotten) * BYTES_PER_SAMPLE);
nSamplesGotten = wantedSampleCount;
}
// If the buffer is out of data, mark this channel to go away.
if(pBuffer->GetReadAvailable() == 0)
{
pChannel->m_bStarved = true;
}
if(voice_showchannels.GetInt() >= 2)
{
Msg("Voice - mixed %d samples from channel %d\n", nSamplesGotten, iChannel);
}
VoiceSE_MoveMouth(pChannel->m_iEntity, (short*)copyBufBytes, nSamplesGotten);
return nSamplesGotten;
}
void Voice_OnAudioSourceShutdown( int iChannel )
{
Voice_EndChannel( iChannel );
}
// ------------------------------------------------------------------------ //
// Internal stuff.
// ------------------------------------------------------------------------ //
CVoiceChannel* GetVoiceChannel(int iChannel, bool bAssert=true)
{
if(iChannel < 0 || iChannel >= VOICE_NUM_CHANNELS)
{
if(bAssert)
{
assert(false);
}
return NULL;
}
else
{
return &g_VoiceChannels[iChannel];
}
}
bool Voice_Init(const char *pCodecName)
{
if ( voice_enable.GetInt() == 0 )
{
return false;
}
Voice_Deinit();
g_bVoiceAtLeastPartiallyInitted = true;
if(!VoiceSE_Init())
return false;
// Get the voice input device.
int rate = g_VoiceSampleFormat.nSamplesPerSec;
g_pVoiceRecord = CreateVoiceRecord_DSound( rate );
if( !g_pVoiceRecord )
{
Msg( "Unable to initialize DirectSoundCapture. You won't be able to speak to other players." );
}
// Get the codec.
char dllName[256];
Q_snprintf( dllName, sizeof( dllName ), "%s.dll", pCodecName );
CreateInterfaceFn createCodecFn;
g_hVoiceCodecDLL = FileSystem_LoadModule(dllName);
if ( !g_hVoiceCodecDLL || !(createCodecFn = Sys_GetFactory(g_hVoiceCodecDLL)) ||
!(g_pEncodeCodec = (IVoiceCodec*)createCodecFn(pCodecName, NULL)) || !g_pEncodeCodec->Init() )
{
Msg("Unable to load voice codec '%s'. Voice disabled.\n", pCodecName);
Voice_Deinit();
return false;
}
for(int i=0; i < VOICE_NUM_CHANNELS; i++)
{
CVoiceChannel *pChannel = &g_VoiceChannels[i];
if(!(pChannel->m_pVoiceCodec = (IVoiceCodec*)createCodecFn(pCodecName, NULL)) || !pChannel->m_pVoiceCodec->Init())
{
Voice_Deinit();
return false;
}
}
g_pMixerControls = GetMixerControls();
if( voice_forcemicrecord.GetInt() )
{
if( g_pMixerControls )
g_pMixerControls->SelectMicrophoneForWaveInput();
}
return true;
}
void Voice_EndChannel(int iChannel)
{
assert(iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS);
CVoiceChannel *pChannel = &g_VoiceChannels[iChannel];
if ( pChannel->m_iEntity != -1 )
{
int iEnt = pChannel->m_iEntity;
pChannel->m_iEntity = -1;
VoiceSE_EndChannel( iChannel );
g_pSoundServices->OnChangeVoiceStatus( iEnt, false );
VoiceSE_CloseMouth( iEnt );
}
}
void Voice_EndAllChannels()
{
for(int i=0; i < VOICE_NUM_CHANNELS; i++)
{
Voice_EndChannel(i);
}
}
void Voice_Deinit()
{
// This call tends to be expensive and when voice is not enabled it will continually
// call in here, so avoid the work if possible.
if( !g_bVoiceAtLeastPartiallyInitted )
return;
Voice_EndAllChannels();
Voice_RecordStop();
for(int i=0; i < VOICE_NUM_CHANNELS; i++)
{
CVoiceChannel *pChannel = &g_VoiceChannels[i];
if(pChannel->m_pVoiceCodec)
{
pChannel->m_pVoiceCodec->Release();
pChannel->m_pVoiceCodec = NULL;
}
}
if(g_pEncodeCodec)
{
g_pEncodeCodec->Release();
g_pEncodeCodec = NULL;
}
if(g_hVoiceCodecDLL)
{
FileSystem_UnloadModule(g_hVoiceCodecDLL);
g_hVoiceCodecDLL = NULL;
}
if(g_pVoiceRecord)
{
g_pVoiceRecord->Release();
g_pVoiceRecord = NULL;
}
VoiceSE_Term();
if( g_pMixerControls )
{
g_pMixerControls->Release();
g_pMixerControls = NULL;
}
g_bVoiceAtLeastPartiallyInitted = false;
}
bool Voice_GetLoopback()
{
return !!voice_loopback.GetInt();
}
void Voice_LocalPlayerTalkingAck()
{
if(!g_bLocalPlayerTalkingAck)
{
// Tell the client DLL when this changes.
g_pSoundServices->OnChangeVoiceStatus(-2, TRUE);
}
g_bLocalPlayerTalkingAck = true;
g_LocalPlayerTalkingTimeout = 0;
}
void Voice_UpdateVoiceTweakMode()
{
if(!g_bInTweakMode || !g_pVoiceRecord)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -