fix issues, add voice stats
This commit is contained in:
parent
39fb1f70f8
commit
fa3a80c9dc
@ -13,28 +13,28 @@
|
||||
|
||||
void DemHandlers::CreateDemMsgStructs(DemDataStructArray& demDataStructs)
|
||||
{
|
||||
demDataStructs[0] = new DemMsg::Dem_Unknown();
|
||||
demDataStructs[1] = new DemMsg::Dem_Packet();
|
||||
demDataStructs[2] = new DemMsg::Dem_Packet();
|
||||
demDataStructs[3] = new DemMsg::Dem_SyncTick();
|
||||
demDataStructs[4] = new DemMsg::Dem_ConsoleCmd();
|
||||
demDataStructs[5] = new DemMsg::Dem_UserCmd();
|
||||
demDataStructs[6] = new DemMsg::Dem_DataTables();
|
||||
demDataStructs[7] = new DemMsg::Dem_Stop();
|
||||
demDataStructs[8] = new DemMsg::Dem_StringTables();
|
||||
demDataStructs[dem_unknown] = new DemMsg::Dem_Unknown();
|
||||
demDataStructs[dem_signon] = new DemMsg::Dem_Packet();
|
||||
demDataStructs[dem_packet] = new DemMsg::Dem_Packet();
|
||||
demDataStructs[dem_synctick] = new DemMsg::Dem_SyncTick();
|
||||
demDataStructs[dem_consolecmd] = new DemMsg::Dem_ConsoleCmd();
|
||||
demDataStructs[dem_usercmd] = new DemMsg::Dem_UserCmd();
|
||||
demDataStructs[dem_datatables] = new DemMsg::Dem_DataTables();
|
||||
demDataStructs[dem_stop] = new DemMsg::Dem_Stop();
|
||||
demDataStructs[dem_stringtables] = new DemMsg::Dem_StringTables();
|
||||
}
|
||||
|
||||
void DemHandlers::DestroyDemMsgStructs(DemDataStructArray& demDataStructs)
|
||||
{
|
||||
delete reinterpret_cast<DemMsg::Dem_Unknown*>(demDataStructs[0]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[1]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[2]);
|
||||
delete reinterpret_cast<DemMsg::Dem_SyncTick*>(demDataStructs[3]);
|
||||
delete reinterpret_cast<DemMsg::Dem_ConsoleCmd*>(demDataStructs[4]);
|
||||
delete reinterpret_cast<DemMsg::Dem_UserCmd*>(demDataStructs[5]);
|
||||
delete reinterpret_cast<DemMsg::Dem_DataTables*>(demDataStructs[6]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Stop*>(demDataStructs[7]);
|
||||
delete reinterpret_cast<DemMsg::Dem_StringTables*>(demDataStructs[8]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Unknown*>(demDataStructs[dem_unknown]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[dem_signon]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[dem_packet]);
|
||||
delete reinterpret_cast<DemMsg::Dem_SyncTick*>(demDataStructs[dem_synctick]);
|
||||
delete reinterpret_cast<DemMsg::Dem_ConsoleCmd*>(demDataStructs[dem_consolecmd]);
|
||||
delete reinterpret_cast<DemMsg::Dem_UserCmd*>(demDataStructs[dem_usercmd]);
|
||||
delete reinterpret_cast<DemMsg::Dem_DataTables*>(demDataStructs[dem_datatables]);
|
||||
delete reinterpret_cast<DemMsg::Dem_Stop*>(demDataStructs[dem_stop]);
|
||||
delete reinterpret_cast<DemMsg::Dem_StringTables*>(demDataStructs[dem_stringtables]);
|
||||
}
|
||||
|
||||
#define DECLARE_DEM_HANDLER_ARRAY(funcname) \
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
#include "logic.h"
|
||||
#include "netmessages/svc_serverinfo.h"
|
||||
#include "io/voicewriter/voicedatawriter.h"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
@ -13,7 +14,8 @@ Logic::Logic(SourceGameContext* context):
|
||||
{"header", {}},
|
||||
{"serverinfo", {}},
|
||||
{"players", {}},
|
||||
{"chat", {}}
|
||||
{"chat", {}},
|
||||
{"voice", {}}
|
||||
});
|
||||
}
|
||||
|
||||
@ -60,6 +62,9 @@ void Logic::Finish(bool dirty)
|
||||
data["demoheader"]["playback_frames"] = context->curFrame;
|
||||
}
|
||||
|
||||
data["voice"]["total_time"] = voiceTotalTime;
|
||||
data["voice"]["active_time"] = voiceActiveTime;
|
||||
|
||||
std::string out = data.dump(2, ' ', false, json::error_handler_t::replace);
|
||||
out.append("\n");
|
||||
fwrite(out.c_str(), out.size(), 1, context->outputFp);
|
||||
@ -101,6 +106,7 @@ void Logic::OnServerInfo(NetMsg::SVC_ServerInfo* serverInfo)
|
||||
|
||||
void Logic::OnClientConnected(int client)
|
||||
{
|
||||
assert(client >= 0 && client < MAX_PLAYERS);
|
||||
assert(clients[client].connected == -1);
|
||||
|
||||
const auto& info = context->players[client].info;
|
||||
@ -124,6 +130,7 @@ void Logic::OnClientConnected(int client)
|
||||
|
||||
void Logic::OnClientDisconnected(int client, const char* reason)
|
||||
{
|
||||
assert(client >= 0 && client < MAX_PLAYERS);
|
||||
assert(clients[client].connected != -1);
|
||||
|
||||
const auto& info = context->players[client].info;
|
||||
@ -146,6 +153,7 @@ void Logic::OnClientDisconnected(int client, const char* reason)
|
||||
|
||||
void Logic::OnClientSettingsChanged(int client)
|
||||
{
|
||||
assert(client >= 0 && client < MAX_PLAYERS);
|
||||
assert(clients[client].connected != -1);
|
||||
|
||||
const auto& info = context->players[client].info;
|
||||
@ -167,6 +175,9 @@ void Logic::OnClientSettingsChanged(int client)
|
||||
|
||||
void Logic::OnClientChat(int client, bool bWantsToChat, const char* msgName, const char* msgSender, const char* msgText)
|
||||
{
|
||||
assert(client >= 0 && client < MAX_PLAYERS);
|
||||
assert(clients[client].connected != -1);
|
||||
|
||||
const auto& info = context->players[client].info;
|
||||
json chat = {
|
||||
{"tick", curTick},
|
||||
@ -181,14 +192,29 @@ void Logic::OnClientChat(int client, bool bWantsToChat, const char* msgName, con
|
||||
|
||||
void Logic::OnClientVoiceChat(int client, float length)
|
||||
{
|
||||
assert(client >= 0 && client < MAX_PLAYERS);
|
||||
assert(clients[client].connected != -1);
|
||||
|
||||
clients[client].voiceTime += length;
|
||||
voiceTotalTime += length;
|
||||
|
||||
float now = curTick * context->fTickRate;
|
||||
float endtime = now + length;
|
||||
if (now >= voiceEndTime)
|
||||
{
|
||||
voiceEndTime = endtime;
|
||||
voiceActiveTime += length;
|
||||
}
|
||||
else if (endtime > voiceEndTime)
|
||||
{
|
||||
voiceActiveTime += (endtime - voiceEndTime);
|
||||
voiceEndTime = endtime;
|
||||
}
|
||||
}
|
||||
|
||||
void Logic::OnVoiceCodec(const char* codec, int quality, int sampleRate)
|
||||
{
|
||||
data["voice_init"] = json({
|
||||
data["voice"] = json({
|
||||
{"codec", codec},
|
||||
{"quality", quality},
|
||||
{"sampleRate", sampleRate}
|
||||
|
@ -34,6 +34,9 @@ struct Logic
|
||||
void OnVoiceCodec(const char* codec, int quality, int sampleRate);
|
||||
|
||||
int32_t curTick = 0;
|
||||
float voiceTotalTime = 0.0f;
|
||||
float voiceActiveTime = 0.0f;
|
||||
float voiceEndTime = 0.0f;
|
||||
SourceGameContext* context = nullptr;
|
||||
json data;
|
||||
};
|
||||
|
@ -19,11 +19,12 @@ SourceGameContext::SourceGameContext(std::string outputDir, std::string outputDi
|
||||
outputDirVoice(outputDirVoice)
|
||||
{
|
||||
stringTables = new StringTableContainer(this);
|
||||
memset(players, 0, sizeof(players));
|
||||
}
|
||||
|
||||
SourceGameContext::~SourceGameContext()
|
||||
{
|
||||
delete voiceWriter;
|
||||
voiceWriter = nullptr;
|
||||
delete logic;
|
||||
logic = nullptr;
|
||||
|
||||
@ -101,7 +102,7 @@ void SourceGameContext::OnNetPacket(NetPacket& packet)
|
||||
|
||||
if(umsg->msgType == UserMsg::SayText2)
|
||||
{
|
||||
int client = msg.ReadByte();
|
||||
int client = msg.ReadByte() - 1;
|
||||
bool bWantsToChat = msg.ReadByte();
|
||||
|
||||
char msgName[2048] = {0};
|
||||
@ -120,7 +121,6 @@ void SourceGameContext::OnNetPacket(NetPacket& packet)
|
||||
{
|
||||
voiceWriter->OnNetPacket(packet);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SourceGameContext::OnGameEvent(const char *name, GameEvents::EventDataMap &data)
|
||||
|
@ -105,7 +105,7 @@ struct SourceGameContext
|
||||
|
||||
struct
|
||||
{
|
||||
bool connected;
|
||||
bool connected = false;
|
||||
player_info_t info;
|
||||
} players[MAX_PLAYERS];
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
void Init(const char* file, uint32_t sampleRate)
|
||||
{
|
||||
assert(!m_Enc);
|
||||
m_SampleRate = sampleRate;
|
||||
m_Samples = 0;
|
||||
|
||||
m_Comments = ope_comments_create();
|
||||
@ -56,8 +57,9 @@ public:
|
||||
m_Samples += numSamples;
|
||||
}
|
||||
|
||||
void PadSilence(uint64_t numSamples)
|
||||
void PadSilence(uint64_t milliseconds)
|
||||
{
|
||||
uint64_t numSamples = (milliseconds * (uint64_t)m_SampleRate) / 1000UL;
|
||||
if(!m_Enc || m_Samples >= numSamples)
|
||||
return;
|
||||
|
||||
@ -75,6 +77,7 @@ public:
|
||||
private:
|
||||
OggOpusComments *m_Comments = nullptr;
|
||||
OggOpusEnc *m_Enc = nullptr;
|
||||
uint32_t m_SampleRate = 0;
|
||||
uint64_t m_Samples = 0;
|
||||
|
||||
static const uint32_t bytesPerSample = 2;
|
||||
|
@ -87,11 +87,11 @@ void CeltVoiceDecoder::DecodeFrame(const uint8_t* compressedData, int16_t* uncom
|
||||
|
||||
bool SilkVoiceDecoder::DoInit(int32_t sampleRate)
|
||||
{
|
||||
m_Silk_DecoderControl.API_sampleRate = sampleRate;
|
||||
if(m_Silk_DecoderState)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_Silk_DecoderControl.API_sampleRate = sampleRate;
|
||||
|
||||
int decoderSize;
|
||||
SKP_Silk_SDK_Get_Decoder_Size(&decoderSize);
|
||||
@ -154,7 +154,7 @@ void VoiceDataWriter::Finish()
|
||||
state.second.celt_decoder.Destroy();
|
||||
state.second.silk_decoder.Destroy();
|
||||
|
||||
state.second.fileWriter.PadSilence(((uint64_t)(m_curTick - m_silenceTicks) * state.second.sampleRate) / context->fTickRate);
|
||||
state.second.fileWriter.PadSilence((((uint64_t)m_curTick - m_silenceTicks) * 1000000UL) / (uint64_t)(context->fTickRate * 1000UL));
|
||||
state.second.fileWriter.Close();
|
||||
state.second.lastVoiceDataTick = -1;
|
||||
}
|
||||
@ -174,22 +174,22 @@ void VoiceDataWriter::StartCommandPacket(const CommandPacket& packet)
|
||||
|
||||
void VoiceDataWriter::EndCommandPacket(const PacketTrailingBits& trailingBits)
|
||||
{
|
||||
const int tickMargin = context->fTickRate / 10.0; // 100ms
|
||||
const int tickMargin = context->fTickInterval * 150.0; // ms
|
||||
if (m_curTick <= tickMargin)
|
||||
return;
|
||||
|
||||
// Skip silence if noone talks for at least 5 seconds
|
||||
if((m_curTick - m_lastVoiceTick) / context->fTickRate > 5.0)
|
||||
// Skip silence if noone talks for at least 3 seconds
|
||||
if((m_curTick - m_lastVoiceTick) / context->fTickRate > 3.0)
|
||||
m_silenceTicks += (m_curTick - m_lastTick);
|
||||
|
||||
for(auto& state : m_playerVoiceStates)
|
||||
{
|
||||
if((m_curTick - state.second.lastVoiceDataTick) > tickMargin)
|
||||
state.second.fileWriter.PadSilence(((uint64_t)(m_curTick - m_silenceTicks) * state.second.sampleRate) / context->fTickRate);
|
||||
state.second.fileWriter.PadSilence((((uint64_t)m_curTick - m_silenceTicks) * 1000000UL) / (uint64_t)(context->fTickRate * 1000UL));
|
||||
}
|
||||
}
|
||||
|
||||
int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerVoiceState& state)
|
||||
int VoiceDataWriter::ParseSteamVoicePacket(const uint8_t* bytes, int numBytes, PlayerVoiceState& state)
|
||||
{
|
||||
int numDecompressedSamples = 0;
|
||||
int pos = 0;
|
||||
@ -222,9 +222,9 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
{
|
||||
if(pos + 2 > dataLen)
|
||||
return numDecompressedSamples;
|
||||
short rate = *((int16_t *)&bytes[pos]);
|
||||
uint16_t rate = *((int16_t *)&bytes[pos]);
|
||||
pos += 2;
|
||||
state.silk_decoder.DoInit(rate);
|
||||
if(state.silk_decoder.DoInit(rate))
|
||||
state.sampleRate = rate;
|
||||
} break;
|
||||
case 10: // Unknown / Unused
|
||||
@ -241,14 +241,16 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
{
|
||||
if(pos + 2 > dataLen)
|
||||
return numDecompressedSamples;
|
||||
short length = *((int16_t *)&bytes[pos]);
|
||||
uint16_t length = *((int16_t *)&bytes[pos]);
|
||||
pos += 2;
|
||||
|
||||
if(pos + length > dataLen)
|
||||
return numDecompressedSamples;
|
||||
|
||||
int freeSamples = (sizeof(m_decodeBuffer) / sizeof(int16_t)) - numDecompressedSamples;
|
||||
if(payloadType == 3)
|
||||
{
|
||||
length = MIN(freeSamples * 2, length);
|
||||
memcpy(&m_decodeBuffer[numDecompressedSamples], &bytes[pos], length);
|
||||
numDecompressedSamples += length / sizeof(int16_t);
|
||||
}
|
||||
@ -258,10 +260,10 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
int maxpos = tpos + length;
|
||||
while(tpos <= (maxpos - 2))
|
||||
{
|
||||
short chunkLength = *((int16_t *)&bytes[tpos]);
|
||||
int16_t chunkLength = *((int16_t *)&bytes[tpos]);
|
||||
tpos += 2;
|
||||
|
||||
if(chunkLength == -1)
|
||||
if(chunkLength < 0)
|
||||
{
|
||||
state.silk_decoder.Reset();
|
||||
continue;
|
||||
@ -270,6 +272,7 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
{
|
||||
// DTX (discontinued transmission)
|
||||
int numEmptySamples = state.sampleRate / 50;
|
||||
numEmptySamples = MIN(freeSamples, numEmptySamples);
|
||||
memset(&m_decodeBuffer[numDecompressedSamples], 0, numEmptySamples * sizeof(int16_t));
|
||||
numDecompressedSamples += numEmptySamples;
|
||||
continue;
|
||||
@ -278,8 +281,7 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
if(tpos + chunkLength > maxpos)
|
||||
return numDecompressedSamples;
|
||||
|
||||
int ret = state.silk_decoder.Decompress(&bytes[tpos], chunkLength, &m_decodeBuffer[numDecompressedSamples],
|
||||
(sizeof(m_decodeBuffer) / sizeof(int16_t)) - numDecompressedSamples);
|
||||
int ret = state.silk_decoder.Decompress(&bytes[tpos], chunkLength, &m_decodeBuffer[numDecompressedSamples], freeSamples);
|
||||
numDecompressedSamples += ret;
|
||||
tpos += chunkLength;
|
||||
}
|
||||
@ -291,7 +293,9 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
|
||||
{
|
||||
if(pos + 2 > dataLen)
|
||||
return numDecompressedSamples;
|
||||
short numSamples = *((int16_t *)&bytes[pos]);
|
||||
uint16_t numSamples = *((uint16_t *)&bytes[pos]);
|
||||
int freeSamples = (sizeof(m_decodeBuffer) / sizeof(int16_t)) - numDecompressedSamples;
|
||||
numSamples = MIN(freeSamples, numSamples);
|
||||
memset(&m_decodeBuffer[numDecompressedSamples], 0, numSamples * sizeof(int16_t));
|
||||
numDecompressedSamples += numSamples;
|
||||
pos += 2;
|
||||
@ -329,7 +333,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
|
||||
assert(voiceData->fromClientIndex < MAX_PLAYERS);
|
||||
const char* guid = context->players[voiceData->fromClientIndex].info.guid;
|
||||
|
||||
uint8_t* bytes = voiceData->data.get();
|
||||
const uint8_t* bytes = voiceData->data.get();
|
||||
assert((voiceData->dataLengthInBits % 8) == 0);
|
||||
const int numBytes = voiceData->dataLengthInBits / 8;
|
||||
|
||||
@ -343,7 +347,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
|
||||
state.sampleRate = config.sampleRate;
|
||||
|
||||
state.celt_decoder.DoInit(m_celtMode, config.frameSizeSamples, config.encodedFrameSizeBytes);
|
||||
numDecompressedSamples = state.celt_decoder.Decompress(bytes, numBytes, m_decodeBuffer, sizeof(m_decodeBuffer));
|
||||
numDecompressedSamples = state.celt_decoder.Decompress(bytes, numBytes, m_decodeBuffer, sizeof(m_decodeBuffer) / sizeof(int16_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -366,7 +370,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
|
||||
{
|
||||
std::string name = std::string(m_outputPath) + "/" + std::string(guid) + ".opus";
|
||||
state.fileWriter.Init(name.c_str(), state.sampleRate);
|
||||
state.fileWriter.PadSilence(((uint64_t)(m_curTick - m_silenceTicks) * state.sampleRate) / context->fTickRate);
|
||||
state.fileWriter.PadSilence((((uint64_t)m_curTick - m_silenceTicks) * 1000000UL) / (uint64_t)(context->fTickRate * 1000UL));
|
||||
}
|
||||
|
||||
state.fileWriter.WriteSamples(m_decodeBuffer, numDecompressedSamples);
|
||||
|
@ -74,7 +74,7 @@ private:
|
||||
int sampleRate = 0;
|
||||
};
|
||||
|
||||
int ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerVoiceState& state);
|
||||
int ParseSteamVoicePacket(const uint8_t* bytes, int numBytes, PlayerVoiceState& state);
|
||||
|
||||
private:
|
||||
SourceGameContext *context = nullptr;
|
||||
@ -88,7 +88,7 @@ private:
|
||||
int32_t m_silenceTicks = 0;
|
||||
const char* m_outputPath = nullptr;
|
||||
|
||||
int16_t m_decodeBuffer[8192];
|
||||
int16_t m_decodeBuffer[16384];
|
||||
|
||||
static const int sQuality = 3;
|
||||
eCodec m_Codec = CODEC_NONE;
|
||||
|
Loading…
Reference in New Issue
Block a user