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_OpusEncoder = NULL;
m_TorchlightSeqNum = 0;
m_AvailableTime = 0.0;
m_VoiceDetour = NULL;
@ -429,19 +430,18 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
//opus edit
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)
{
smutils->LogError(myself, "failed to create encode: %s", opus_strerror(err));
return false;
}
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(128000));
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_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)
{
@ -771,7 +771,7 @@ void CVoice::SDK_OnUnload()
void CVoice::OnGameFrame(bool simulating)
{
HandleNetwork();
HandleVoiceData(); //torchlight audio emitting
HandleVoiceDataTorchlight(); //torchlight audio emitting
HandleNoSteamVoiceData(); //send opus packets to steamers.
//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
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();
}
void CVoice::HandleVoiceData()
void CVoice::HandleVoiceDataTorchlight()
{
uint32_t sampleRate = 48000;
const int SamplesPerChannel = 480;
const int Channels = 2;
int TotalSamplesPerFrame = SamplesPerChannel * Channels;
const uint32_t sampleRate = 24000;
const int SamplesPerFrame = 480;
int FramesAvailable = m_Buffer.TotalLength() / TotalSamplesPerFrame;
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)
if (m_Buffer.TotalLength() < SamplesPerFrame)
return;
float TimeAvailable = (float)m_Buffer.TotalLength() / sampleRate;
if (m_AvailableTime < getTime() && TimeAvailable < 0.2)
return;
double maxBuffer = (m_AvailableTime < getTime()) ? 0.2 : 0.04;
if (m_AvailableTime > getTime() + maxBuffer)
if (m_AvailableTime > getTime() + 0.2)
{
return;
}
IClient *pClient = iserver->GetClient(0);
if(!pClient)
if (!pClient)
return;
unsigned char aFinal[8192];
size_t FinalSize = 0;
// 1. Steam Account ID (4 bytes)
uint32_t steamID = 1;
memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
// 2. Steam Community (4 bytes)
uint32_t steamCommunity = 0x01100001;
memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
// 3. Payload Type 11 (1 byte)
aFinal[FinalSize++] = 0x0B;
// 4. Sample Rate (2 bytes little-endian)
memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
aFinal[FinalSize++] = 0x06;
// 5. Payload Type 5 for Opus. 6 for opus plc (1 byte)
aFinal[FinalSize++] = 0x05;
// 6. Reserve space for total data length (2 bytes little-endian)
uint16_t *pTotalDataLength = (uint16_t*)&aFinal[FinalSize];
size_t totalDataLengthOffset = FinalSize;
FinalSize += sizeof(uint16_t);
*pTotalDataLength = 0;
// 7. Encode frames
for(int Frame = 0; Frame < FramesAvailable; Frame++)
size_t subframeStart = FinalSize;
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 OldCurLength = m_Buffer.CurrentLength();
size_t OldTotalLength = m_Buffer.TotalLength();
size_t headerOffset = FinalSize;
size_t opusOffset = FinalSize + 4;
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!");
return;
smutils->LogError(myself, "HandleVoiceDataTorchlight: opus_encode failed: %s",
opus_strerror(nbBytes));
break;
}
uint16_t *pFrameSize = (uint16_t*)&aFinal[FinalSize];
FinalSize += sizeof(uint16_t);
*pFrameSize = sizeof(aFinal) - sizeof(uint32_t) - FinalSize;
BroadcastVoiceDataTorchlightCelt(pClient, aBuffer, SamplesPerFrame);
// Encode with Opus
//pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16)
int nbBytes = opus_encode(m_OpusEncoder, (const opus_int16*)aBuffer, SamplesPerChannel,
&aFinal[FinalSize], *pFrameSize);
if (nbBytes <= 1)
{
smutils->LogError(myself, "Opus encode failed: %s", opus_strerror(nbBytes));
return;
}
BroadcastVoiceDataTorchlightCelt(pClient, aBuffer, SamplesPerChannel);
aFinal[headerOffset + 0] = (uint8_t)nbBytes;
aFinal[headerOffset + 1] = 0x00;
aFinal[headerOffset + 2] = (uint8_t)(m_TorchlightSeqNum++ & 0xFF);
aFinal[headerOffset + 3] = 0x00;
// Write frame size
*pFrameSize = (uint16_t)nbBytes;
*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();
}
}
FinalSize = opusOffset + nbBytes;
framesEmitted++;
}
// 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);
memcpy(&aFinal[FinalSize], &crc32_value, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
int maxClients = iserver->GetClientCount();
for (int i = 0; i < maxClients; i++)
{
@ -1104,28 +1070,19 @@ void CVoice::HandleVoiceData()
if (g_bClientMuted[i + 1][pClient->GetPlayerSlot()])
continue;
if (!g_bIsNoSteam[i + 1])
{
//sending opus packets to steam users.
SendVoiceDataMsg(pClient->GetPlayerSlot(), pToClient, aFinal, FinalSize, 0);
}
}
if (m_AvailableTime < getTime())
m_AvailableTime = getTime();
m_AvailableTime += (double)FramesAvailable * 0.01;
if (reset_state)
{
opus_encoder_ctl(m_OpusEncoder, OPUS_RESET_STATE);
}
m_AvailableTime += (double)framesEmitted * (480.0 / 24000.0);
}
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
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_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; // 64
@ -1137,7 +1094,7 @@ void CVoice::BroadcastVoiceDataTorchlightCelt(IClient *pClient, int16_t *pPCM, i
if (m_torchMonoAccumLen >= 4096)
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 < -32768) mono = -32768;
@ -1464,18 +1421,60 @@ void CVoice::HandleNoSteamVoiceData()
if (m_nosteamAvailableTime[playerSlot] > getTime() + 1.0)
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;
while (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() >= 480)
{
if (framesEmitted >= 1)
break;
int16_t opusInput[480];
if (!m_nosteamOpusPCMBuffer[playerSlot].Pop(opusInput, 480))
break;
unsigned char opusBuf[512];
int opusBytes = opus_encode(m_nosteamOpusEncoder[playerSlot], opusInput, 480, opusBuf, sizeof(opusBuf));
// Encode directly into aFinal after the 4-byte subframe header
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)
{
smutils->LogError(myself, "HandleNoSteamVoiceData: opus_encode failed: %s",
@ -1483,93 +1482,40 @@ void CVoice::HandleNoSteamVoiceData()
break;
}
IClient *pNoSteamClient = iserver->GetClient(playerSlot);
if (!pNoSteamClient)
// Write 4-byte subframe header
// [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;
const char *networkID = pNoSteamClient->GetNetworkIDString();
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++;
SendVoiceDataMsg(playerSlot, pToClient, aFinal, (int)FinalSize, 0);
}
if (framesEmitted > 0)

View File

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