first commit to transcode branch for supporting both nosteam celt and steam opus

This commit is contained in:
jenz 2026-05-31 00:13:47 +01:00
parent e6fb78cdf9
commit 2184fd917a
27 changed files with 1634 additions and 15 deletions

View File

@ -36,10 +36,12 @@ for sdk_name in SM.sdks:
binary.compiler.cxxincludes += [
os.path.join(SM.sm_root, 'public', 'extensions'),
os.path.join(builder.sourcePath, 'opus')
os.path.join(builder.sourcePath, 'opus'),
os.path.join(builder.sourcePath, 'celt')
]
binary.compiler.linkflags += [
os.path.join(builder.sourcePath, 'opus', 'libopus.a')
os.path.join(builder.sourcePath, 'opus', 'libopus.a'),
os.path.join(builder.sourcePath, 'celt', 'libcelt0_patched.a')
]
SM.extensions += builder.Add(project)

15
README.md Normal file
View File

@ -0,0 +1,15 @@
This new branch is used for allowing both nosteamers and linux users to communicate at the same time.
Nosteamers are stuck with CELT codec as they dont support steam/opus.
Linux users are stuck with steam as they dont support celt without manually fixing the .so file.
This branch discards BroadCastVoiceData for instead creating our own SendNetMsg using SVC_VoiceData
that enables us to take opus packets, decode to raw pcm and encode with celt.
that enables us to take celt packets, decode to raw pcm and encode with opus.
currently does the support for nosteamers to hear steam voice chat still have to be improved, but steam users can hear nosteam users very fine.
the sourcemod plugin used for stubbing nosteamers with SVC_VoiceInit: https://git.unloze.com/UNLOZE/sm-plugins/src/branch/master/CELT_VOICE

325
celt/celt.h Normal file
View File

@ -0,0 +1,325 @@
/* Copyright (c) 2007-2008 CSIRO
Copyright (c) 2007-2009 Xiph.Org Foundation
Copyright (c) 2008 Gregory Maxwell
Written by Jean-Marc Valin and Gregory Maxwell */
/**
@file celt.h
@brief Contains all the functions for encoding and decoding audio
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CELT_H
#define CELT_H
#include "celt_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__GNUC__) && defined(CELT_BUILD)
#define EXPORT __attribute__ ((visibility ("default")))
#elif defined(WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
#define _celt_check_int(x) (((void)((x) == (celt_int32)0)), (celt_int32)(x))
#define _celt_check_mode_ptr_ptr(ptr) ((ptr) + ((ptr) - (const CELTMode**)(ptr)))
#define _celt_check_int_ptr(ptr) ((ptr) + ((ptr) - (int*)(ptr)))
/* Error codes */
/** No error */
#define CELT_OK 0
/** An (or more) invalid argument (e.g. out of range) */
#define CELT_BAD_ARG -1
/** The mode struct passed is invalid */
#define CELT_BUFFER_TOO_SMALL -2
/** An internal error was detected */
#define CELT_INTERNAL_ERROR -3
/** The data passed (e.g. compressed data to decoder) is corrupted */
#define CELT_CORRUPTED_DATA -4
/** Invalid/unsupported request number */
#define CELT_UNIMPLEMENTED -5
/** An encoder or decoder structure is invalid or already freed */
#define CELT_INVALID_STATE -6
/** Memory allocation has failed */
#define CELT_ALLOC_FAIL -7
/* Encoder/decoder Requests */
#define CELT_SET_COMPLEXITY_REQUEST 2
/** Controls the complexity from 0-10 (int) */
#define CELT_SET_COMPLEXITY(x) CELT_SET_COMPLEXITY_REQUEST, _celt_check_int(x)
#define CELT_SET_PREDICTION_REQUEST 4
/** Controls the use of interframe prediction.
0=Independent frames
1=Short term interframe prediction allowed
2=Long term prediction allowed
*/
#define CELT_SET_PREDICTION(x) CELT_SET_PREDICTION_REQUEST, _celt_check_int(x)
#define CELT_SET_BITRATE_REQUEST 6
/** Set the target VBR rate in bits per second(int); 0=CBR (default) */
#define CELT_SET_BITRATE(x) CELT_SET_BITRATE_REQUEST, _celt_check_int(x)
#define CELT_RESET_STATE_REQUEST 8
/** Reset the encoder/decoder memories to zero*/
#define CELT_RESET_STATE CELT_RESET_STATE_REQUEST
#define CELT_SET_VBR_CONSTRAINT_REQUEST 10
#define CELT_SET_VBR_CONSTRAINT(x) CELT_SET_VBR_CONSTRAINT_REQUEST, _celt_check_int(x)
#define CELT_SET_VBR_REQUEST 12
#define CELT_SET_VBR(x) CELT_SET_VBR_REQUEST, _celt_check_int(x)
#define CELT_SET_INPUT_CLIPPING_REQUEST 14
#define CELT_SET_INPUT_CLIPPING(x) CELT_SET_INPUT_CLIPPING_REQUEST, _celt_check_int(x)
#define CELT_GET_AND_CLEAR_ERROR_REQUEST 15
#define CELT_GET_AND_CLEAR_ERROR(x) CELT_GET_AND_CLEAR_ERROR_REQUEST, _celt_check_int_ptr(x)
#define CELT_GET_LOOKAHEAD_REQUEST 17
#define CELT_GET_LOOKAHEAD(x) CELT_GET_LOOKAHEAD_REQUEST, _celt_check_int_ptr(x)
#define CELT_SET_CHANNELS_REQUEST 18
#define CELT_SET_CHANNELS(x) CELT_SET_CHANNELS_REQUEST, _celt_check_int(x)
#define CELT_SET_LOSS_PERC_REQUEST 20
#define CELT_SET_LOSS_PERC(x) CELT_SET_LOSS_PERC_REQUEST, _celt_check_int(x)
/* Internal */
#define CELT_SET_START_BAND_REQUEST 10000
#define CELT_SET_START_BAND(x) CELT_SET_START_BAND_REQUEST, _celt_check_int(x)
#define CELT_SET_END_BAND_REQUEST 10001
#define CELT_SET_END_BAND(x) CELT_SET_END_BAND_REQUEST, _celt_check_int(x)
/** Contains the state of an encoder. One encoder state is needed
for each stream. It is initialised once at the beginning of the
stream. Do *not* re-initialise the state for every frame.
@brief Encoder state
*/
typedef struct CELTEncoder CELTEncoder;
/** State of the decoder. One decoder state is needed for each stream.
It is initialised once at the beginning of the stream. Do *not*
re-initialise the state for every frame */
typedef struct CELTDecoder CELTDecoder;
/** The mode contains all the information necessary to create an
encoder. Both the encoder and decoder need to be initialised
with exactly the same mode, otherwise the quality will be very
bad */
typedef struct CELTMode CELTMode;
/** \defgroup codec Encoding and decoding */
/* @{ */
/* Mode calls */
/** Creates a new mode struct. This will be passed to an encoder or
decoder. The mode MUST NOT BE DESTROYED until the encoders and
decoders that use it are destroyed as well.
@param Fs Sampling rate (32000 to 96000 Hz)
@param frame_size Number of samples (per channel) to encode in each
packet (even values; 64 - 512)
@param error Returned error code (if NULL, no error will be returned)
@return A newly created mode
*/
EXPORT CELTMode *celt_mode_create(celt_int32 Fs, int frame_size, int *error);
/** Destroys a mode struct. Only call this after all encoders and
decoders using this mode are destroyed as well.
@param mode Mode to be destroyed
*/
EXPORT void celt_mode_destroy(CELTMode *mode);
/* Encoder stuff */
EXPORT int celt_encoder_get_size(int channels);
EXPORT int celt_encoder_get_size_custom(const CELTMode *mode, int channels);
/** Creates a new encoder state. Each stream needs its own encoder
state (can't be shared across simultaneous streams).
@param channels Number of channels
@param error Returns an error code
@return Newly created encoder state.
*/
EXPORT CELTEncoder *celt_encoder_create(int sampling_rate, int channels, int *error);
/** Creates a new encoder state. Each stream needs its own encoder
state (can't be shared across simultaneous streams).
@param mode Contains all the information about the characteristics of
* the stream (must be the same characteristics as used for the
* decoder)
@param channels Number of channels
@param error Returns an error code
@return Newly created encoder state.
*/
EXPORT CELTEncoder *celt_encoder_create_custom(const CELTMode *mode, int channels, int *error);
EXPORT CELTEncoder *celt_encoder_init(CELTEncoder *st, int sampling_rate, int channels, int *error);
EXPORT CELTEncoder *celt_encoder_init_custom(CELTEncoder *st, const CELTMode *mode, int channels, int *error);
/** Destroys a an encoder state.
@param st Encoder state to be destroyed
*/
EXPORT void celt_encoder_destroy(CELTEncoder *st);
/** Encodes a frame of audio.
@param st Encoder state
@param pcm PCM audio in float format, with a normal range of ±1.0.
* Samples with a range beyond ±1.0 are supported but will
* be clipped by decoders using the integer API and should
* only be used if it is known that the far end supports
* extended dynmaic range. There must be exactly
* frame_size samples per channel.
@param compressed The compressed data is written here. This may not alias pcm or
* optional_synthesis.
@param nbCompressedBytes Maximum number of bytes to use for compressing the frame
* (can change from one frame to another)
@return Number of bytes written to "compressed". Will be the same as
* "nbCompressedBytes" unless the stream is VBR and will never be larger.
* If negative, an error has occurred (see error codes). It is IMPORTANT that
* the length returned be somehow transmitted to the decoder. Otherwise, no
* decoding is possible.
*/
EXPORT int celt_encode_float(CELTEncoder *st, const float *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes);
/** Encodes a frame of audio.
@param st Encoder state
@param pcm PCM audio in signed 16-bit format (native endian). There must be
* exactly frame_size samples per channel.
@param compressed The compressed data is written here. This may not alias pcm or
* optional_synthesis.
@param nbCompressedBytes Maximum number of bytes to use for compressing the frame
* (can change from one frame to another)
@return Number of bytes written to "compressed". Will be the same as
* "nbCompressedBytes" unless the stream is VBR and will never be larger.
* If negative, an error has occurred (see error codes). It is IMPORTANT that
* the length returned be somehow transmitted to the decoder. Otherwise, no
* decoding is possible.
*/
EXPORT int celt_encode(CELTEncoder *st, const celt_int16 *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes);
/** Query and set encoder parameters
@param st Encoder state
@param request Parameter to change or query
@param value Pointer to a 32-bit int value
@return Error code
*/
EXPORT int celt_encoder_ctl(CELTEncoder * st, int request, ...);
/* Decoder stuff */
EXPORT int celt_decoder_get_size(int channels);
EXPORT int celt_decoder_get_size_custom(const CELTMode *mode, int channels);
/** Creates a new decoder state. Each stream needs its own decoder state (can't
be shared across simultaneous streams).
@param mode Contains all the information about the characteristics of the
stream (must be the same characteristics as used for the encoder)
@param channels Number of channels
@param error Returns an error code
@return Newly created decoder state.
*/
EXPORT CELTDecoder *celt_decoder_create(int sampling_rate, int channels, int *error);
/** Creates a new decoder state. Each stream needs its own decoder state (can't
be shared across simultaneous streams).
@param mode Contains all the information about the characteristics of the
stream (must be the same characteristics as used for the encoder)
@param channels Number of channels
@param error Returns an error code
@return Newly created decoder state.
*/
EXPORT CELTDecoder *celt_decoder_create_custom(const CELTMode *mode, int channels, int *error);
EXPORT CELTDecoder *celt_decoder_init(CELTDecoder *st, int sampling_rate, int channels, int *error);
EXPORT CELTDecoder *celt_decoder_init_custom(CELTDecoder *st, const CELTMode *mode, int channels, int *error);
/** Destroys a a decoder state.
@param st Decoder state to be destroyed
*/
EXPORT void celt_decoder_destroy(CELTDecoder *st);
/** Decodes a frame of audio.
@param st Decoder state
@param data Compressed data produced by an encoder
@param len Number of bytes to read from "data". This MUST be exactly the number
of bytes returned by the encoder. Using a larger value WILL NOT WORK.
@param pcm One frame (frame_size samples per channel) of decoded PCM will be
returned here in float format.
@return Error code.
*/
EXPORT int celt_decode_float(CELTDecoder *st, const unsigned char *data, int len, float *pcm, int frame_size);
/** Decodes a frame of audio.
@param st Decoder state
@param data Compressed data produced by an encoder
@param len Number of bytes to read from "data". This MUST be exactly the number
of bytes returned by the encoder. Using a larger value WILL NOT WORK.
@param pcm One frame (frame_size samples per channel) of decoded PCM will be
returned here in 16-bit PCM format (native endian).
@return Error code.
*/
EXPORT int celt_decode(CELTDecoder *st, const unsigned char *data, int len, celt_int16 *pcm, int frame_size);
/** Query and set decoder parameters
@param st Decoder state
@param request Parameter to change or query
@param value Pointer to a 32-bit int value
@return Error code
*/
EXPORT int celt_decoder_ctl(CELTDecoder * st, int request, ...);
/** Returns the English string that corresponds to an error code
* @param error Error code (negative for an error, 0 for success
* @return Constant string (must NOT be freed)
*/
EXPORT const char *celt_strerror(int error);
/* @} */
#ifdef __cplusplus
}
#endif
#endif /*CELT_H */

66
celt/celt_header.h Normal file
View File

@ -0,0 +1,66 @@
/* Copyright (c) 2007 CSIRO
Copyright (c) 2007-2008 Xiph.Org Foundation
Written by Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CELT_HEADER_H
#define CELT_HEADER_H
#ifdef __cplusplus
extern "C" {
#endif
#include "celt.h"
#include "celt_types.h"
/** Header data to be used for Ogg files (or possibly other encapsulation)
@brief Header data
*/
typedef struct {
char codec_id[8]; /**< MUST be "CELT " (four spaces) */
char codec_version[20]; /**< Version used (as string) */
celt_int32 version_id; /**< Version id (negative for until stream is frozen) */
celt_int32 header_size; /**< Size of this header */
celt_int32 sample_rate; /**< Sampling rate of the original audio */
celt_int32 nb_channels; /**< Number of channels */
celt_int32 frame_size; /**< Samples per frame (per channel) */
celt_int32 overlap; /**< Overlapping samples (per channel) */
celt_int32 bytes_per_packet; /**< Number of bytes per compressed packet (0 if unknown) */
celt_int32 extra_headers; /**< Number of additional headers that follow this header */
} CELTHeader;
/** Creates a basic header struct */
EXPORT int celt_header_init(CELTHeader *header, const CELTMode *m, int frame_size, int channels);
EXPORT int celt_header_to_packet(const CELTHeader *header, unsigned char *packet, celt_uint32 size);
EXPORT int celt_header_from_packet(const unsigned char *packet, celt_uint32 size, CELTHeader *header);
#ifdef __cplusplus
}
#endif
#endif /* CELT_HEADER_H */

151
celt/celt_types.h Normal file
View File

@ -0,0 +1,151 @@
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
/* Modified by Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* celt_types.h taken from libogg */
/**
@file celt_types.h
@brief CELT types
*/
#ifndef _CELT_TYPES_H
#define _CELT_TYPES_H
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
#if (defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
#include <stdint.h>
typedef int16_t celt_int16;
typedef uint16_t celt_uint16;
typedef int32_t celt_int32;
typedef uint32_t celt_uint32;
#elif defined(_WIN32)
# if defined(__CYGWIN__)
# include <_G_config.h>
typedef _G_int32_t celt_int32;
typedef _G_uint32_t celt_uint32;
typedef _G_int16 celt_int16;
typedef _G_uint16 celt_uint16;
# elif defined(__MINGW32__)
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef int celt_int32;
typedef unsigned int celt_uint32;
# elif defined(__MWERKS__)
typedef int celt_int32;
typedef unsigned int celt_uint32;
typedef short celt_int16;
typedef unsigned short celt_uint16;
# else
/* MSVC/Borland */
typedef __int32 celt_int32;
typedef unsigned __int32 celt_uint32;
typedef __int16 celt_int16;
typedef unsigned __int16 celt_uint16;
# endif
#elif defined(__MACOS__)
# include <sys/types.h>
typedef SInt16 celt_int16;
typedef UInt16 celt_uint16;
typedef SInt32 celt_int32;
typedef UInt32 celt_uint32;
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <sys/types.h>
typedef int16_t celt_int16;
typedef u_int16_t celt_uint16;
typedef int32_t celt_int32;
typedef u_int32_t celt_uint32;
#elif defined(__BEOS__)
/* Be */
# include <inttypes.h>
typedef int16 celt_int16;
typedef u_int16 celt_uint16;
typedef int32_t celt_int32;
typedef u_int32_t celt_uint32;
#elif defined (__EMX__)
/* OS/2 GCC */
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef int celt_int32;
typedef unsigned int celt_uint32;
#elif defined (DJGPP)
/* DJGPP */
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef int celt_int32;
typedef unsigned int celt_uint32;
#elif defined(R5900)
/* PS2 EE */
typedef int celt_int32;
typedef unsigned celt_uint32;
typedef short celt_int16;
typedef unsigned short celt_uint16;
#elif defined(__SYMBIAN32__)
/* Symbian GCC */
typedef signed short celt_int16;
typedef unsigned short celt_uint16;
typedef signed int celt_int32;
typedef unsigned int celt_uint32;
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef long celt_int32;
typedef unsigned long celt_uint32;
#elif defined(CONFIG_TI_C6X)
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef int celt_int32;
typedef unsigned int celt_uint32;
#else
/* Give up, take a reasonable guess */
typedef short celt_int16;
typedef unsigned short celt_uint16;
typedef int celt_int32;
typedef unsigned int celt_uint32;
#endif
#endif /* _CELT_TYPES_H */

BIN
celt/celt_work/bands.o Normal file

Binary file not shown.

BIN
celt/celt_work/celt.o Normal file

Binary file not shown.

BIN
celt/celt_work/cwrs.o Normal file

Binary file not shown.

BIN
celt/celt_work/entcode.o Normal file

Binary file not shown.

BIN
celt/celt_work/entdec.o Normal file

Binary file not shown.

BIN
celt/celt_work/entenc.o Normal file

Binary file not shown.

BIN
celt/celt_work/header.o Normal file

Binary file not shown.

BIN
celt/celt_work/kiss_fft.o Normal file

Binary file not shown.

BIN
celt/celt_work/laplace.o Normal file

Binary file not shown.

BIN
celt/celt_work/mathops.o Normal file

Binary file not shown.

BIN
celt/celt_work/mdct.o Normal file

Binary file not shown.

BIN
celt/celt_work/modes.o Normal file

Binary file not shown.

BIN
celt/celt_work/pitch.o Normal file

Binary file not shown.

BIN
celt/celt_work/plc.o Normal file

Binary file not shown.

Binary file not shown.

BIN
celt/celt_work/rate.o Normal file

Binary file not shown.

BIN
celt/celt_work/vq.o Normal file

Binary file not shown.

BIN
celt/libcelt0.a Normal file

Binary file not shown.

BIN
celt/libcelt0_patched.a Normal file

Binary file not shown.

View File

@ -44,6 +44,8 @@
#include <iserver.h>
#include <ISDKTools.h>
#include "inetmessage.h"
#include "CDetour/detours.h"
#include "extension.h"
@ -136,11 +138,133 @@ IServer *iserver = NULL;
double g_fLastVoiceData[SM_MAXPLAYERS + 1];
int g_aFrameVoiceBytes[SM_MAXPLAYERS + 1];
bool g_bIsNoSteam[SM_MAXPLAYERS + 1];
bool g_bClientMuted[SM_MAXPLAYERS + 1][SM_MAXPLAYERS + 1]; // [muter][mutee]
static void SendVoiceDataMsg(int fromClientSlot, IClient *pToClient, unsigned char *data, int nBytes, int64 xuid)
{
SVC_VoiceData msg;
msg.m_bProximity = false;
msg.m_nLength = nBytes * 8; // length in bits
msg.m_xuid = xuid;
msg.m_nFromClient = fromClientSlot;
msg.m_DataOut = data;
pToClient->SendNetMsg(msg);
}
#define Bits2Bytes(b) ((b+7)>>3)
/*
objdump -d /home/gameservers/css_dev/bin/engine_srv.so | grep -A 10 "_ZNK11CBaseClient13GetPlayerSlotEv"
00081a90 <_ZNK11CBaseClient13GetPlayerSlotEv>:
81a90: 55 push %ebp
81a91: 89 e5 mov %esp,%ebp
81a93: 8b 45 08 mov 0x8(%ebp),%eax
81a96: 5d pop %ebp
81a97: 8b 40 0c mov 0xc(%eax),%eax
81a9a: c3 ret
81a9b: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
81a9f: 90 nop
00081aa0 <_ZThn4_NK11CBaseClient13GetPlayerSlotEv>:
mov 0xc(%eax),%eax -> offset is 12 bytes (0x0c)
*/
DETOUR_DECL_MEMBER1(ProcessVoiceData, bool, void *, msg)
{
//in member class we use "this" to obtain the client.
int playerSlot = *(int*)((char*)this + 12); //m_nClientSlot member CBaseClient which CGameClient inherits from.
int clientIndex = playerSlot + 1;
CLC_VoiceData *voiceMsg = (CLC_VoiceData *)msg;
char voiceDataBuffer[4096];
int bitsRead = voiceMsg->m_DataIn.ReadBitsClamped(voiceDataBuffer, voiceMsg->m_nLength);
int nBytes = Bits2Bytes(bitsRead);
//still need this super important check for preventing empty voice packets spam.
if (nBytes < 1)
return true;
if (clientIndex < 1 || clientIndex > SM_MAXPLAYERS)
return true;
// Reject voice packet if we'd send more than NET_MAX_VOICE_BYTES_FRAME voice bytes from this client in the current frame.
// 5 = SVC_VoiceData header/overhead
g_aFrameVoiceBytes[clientIndex] += 5 + nBytes;
if (g_aFrameVoiceBytes[clientIndex] > NET_MAX_VOICE_BYTES_FRAME)
return true;
g_fLastVoiceData[clientIndex] = gpGlobals->curtime;
// Get IClient for sending
IClient *pClient = iserver->GetClient(playerSlot);
if (!pClient)
return true;
// Detect NoSteam
if (g_bIsNoSteam[clientIndex])
{
//convert nosteam CELT packet to OPUS for steam clients
g_Interface.TranscodeNoSteamToSteam(playerSlot, nBytes, voiceDataBuffer);
//send nosteam CELT packet to no steam clients
int maxClients = iserver->GetClientCount();
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient())
continue;
bool bSelf = (i == playerSlot);
if (bSelf)
continue;
if (!g_bIsNoSteam[i + 1])
continue;
if (g_bClientMuted[i + 1][clientIndex])
continue;
//smutils->LogMessage(myself, "in ProcessVoiceData before SendVoiceDataMsg");
SendVoiceDataMsg(playerSlot, pToClient, (unsigned char *)voiceDataBuffer, nBytes, voiceMsg->m_xuid);
}
}
else
{
//convert steam OPUS packet to CELT for no steam clients
g_Interface.PushPlayerVoiceData(playerSlot, nBytes, voiceDataBuffer);
// Send steam Opus packet to Steam clients
int maxClients = iserver->GetClientCount();
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient())
continue;
bool bSelf = (i == playerSlot);
if (bSelf)
continue;
if (g_bIsNoSteam[i + 1])
continue;
if (g_bClientMuted[i + 1][clientIndex])
continue;
//smutils->LogMessage(myself, "in ProcessVoiceData before SendVoiceDataMsg");
SendVoiceDataMsg(playerSlot, pToClient, (unsigned char *)voiceDataBuffer, nBytes, voiceMsg->m_xuid);
}
}
return true;
}
DETOUR_DECL_STATIC4(SV_BroadcastVoiceData, void, IClient *, pClient, int, nBytes, char *, data, int64, xuid)
{
if(g_Interface.OnBroadcastVoiceData(pClient, nBytes, data))
//if(g_Interface.OnBroadcastVoiceData(pClient, nBytes, data))
/*
if (1 == 2) //we dont do dis no more
{
DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, data, xuid);
}
*/
}
#ifdef _WIN32
@ -191,7 +315,40 @@ CVoice::CVoice()
m_AvailableTime = 0.0;
m_VoiceDetour = NULL;
m_ProcessVoiceDataDetour = NULL;
m_SV_BroadcastVoiceData = NULL;
m_TorchlightOpusDecoder = NULL;
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
m_PlayerOpusDecoder[i] = NULL;
}
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
m_pCeltDecoder[i] = NULL;
}
m_pCeltMode = NULL;
m_pCeltCodec = NULL;
m_pCeltModePlayer = NULL;
m_torchMonoAccumLen = 0;
m_torchResampleAccum = 0;
memset(m_playerResampleAccum, 0, sizeof(m_playerResampleAccum));
memset(m_torchMonoAccum, 0, sizeof(m_torchMonoAccum));
memset(m_nosteamSeqNum, 0, sizeof(m_nosteamSeqNum));
memset(g_bIsNoSteam, 0, sizeof(g_bIsNoSteam));
memset(m_playerAvailableTime, 0, sizeof(m_playerAvailableTime));
for (int i = 0; i <= SM_MAXPLAYERS; i++)
m_pCeltCodecPlayer[i] = NULL;
memset(g_bClientMuted, 0, sizeof(g_bClientMuted));
memset(m_nosteamResampleAccum, 0, sizeof(m_nosteamResampleAccum));
memset(m_nosteamAvailableTime, 0, sizeof(m_nosteamAvailableTime));
for (int i = 0; i <= SM_MAXPLAYERS; i++)
m_nosteamOpusEncoder[i] = NULL;
}
bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
@ -266,6 +423,8 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
dlclose(pEngineSo);
return false;
}
//2026 signature of CGameClient::ProcessVoiceData
void *adrProcessVoiceData = memutils->ResolveSymbol(pEngineSo, "_ZN11CGameClient16ProcessVoiceDataEP13CLC_VoiceData");
dlclose(pEngineSo);
m_SV_BroadcastVoiceData = (t_SV_BroadcastVoiceData)adrVoiceData;
@ -299,6 +458,15 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
m_VoiceDetour->EnableDetour();
//2026 detouring CGameClient::ProcessVoiceData
m_ProcessVoiceDataDetour = DETOUR_CREATE_MEMBER(ProcessVoiceData, adrProcessVoiceData);
if (!m_ProcessVoiceDataDetour)
{
g_SMAPI->Format(error, maxlength, "ProcessVoiceData detour failed.");
return false;
}
m_ProcessVoiceDataDetour->EnableDetour();
//opus edit
int err;
//m_OpusEncoder = opus_encoder_create(24000, 2, OPUS_APPLICATION_AUDIO, &err);
@ -322,6 +490,56 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late)
return false;
}
// Opus decoder for torchlight (48000 Hz stereo)
int err2;
m_TorchlightOpusDecoder = opus_decoder_create(48000, 2, &err2);
if (err2 < 0)
{
smutils->LogError(myself, "failed to create torchlight opus decoder: %s", opus_strerror(err2));
return false;
}
// CELT encoder (22050 Hz mono, 512 samples/frame, 64 byte packets)
m_CeltEncoderSettings.SampleRate_Hz = 22050;
m_CeltEncoderSettings.TargetBitRate_Kbps = 64;
m_CeltEncoderSettings.FrameSize = 512;
m_CeltEncoderSettings.PacketSize = 64;
m_CeltEncoderSettings.Complexity = 10;
m_CeltEncoderSettings.FrameTime = (double)m_CeltEncoderSettings.FrameSize
/ (double)m_CeltEncoderSettings.SampleRate_Hz;
int theError;
//torchlight celt
m_pCeltMode = celt_mode_create(m_CeltEncoderSettings.SampleRate_Hz,
m_CeltEncoderSettings.FrameSize, &theError);
if (!m_pCeltMode)
{
g_SMAPI->Format(error, maxlength, "celt_mode_create error: %d", theError);
SDK_OnUnload();
return false;
}
m_pCeltCodec = celt_encoder_create_custom(m_pCeltMode, 1, &theError);
if (!m_pCeltCodec)
{
g_SMAPI->Format(error, maxlength, "celt_encoder_create_custom error: %d", theError);
SDK_OnUnload();
return false;
}
celt_encoder_ctl(m_pCeltCodec, CELT_RESET_STATE_REQUEST, NULL);
celt_encoder_ctl(m_pCeltCodec, CELT_SET_BITRATE(m_CeltEncoderSettings.TargetBitRate_Kbps * 1000));
celt_encoder_ctl(m_pCeltCodec, CELT_SET_COMPLEXITY(m_CeltEncoderSettings.Complexity));
//Player celt
m_pCeltModePlayer = celt_mode_create(m_CeltEncoderSettings.SampleRate_Hz,
m_CeltEncoderSettings.FrameSize, &theError);
if (!m_pCeltModePlayer)
{
g_SMAPI->Format(error, maxlength, "celt_mode_create 2 error: %d", theError);
SDK_OnUnload();
return false;
}
return true;
}
@ -360,9 +578,54 @@ cell_t IsClientTalking(IPluginContext *pContext, const cell_t *params)
return true;
}
cell_t Native_SetClientNoSteam(IPluginContext *pContext, const cell_t *params)
{
int client = params[1];
bool IsSteam = params[2];
if (client < 1 || client > SM_MAXPLAYERS)
return pContext->ThrowNativeError("Invalid client index %d", client);
g_bIsNoSteam[client] = IsSteam;
return 1;
}
cell_t Native_SendCeltVoiceInit(IPluginContext *pContext, const cell_t *params)
{
int client = params[1];
if (client < 1 || client > SM_MAXPLAYERS)
return pContext->ThrowNativeError("Invalid client index %d", client);
IClient *pClient = iserver->GetClient(client - 1);
if (!pClient || !pClient->IsConnected())
return 0;
SVC_VoiceInit msg("vaudio_celt", 22050);
pClient->SendNetMsg(msg);
smutils->LogMessage(myself, "Sent SVC_VoiceInit vaudio_celt to client %d", client);
return 1;
}
cell_t Native_ClientMutedOtherClient(IPluginContext *pContext, const cell_t *params)
{
int clientmuter = params[1];
int clientmutee = params[2];
if (clientmuter < 1 || clientmuter > SM_MAXPLAYERS)
return pContext->ThrowNativeError("Invalid clientmuter index %d", clientmuter);
if (clientmutee < 0 || clientmutee > SM_MAXPLAYERS) //we allow muting the SourceTV
return pContext->ThrowNativeError("Invalid clientmutee index %d", clientmutee);
g_bClientMuted[clientmuter][clientmutee] = params[3]; //true or false
//smutils->LogMessage(myself, "native g_bClientMuted: %i. clientmuter: %i. clientmutee: %i", g_bClientMuted[clientmuter][clientmutee], clientmuter, clientmutee);
return 1;
}
const sp_nativeinfo_t MyNatives[] =
{
{ "IsClientTalking", IsClientTalking },
{ "SetClientNoSteam", Native_SetClientNoSteam },
{ "SendCeltVoiceInit", Native_SendCeltVoiceInit },
{ "ClientMutedOtherClient", Native_ClientMutedOtherClient },
{ NULL, NULL }
};
@ -462,6 +725,12 @@ void CVoice::SDK_OnUnload()
m_VoiceDetour = NULL;
}
if (m_ProcessVoiceDataDetour)
{
m_ProcessVoiceDataDetour->Destroy();
m_ProcessVoiceDataDetour = NULL;
}
if(m_ListenSocket != -1)
{
close(m_ListenSocket);
@ -478,12 +747,74 @@ void CVoice::SDK_OnUnload()
}
opus_encoder_destroy(m_OpusEncoder);
if (m_TorchlightOpusDecoder)
{
opus_decoder_destroy(m_TorchlightOpusDecoder);
m_TorchlightOpusDecoder = NULL;
}
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
if (m_PlayerOpusDecoder[i])
{
opus_decoder_destroy(m_PlayerOpusDecoder[i]);
m_PlayerOpusDecoder[i] = NULL;
}
}
if (m_pCeltCodec)
{
celt_encoder_destroy(m_pCeltCodec);
m_pCeltCodec = NULL;
}
if (m_pCeltMode)
{
celt_mode_destroy(m_pCeltMode);
m_pCeltMode = NULL;
}
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
if (m_pCeltCodecPlayer[i])
{
celt_encoder_destroy(m_pCeltCodecPlayer[i]);
m_pCeltCodecPlayer[i] = NULL;
}
}
if (m_pCeltModePlayer)
{
celt_mode_destroy(m_pCeltModePlayer);
m_pCeltModePlayer = NULL;
}
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
if (m_pCeltDecoder[i])
{
celt_decoder_destroy(m_pCeltDecoder[i]);
m_pCeltDecoder[i] = NULL;
}
}
for (int i = 0; i <= SM_MAXPLAYERS; i++)
{
if (m_nosteamOpusEncoder[i])
{
opus_encoder_destroy(m_nosteamOpusEncoder[i]);
m_nosteamOpusEncoder[i] = NULL;
}
}
}
void CVoice::OnGameFrame(bool simulating)
{
HandleNetwork();
HandleVoiceData();
HandlePlayerVoiceData(); //send celt packets to nosteamers.
HandleNoSteamVoiceData(); //send opus packets to steamers.
// Reset per-client voice byte counter to 0 every frame.
memset(g_aFrameVoiceBytes, 0, sizeof(g_aFrameVoiceBytes));
@ -497,6 +828,11 @@ bool CVoice::OnBroadcastVoiceData(IClient *pClient, int nBytes, char *data)
int client = pClient->GetPlayerSlot() + 1;
if (client < 1 || client > SM_MAXPLAYERS)
{
return false;
}
// Reject voice packet if we'd send more than NET_MAX_VOICE_BYTES_FRAME voice bytes from this client in the current frame.
// 5 = SVC_VoiceData header/overhead
g_aFrameVoiceBytes[client] += 5 + nBytes;
@ -690,9 +1026,9 @@ void CVoice::OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples)
pClient->m_LastValidData = getTime();
}
void CVoice::HandleVoiceData()
{
//uint32_t sampleRate = 24000;
uint32_t sampleRate = 48000;
const int SamplesPerChannel = 480;
const int Channels = 2;
@ -784,6 +1120,7 @@ void CVoice::HandleVoiceData()
smutils->LogError(myself, "Opus encode failed: %s", opus_strerror(nbBytes));
return;
}
BroadcastVoiceDataCelt(pClient, aBuffer, SamplesPerChannel);
// Write frame size
*pFrameSize = (uint16_t)nbBytes;
@ -813,7 +1150,20 @@ void CVoice::HandleVoiceData()
memcpy(&aFinal[FinalSize], &crc32_value, sizeof(uint32_t));
FinalSize += sizeof(uint32_t);
BroadcastVoiceData(pClient, FinalSize, aFinal);
int maxClients = iserver->GetClientCount();
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient())
continue;
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();
@ -825,14 +1175,483 @@ void CVoice::HandleVoiceData()
}
}
void CVoice::BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData)
void CVoice::BroadcastVoiceDataCelt(IClient *pClient, int16_t *pPCM, int nSamples)
{
#ifdef _WIN32
__asm mov ecx, pClient;
__asm mov edx, nBytes;
// 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 CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize; // 512
const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; // 64
DETOUR_STATIC_CALL(SV_BroadcastVoiceData_LTCG)((char *)pData, 0);
#else
DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, (char *)pData, 0);
#endif
int fromSlot = pClient->GetPlayerSlot();
int maxClients = iserver->GetClientCount();
for (int i = 0; i < nSamples; i++)
{
if (m_torchMonoAccumLen >= 4096)
break;
int32_t mono = ((int32_t)pPCM[i * 2] + (int32_t)pPCM[i * 2 + 1]) / 2;
if (mono > 32767) mono = 32767;
if (mono < -32768) mono = -32768;
m_torchResampleAccum += STEP;
while (m_torchResampleAccum >= DIV)
{
m_torchResampleAccum -= DIV;
if (m_torchMonoAccumLen < 4096)
m_torchMonoAccum[m_torchMonoAccumLen++] = (int16_t)mono;
}
}
while (m_torchMonoAccumLen >= CELT_FRAME_SIZE)
{
unsigned char celtPacket[CELT_PACKET_SIZE];
int celtBytes = celt_encode(m_pCeltCodec, m_torchMonoAccum, CELT_FRAME_SIZE,
celtPacket, CELT_PACKET_SIZE);
if (celtBytes > 0)
{
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]) //i dont actually understand why the index is one off here.
continue;
if (g_bClientMuted[i + 1][fromSlot])
continue;
//smutils->LogMessage(myself, "right before SendVoiceDataMsg");
SendVoiceDataMsg(fromSlot, pToClient, celtPacket, celtBytes, 0);
}
}
else
{
smutils->LogError(myself, "BroadcastVoiceDataCelt: celt_encode failed: %d", celtBytes);
}
m_torchMonoAccumLen -= CELT_FRAME_SIZE;
if (m_torchMonoAccumLen > 0)
memmove(m_torchMonoAccum, m_torchMonoAccum + CELT_FRAME_SIZE,
m_torchMonoAccumLen * sizeof(int16_t));
}
}
void CVoice::PushPlayerVoiceData(int playerSlot, int nBytes, char *data)
{
if (playerSlot < 0 || playerSlot >= SM_MAXPLAYERS)
return;
if (!m_pCeltCodecPlayer[playerSlot])
{
// First time this player speaks - create encoder
int theError;
m_pCeltCodecPlayer[playerSlot] = celt_encoder_create_custom(
m_pCeltModePlayer, 1, &theError);
if (!m_pCeltCodecPlayer[playerSlot])
{
smutils->LogError(myself, "PushPlayerVoiceData: celt_encoder_create_custom failed: %d", theError);
return;
}
celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_RESET_STATE_REQUEST, NULL);
celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_SET_BITRATE(
m_CeltEncoderSettings.TargetBitRate_Kbps * 1000));
celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_SET_COMPLEXITY(
m_CeltEncoderSettings.Complexity));
}
if (!m_PlayerOpusDecoder[playerSlot])
{
int err;
m_PlayerOpusDecoder[playerSlot] = opus_decoder_create(24000, 1, &err);
if (err < 0)
{
smutils->LogError(myself, "PushPlayerVoiceData: opus_decoder_create failed: %s",
opus_strerror(err));
return;
}
}
const int OPUS_SAMPLE_RATE = 24000;
const unsigned char *p = (const unsigned char *)data;
/*
// debugging real steam clients voice packets.
int dumpLen = nBytes < 32 ? nBytes : 32;
char hexBuf[32 * 3 + 1];
int pos = 0;
for (int i = 0; i < dumpLen; i++)
{
static const char hex[] = "0123456789ABCDEF";
hexBuf[pos++] = hex[(p[i] >> 4) & 0xF];
hexBuf[pos++] = hex[p[i] & 0xF];
hexBuf[pos++] = ' ';
}
hexBuf[pos] = '\0';
smutils->LogMessage(myself, "Steam packet in PushPlayerVoiceData: nBytes=%d first %d bytes: %s", nBytes, dumpLen, hexBuf);
*/
//if its not opus packets we dont process it
if (nBytes < 18 || p[8] != 0x0B || (p[11] != 0x05 && p[11] != 0x06))
return;
uint16_t totalDataLength;
memcpy(&totalDataLength, p + 12, sizeof(uint16_t));
if ((int)(14 + totalDataLength + 4) != nBytes)
return;
uint32_t expectedCRC;
memcpy(&expectedCRC, p + nBytes - 4, sizeof(uint32_t));
if (UTIL_CRC32(p, nBytes - 4) != expectedCRC)
return;
int offset = 14;
int end = 14 + (int)totalDataLength;
if (offset + 4 <= end)
{
uint16_t frameLen;
memcpy(&frameLen, p + offset, sizeof(uint16_t));
offset += 2;
offset += 2;
if (frameLen <= 2 || offset + (int)(frameLen - 2) > end)
return;
const unsigned char *opusFrame = p + offset;
int16_t pcmBuf[5760];
int decoded = opus_decode(m_PlayerOpusDecoder[playerSlot],
opusFrame, frameLen - 2,
pcmBuf, 5760, 0);
if (decoded <= 0)
{
smutils->LogError(myself, "PushPlayerVoiceData: opus_decode failed: %s",
opus_strerror(decoded));
return;
}
const int STEP = m_CeltEncoderSettings.SampleRate_Hz;
const int DIV = OPUS_SAMPLE_RATE;
for (int i = 0; i < decoded; i++)
{
m_playerResampleAccum[playerSlot] += STEP;
while (m_playerResampleAccum[playerSlot] >= DIV)
{
m_playerResampleAccum[playerSlot] -= DIV;
int16_t sample = pcmBuf[i];
if (m_playerVoiceBuffer[playerSlot].CurrentFree() > 0)
m_playerVoiceBuffer[playerSlot].Push(&sample, 1);
}
}
// If buffer still doesn't have enough for one CELT frame, use Opus PLC to fill
if (m_playerVoiceBuffer[playerSlot].TotalLength() < (size_t)m_CeltEncoderSettings.FrameSize)
{
int16_t plcBuf[5760];
int plcDecoded = opus_decode(m_PlayerOpusDecoder[playerSlot],
NULL, 0, // NULL = PLC mode
plcBuf, 480, 0);
if (plcDecoded > 0)
{
for (int i = 0; i < plcDecoded; i++)
{
m_playerResampleAccum[playerSlot] += m_CeltEncoderSettings.SampleRate_Hz;
while (m_playerResampleAccum[playerSlot] >= 24000)
{
m_playerResampleAccum[playerSlot] -= 24000;
int16_t sample = plcBuf[i];
if (m_playerVoiceBuffer[playerSlot].CurrentFree() > 0)
m_playerVoiceBuffer[playerSlot].Push(&sample, 1);
}
}
}
}
}
//smutils->LogMessage(myself, "PushPlayerVoiceData: pushed, bufLen=%d", (int)m_playerVoiceBuffer[playerSlot].TotalLength());
}
void CVoice::HandlePlayerVoiceData()
{
const int CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize;
const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize;
int maxClients = iserver->GetClientCount();
for (int playerSlot = 0; playerSlot < maxClients; playerSlot++)
{
if (!m_pCeltCodecPlayer[playerSlot])
continue;
if (m_playerVoiceBuffer[playerSlot].TotalLength() < (size_t)CELT_FRAME_SIZE)
continue;
float timeAvailable = (float)m_playerVoiceBuffer[playerSlot].TotalLength()
/ (float)m_CeltEncoderSettings.SampleRate_Hz;
//warmup encoder with 5 frames
if (m_playerAvailableTime[playerSlot] == 0.0 &&
m_playerVoiceBuffer[playerSlot].TotalLength() >= (size_t)CELT_FRAME_SIZE * 11)
{
for (int w = 0; w < 10; w++)
{
int16_t warmupInput[CELT_FRAME_SIZE];
if (!m_playerVoiceBuffer[playerSlot].Pop(warmupInput, CELT_FRAME_SIZE))
break;
unsigned char discard[64];
celt_encode(m_pCeltCodecPlayer[playerSlot], warmupInput, CELT_FRAME_SIZE, discard, 64);
}
m_playerAvailableTime[playerSlot] = getTime();
}
//if (m_playerAvailableTime[playerSlot] < getTime() && timeAvailable < 0.2)
if (m_playerAvailableTime[playerSlot] < getTime() && timeAvailable < 0.05)
continue;
if (m_playerAvailableTime[playerSlot] > getTime() + 1.0)
continue;
int framesEmitted = 0;
while (m_playerVoiceBuffer[playerSlot].TotalLength() >= (size_t)CELT_FRAME_SIZE)
{
//smutils->LogMessage(myself, "framesEmitted: %d", framesEmitted);
if (framesEmitted >= 1)
break;
int16_t celtInput[CELT_FRAME_SIZE];
if (!m_playerVoiceBuffer[playerSlot].Pop(celtInput, CELT_FRAME_SIZE))
break;
unsigned char celtPacket[CELT_PACKET_SIZE];
int celtBytes = celt_encode(m_pCeltCodecPlayer[playerSlot], celtInput,
CELT_FRAME_SIZE, celtPacket, CELT_PACKET_SIZE);
if (celtBytes > 0)
{
for (int i = 0; i < maxClients; i++)
{
IClient *pToClient = iserver->GetClient(i);
if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive())
continue;
if (!g_bIsNoSteam[i + 1])
continue;
if (g_bClientMuted[i + 1][playerSlot + 1])
continue;
SendVoiceDataMsg(playerSlot, pToClient, celtPacket, celtBytes, 0);
}
framesEmitted++;
}
else
{
smutils->LogError(myself, "HandlePlayerVoiceData: celt_encode failed: %d", celtBytes);
break;
}
}
if (framesEmitted > 0)
{
if (m_playerAvailableTime[playerSlot] < getTime())
m_playerAvailableTime[playerSlot] = getTime();
m_playerAvailableTime[playerSlot] += (double)framesEmitted * m_CeltEncoderSettings.FrameTime;
}
}
}
void CVoice::TranscodeNoSteamToSteam(int playerSlot, int nBytes, char *data)
{
if (playerSlot < 0 || playerSlot >= SM_MAXPLAYERS)
return;
if (!m_pCeltDecoder[playerSlot])
{
int err;
m_pCeltDecoder[playerSlot] = celt_decoder_create_custom(m_pCeltModePlayer, 1, &err);
if (!m_pCeltDecoder[playerSlot])
{
smutils->LogError(myself, "TranscodeNoSteamToSteam: celt_decoder_create_custom failed: %d", err);
return;
}
}
if (!m_nosteamOpusEncoder[playerSlot])
{
int err;
m_nosteamOpusEncoder[playerSlot] = opus_encoder_create(24000, 1, OPUS_APPLICATION_AUDIO, &err);
if (err < 0)
{
smutils->LogError(myself, "TranscodeNoSteamToSteam: opus_encoder_create failed: %s", opus_strerror(err));
return;
}
/*
0x68 TOC -> 01101000 -> c bits (6-7): 00 -> s bit (5): 0 (Mono) -> config bits (0-4): 01101 (13)
Mode: Hybrid (SILK + CELT)
Bandwidth: SWB
Frame Size: 20 ms
BITRATE 640000 changes TOC to 0xD8
*/
opus_encoder_ctl(m_nosteamOpusEncoder[playerSlot], OPUS_SET_BITRATE(32000));
opus_encoder_ctl(m_nosteamOpusEncoder[playerSlot], OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
}
int16_t pcmBuf[512];
int decoded = celt_decode(m_pCeltDecoder[playerSlot],
(const unsigned char *)data, nBytes,
pcmBuf, m_CeltEncoderSettings.FrameSize);
if (decoded < 0)
{
smutils->LogError(myself, "TranscodeNoSteamToSteam: celt_decode failed: %d", decoded);
return;
}
// Resample 22050 -> 24000 and push into ring buffer
const int STEP = 24000;
const int DIV = 22050;
for (int i = 0; i < decoded; i++)
{
m_nosteamResampleAccum[playerSlot] += STEP;
while (m_nosteamResampleAccum[playerSlot] >= DIV)
{
m_nosteamResampleAccum[playerSlot] -= DIV;
int16_t sample = pcmBuf[i];
if (m_nosteamOpusPCMBuffer[playerSlot].CurrentFree() > 0)
m_nosteamOpusPCMBuffer[playerSlot].Push(&sample, 1);
}
}
}
void CVoice::HandleNoSteamVoiceData()
{
int maxClients = iserver->GetClientCount();
for (int playerSlot = 0; playerSlot < maxClients; playerSlot++)
{
if (!m_nosteamOpusEncoder[playerSlot])
continue;
if (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() < 480)
continue;
float timeAvailable = (float)m_nosteamOpusPCMBuffer[playerSlot].TotalLength() / 24000.0f;
if (m_nosteamAvailableTime[playerSlot] < getTime() && timeAvailable < 0.1)
continue;
if (m_nosteamAvailableTime[playerSlot] > getTime() + 1.0)
continue;
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));
if (opusBytes < 0)
{
smutils->LogError(myself, "HandleNoSteamVoiceData: opus_encode failed: %s",
opus_strerror(opusBytes));
break;
}
IClient *pNoSteamClient = iserver->GetClient(playerSlot);
if (!pNoSteamClient)
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++;
}
if (framesEmitted > 0)
{
if (m_nosteamAvailableTime[playerSlot] < getTime())
m_nosteamAvailableTime[playerSlot] = getTime();
m_nosteamAvailableTime[playerSlot] += (double)framesEmitted * (480.0 / 24000.0);
}
}
}

View File

@ -34,6 +34,7 @@
#include <poll.h>
#include "smsdk_ext.h"
#include "celt_header.h"
#include "ringbuffer.h"
/**
@ -139,6 +140,9 @@ public:
bool OnBroadcastVoiceData(IClient *pClient, int nBytes, char *data);
void ListenSocket();
void PushPlayerVoiceData(int playerSlot, int nBytes, char *data);
void BroadcastVoiceDataCelt(IClient *pClient, int16_t *pPCM, int nSamples);
void TranscodeNoSteamToSteam(int playerSlot, int nBytes, char *data);
private:
int m_ListenSocket;
@ -161,16 +165,58 @@ private:
OpusEncoder *m_OpusEncoder;
double m_AvailableTime;
// Torchlight transcode state (48000 Hz stereo -> 22050 Hz mono -> CELT)
OpusDecoder *m_TorchlightOpusDecoder;
int16_t m_torchMonoAccum[4096];
int m_torchMonoAccumLen;
int m_torchResampleAccum;
// Player transcode state (24000 Hz mono -> 22050 Hz mono -> CELT)
OpusDecoder *m_PlayerOpusDecoder[SM_MAXPLAYERS + 1];
int m_playerResampleAccum[SM_MAXPLAYERS + 1];
double m_AvailableTime;
double m_playerAvailableTime[SM_MAXPLAYERS + 1];
// NoSteam->Steam transcode state per player
CRingBuffer m_nosteamOpusPCMBuffer[SM_MAXPLAYERS + 1];
int m_nosteamResampleAccum[SM_MAXPLAYERS + 1];
OpusEncoder *m_nosteamOpusEncoder[SM_MAXPLAYERS + 1];
// CELT encoder (shared by both paths, same output format)
struct CEncoderSettings
{
int32_t SampleRate_Hz;
int32_t TargetBitRate_Kbps;
int32_t FrameSize;
int32_t PacketSize;
int32_t Complexity;
double FrameTime;
} m_CeltEncoderSettings;
CELTMode *m_pCeltMode;
CELTEncoder *m_pCeltCodec;
CELTMode *m_pCeltModePlayer;
CELTEncoder *m_pCeltCodecPlayer[SM_MAXPLAYERS + 1];
CELTDecoder *m_pCeltDecoder[SM_MAXPLAYERS + 1];
CRingBuffer m_playerVoiceBuffer[SM_MAXPLAYERS + 1];
t_SV_BroadcastVoiceData m_SV_BroadcastVoiceData;
CDetour *m_VoiceDetour;
CDetour *m_ProcessVoiceDataDetour;
uint16_t m_nosteamSeqNum[SM_MAXPLAYERS + 1];
double m_nosteamAvailableTime[SM_MAXPLAYERS + 1];
void HandleNetwork();
void OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples);
void HandleVoiceData();
void BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData);
void HandlePlayerVoiceData();
void HandleNoSteamVoiceData();
};
#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_

195
inetmessage.h Normal file
View File

@ -0,0 +1,195 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: INetMessage interface
//
// $NoKeywords: $
//=============================================================================//
#ifndef INETMESSAGE_FRAEVEN_H
#define INETMESSAGE_FRAEVEN_H
#include "smsdk_ext.h"
#include "bitbuf.h"
#include "inetchannelinfo.h"
class INetMsgHandler;
class INetMessage;
class INetChannel;
// typedef bool (INetMsgHandler::*PROCESSFUNCPTR)(INetMessage*);
// #define CASTPROCPTR( fn ) static_cast <bool (INetMsgHandler::*)(INetMessage*)> (fn)
class INetMessage
{
public:
virtual ~INetMessage() {};
// Use these to setup who can hear whose voice.
// Pass in client indices (which are their ent indices - 1).
virtual void SetNetChannel(INetChannel * netchan) = 0; // netchannel this message is from/for
virtual void SetReliable( bool state ) = 0; // set to true if it's a reliable message
virtual bool Process( void ) = 0; // calles the recently set handler to process this message
virtual bool ReadFromBuffer( bf_read &buffer ) = 0; // returns true if parsing was OK
virtual bool WriteToBuffer( bf_write &buffer ) = 0; // returns true if writing was OK
virtual bool IsReliable( void ) const = 0; // true, if message needs reliable handling
virtual int GetType( void ) const = 0; // returns module specific header tag eg svc_serverinfo
virtual int GetGroup( void ) const = 0; // returns net message group of this message
virtual const char *GetName( void ) const = 0; // returns network message name, eg "svc_serverinfo"
virtual INetChannel *GetNetChannel( void ) const = 0;
virtual const char *ToString( void ) const = 0; // returns a human readable string about message content
};
class CNetMessage : public INetMessage
{
public:
CNetMessage() { m_bReliable = true;
m_NetChannel = NULL; }
virtual ~CNetMessage() {};
virtual int GetGroup() const { return INetChannelInfo::GENERIC; }
INetChannel *GetNetChannel() const { return m_NetChannel; }
virtual void SetReliable( bool state) {m_bReliable = state;};
virtual bool IsReliable() const { return m_bReliable; };
virtual void SetNetChannel(INetChannel * netchan) { m_NetChannel = netchan; }
virtual bool Process() { return false; }; // no handler set
protected:
bool m_bReliable; // true if message should be send reliable
INetChannel *m_NetChannel; // netchannel this message is from/for
};
#define svc_VoiceData 15
#define DECLARE_BASE_MESSAGE( msgtype ) \
public: \
bool ReadFromBuffer( bf_read &buffer ); \
bool WriteToBuffer( bf_write &buffer ); \
const char *ToString() const; \
int GetType() const { return msgtype; } \
const char *GetName() const { return #msgtype;}\
#define DECLARE_SVC_MESSAGE( name ) \
DECLARE_BASE_MESSAGE( svc_##name ); \
IServerMessageHandler *m_pMessageHandler;\
bool Process() { return m_pMessageHandler->Process##name( this ); }\
class SVC_VoiceData : public CNetMessage
{
DECLARE_SVC_MESSAGE( VoiceData );
int GetGroup() const { return INetChannelInfo::VOICE; }
SVC_VoiceData() { m_bReliable = false; }
public:
int m_nFromClient; // client who has spoken
bool m_bProximity;
int m_nLength; // data length in bits
uint64 m_xuid; // X360 player ID
bf_read m_DataIn;
void *m_DataOut;
};
#define NETMSG_TYPE_BITS 6
bool SVC_VoiceData::WriteToBuffer( bf_write &buffer )
{
buffer.WriteUBitLong( GetType(), NETMSG_TYPE_BITS );
buffer.WriteByte( m_nFromClient );
buffer.WriteByte( m_bProximity );
buffer.WriteWord( m_nLength );
return buffer.WriteBits( m_DataOut, m_nLength );
}
bool SVC_VoiceData::ReadFromBuffer( bf_read &buffer )
{
// VPROF( "SVC_VoiceData::ReadFromBuffer" );
m_nFromClient = buffer.ReadByte();
m_bProximity = !!buffer.ReadByte();
m_nLength = buffer.ReadWord();
// if ( IsX360() )
// {
// m_xuid = buffer.ReadLongLong();
// }
m_DataIn = buffer;
return buffer.SeekRelative( m_nLength );
}
const char *SVC_VoiceData::ToString(void) const
{
// Q_snprintf(s_text, sizeof(s_text), "%s: client %i, bytes %i", GetName(), m_nFromClient, Bits2Bytes(m_nLength) );
// return s_text;
return "idc";
}
#define svc_VoiceInit 14
class SVC_VoiceInit : public CNetMessage
{
DECLARE_SVC_MESSAGE( VoiceInit );
int GetGroup() const { return INetChannelInfo::SIGNON; }
SVC_VoiceInit() : m_nSampleRate(0)
{
m_bReliable = true;
memset(m_szVoiceCodec, 0, sizeof(m_szVoiceCodec));
}
SVC_VoiceInit(const char *pCodec, int nSampleRate) : m_nSampleRate(nSampleRate)
{
m_bReliable = true;
Q_strncpy(m_szVoiceCodec, pCodec ? pCodec : "", sizeof(m_szVoiceCodec));
}
public:
char m_szVoiceCodec[MAX_PATH];
int m_nSampleRate;
};
bool SVC_VoiceInit::WriteToBuffer(bf_write &buffer)
{
buffer.WriteUBitLong(GetType(), NETMSG_TYPE_BITS);
buffer.WriteString(m_szVoiceCodec);
buffer.WriteShort(m_nSampleRate);
return !buffer.IsOverflowed();
}
bool SVC_VoiceInit::ReadFromBuffer(bf_read &buffer)
{
buffer.ReadString(m_szVoiceCodec, sizeof(m_szVoiceCodec));
m_nSampleRate = buffer.ReadShort();
return !buffer.IsOverflowed();
}
const char *SVC_VoiceInit::ToString() const
{
return "SVC_VoiceInit";
}
#define clc_VoiceData 10
//objdump -d /home/gameservers/css_dev/bin/engine_srv.so | grep -A 15 "_ZN11CGameClient16ProcessVoiceDataEP13CLC_VoiceData"
class CLC_VoiceData
{
public:
uint8_t _pad[20]; // vtable + CNetMessage base fields + padding up to m_nLength (20 offset)
int m_nLength;
bf_read m_DataIn;
bf_write m_DataOut;
uint64 m_xuid;
};
#endif