fix issues, add voice stats

This commit is contained in:
BotoX 2021-05-09 23:22:12 +02:00
parent 39fb1f70f8
commit fa3a80c9dc
8 changed files with 83 additions and 47 deletions

View File

@ -13,28 +13,28 @@
void DemHandlers::CreateDemMsgStructs(DemDataStructArray& demDataStructs) void DemHandlers::CreateDemMsgStructs(DemDataStructArray& demDataStructs)
{ {
demDataStructs[0] = new DemMsg::Dem_Unknown(); demDataStructs[dem_unknown] = new DemMsg::Dem_Unknown();
demDataStructs[1] = new DemMsg::Dem_Packet(); demDataStructs[dem_signon] = new DemMsg::Dem_Packet();
demDataStructs[2] = new DemMsg::Dem_Packet(); demDataStructs[dem_packet] = new DemMsg::Dem_Packet();
demDataStructs[3] = new DemMsg::Dem_SyncTick(); demDataStructs[dem_synctick] = new DemMsg::Dem_SyncTick();
demDataStructs[4] = new DemMsg::Dem_ConsoleCmd(); demDataStructs[dem_consolecmd] = new DemMsg::Dem_ConsoleCmd();
demDataStructs[5] = new DemMsg::Dem_UserCmd(); demDataStructs[dem_usercmd] = new DemMsg::Dem_UserCmd();
demDataStructs[6] = new DemMsg::Dem_DataTables(); demDataStructs[dem_datatables] = new DemMsg::Dem_DataTables();
demDataStructs[7] = new DemMsg::Dem_Stop(); demDataStructs[dem_stop] = new DemMsg::Dem_Stop();
demDataStructs[8] = new DemMsg::Dem_StringTables(); demDataStructs[dem_stringtables] = new DemMsg::Dem_StringTables();
} }
void DemHandlers::DestroyDemMsgStructs(DemDataStructArray& demDataStructs) void DemHandlers::DestroyDemMsgStructs(DemDataStructArray& demDataStructs)
{ {
delete reinterpret_cast<DemMsg::Dem_Unknown*>(demDataStructs[0]); delete reinterpret_cast<DemMsg::Dem_Unknown*>(demDataStructs[dem_unknown]);
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[1]); delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[dem_signon]);
delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[2]); delete reinterpret_cast<DemMsg::Dem_Packet*>(demDataStructs[dem_packet]);
delete reinterpret_cast<DemMsg::Dem_SyncTick*>(demDataStructs[3]); delete reinterpret_cast<DemMsg::Dem_SyncTick*>(demDataStructs[dem_synctick]);
delete reinterpret_cast<DemMsg::Dem_ConsoleCmd*>(demDataStructs[4]); delete reinterpret_cast<DemMsg::Dem_ConsoleCmd*>(demDataStructs[dem_consolecmd]);
delete reinterpret_cast<DemMsg::Dem_UserCmd*>(demDataStructs[5]); delete reinterpret_cast<DemMsg::Dem_UserCmd*>(demDataStructs[dem_usercmd]);
delete reinterpret_cast<DemMsg::Dem_DataTables*>(demDataStructs[6]); delete reinterpret_cast<DemMsg::Dem_DataTables*>(demDataStructs[dem_datatables]);
delete reinterpret_cast<DemMsg::Dem_Stop*>(demDataStructs[7]); delete reinterpret_cast<DemMsg::Dem_Stop*>(demDataStructs[dem_stop]);
delete reinterpret_cast<DemMsg::Dem_StringTables*>(demDataStructs[8]); delete reinterpret_cast<DemMsg::Dem_StringTables*>(demDataStructs[dem_stringtables]);
} }
#define DECLARE_DEM_HANDLER_ARRAY(funcname) \ #define DECLARE_DEM_HANDLER_ARRAY(funcname) \

View File

@ -1,6 +1,7 @@
#include "logic.h" #include "logic.h"
#include "netmessages/svc_serverinfo.h" #include "netmessages/svc_serverinfo.h"
#include "io/voicewriter/voicedatawriter.h"
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
@ -13,7 +14,8 @@ Logic::Logic(SourceGameContext* context):
{"header", {}}, {"header", {}},
{"serverinfo", {}}, {"serverinfo", {}},
{"players", {}}, {"players", {}},
{"chat", {}} {"chat", {}},
{"voice", {}}
}); });
} }
@ -60,6 +62,9 @@ void Logic::Finish(bool dirty)
data["demoheader"]["playback_frames"] = context->curFrame; 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); std::string out = data.dump(2, ' ', false, json::error_handler_t::replace);
out.append("\n"); out.append("\n");
fwrite(out.c_str(), out.size(), 1, context->outputFp); 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) void Logic::OnClientConnected(int client)
{ {
assert(client >= 0 && client < MAX_PLAYERS);
assert(clients[client].connected == -1); assert(clients[client].connected == -1);
const auto& info = context->players[client].info; const auto& info = context->players[client].info;
@ -124,6 +130,7 @@ void Logic::OnClientConnected(int client)
void Logic::OnClientDisconnected(int client, const char* reason) void Logic::OnClientDisconnected(int client, const char* reason)
{ {
assert(client >= 0 && client < MAX_PLAYERS);
assert(clients[client].connected != -1); assert(clients[client].connected != -1);
const auto& info = context->players[client].info; const auto& info = context->players[client].info;
@ -146,6 +153,7 @@ void Logic::OnClientDisconnected(int client, const char* reason)
void Logic::OnClientSettingsChanged(int client) void Logic::OnClientSettingsChanged(int client)
{ {
assert(client >= 0 && client < MAX_PLAYERS);
assert(clients[client].connected != -1); assert(clients[client].connected != -1);
const auto& info = context->players[client].info; 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) 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; const auto& info = context->players[client].info;
json chat = { json chat = {
{"tick", curTick}, {"tick", curTick},
@ -181,16 +192,31 @@ void Logic::OnClientChat(int client, bool bWantsToChat, const char* msgName, con
void Logic::OnClientVoiceChat(int client, float length) void Logic::OnClientVoiceChat(int client, float length)
{ {
assert(client >= 0 && client < MAX_PLAYERS);
assert(clients[client].connected != -1); assert(clients[client].connected != -1);
clients[client].voiceTime += length; 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) void Logic::OnVoiceCodec(const char* codec, int quality, int sampleRate)
{ {
data["voice_init"] = json({ data["voice"] = json({
{"codec", codec}, {"codec", codec},
{"quality", quality}, {"quality", quality},
{"sampleRate", sampleRate} {"sampleRate", sampleRate}
}); });
} }

View File

@ -34,6 +34,9 @@ struct Logic
void OnVoiceCodec(const char* codec, int quality, int sampleRate); void OnVoiceCodec(const char* codec, int quality, int sampleRate);
int32_t curTick = 0; int32_t curTick = 0;
float voiceTotalTime = 0.0f;
float voiceActiveTime = 0.0f;
float voiceEndTime = 0.0f;
SourceGameContext* context = nullptr; SourceGameContext* context = nullptr;
json data; json data;
}; };

View File

@ -19,11 +19,12 @@ SourceGameContext::SourceGameContext(std::string outputDir, std::string outputDi
outputDirVoice(outputDirVoice) outputDirVoice(outputDirVoice)
{ {
stringTables = new StringTableContainer(this); stringTables = new StringTableContainer(this);
memset(players, 0, sizeof(players));
} }
SourceGameContext::~SourceGameContext() SourceGameContext::~SourceGameContext()
{ {
delete voiceWriter;
voiceWriter = nullptr;
delete logic; delete logic;
logic = nullptr; logic = nullptr;
@ -101,7 +102,7 @@ void SourceGameContext::OnNetPacket(NetPacket& packet)
if(umsg->msgType == UserMsg::SayText2) if(umsg->msgType == UserMsg::SayText2)
{ {
int client = msg.ReadByte(); int client = msg.ReadByte() - 1;
bool bWantsToChat = msg.ReadByte(); bool bWantsToChat = msg.ReadByte();
char msgName[2048] = {0}; char msgName[2048] = {0};
@ -120,7 +121,6 @@ void SourceGameContext::OnNetPacket(NetPacket& packet)
{ {
voiceWriter->OnNetPacket(packet); voiceWriter->OnNetPacket(packet);
} }
} }
void SourceGameContext::OnGameEvent(const char *name, GameEvents::EventDataMap &data) void SourceGameContext::OnGameEvent(const char *name, GameEvents::EventDataMap &data)

View File

@ -105,7 +105,7 @@ struct SourceGameContext
struct struct
{ {
bool connected; bool connected = false;
player_info_t info; player_info_t info;
} players[MAX_PLAYERS]; } players[MAX_PLAYERS];
}; };

View File

@ -24,6 +24,7 @@ public:
void Init(const char* file, uint32_t sampleRate) void Init(const char* file, uint32_t sampleRate)
{ {
assert(!m_Enc); assert(!m_Enc);
m_SampleRate = sampleRate;
m_Samples = 0; m_Samples = 0;
m_Comments = ope_comments_create(); m_Comments = ope_comments_create();
@ -56,8 +57,9 @@ public:
m_Samples += numSamples; 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) if(!m_Enc || m_Samples >= numSamples)
return; return;
@ -75,6 +77,7 @@ public:
private: private:
OggOpusComments *m_Comments = nullptr; OggOpusComments *m_Comments = nullptr;
OggOpusEnc *m_Enc = nullptr; OggOpusEnc *m_Enc = nullptr;
uint32_t m_SampleRate = 0;
uint64_t m_Samples = 0; uint64_t m_Samples = 0;
static const uint32_t bytesPerSample = 2; static const uint32_t bytesPerSample = 2;

View File

@ -87,11 +87,11 @@ void CeltVoiceDecoder::DecodeFrame(const uint8_t* compressedData, int16_t* uncom
bool SilkVoiceDecoder::DoInit(int32_t sampleRate) bool SilkVoiceDecoder::DoInit(int32_t sampleRate)
{ {
m_Silk_DecoderControl.API_sampleRate = sampleRate;
if(m_Silk_DecoderState) if(m_Silk_DecoderState)
{ {
return false; return false;
} }
m_Silk_DecoderControl.API_sampleRate = sampleRate;
int decoderSize; int decoderSize;
SKP_Silk_SDK_Get_Decoder_Size(&decoderSize); SKP_Silk_SDK_Get_Decoder_Size(&decoderSize);
@ -154,7 +154,7 @@ void VoiceDataWriter::Finish()
state.second.celt_decoder.Destroy(); state.second.celt_decoder.Destroy();
state.second.silk_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.fileWriter.Close();
state.second.lastVoiceDataTick = -1; state.second.lastVoiceDataTick = -1;
} }
@ -174,22 +174,22 @@ void VoiceDataWriter::StartCommandPacket(const CommandPacket& packet)
void VoiceDataWriter::EndCommandPacket(const PacketTrailingBits& trailingBits) 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) if (m_curTick <= tickMargin)
return; return;
// Skip silence if noone talks for at least 5 seconds // Skip silence if noone talks for at least 3 seconds
if((m_curTick - m_lastVoiceTick) / context->fTickRate > 5.0) if((m_curTick - m_lastVoiceTick) / context->fTickRate > 3.0)
m_silenceTicks += (m_curTick - m_lastTick); m_silenceTicks += (m_curTick - m_lastTick);
for(auto& state : m_playerVoiceStates) for(auto& state : m_playerVoiceStates)
{ {
if((m_curTick - state.second.lastVoiceDataTick) > tickMargin) 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 numDecompressedSamples = 0;
int pos = 0; int pos = 0;
@ -222,10 +222,10 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
{ {
if(pos + 2 > dataLen) if(pos + 2 > dataLen)
return numDecompressedSamples; return numDecompressedSamples;
short rate = *((int16_t *)&bytes[pos]); uint16_t rate = *((int16_t *)&bytes[pos]);
pos += 2; pos += 2;
state.silk_decoder.DoInit(rate); if(state.silk_decoder.DoInit(rate))
state.sampleRate = rate; state.sampleRate = rate;
} break; } break;
case 10: // Unknown / Unused case 10: // Unknown / Unused
{ {
@ -241,14 +241,16 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
{ {
if(pos + 2 > dataLen) if(pos + 2 > dataLen)
return numDecompressedSamples; return numDecompressedSamples;
short length = *((int16_t *)&bytes[pos]); uint16_t length = *((int16_t *)&bytes[pos]);
pos += 2; pos += 2;
if(pos + length > dataLen) if(pos + length > dataLen)
return numDecompressedSamples; return numDecompressedSamples;
int freeSamples = (sizeof(m_decodeBuffer) / sizeof(int16_t)) - numDecompressedSamples;
if(payloadType == 3) if(payloadType == 3)
{ {
length = MIN(freeSamples * 2, length);
memcpy(&m_decodeBuffer[numDecompressedSamples], &bytes[pos], length); memcpy(&m_decodeBuffer[numDecompressedSamples], &bytes[pos], length);
numDecompressedSamples += length / sizeof(int16_t); numDecompressedSamples += length / sizeof(int16_t);
} }
@ -258,10 +260,10 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
int maxpos = tpos + length; int maxpos = tpos + length;
while(tpos <= (maxpos - 2)) while(tpos <= (maxpos - 2))
{ {
short chunkLength = *((int16_t *)&bytes[tpos]); int16_t chunkLength = *((int16_t *)&bytes[tpos]);
tpos += 2; tpos += 2;
if(chunkLength == -1) if(chunkLength < 0)
{ {
state.silk_decoder.Reset(); state.silk_decoder.Reset();
continue; continue;
@ -270,6 +272,7 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
{ {
// DTX (discontinued transmission) // DTX (discontinued transmission)
int numEmptySamples = state.sampleRate / 50; int numEmptySamples = state.sampleRate / 50;
numEmptySamples = MIN(freeSamples, numEmptySamples);
memset(&m_decodeBuffer[numDecompressedSamples], 0, numEmptySamples * sizeof(int16_t)); memset(&m_decodeBuffer[numDecompressedSamples], 0, numEmptySamples * sizeof(int16_t));
numDecompressedSamples += numEmptySamples; numDecompressedSamples += numEmptySamples;
continue; continue;
@ -278,8 +281,7 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
if(tpos + chunkLength > maxpos) if(tpos + chunkLength > maxpos)
return numDecompressedSamples; return numDecompressedSamples;
int ret = state.silk_decoder.Decompress(&bytes[tpos], chunkLength, &m_decodeBuffer[numDecompressedSamples], int ret = state.silk_decoder.Decompress(&bytes[tpos], chunkLength, &m_decodeBuffer[numDecompressedSamples], freeSamples);
(sizeof(m_decodeBuffer) / sizeof(int16_t)) - numDecompressedSamples);
numDecompressedSamples += ret; numDecompressedSamples += ret;
tpos += chunkLength; tpos += chunkLength;
} }
@ -291,7 +293,9 @@ int VoiceDataWriter::ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerV
{ {
if(pos + 2 > dataLen) if(pos + 2 > dataLen)
return numDecompressedSamples; 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)); memset(&m_decodeBuffer[numDecompressedSamples], 0, numSamples * sizeof(int16_t));
numDecompressedSamples += numSamples; numDecompressedSamples += numSamples;
pos += 2; pos += 2;
@ -329,7 +333,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
assert(voiceData->fromClientIndex < MAX_PLAYERS); assert(voiceData->fromClientIndex < MAX_PLAYERS);
const char* guid = context->players[voiceData->fromClientIndex].info.guid; 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); assert((voiceData->dataLengthInBits % 8) == 0);
const int numBytes = voiceData->dataLengthInBits / 8; const int numBytes = voiceData->dataLengthInBits / 8;
@ -343,7 +347,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
state.sampleRate = config.sampleRate; state.sampleRate = config.sampleRate;
state.celt_decoder.DoInit(m_celtMode, config.frameSizeSamples, config.encodedFrameSizeBytes); 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 else
{ {
@ -366,7 +370,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet)
{ {
std::string name = std::string(m_outputPath) + "/" + std::string(guid) + ".opus"; std::string name = std::string(m_outputPath) + "/" + std::string(guid) + ".opus";
state.fileWriter.Init(name.c_str(), state.sampleRate); 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); state.fileWriter.WriteSamples(m_decodeBuffer, numDecompressedSamples);

View File

@ -74,7 +74,7 @@ private:
int sampleRate = 0; int sampleRate = 0;
}; };
int ParseSteamVoicePacket(uint8_t* bytes, int numBytes, PlayerVoiceState& state); int ParseSteamVoicePacket(const uint8_t* bytes, int numBytes, PlayerVoiceState& state);
private: private:
SourceGameContext *context = nullptr; SourceGameContext *context = nullptr;
@ -88,7 +88,7 @@ private:
int32_t m_silenceTicks = 0; int32_t m_silenceTicks = 0;
const char* m_outputPath = nullptr; const char* m_outputPath = nullptr;
int16_t m_decodeBuffer[8192]; int16_t m_decodeBuffer[16384];
static const int sQuality = 3; static const int sQuality = 3;
eCodec m_Codec = CODEC_NONE; eCodec m_Codec = CODEC_NONE;