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,66 +704,60 @@ 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);
//smutils->LogMessage(myself, "TotalLength: %d", m_Buffer.TotalLength());
smutils->LogMessage(myself, "TimeAvailable: %f", TimeAvailable);
// 5 = max frames per packet
FramesAvailable = min_ext(FramesAvailable, 5); FramesAvailable = min_ext(FramesAvailable, 5);
// 0 = SourceTV
IClient *pClient = iserver->GetClient(0); IClient *pClient = iserver->GetClient(0);
if(!pClient) if(!pClient)
return; return;
SteamVoiceHeader Header; unsigned char aFinal[8192];
size_t HeaderSize = 14; size_t FinalSize = 0;
Header.iSteamAccountID = 1; // Steam Account ID
Header.iSteamCommunity = 0x01100001; // Steam Community ID part: 0x01100001 << 32
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). // 1. Steam Account ID (4 bytes)
uint32_t steamID = 1;
memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
unsigned char aFinal[8192]; // A large buffer for the final packet. // 2. Steam Community (4 bytes)
size_t FinalSize = HeaderSize; 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)
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];
@ -771,23 +768,30 @@ void CVoice::HandleVoiceData()
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
*pFrameSize = (uint16_t)nbBytes;
*pTotalDataLength += sizeof(uint16_t) + nbBytes;
FinalSize += nbBytes;
// Buffer underrun check
for(int Client = 0; Client < MAX_CLIENTS; Client++) for(int Client = 0; Client < MAX_CLIENTS; Client++)
{ {
CClient *pClient = &m_aClients[Client]; CClient *pClient = &m_aClients[Client];
@ -795,7 +799,6 @@ void CVoice::HandleVoiceData()
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,7 +5,7 @@
#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))
{ {
@ -14,6 +14,7 @@ CRingBuffer::CRingBuffer() : m_BufferSize(sizeof(m_aBuffer) / sizeof(*m_aBuffer)
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())
@ -39,14 +40,12 @@ bool CRingBuffer::Pop(int16_t *pBuffer, size_t Samples)
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]; int16_t *pBuffer = &m_aBuffer[m_WriteIndex];
while(Samples--) while(Samples--)
{ {
@ -65,15 +64,15 @@ void CRingBuffer::Mix(int16_t *pData, size_t Samples)
} }
} }
// 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)
@ -99,7 +98,6 @@ bool CRingBuffer::Push(int16_t *pData, size_t Samples)
Samples -= ToMix; Samples -= ToMix;
} }
//
if(!Samples) if(!Samples)
return true; return true;
@ -123,10 +121,10 @@ bool CRingBuffer::Push(int16_t *pData, size_t Samples)
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;
@ -160,5 +158,5 @@ size_t CRingBuffer::GetWriteIndex()
void CRingBuffer::SetWriteIndex(size_t WriteIndex) void CRingBuffer::SetWriteIndex(size_t WriteIndex)
{ {
m_WriteIndex = WriteIndex; m_WriteIndex = WriteIndex % m_BufferSize;
} }