it plays audio
This commit is contained in:
parent
bb71861348
commit
678155ff57
149
extension.cpp
149
extension.cpp
@ -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)
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user