sending multi packets for nosteamers. reducing torchlight to 24000 hertz and mono channel at 32k bitrate

This commit is contained in:
jenz 2026-06-04 00:34:30 +01:00
parent 718284e7dd
commit ddad8421e7
2 changed files with 140 additions and 192 deletions

View File

@ -284,6 +284,7 @@ CVoice::CVoice()
m_aClients[i].m_Socket = -1; m_aClients[i].m_Socket = -1;
m_OpusEncoder = NULL; m_OpusEncoder = NULL;
m_TorchlightSeqNum = 0;
m_AvailableTime = 0.0; m_AvailableTime = 0.0;
m_VoiceDetour = NULL; m_VoiceDetour = NULL;
@ -429,19 +430,18 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
//opus edit //opus edit
int err; int err;
m_OpusEncoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_AUDIO, &err); m_OpusEncoder = opus_encoder_create(24000, 1, OPUS_APPLICATION_AUDIO, &err);
//m_OpusEncoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_AUDIO, &err);
if (err<0) if (err<0)
{ {
smutils->LogError(myself, "failed to create encode: %s", opus_strerror(err)); smutils->LogError(myself, "failed to create encode: %s", opus_strerror(err));
return false; return false;
} }
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BITRATE(128000)); //opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BITRATE(128000));
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BITRATE(32000));
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND)); //required for forcing hybrid mode to get TOC 0x68
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); //MDCT mode opus_encoder_ctl(m_OpusEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); //MDCT mode
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_LSB_DEPTH(16));
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_PACKET_LOSS_PERC(0));
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_FORCE_CHANNELS(2));
if (err<0) if (err<0)
{ {
@ -771,7 +771,7 @@ void CVoice::SDK_OnUnload()
void CVoice::OnGameFrame(bool simulating) void CVoice::OnGameFrame(bool simulating)
{ {
HandleNetwork(); HandleNetwork();
HandleVoiceData(); //torchlight audio emitting HandleVoiceDataTorchlight(); //torchlight audio emitting
HandleNoSteamVoiceData(); //send opus packets to steamers. HandleNoSteamVoiceData(); //send opus packets to steamers.
//send celt packets to nosteamers //send celt packets to nosteamers
@ -784,7 +784,7 @@ void CVoice::OnGameFrame(bool simulating)
// Keep draining 512-sample blocks until the buffer has less than one full frame remaining // Keep draining 512-sample blocks until the buffer has less than one full frame remaining
while (m_playerVoiceBuffer[i].TotalLength() >= m_CeltEncoderSettings.FrameSize) while (m_playerVoiceBuffer[i].TotalLength() >= m_CeltEncoderSettings.FrameSize)
{ {
HandlePlayerVoiceData(i); HandlePlayerVoiceData(i); //send celt packets to nosteamers
} }
} }
@ -971,130 +971,96 @@ void CVoice::OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples)
pClient->m_LastValidData = getTime(); pClient->m_LastValidData = getTime();
} }
void CVoice::HandleVoiceDataTorchlight()
void CVoice::HandleVoiceData()
{ {
uint32_t sampleRate = 48000; const uint32_t sampleRate = 24000;
const int SamplesPerChannel = 480; const int SamplesPerFrame = 480;
const int Channels = 2;
int TotalSamplesPerFrame = SamplesPerChannel * Channels;
int FramesAvailable = m_Buffer.TotalLength() / TotalSamplesPerFrame; if (m_Buffer.TotalLength() < SamplesPerFrame)
if(!FramesAvailable)
return;
//int FramesMax = 5;
int FramesMax = 2; //used to be 5
bool reset_state = false;
if (FramesAvailable <= FramesMax)
{
reset_state = true;
}
FramesAvailable = min_ext(FramesAvailable, FramesMax);
// Allow less buffering after audio has started playing
if (m_Buffer.TotalLength() < TotalSamplesPerFrame)
return; return;
float TimeAvailable = (float)m_Buffer.TotalLength() / sampleRate; float TimeAvailable = (float)m_Buffer.TotalLength() / sampleRate;
if (m_AvailableTime < getTime() && TimeAvailable < 0.2) if (m_AvailableTime < getTime() && TimeAvailable < 0.2)
return; return;
double maxBuffer = (m_AvailableTime < getTime()) ? 0.2 : 0.04; if (m_AvailableTime > getTime() + 0.2)
if (m_AvailableTime > getTime() + maxBuffer) {
return; return;
}
IClient *pClient = iserver->GetClient(0); IClient *pClient = iserver->GetClient(0);
if(!pClient) if (!pClient)
return; return;
unsigned char aFinal[8192]; unsigned char aFinal[8192];
size_t FinalSize = 0; size_t FinalSize = 0;
// 1. Steam Account ID (4 bytes)
uint32_t steamID = 1; uint32_t steamID = 1;
memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t)); memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
FinalSize += sizeof(uint32_t); FinalSize += sizeof(uint32_t);
// 2. Steam Community (4 bytes)
uint32_t steamCommunity = 0x01100001; uint32_t steamCommunity = 0x01100001;
memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t)); memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t));
FinalSize += sizeof(uint32_t); FinalSize += sizeof(uint32_t);
// 3. Payload Type 11 (1 byte)
aFinal[FinalSize++] = 0x0B; aFinal[FinalSize++] = 0x0B;
// 4. Sample Rate (2 bytes little-endian)
memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t)); memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t));
FinalSize += sizeof(uint16_t); FinalSize += sizeof(uint16_t);
aFinal[FinalSize++] = 0x06;
// 5. Payload Type 5 for Opus. 6 for opus plc (1 byte) size_t totalDataLengthOffset = FinalSize;
aFinal[FinalSize++] = 0x05;
// 6. Reserve space for total data length (2 bytes little-endian)
uint16_t *pTotalDataLength = (uint16_t*)&aFinal[FinalSize];
FinalSize += sizeof(uint16_t); FinalSize += sizeof(uint16_t);
*pTotalDataLength = 0;
// 7. Encode frames size_t subframeStart = FinalSize;
for(int Frame = 0; Frame < FramesAvailable; Frame++) int framesEmitted = 0;
while (m_Buffer.TotalLength() >= SamplesPerFrame && framesEmitted < 5)
{ {
int16_t aBuffer[TotalSamplesPerFrame]; int16_t aBuffer[SamplesPerFrame];
if (!m_Buffer.Pop(aBuffer, SamplesPerFrame))
break;
size_t OldReadIdx = m_Buffer.m_ReadIndex; size_t headerOffset = FinalSize;
size_t OldCurLength = m_Buffer.CurrentLength(); size_t opusOffset = FinalSize + 4;
size_t OldTotalLength = m_Buffer.TotalLength();
if(!m_Buffer.Pop(aBuffer, TotalSamplesPerFrame)) int nbBytes = opus_encode(m_OpusEncoder,
(const opus_int16*)aBuffer, SamplesPerFrame,
&aFinal[opusOffset], sizeof(aFinal) - opusOffset - 4);
if (nbBytes <= 0)
{ {
smutils->LogError(myself, "Buffer pop failed!"); smutils->LogError(myself, "HandleVoiceDataTorchlight: opus_encode failed: %s",
return; opus_strerror(nbBytes));
break;
} }
uint16_t *pFrameSize = (uint16_t*)&aFinal[FinalSize]; BroadcastVoiceDataTorchlightCelt(pClient, aBuffer, SamplesPerFrame);
FinalSize += sizeof(uint16_t);
*pFrameSize = sizeof(aFinal) - sizeof(uint32_t) - FinalSize;
// Encode with Opus aFinal[headerOffset + 0] = (uint8_t)nbBytes;
//pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) aFinal[headerOffset + 1] = 0x00;
int nbBytes = opus_encode(m_OpusEncoder, (const opus_int16*)aBuffer, SamplesPerChannel, aFinal[headerOffset + 2] = (uint8_t)(m_TorchlightSeqNum++ & 0xFF);
&aFinal[FinalSize], *pFrameSize); aFinal[headerOffset + 3] = 0x00;
if (nbBytes <= 1)
{
smutils->LogError(myself, "Opus encode failed: %s", opus_strerror(nbBytes));
return;
}
BroadcastVoiceDataTorchlightCelt(pClient, aBuffer, SamplesPerChannel);
// Write frame size FinalSize = opusOffset + nbBytes;
*pFrameSize = (uint16_t)nbBytes; framesEmitted++;
*pTotalDataLength += sizeof(uint16_t) + nbBytes;
FinalSize += nbBytes;
// Check for buffer underruns
for(int Client = 0; Client < MAX_CLIENTS; Client++)
{
CClient *pClient = &m_aClients[Client];
if(pClient->m_Socket == -1 || pClient->m_New == true)
continue;
m_Buffer.SetWriteIndex(pClient->m_BufferWriteIndex);
if(m_Buffer.CurrentLength() > pClient->m_LastLength)
{
pClient->m_BufferWriteIndex = m_Buffer.GetReadIndex();
m_Buffer.SetWriteIndex(pClient->m_BufferWriteIndex);
pClient->m_LastLength = m_Buffer.CurrentLength();
}
}
} }
// 8. Add CRC32
if (framesEmitted == 0)
return;
if (m_Buffer.TotalLength() < SamplesPerFrame)
{
opus_encoder_ctl(m_OpusEncoder, OPUS_RESET_STATE);
}
uint16_t totalDataLength = (uint16_t)(FinalSize - subframeStart);
memcpy(&aFinal[totalDataLengthOffset], &totalDataLength, sizeof(uint16_t));
uint32_t crc32_value = UTIL_CRC32(aFinal, FinalSize); uint32_t crc32_value = UTIL_CRC32(aFinal, FinalSize);
memcpy(&aFinal[FinalSize], &crc32_value, sizeof(uint32_t)); memcpy(&aFinal[FinalSize], &crc32_value, sizeof(uint32_t));
FinalSize += sizeof(uint32_t); FinalSize += sizeof(uint32_t);
int maxClients = iserver->GetClientCount(); int maxClients = iserver->GetClientCount();
for (int i = 0; i < maxClients; i++) for (int i = 0; i < maxClients; i++)
{ {
@ -1104,28 +1070,19 @@ void CVoice::HandleVoiceData()
if (g_bClientMuted[i + 1][pClient->GetPlayerSlot()]) if (g_bClientMuted[i + 1][pClient->GetPlayerSlot()])
continue; continue;
if (!g_bIsNoSteam[i + 1]) if (!g_bIsNoSteam[i + 1])
{
//sending opus packets to steam users.
SendVoiceDataMsg(pClient->GetPlayerSlot(), pToClient, aFinal, FinalSize, 0); SendVoiceDataMsg(pClient->GetPlayerSlot(), pToClient, aFinal, FinalSize, 0);
}
} }
if (m_AvailableTime < getTime()) if (m_AvailableTime < getTime())
m_AvailableTime = getTime(); m_AvailableTime = getTime();
m_AvailableTime += (double)FramesAvailable * 0.01; m_AvailableTime += (double)framesEmitted * (480.0 / 24000.0);
if (reset_state)
{
opus_encoder_ctl(m_OpusEncoder, OPUS_RESET_STATE);
}
} }
void CVoice::BroadcastVoiceDataTorchlightCelt(IClient *pClient, int16_t *pPCM, int nSamples) void CVoice::BroadcastVoiceDataTorchlightCelt(IClient *pClient, int16_t *pPCM, int nSamples)
{ {
// pPCM is stereo interleaved at 48000 Hz (SamplesPerChannel * 2 total samples)
// Downsample to mono 22050 Hz and accumulate into m_torchMonoAccum // Downsample to mono 22050 Hz and accumulate into m_torchMonoAccum
const int STEP = m_CeltEncoderSettings.SampleRate_Hz; // 22050 const int STEP = m_CeltEncoderSettings.SampleRate_Hz; // 22050
const int DIV = 48000; const int DIV = 24000;
const int CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize; // 512 const int CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize; // 512
const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; // 64 const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; // 64
@ -1137,7 +1094,7 @@ void CVoice::BroadcastVoiceDataTorchlightCelt(IClient *pClient, int16_t *pPCM, i
if (m_torchMonoAccumLen >= 4096) if (m_torchMonoAccumLen >= 4096)
break; break;
int32_t mono = ((int32_t)pPCM[i * 2] + (int32_t)pPCM[i * 2 + 1]) / 2; int32_t mono = pPCM[i];
if (mono > 32767) mono = 32767; if (mono > 32767) mono = 32767;
if (mono < -32768) mono = -32768; if (mono < -32768) mono = -32768;
@ -1464,18 +1421,60 @@ void CVoice::HandleNoSteamVoiceData()
if (m_nosteamAvailableTime[playerSlot] > getTime() + 1.0) if (m_nosteamAvailableTime[playerSlot] > getTime() + 1.0)
continue; continue;
IClient *pNoSteamClient = iserver->GetClient(playerSlot);
if (!pNoSteamClient)
continue;
const char *networkID = pNoSteamClient->GetNetworkIDString();
uint32_t accountID = 0;
const char *numStart = strrchr(networkID, ':');
if (numStart)
accountID = (uint32_t)atoi(numStart + 1);
unsigned char aFinal[8192];
size_t FinalSize = 0;
// Steam voice packet header (12 bytes fixed)
uint32_t steamID = accountID;
uint32_t steamCommunity = 0x01100001;
memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
aFinal[FinalSize++] = 0x0B; // payload type
uint16_t sampleRate = 24000;
memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
aFinal[FinalSize++] = 0x06; // codec type Opus PLC
// Reserve totalDataLength field
size_t totalDataLengthOffset = FinalSize;
FinalSize += sizeof(uint16_t);
// Encode as many 480-sample frames as we can fit
size_t subframeStart = FinalSize;
int framesEmitted = 0; int framesEmitted = 0;
while (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() >= 480) while (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() >= 480)
{ {
if (framesEmitted >= 1)
break;
int16_t opusInput[480]; int16_t opusInput[480];
if (!m_nosteamOpusPCMBuffer[playerSlot].Pop(opusInput, 480)) if (!m_nosteamOpusPCMBuffer[playerSlot].Pop(opusInput, 480))
break; break;
unsigned char opusBuf[512]; // Encode directly into aFinal after the 4-byte subframe header
int opusBytes = opus_encode(m_nosteamOpusEncoder[playerSlot], opusInput, 480, opusBuf, sizeof(opusBuf)); size_t headerOffset = FinalSize;
size_t opusOffset = FinalSize + 4;
if (opusOffset + 512 > sizeof(aFinal) - 4) // leave room for CRC
break;
int opusBytes = opus_encode(
m_nosteamOpusEncoder[playerSlot],
opusInput, 480,
&aFinal[opusOffset], 512
);
if (opusBytes < 0) if (opusBytes < 0)
{ {
smutils->LogError(myself, "HandleNoSteamVoiceData: opus_encode failed: %s", smutils->LogError(myself, "HandleNoSteamVoiceData: opus_encode failed: %s",
@ -1483,93 +1482,40 @@ void CVoice::HandleNoSteamVoiceData()
break; break;
} }
IClient *pNoSteamClient = iserver->GetClient(playerSlot); // Write 4-byte subframe header
if (!pNoSteamClient) // [frameSize(1)] [0x00] [seqNum(1)] [0x00]
aFinal[headerOffset + 0] = (uint8_t)opusBytes;
aFinal[headerOffset + 1] = 0x00;
aFinal[headerOffset + 2] = (uint8_t)(m_nosteamSeqNum[playerSlot]++ & 0xFF);
aFinal[headerOffset + 3] = 0x00;
FinalSize = opusOffset + opusBytes;
framesEmitted++;
}
if (framesEmitted == 0)
continue;
// Fill in totalDataLength
uint16_t totalDataLength = (uint16_t)(FinalSize - subframeStart);
memcpy(&aFinal[totalDataLengthOffset], &totalDataLength, sizeof(uint16_t));
// Append CRC32
uint32_t crc = UTIL_CRC32(aFinal, FinalSize);
memcpy(&aFinal[FinalSize], &crc, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient())
continue;
if (g_bIsNoSteam[i + 1])
continue;
if (g_bClientMuted[i + 1][playerSlot + 1])
continue; continue;
const char *networkID = pNoSteamClient->GetNetworkIDString(); SendVoiceDataMsg(playerSlot, pToClient, aFinal, (int)FinalSize, 0);
uint32_t accountID = 0;
// Parse [U:1:XXXXXXXX] format
const char *numStart = strrchr(networkID, ':');
if (numStart)
accountID = (uint32_t)atoi(numStart + 1);
// Build modern Steam voice packet with working routing blocks
unsigned char aFinal[8192];
size_t FinalSize = 0;
// Build Steam voice packet header
uint32_t steamID = accountID;
uint32_t steamCommunity = 0x01100001;
memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
// 2. Payload Type (1 byte)
aFinal[FinalSize++] = 0x0B;
// 3. Sample Rate (2 bytes little-endian)
uint16_t sampleRate = 24000;
memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
// 4. Codec Type (1 byte) - 0x06 for Opus PLC
aFinal[FinalSize++] = 0x06;
// 5. Track where Size 1 lives
size_t totalDataLengthOffset = FinalSize;
FinalSize += sizeof(uint16_t); // Reserve 2 bytes
// 6. Sub-Frame Header size (2 bytes) - MUST be exactly raw opusBytes count
uint16_t frameSize = (uint16_t)opusBytes;
memcpy(&aFinal[FinalSize], &frameSize, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
// 7. Sequence Number (2 bytes)
uint16_t seq = m_nosteamSeqNum[playerSlot]++;
memcpy(&aFinal[FinalSize], &seq, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
// 8. Raw Opus Audio Payload
memcpy(&aFinal[FinalSize], opusBuf, opusBytes);
FinalSize += opusBytes;
uint16_t totalRemainingBytes = (uint16_t)(sizeof(uint16_t) + sizeof(uint16_t) + opusBytes);
memcpy(&aFinal[totalDataLengthOffset], &totalRemainingBytes, sizeof(uint16_t));
// 10. Compute and append CRC32 checksum over the complete array built so far
uint32_t crc = UTIL_CRC32(aFinal, FinalSize);
memcpy(&aFinal[FinalSize], &crc, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient())
continue;
if (g_bIsNoSteam[i + 1])
continue;
if (g_bClientMuted[i + 1][playerSlot + 1])
continue;
// debugging voice packets
/*
int dumpLen = (int)FinalSize < 32 ? (int)FinalSize : 32;
char hexBuf[32 * 3 + 1];
int pos = 0;
for (int i = 0; i < dumpLen; i++)
{
static const char hex[] = "0123456789ABCDEF";
hexBuf[pos++] = hex[(aFinal[i] >> 4) & 0xF];
hexBuf[pos++] = hex[aFinal[i] & 0xF];
hexBuf[pos++] = ' ';
}
hexBuf[pos] = '\0';
smutils->LogMessage(myself, "CVoice::HandleNoSteamVoiceData packet: FinalSize=%d first %d bytes: %s", (int)FinalSize, dumpLen, hexBuf);
*/
SendVoiceDataMsg(playerSlot, pToClient, aFinal, (int)FinalSize, 0);
}
framesEmitted++;
} }
if (framesEmitted > 0) if (framesEmitted > 0)

View File

@ -211,9 +211,11 @@ private:
double m_nosteamAvailableTime[SM_MAXPLAYERS + 1]; double m_nosteamAvailableTime[SM_MAXPLAYERS + 1];
int m_TorchlightSeqNum;
void HandleNetwork(); void HandleNetwork();
void OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples); void OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples);
void HandleVoiceData(); void HandleVoiceDataTorchlight();
void HandleNoSteamVoiceData(); void HandleNoSteamVoiceData();
}; };