it plays audio

This commit is contained in:
jenz 2025-10-12 20:51:42 +01:00
parent bb71861348
commit 678155ff57
2 changed files with 180 additions and 177 deletions

View File

@ -300,21 +300,26 @@ 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(48000, 2, OPUS_APPLICATION_VOIP, &err);
//m_OpusEncoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_AUDIO, &err);I //content, broadcast, and applications requiring less than 15 ms of coding delay.
m_OpusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &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;
} }
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BITRATE(64000)); //*(_DWORD *)(v0 + 12) = 32000; opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BITRATE(48000));
//i dont know if these actually matter. opus_encoder_ctl(m_OpusEncoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); // Force SILK mode
/* opus_encoder_ctl(m_OpusEncoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND));
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_FORCE_CHANNELS(1)); opus_encoder_ctl(m_OpusEncoder, OPUS_SET_COMPLEXITY(10));
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_INBAND_FEC(1)); opus_encoder_ctl(m_OpusEncoder, OPUS_SET_VBR(0));
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_PACKET_LOSS_PERC(10));
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_COMPLEXITY(10)); opus_encoder_ctl(m_OpusEncoder, OPUS_SET_DTX(0));
err = opus_encoder_ctl(m_OpusEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); opus_encoder_ctl(m_OpusEncoder, OPUS_SET_INBAND_FEC(1));
*/ opus_encoder_ctl(m_OpusEncoder, OPUS_SET_PACKET_LOSS_PERC(15));
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_PREDICTION_DISABLED(0)); // Enable prediction
opus_encoder_ctl(m_OpusEncoder, OPUS_SET_LSB_DEPTH(16)); // 16-bit samples
if (err<0) if (err<0)
{ {
smutils->LogError(myself, "failed to set bitrate: %s\n", opus_strerror(err)); smutils->LogError(myself, "failed to set bitrate: %s\n", opus_strerror(err));
@ -669,7 +674,7 @@ void CVoice::OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples)
// Discard empty data if last vaild data was more than a second ago. // Discard empty data if last vaild data was more than a second ago.
if(pClient->m_LastValidData + 1.0 < getTime()) if(pClient->m_LastValidData + 1.0 < getTime())
{ {
// All emptyTotalSamplesPerFrame // All empty
if(DataStartsAt == -1) if(DataStartsAt == -1)
return; return;
@ -678,8 +683,6 @@ void CVoice::OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples)
Samples -= DataStartsAt; Samples -= DataStartsAt;
} }
smutils->LogMessage(myself, "OnDataReceived samples: %d", Samples);
if(!m_Buffer.Push(pData, Samples)) if(!m_Buffer.Push(pData, Samples))
{ {
smutils->LogError(myself, "Buffer push failed!!! Samples: %u, Free: %u\n", Samples, m_Buffer.CurrentFree()); smutils->LogError(myself, "Buffer push failed!!! Samples: %u, Free: %u\n", Samples, m_Buffer.CurrentFree());
@ -701,101 +704,101 @@ struct SteamVoiceHeader
void CVoice::HandleVoiceData() void CVoice::HandleVoiceData()
{ {
//const int SampleRate = 24000;
const int SampleRate = 48000; const int SampleRate = 48000;
// This MUST be the number of SAMPLES PER CHANNEL (960 samples = 20ms at 48kHz) const int SamplesPerChannel = 480;
//const int SamplesPerChannel = 480; const int Channels = 1;
const int SamplesPerChannel = 960; int TotalSamplesPerFrame = SamplesPerChannel * Channels;
const int Channels = 2;
int FramesAvailable = m_Buffer.TotalLength() / SamplesPerChannel; int FramesAvailable = m_Buffer.TotalLength() / TotalSamplesPerFrame;
float TimeAvailable = (float)m_Buffer.TotalLength() / (SampleRate); float TimeAvailable = (float)m_Buffer.TotalLength() / SampleRate;
if(!FramesAvailable) if(!FramesAvailable)
return; return;
//if(m_AvailableTime < getTime() && TimeAvailable < 0.1)
// Before starting playback we want at least 100ms in the buffer if(m_AvailableTime < getTime() && TimeAvailable < 0.05)
if(m_AvailableTime < getTime() && TimeAvailable < 0.1)
{
//smutils->LogMessage(myself, "return 1");
return; return;
}
//let the clients have no more than 500ms
if(m_AvailableTime > getTime() + 0.5) if(m_AvailableTime > getTime() + 0.5)
{
//smutils->LogMessage(myself, "return 2");
return; return;
} if (m_Buffer.TotalLength() < TotalSamplesPerFrame)
// The Opus encoder requires a complete frame.
int TotalSamplesPerFrame = SamplesPerChannel * Channels;
if (m_Buffer.TotalLength() < TotalSamplesPerFrame) {
return; return;
}
smutils->LogMessage(myself, "pre FramesAvailable: %d", FramesAvailable); FramesAvailable = min_ext(FramesAvailable, 5);
//smutils->LogMessage(myself, "TotalLength: %d", m_Buffer.TotalLength());
smutils->LogMessage(myself, "TimeAvailable: %f", TimeAvailable);
// 5 = max frames per packet IClient *pClient = iserver->GetClient(0);
FramesAvailable = min_ext(FramesAvailable, 5); if(!pClient)
return;
// 0 = SourceTV unsigned char aFinal[8192];
IClient *pClient = iserver->GetClient(0); size_t FinalSize = 0;
if(!pClient)
return;
SteamVoiceHeader Header; // 1. Steam Account ID (4 bytes)
size_t HeaderSize = 14; uint32_t steamID = 1;
Header.iSteamAccountID = 1; // Steam Account ID memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
Header.iSteamCommunity = 0x01100001; // Steam Community ID part: 0x01100001 << 32 FinalSize += sizeof(uint32_t);
Header.nPayload1 = 11; // nPayLoad | Type 11 =
Header.iSampleRate = SampleRate; //
Header.nPayload2 = 0x06; //type 6 = opus PLC?
//Header.nPayload2 = 4; // nPayLoad | Type 4 = Silk Frames
Header.iDataLength = 0; // Silk Frames total length
//unsigned char aFinal[4000]; //max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). // 2. Steam Community (4 bytes)
uint32_t steamCommunity = 0x01100001;
memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
unsigned char aFinal[8192]; // A large buffer for the final packet. // 3. Payload Type 11 (1 byte)
size_t FinalSize = HeaderSize; aFinal[FinalSize++] = 0x0B;
// 4. Sample Rate (2 bytes little-endian)
uint16_t sampleRate = 48000;
memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t));
FinalSize += sizeof(uint16_t);
// 5. Payload Type 6 for Opus (1 byte)
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);
*pTotalDataLength = 0;
// 7. Encode frames
for(int Frame = 0; Frame < FramesAvailable; Frame++) for(int Frame = 0; Frame < FramesAvailable; Frame++)
{ {
int16_t aBuffer[TotalSamplesPerFrame]; int16_t aBuffer[TotalSamplesPerFrame];
size_t OldReadIdx = m_Buffer.m_ReadIndex; size_t OldReadIdx = m_Buffer.m_ReadIndex;
size_t OldCurLength = m_Buffer.CurrentLength(); size_t OldCurLength = m_Buffer.CurrentLength();
size_t OldTotalLength = m_Buffer.TotalLength(); size_t OldTotalLength = m_Buffer.TotalLength();
if(!m_Buffer.Pop(aBuffer, TotalSamplesPerFrame)) if(!m_Buffer.Pop(aBuffer, TotalSamplesPerFrame))
{ {
smutils->LogError(myself, "Buffer pop failed!!! Samples: %u, Length: %zu\n", TotalSamplesPerFrame, m_Buffer.TotalLength()); smutils->LogError(myself, "Buffer pop failed!");
return; return;
} }
FinalSize += sizeof(int16_t); uint16_t *pFrameSize = (uint16_t*)&aFinal[FinalSize];
Header.iDataLength += sizeof(int16_t); FinalSize += sizeof(uint16_t);
*pFrameSize = sizeof(aFinal) - sizeof(uint32_t) - FinalSize;
// opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) // Encode with Opus
int nbBytes = opus_encode(m_OpusEncoder, (const opus_int16*)aBuffer, SamplesPerChannel, &aFinal[FinalSize], sizeof(aFinal)); int nbBytes = opus_encode(m_OpusEncoder, (const opus_int16*)aBuffer, SamplesPerChannel,
// Handle DTX (Discontinuous Transmission). If the length is 1, no data should be sent. &aFinal[FinalSize], *pFrameSize);
if (nbBytes<=1)
if (nbBytes <= 1)
{ {
smutils->LogError(myself, "encode failed: %s\n", opus_strerror(nbBytes)); smutils->LogError(myself, "Opus encode failed: %s", opus_strerror(nbBytes));
return; return;
} }
// Check for buffer underruns // Write frame size
for(int Client = 0; Client < MAX_CLIENTS; Client++) *pFrameSize = (uint16_t)nbBytes;
{ *pTotalDataLength += sizeof(uint16_t) + nbBytes;
FinalSize += nbBytes;
// Buffer underrun check
for(int Client = 0; Client < MAX_CLIENTS; Client++)
{
CClient *pClient = &m_aClients[Client]; CClient *pClient = &m_aClients[Client];
if(pClient->m_Socket == -1 || pClient->m_New == true) if(pClient->m_Socket == -1 || pClient->m_New == true)
continue; continue;
m_Buffer.SetWriteIndex(pClient->m_BufferWriteIndex); m_Buffer.SetWriteIndex(pClient->m_BufferWriteIndex);
if(m_Buffer.CurrentLength() > pClient->m_LastLength) if(m_Buffer.CurrentLength() > pClient->m_LastLength)
{ {
pClient->m_BufferWriteIndex = m_Buffer.GetReadIndex(); pClient->m_BufferWriteIndex = m_Buffer.GetReadIndex();
@ -803,24 +806,26 @@ void CVoice::HandleVoiceData()
pClient->m_LastLength = m_Buffer.CurrentLength(); pClient->m_LastLength = m_Buffer.CurrentLength();
} }
} }
Header.iDataLength += nbBytes;
FinalSize += nbBytes;
} }
memcpy(&aFinal[0], &Header, HeaderSize);
// Calculate the total packet size for CRC32 calculation. // 8. Add CRC32
//these 3 lines are fine
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);
//18 in distance between finalsize and idatalength, 14 for header bytes and 4 for CRC. /*
smutils->LogMessage(myself, "FinalSize: %d. iDataLength: %d. length of aFinal: %d", FinalSize, Header.iDataLength, sizeof(aFinal)); smutils->LogMessage(myself, "=== OPUS PACKET ===");
smutils->LogMessage(myself, "FinalSize: %d, Frames: %d", FinalSize, FramesAvailable);
smutils->LogMessage(myself, "Header bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
aFinal[0], aFinal[1], aFinal[2], aFinal[3], aFinal[4], aFinal[5], aFinal[6],
aFinal[7], aFinal[8], aFinal[9], aFinal[10], aFinal[11], aFinal[12], aFinal[13]);
*/
BroadcastVoiceData(pClient, FinalSize, aFinal); BroadcastVoiceData(pClient, FinalSize, aFinal);
if (m_AvailableTime < getTime()) if (m_AvailableTime < getTime())
m_AvailableTime = getTime(); m_AvailableTime = getTime();
m_AvailableTime += (double)FramesAvailable * ((double)20 / 1000.0); m_AvailableTime += (double)FramesAvailable * 0.01;
} }
void CVoice::BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData) void CVoice::BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData)

View File

@ -5,160 +5,158 @@
#undef NDEBUG #undef NDEBUG
#include <assert.h> #include <assert.h>
template <typename T> inline T min(T a, T b) { return a<b?a:b; } template <typename T> inline T min(T a, T b) { return a < b ? a : b; }
CRingBuffer::CRingBuffer() : m_BufferSize(sizeof(m_aBuffer) / sizeof(*m_aBuffer)) CRingBuffer::CRingBuffer() : m_BufferSize(sizeof(m_aBuffer) / sizeof(*m_aBuffer))
{ {
m_ReadIndex = 0; m_ReadIndex = 0;
m_WriteIndex = 0; m_WriteIndex = 0;
m_Length = 0; m_Length = 0;
} }
// Pop `Samples` int16_t values from buffer
bool CRingBuffer::Pop(int16_t *pBuffer, size_t Samples) bool CRingBuffer::Pop(int16_t *pBuffer, size_t Samples)
{ {
if(Samples > TotalLength()) if(Samples > TotalLength())
return false; return false;
if(m_ReadIndex + Samples > m_BufferSize) if(m_ReadIndex + Samples > m_BufferSize)
{ {
size_t TowardsEnd = m_BufferSize - m_ReadIndex; size_t TowardsEnd = m_BufferSize - m_ReadIndex;
memcpy(pBuffer, &m_aBuffer[m_ReadIndex], TowardsEnd * sizeof(*m_aBuffer)); memcpy(pBuffer, &m_aBuffer[m_ReadIndex], TowardsEnd * sizeof(*m_aBuffer));
m_ReadIndex = 0; m_ReadIndex = 0;
size_t Left = Samples - TowardsEnd; size_t Left = Samples - TowardsEnd;
memcpy(&pBuffer[TowardsEnd], m_aBuffer, Left * sizeof(*m_aBuffer)); memcpy(&pBuffer[TowardsEnd], m_aBuffer, Left * sizeof(*m_aBuffer));
m_ReadIndex = Left; m_ReadIndex = Left;
} }
else else
{ {
memcpy(pBuffer, &m_aBuffer[m_ReadIndex], Samples * sizeof(*m_aBuffer)); memcpy(pBuffer, &m_aBuffer[m_ReadIndex], Samples * sizeof(*m_aBuffer));
m_ReadIndex += Samples; m_ReadIndex += Samples;
} }
if(m_ReadIndex == m_BufferSize) if(m_ReadIndex == m_BufferSize)
m_ReadIndex = 0; m_ReadIndex = 0;
m_Length -= Samples; m_Length -= Samples;
return true;
return true;
} }
// Mix int16_t data into buffer at write index
void CRingBuffer::Mix(int16_t *pData, size_t Samples) void CRingBuffer::Mix(int16_t *pData, size_t Samples)
{ {
assert(!(m_WriteIndex + Samples > m_BufferSize)); int16_t *pBuffer = &m_aBuffer[m_WriteIndex];
while(Samples--)
{
int32_t Sample = *pBuffer;
Sample += *pData;
int16_t *pBuffer = &m_aBuffer[m_WriteIndex]; if(Sample > INT16_MAX)
while(Samples--) *pBuffer = INT16_MAX;
{ else if(Sample < INT16_MIN)
int32_t Sample = *pBuffer; *pBuffer = INT16_MIN;
Sample += *pData; else
*pBuffer = Sample;
if(Sample > INT16_MAX) pBuffer++;
*pBuffer = INT16_MAX; pData++;
else if(Sample < INT16_MIN) }
*pBuffer = INT16_MIN;
else
*pBuffer = Sample;
pBuffer++;
pData++;
}
} }
// Push `Samples` int16_t values into buffer
bool CRingBuffer::Push(int16_t *pData, size_t Samples) bool CRingBuffer::Push(int16_t *pData, size_t Samples)
{ {
if(Samples > CurrentFree()) if(Samples > CurrentFree())
return false; return false;
// Mix with data in front of us // Mix with existing data
if(CurrentLength() < TotalLength()) if(CurrentLength() < TotalLength())
{ {
// size_t ToMix = min(Samples, TotalLength() - CurrentLength());
size_t ToMix = min(Samples, TotalLength() - CurrentLength());
if(m_WriteIndex + ToMix > m_BufferSize) if(m_WriteIndex + ToMix > m_BufferSize)
{ {
size_t TowardsEnd = m_BufferSize - m_WriteIndex; size_t TowardsEnd = m_BufferSize - m_WriteIndex;
Mix(pData, TowardsEnd); Mix(pData, TowardsEnd);
m_WriteIndex = 0; m_WriteIndex = 0;
size_t Left = ToMix - TowardsEnd; size_t Left = ToMix - TowardsEnd;
Mix(&pData[TowardsEnd], Left); Mix(&pData[TowardsEnd], Left);
m_WriteIndex = Left; m_WriteIndex = Left;
} }
else else
{ {
Mix(pData, ToMix); Mix(pData, ToMix);
m_WriteIndex += ToMix; m_WriteIndex += ToMix;
} }
if(m_WriteIndex == m_BufferSize) if(m_WriteIndex == m_BufferSize)
m_WriteIndex = 0; m_WriteIndex = 0;
pData += ToMix; pData += ToMix;
Samples -= ToMix; Samples -= ToMix;
} }
// if(!Samples)
if(!Samples) return true;
return true;
if(m_WriteIndex + Samples > m_BufferSize) if(m_WriteIndex + Samples > m_BufferSize)
{ {
size_t TowardsEnd = m_BufferSize - m_WriteIndex; size_t TowardsEnd = m_BufferSize - m_WriteIndex;
memcpy(&m_aBuffer[m_WriteIndex], pData, TowardsEnd * sizeof(*m_aBuffer)); memcpy(&m_aBuffer[m_WriteIndex], pData, TowardsEnd * sizeof(*m_aBuffer));
m_WriteIndex = 0; m_WriteIndex = 0;
size_t Left = Samples - TowardsEnd; size_t Left = Samples - TowardsEnd;
memcpy(m_aBuffer, &pData[TowardsEnd], Left * sizeof(*m_aBuffer)); memcpy(m_aBuffer, &pData[TowardsEnd], Left * sizeof(*m_aBuffer));
m_WriteIndex = Left; m_WriteIndex = Left;
} }
else else
{ {
memcpy(&m_aBuffer[m_WriteIndex], pData, Samples * sizeof(*m_aBuffer)); memcpy(&m_aBuffer[m_WriteIndex], pData, Samples * sizeof(*m_aBuffer));
m_WriteIndex += Samples; m_WriteIndex += Samples;
} }
if(m_WriteIndex == m_BufferSize) if(m_WriteIndex == m_BufferSize)
m_WriteIndex = 0; m_WriteIndex = 0;
m_Length += Samples; m_Length += Samples;
return true;
return true;
} }
// Helper functions
size_t CRingBuffer::TotalLength() size_t CRingBuffer::TotalLength()
{ {
return m_Length; return m_Length;
} }
size_t CRingBuffer::TotalFree() size_t CRingBuffer::TotalFree()
{ {
return m_BufferSize - m_Length - 1; return m_BufferSize - m_Length - 1;
} }
size_t CRingBuffer::CurrentLength() size_t CRingBuffer::CurrentLength()
{ {
return ((ssize_t)m_WriteIndex - (ssize_t)m_ReadIndex) % m_BufferSize; return ((ssize_t)m_WriteIndex - (ssize_t)m_ReadIndex) % m_BufferSize;
} }
size_t CRingBuffer::CurrentFree() size_t CRingBuffer::CurrentFree()
{ {
size_t BufferFree = ((ssize_t)m_ReadIndex - (ssize_t)m_WriteIndex) % m_BufferSize; size_t BufferFree = ((ssize_t)m_ReadIndex - (ssize_t)m_WriteIndex) % m_BufferSize;
return (BufferFree ? BufferFree : m_BufferSize) - 1; return (BufferFree ? BufferFree : m_BufferSize) - 1;
} }
size_t CRingBuffer::GetReadIndex() size_t CRingBuffer::GetReadIndex()
{ {
return m_ReadIndex; return m_ReadIndex;
} }
size_t CRingBuffer::GetWriteIndex() size_t CRingBuffer::GetWriteIndex()
{ {
return m_WriteIndex; return m_WriteIndex;
} }
void CRingBuffer::SetWriteIndex(size_t WriteIndex) void CRingBuffer::SetWriteIndex(size_t WriteIndex)
{ {
m_WriteIndex = WriteIndex; m_WriteIndex = WriteIndex % m_BufferSize;
} }