diff --git a/demboyz/demmessages/demhandlers.cpp b/demboyz/demmessages/demhandlers.cpp index 561a46c..727b4e1 100644 --- a/demboyz/demmessages/demhandlers.cpp +++ b/demboyz/demmessages/demhandlers.cpp @@ -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(demDataStructs[0]); - delete reinterpret_cast(demDataStructs[1]); - delete reinterpret_cast(demDataStructs[2]); - delete reinterpret_cast(demDataStructs[3]); - delete reinterpret_cast(demDataStructs[4]); - delete reinterpret_cast(demDataStructs[5]); - delete reinterpret_cast(demDataStructs[6]); - delete reinterpret_cast(demDataStructs[7]); - delete reinterpret_cast(demDataStructs[8]); + delete reinterpret_cast(demDataStructs[dem_unknown]); + delete reinterpret_cast(demDataStructs[dem_signon]); + delete reinterpret_cast(demDataStructs[dem_packet]); + delete reinterpret_cast(demDataStructs[dem_synctick]); + delete reinterpret_cast(demDataStructs[dem_consolecmd]); + delete reinterpret_cast(demDataStructs[dem_usercmd]); + delete reinterpret_cast(demDataStructs[dem_datatables]); + delete reinterpret_cast(demDataStructs[dem_stop]); + delete reinterpret_cast(demDataStructs[dem_stringtables]); } #define DECLARE_DEM_HANDLER_ARRAY(funcname) \ diff --git a/demboyz/game/logic.cpp b/demboyz/game/logic.cpp index be2a1fe..9340967 100644 --- a/demboyz/game/logic.cpp +++ b/demboyz/game/logic.cpp @@ -1,6 +1,7 @@ #include "logic.h" #include "netmessages/svc_serverinfo.h" +#include "io/voicewriter/voicedatawriter.h" #include #include @@ -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,16 +192,31 @@ 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} }); -} \ No newline at end of file +} diff --git a/demboyz/game/logic.h b/demboyz/game/logic.h index 3c78043..8828249 100644 --- a/demboyz/game/logic.h +++ b/demboyz/game/logic.h @@ -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; }; diff --git a/demboyz/game/sourcecontext.cpp b/demboyz/game/sourcecontext.cpp index ae69698..a860a4d 100644 --- a/demboyz/game/sourcecontext.cpp +++ b/demboyz/game/sourcecontext.cpp @@ -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) diff --git a/demboyz/game/sourcecontext.h b/demboyz/game/sourcecontext.h index 0834bc2..c0ade55 100644 --- a/demboyz/game/sourcecontext.h +++ b/demboyz/game/sourcecontext.h @@ -105,7 +105,7 @@ struct SourceGameContext struct { - bool connected; + bool connected = false; player_info_t info; } players[MAX_PLAYERS]; }; diff --git a/demboyz/io/voicewriter/opusfilewriter.h b/demboyz/io/voicewriter/opusfilewriter.h index fd162b3..3aaa04c 100644 --- a/demboyz/io/voicewriter/opusfilewriter.h +++ b/demboyz/io/voicewriter/opusfilewriter.h @@ -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; diff --git a/demboyz/io/voicewriter/voicedatawriter.cpp b/demboyz/io/voicewriter/voicedatawriter.cpp index c53ed50..568884a 100644 --- a/demboyz/io/voicewriter/voicedatawriter.cpp +++ b/demboyz/io/voicewriter/voicedatawriter.cpp @@ -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,10 +222,10 @@ 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); - state.sampleRate = 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); diff --git a/demboyz/io/voicewriter/voicedatawriter.h b/demboyz/io/voicewriter/voicedatawriter.h index 48a74d2..f52b822 100644 --- a/demboyz/io/voicewriter/voicedatawriter.h +++ b/demboyz/io/voicewriter/voicedatawriter.h @@ -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;