first commit to transcode branch for supporting both nosteam celt and steam opus
This commit is contained in:
parent
e6fb78cdf9
commit
2184fd917a
@ -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
15
README.md
Normal 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
325
celt/celt.h
Normal 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
66
celt/celt_header.h
Normal 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
151
celt/celt_types.h
Normal 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
BIN
celt/celt_work/bands.o
Normal file
Binary file not shown.
BIN
celt/celt_work/celt.o
Normal file
BIN
celt/celt_work/celt.o
Normal file
Binary file not shown.
BIN
celt/celt_work/cwrs.o
Normal file
BIN
celt/celt_work/cwrs.o
Normal file
Binary file not shown.
BIN
celt/celt_work/entcode.o
Normal file
BIN
celt/celt_work/entcode.o
Normal file
Binary file not shown.
BIN
celt/celt_work/entdec.o
Normal file
BIN
celt/celt_work/entdec.o
Normal file
Binary file not shown.
BIN
celt/celt_work/entenc.o
Normal file
BIN
celt/celt_work/entenc.o
Normal file
Binary file not shown.
BIN
celt/celt_work/header.o
Normal file
BIN
celt/celt_work/header.o
Normal file
Binary file not shown.
BIN
celt/celt_work/kiss_fft.o
Normal file
BIN
celt/celt_work/kiss_fft.o
Normal file
Binary file not shown.
BIN
celt/celt_work/laplace.o
Normal file
BIN
celt/celt_work/laplace.o
Normal file
Binary file not shown.
BIN
celt/celt_work/mathops.o
Normal file
BIN
celt/celt_work/mathops.o
Normal file
Binary file not shown.
BIN
celt/celt_work/mdct.o
Normal file
BIN
celt/celt_work/mdct.o
Normal file
Binary file not shown.
BIN
celt/celt_work/modes.o
Normal file
BIN
celt/celt_work/modes.o
Normal file
Binary file not shown.
BIN
celt/celt_work/pitch.o
Normal file
BIN
celt/celt_work/pitch.o
Normal file
Binary file not shown.
BIN
celt/celt_work/plc.o
Normal file
BIN
celt/celt_work/plc.o
Normal file
Binary file not shown.
BIN
celt/celt_work/quant_bands.o
Normal file
BIN
celt/celt_work/quant_bands.o
Normal file
Binary file not shown.
BIN
celt/celt_work/rate.o
Normal file
BIN
celt/celt_work/rate.o
Normal file
Binary file not shown.
BIN
celt/celt_work/vq.o
Normal file
BIN
celt/celt_work/vq.o
Normal file
Binary file not shown.
BIN
celt/libcelt0.a
Normal file
BIN
celt/libcelt0.a
Normal file
Binary file not shown.
BIN
celt/libcelt0_patched.a
Normal file
BIN
celt/libcelt0_patched.a
Normal file
Binary file not shown.
841
extension.cpp
841
extension.cpp
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
extension.h
50
extension.h
@ -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
195
inetmessage.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user