2017-04-17 20:57:07 +02:00
|
|
|
//
|
|
|
|
// libtgvoip is free and unencumbered public domain software.
|
|
|
|
// For more information, see http://unlicense.org or the UNLICENSE file
|
|
|
|
// you should have received with this source code distribution.
|
|
|
|
//
|
|
|
|
|
2020-01-23 16:45:53 +01:00
|
|
|
#pragma once
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2017-04-17 20:57:07 +02:00
|
|
|
#ifndef _WIN32
|
2017-02-02 17:24:40 +01:00
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/in.h>
|
2017-04-17 20:57:07 +02:00
|
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
|
|
#include <TargetConditionals.h>
|
2018-05-15 20:23:46 +02:00
|
|
|
#include "os/darwin/AudioUnitIO.h"
|
2017-04-17 20:57:07 +02:00
|
|
|
#endif
|
2017-02-02 17:24:40 +01:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <vector>
|
2020-01-22 12:43:51 +01:00
|
|
|
#include <deque>
|
2020-01-24 20:26:34 +01:00
|
|
|
#include <fstream>
|
|
|
|
#include <iomanip>
|
2017-03-30 16:06:59 +02:00
|
|
|
#include <string>
|
2018-06-04 21:37:43 +02:00
|
|
|
#include <unordered_map>
|
2018-11-09 16:44:01 +01:00
|
|
|
#include <map>
|
2018-06-04 21:37:43 +02:00
|
|
|
#include <memory>
|
2018-11-09 16:44:01 +01:00
|
|
|
#include "video/VideoSource.h"
|
|
|
|
#include "video/VideoRenderer.h"
|
2018-08-24 01:29:07 +02:00
|
|
|
#include <atomic>
|
2019-03-10 23:12:09 +01:00
|
|
|
#include "video/ScreamCongestionController.h"
|
2017-02-02 17:24:40 +01:00
|
|
|
#include "audio/AudioInput.h"
|
2020-01-23 16:45:53 +01:00
|
|
|
#include "audio/Device.h"
|
2020-01-22 12:43:51 +01:00
|
|
|
#include "tools/BlockingQueue.h"
|
2017-02-02 17:24:40 +01:00
|
|
|
#include "audio/AudioOutput.h"
|
2018-07-17 18:48:21 +02:00
|
|
|
#include "audio/AudioIO.h"
|
2020-01-22 12:43:51 +01:00
|
|
|
#include "controller/net/JitterBuffer.h"
|
2020-01-23 16:45:53 +01:00
|
|
|
#include "controller/net/Endpoint.h"
|
2020-01-22 12:51:17 +01:00
|
|
|
#include "controller/audio/OpusDecoder.h"
|
|
|
|
#include "controller/audio/OpusEncoder.h"
|
2020-01-22 12:43:51 +01:00
|
|
|
#include "controller/audio/EchoCanceller.h"
|
|
|
|
#include "controller/net/CongestionControl.h"
|
2020-01-28 13:18:38 +01:00
|
|
|
#include "controller/protocol/PacketManager.h"
|
2020-01-27 19:53:32 +01:00
|
|
|
#include "controller/protocol/PacketStructs.h"
|
2020-01-22 12:43:51 +01:00
|
|
|
#include "tools/Buffers.h"
|
2020-01-25 18:36:49 +01:00
|
|
|
#include "controller/net/PacketReassembler.h"
|
2020-01-22 12:43:51 +01:00
|
|
|
#include "tools/MessageThread.h"
|
|
|
|
#include "tools/utils.h"
|
|
|
|
#include "controller/PrivateDefines.h"
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
#if defined HAVE_CONFIG_H || defined TGVOIP_USE_INSTALLED_OPUS
|
|
|
|
#include <opus/opus.h>
|
|
|
|
#else
|
|
|
|
#include <opus/opus.h>
|
|
|
|
#endif
|
|
|
|
|
2019-04-15 01:43:10 +02:00
|
|
|
#define LIBTGVOIP_VERSION "2.5"
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2017-04-17 20:57:07 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
#undef GetCurrentTime
|
2018-05-24 18:42:28 +02:00
|
|
|
#undef ERROR_TIMEOUT
|
2017-04-17 20:57:07 +02:00
|
|
|
#endif
|
|
|
|
|
2020-01-25 18:11:15 +01:00
|
|
|
#define ENFORCE_MSG_THREAD assert(messageThread.IsCurrent())
|
|
|
|
|
2018-05-15 20:23:46 +02:00
|
|
|
#define TGVOIP_PEER_CAP_GROUP_CALLS 1
|
2018-11-09 16:44:01 +01:00
|
|
|
#define TGVOIP_PEER_CAP_VIDEO_CAPTURE 2
|
|
|
|
#define TGVOIP_PEER_CAP_VIDEO_DISPLAY 4
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
namespace tgvoip
|
|
|
|
{
|
2017-04-17 20:57:07 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
enum
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
PROXY_NONE = 0,
|
|
|
|
PROXY_SOCKS5,
|
|
|
|
//PROXY_HTTP
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2017-07-03 03:42:49 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
enum
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
STATE_WAIT_INIT = 1,
|
|
|
|
STATE_WAIT_INIT_ACK,
|
|
|
|
STATE_ESTABLISHED,
|
|
|
|
STATE_FAILED,
|
|
|
|
STATE_RECONNECTING
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2017-04-17 20:57:07 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
enum
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
ERROR_UNKNOWN = 0,
|
|
|
|
ERROR_INCOMPATIBLE,
|
|
|
|
ERROR_TIMEOUT,
|
|
|
|
ERROR_AUDIO_IO,
|
|
|
|
ERROR_PROXY
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2017-04-28 13:17:56 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
enum
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
NET_TYPE_UNKNOWN = 0,
|
|
|
|
NET_TYPE_GPRS,
|
|
|
|
NET_TYPE_EDGE,
|
|
|
|
NET_TYPE_3G,
|
|
|
|
NET_TYPE_HSPA,
|
|
|
|
NET_TYPE_LTE,
|
|
|
|
NET_TYPE_WIFI,
|
|
|
|
NET_TYPE_ETHERNET,
|
|
|
|
NET_TYPE_OTHER_HIGH_SPEED,
|
|
|
|
NET_TYPE_OTHER_LOW_SPEED,
|
|
|
|
NET_TYPE_DIALUP,
|
|
|
|
NET_TYPE_OTHER_MOBILE
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2017-04-28 13:17:56 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
enum
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
DATA_SAVING_NEVER = 0,
|
|
|
|
DATA_SAVING_MOBILE,
|
|
|
|
DATA_SAVING_ALWAYS
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2018-05-15 20:23:46 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
struct CryptoFunctions
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
void (*rand_bytes)(uint8_t *buffer, size_t length);
|
|
|
|
void (*sha1)(uint8_t *msg, size_t length, uint8_t *output);
|
|
|
|
void (*sha256)(uint8_t *msg, size_t length, uint8_t *output);
|
|
|
|
void (*aes_ige_encrypt)(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv);
|
|
|
|
void (*aes_ige_decrypt)(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv);
|
|
|
|
void (*aes_ctr_encrypt)(uint8_t *inout, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num);
|
|
|
|
void (*aes_cbc_encrypt)(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv);
|
|
|
|
void (*aes_cbc_decrypt)(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv);
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2018-06-04 21:37:43 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
struct CellularCarrierInfo
|
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string name;
|
|
|
|
std::string mcc;
|
|
|
|
std::string mnc;
|
|
|
|
std::string countryCode;
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2019-04-15 01:43:10 +02:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
class PacketSender;
|
|
|
|
|
2020-01-28 23:45:47 +01:00
|
|
|
class VoIPController
|
2020-01-22 12:43:51 +01:00
|
|
|
{
|
2020-01-23 16:52:40 +01:00
|
|
|
friend class VoIPGroupController;
|
|
|
|
friend class PacketSender;
|
2020-01-22 12:43:51 +01:00
|
|
|
|
|
|
|
public:
|
2020-01-23 16:52:40 +01:00
|
|
|
TGVOIP_DISALLOW_COPY_AND_ASSIGN(VoIPController);
|
|
|
|
struct Config
|
|
|
|
{
|
|
|
|
Config(double initTimeout = 30.0, double recvTimeout = 20.0, int dataSaving = DATA_SAVING_NEVER, bool enableAEC = false, bool enableNS = false, bool enableAGC = false, bool enableCallUpgrade = false)
|
|
|
|
{
|
|
|
|
this->initTimeout = initTimeout;
|
|
|
|
this->recvTimeout = recvTimeout;
|
|
|
|
this->dataSaving = dataSaving;
|
|
|
|
this->enableAEC = enableAEC;
|
|
|
|
this->enableNS = enableNS;
|
|
|
|
this->enableAGC = enableAGC;
|
|
|
|
this->enableCallUpgrade = enableCallUpgrade;
|
|
|
|
}
|
|
|
|
|
|
|
|
double initTimeout;
|
|
|
|
double recvTimeout;
|
|
|
|
int dataSaving;
|
2018-08-28 22:41:01 +02:00
|
|
|
#ifndef _WIN32
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string logFilePath = "";
|
|
|
|
std::string statsDumpFilePath = "";
|
2018-08-28 22:41:01 +02:00
|
|
|
#else
|
2020-01-23 16:52:40 +01:00
|
|
|
std::wstring logFilePath = L"";
|
|
|
|
std::wstring statsDumpFilePath = L"";
|
2018-08-28 22:41:01 +02:00
|
|
|
#endif
|
2018-06-04 21:37:43 +02:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
bool enableAEC;
|
|
|
|
bool enableNS;
|
|
|
|
bool enableAGC;
|
|
|
|
|
|
|
|
bool enableCallUpgrade;
|
|
|
|
|
|
|
|
bool logPacketStats = false;
|
|
|
|
bool enableVolumeControl = false;
|
|
|
|
|
|
|
|
bool enableVideoSend = false;
|
|
|
|
bool enableVideoReceive = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct TrafficStats
|
|
|
|
{
|
2020-01-24 20:26:34 +01:00
|
|
|
uint64_t bytesSentWifi = 0;
|
|
|
|
uint64_t bytesRecvdWifi = 0;
|
|
|
|
uint64_t bytesSentMobile = 0;
|
|
|
|
uint64_t bytesRecvdMobile = 0;
|
2020-01-23 16:52:40 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
VoIPController();
|
|
|
|
virtual ~VoIPController();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the initial endpoints (relays)
|
|
|
|
* @param endpoints Endpoints converted from phone.PhoneConnection TL objects
|
|
|
|
* @param allowP2p Whether p2p connectivity is allowed
|
|
|
|
* @param connectionMaxLayer The max_layer field from the phoneCallProtocol object returned by Telegram server.
|
|
|
|
* DO NOT HARDCODE THIS VALUE, it's extremely important for backwards compatibility.
|
|
|
|
*/
|
|
|
|
void SetRemoteEndpoints(std::vector<Endpoint> endpoints, bool allowP2p, int32_t connectionMaxLayer);
|
|
|
|
/**
|
|
|
|
* Initialize and start all the internal threads
|
|
|
|
*/
|
|
|
|
void Start();
|
|
|
|
/**
|
|
|
|
* Stop any internal threads. Don't call any other methods after this.
|
|
|
|
*/
|
|
|
|
void Stop();
|
|
|
|
/**
|
|
|
|
* Initiate connection
|
|
|
|
*/
|
|
|
|
void Connect();
|
|
|
|
Endpoint &GetRemoteEndpoint();
|
|
|
|
/**
|
|
|
|
* Get the debug info string to be displayed in client UI
|
|
|
|
*/
|
|
|
|
virtual std::string GetDebugString();
|
|
|
|
/**
|
|
|
|
* Notify the library of network type change
|
|
|
|
* @param type The new network type
|
|
|
|
*/
|
|
|
|
virtual void SetNetworkType(int type);
|
|
|
|
/**
|
|
|
|
* Get the average round-trip time for network packets
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
double GetAverageRTT();
|
|
|
|
static double GetCurrentTime();
|
|
|
|
/**
|
|
|
|
* Use this field to store any of your context data associated with this call
|
|
|
|
*/
|
|
|
|
void *implData;
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param mute
|
|
|
|
*/
|
|
|
|
virtual void SetMicMute(bool mute);
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param key
|
|
|
|
* @param isOutgoing
|
|
|
|
*/
|
2020-01-23 18:06:11 +01:00
|
|
|
void SetEncryptionKey(std::vector<uint8_t>, bool isOutgoing);
|
2020-01-23 16:52:40 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param cfg
|
|
|
|
*/
|
|
|
|
void SetConfig(const Config &cfg);
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param stats
|
|
|
|
*/
|
|
|
|
void GetStats(TrafficStats *stats);
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
int64_t GetPreferredRelayID();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
int GetLastError();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static CryptoFunctions crypto;
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
static const char *GetVersion();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
std::string GetDebugLog();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
static std::vector<AudioInputDevice> EnumerateAudioInputs();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
static std::vector<AudioOutputDevice> EnumerateAudioOutputs();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param id
|
|
|
|
*/
|
|
|
|
void SetCurrentAudioInput(std::string id);
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param id
|
|
|
|
*/
|
|
|
|
void SetCurrentAudioOutput(std::string id);
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
std::string GetCurrentAudioInputID();
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
std::string GetCurrentAudioOutputID();
|
|
|
|
/**
|
|
|
|
* Set the proxy server to route the data through. Call this before connecting.
|
|
|
|
* @param protocol PROXY_NONE or PROXY_SOCKS5
|
|
|
|
* @param address IP address or domain name of the server
|
|
|
|
* @param port Port of the server
|
|
|
|
* @param username Username; empty string for anonymous
|
|
|
|
* @param password Password; empty string if none
|
|
|
|
*/
|
|
|
|
void SetProxy(int protocol, std::string address, uint16_t port, std::string username, std::string password);
|
|
|
|
/**
|
|
|
|
* Get the number of signal bars to display in the client UI.
|
|
|
|
* @return the number of signal bars, from 1 to 4
|
|
|
|
*/
|
|
|
|
int GetSignalBarsCount();
|
|
|
|
/**
|
|
|
|
* Enable or disable AGC (automatic gain control) on audio output. Should only be enabled on phones when the earpiece speaker is being used.
|
|
|
|
* The audio output will be louder with this on.
|
|
|
|
* AGC with speakerphone or other kinds of loud speakers has detrimental effects on some echo cancellation implementations.
|
|
|
|
* @param enabled I usually pick argument names to be self-explanatory
|
|
|
|
*/
|
|
|
|
void SetAudioOutputGainControlEnabled(bool enabled);
|
|
|
|
/**
|
|
|
|
* Get the additional capabilities of the peer client app
|
|
|
|
* @return corresponding TGVOIP_PEER_CAP_* flags OR'ed together
|
|
|
|
*/
|
|
|
|
uint32_t GetPeerCapabilities();
|
|
|
|
/**
|
|
|
|
* Send the peer the key for the group call to prepare this private call to an upgrade to a E2E group call.
|
|
|
|
* The peer must have the TGVOIP_PEER_CAP_GROUP_CALLS capability. After the peer acknowledges the key, Callbacks::groupCallKeySent will be called.
|
|
|
|
* @param key newly-generated group call key, must be exactly 265 bytes long
|
|
|
|
*/
|
2020-01-23 18:06:11 +01:00
|
|
|
void SendGroupCallKey(uint8_t *key);
|
2020-01-23 16:52:40 +01:00
|
|
|
/**
|
|
|
|
* In an incoming call, request the peer to generate a new encryption key, send it to you and upgrade this call to a E2E group call.
|
|
|
|
*/
|
|
|
|
void RequestCallUpgrade();
|
|
|
|
void SetEchoCancellationStrength(int strength);
|
|
|
|
int GetConnectionState();
|
|
|
|
bool NeedRate();
|
|
|
|
/**
|
|
|
|
* Get the maximum connection layer supported by this libtgvoip version.
|
|
|
|
* Pass this as <code>max_layer</code> in the phone.phoneConnection TL object when requesting and accepting calls.
|
|
|
|
*/
|
|
|
|
static const int32_t GetConnectionMaxLayer()
|
|
|
|
{
|
|
|
|
return 92;
|
|
|
|
};
|
|
|
|
/**
|
|
|
|
* Get the persistable state of the library, like proxy capabilities, to save somewhere on the disk. Call this at the end of the call.
|
|
|
|
* Using this will speed up the connection establishment in some cases.
|
|
|
|
*/
|
|
|
|
std::vector<uint8_t> GetPersistentState();
|
|
|
|
/**
|
|
|
|
* Load the persistable state. Call this before starting the call.
|
|
|
|
*/
|
|
|
|
void SetPersistentState(std::vector<uint8_t> state);
|
2018-11-09 16:44:01 +01:00
|
|
|
|
2018-09-01 00:59:09 +02:00
|
|
|
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
2020-01-23 16:52:40 +01:00
|
|
|
void SetAudioDataCallbacks(std::function<void(int16_t *, size_t)> input, std::function<void(int16_t *, size_t)> output, std::function<void(int16_t *, size_t)> preprocessed);
|
2018-09-01 00:59:09 +02:00
|
|
|
#endif
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
void SetVideoCodecSpecificData(const std::vector<Buffer> &data);
|
|
|
|
|
|
|
|
struct Callbacks
|
|
|
|
{
|
|
|
|
void (*connectionStateChanged)(VoIPController *, int);
|
|
|
|
void (*signalBarCountChanged)(VoIPController *, int);
|
|
|
|
void (*groupCallKeySent)(VoIPController *);
|
2020-01-23 18:06:11 +01:00
|
|
|
void (*groupCallKeyReceived)(VoIPController *, const uint8_t *);
|
2020-01-23 16:52:40 +01:00
|
|
|
void (*upgradeToGroupCallRequested)(VoIPController *);
|
|
|
|
};
|
|
|
|
void SetCallbacks(Callbacks callbacks);
|
|
|
|
|
|
|
|
float GetOutputLevel()
|
|
|
|
{
|
|
|
|
return 0.0f;
|
|
|
|
};
|
|
|
|
void SetVideoSource(video::VideoSource *source);
|
|
|
|
void SetVideoRenderer(video::VideoRenderer *renderer);
|
|
|
|
|
|
|
|
void SetInputVolume(float level);
|
|
|
|
void SetOutputVolume(float level);
|
2018-12-30 00:24:55 +01:00
|
|
|
#if defined(__APPLE__) && defined(TARGET_OS_OSX)
|
2020-01-23 16:52:40 +01:00
|
|
|
void SetAudioOutputDuckingEnabled(bool enabled);
|
2018-12-30 00:24:55 +01:00
|
|
|
#endif
|
2018-05-15 20:23:46 +02:00
|
|
|
|
2020-01-27 19:53:32 +01:00
|
|
|
enum StreamType
|
|
|
|
{
|
|
|
|
STREAM_TYPE_AUDIO = 1,
|
|
|
|
STREAM_TYPE_VIDEO
|
|
|
|
};
|
|
|
|
|
2020-01-28 23:45:47 +01:00
|
|
|
struct Stream
|
2020-01-23 16:52:40 +01:00
|
|
|
{
|
|
|
|
int32_t userID;
|
2020-01-23 18:06:11 +01:00
|
|
|
uint8_t id;
|
2020-01-27 19:53:32 +01:00
|
|
|
StreamType type;
|
2020-01-23 16:52:40 +01:00
|
|
|
uint32_t codec;
|
|
|
|
bool enabled;
|
|
|
|
bool extraECEnabled;
|
|
|
|
uint16_t frameDuration;
|
|
|
|
std::shared_ptr<JitterBuffer> jitterBuffer;
|
|
|
|
std::shared_ptr<OpusDecoder> decoder;
|
|
|
|
std::shared_ptr<PacketReassembler> packetReassembler;
|
|
|
|
std::shared_ptr<CallbackWrapper> callbackWrapper;
|
2020-01-27 19:53:32 +01:00
|
|
|
std::unique_ptr<PacketSender> packetSender;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::vector<Buffer> codecSpecificData;
|
|
|
|
bool csdIsValid = false;
|
|
|
|
bool paused = false;
|
|
|
|
int resolution;
|
2020-01-27 17:18:33 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
unsigned int width = 0;
|
|
|
|
unsigned int height = 0;
|
|
|
|
uint16_t rotation = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ProtocolInfo
|
|
|
|
{
|
|
|
|
uint32_t version;
|
|
|
|
uint32_t maxVideoResolution;
|
|
|
|
std::vector<uint32_t> videoDecoders;
|
|
|
|
bool videoCaptureSupported;
|
|
|
|
bool videoDisplaySupported;
|
|
|
|
bool callUpgradeSupported;
|
|
|
|
};
|
2020-01-22 12:43:51 +01:00
|
|
|
|
|
|
|
protected:
|
2020-01-23 16:52:40 +01:00
|
|
|
virtual void ProcessIncomingPacket(NetworkPacket &packet, Endpoint &srcEndpoint);
|
|
|
|
virtual void ProcessExtraData(Buffer &data);
|
2020-01-29 15:52:43 +01:00
|
|
|
virtual uint8_t WritePacketHeader(PendingOutgoingPacket &pkt, BufferOutputStream &s, PacketSender *source);
|
|
|
|
virtual void SendPacket(unsigned char *data, size_t len, Endpoint &ep, uint32_t seq, uint8_t type, uint8_t transportId);
|
2020-01-23 16:52:40 +01:00
|
|
|
virtual void SendInit();
|
|
|
|
virtual void SendUdpPing(Endpoint &endpoint);
|
|
|
|
virtual void SendRelayPings();
|
|
|
|
virtual void OnAudioOutputReady();
|
|
|
|
virtual void SendExtra(Buffer &data, unsigned char type);
|
|
|
|
void SendStreamFlags(Stream &stream);
|
|
|
|
void InitializeTimers();
|
|
|
|
void ResetEndpointPingStats();
|
|
|
|
void ProcessIncomingVideoFrame(Buffer frame, uint32_t pts, bool keyframe, uint16_t rotation);
|
2020-01-27 19:53:32 +01:00
|
|
|
std::shared_ptr<Stream> GetStreamByType(StreamType type, bool outgoing);
|
2020-01-23 16:52:40 +01:00
|
|
|
std::shared_ptr<Stream> GetStreamByID(unsigned char id, bool outgoing);
|
|
|
|
Endpoint *GetEndpointForPacket(const PendingOutgoingPacket &pkt);
|
2020-01-29 15:52:43 +01:00
|
|
|
Endpoint *GetEndpointById(const int64_t id);
|
2020-01-23 16:52:40 +01:00
|
|
|
bool SendOrEnqueuePacket(PendingOutgoingPacket pkt, bool enqueue = true, PacketSender *source = NULL);
|
|
|
|
CellularCarrierInfo GetCarrierInfo();
|
2020-01-22 12:43:51 +01:00
|
|
|
|
|
|
|
private:
|
2020-01-26 10:28:30 +01:00
|
|
|
struct RawPendingOutgoingPacket
|
2020-01-23 16:52:40 +01:00
|
|
|
{
|
2020-01-26 10:28:30 +01:00
|
|
|
TGVOIP_MOVE_ONLY(RawPendingOutgoingPacket);
|
|
|
|
NetworkPacket packet;
|
|
|
|
std::shared_ptr<NetworkSocket> socket;
|
2020-01-23 16:52:40 +01:00
|
|
|
};
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
UDP_UNKNOWN = 0,
|
|
|
|
UDP_PING_PENDING,
|
|
|
|
UDP_PING_SENT,
|
|
|
|
UDP_AVAILABLE,
|
|
|
|
UDP_NOT_AVAILABLE,
|
|
|
|
UDP_BAD
|
|
|
|
};
|
|
|
|
|
|
|
|
void RunRecvThread();
|
|
|
|
void RunSendThread();
|
|
|
|
void UpdateAudioBitrateLimit();
|
|
|
|
void SetState(int state);
|
|
|
|
void UpdateAudioOutputState();
|
|
|
|
void InitUDPProxy();
|
|
|
|
void UpdateDataSavingState();
|
2020-01-26 11:18:18 +01:00
|
|
|
|
2020-01-25 20:45:43 +01:00
|
|
|
size_t decryptPacket(unsigned char *buffer, BufferInputStream &in);
|
2020-01-26 11:18:18 +01:00
|
|
|
void encryptPacket(unsigned char *data, size_t len, BufferOutputStream &out);
|
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
void KDF(unsigned char *msgKey, size_t x, unsigned char *aesKey, unsigned char *aesIv);
|
|
|
|
void KDF2(unsigned char *msgKey, size_t x, unsigned char *aesKey, unsigned char *aesIv);
|
2020-01-26 11:18:18 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
void SendPublicEndpointsRequest();
|
|
|
|
void SendPublicEndpointsRequest(const Endpoint &relay);
|
|
|
|
Endpoint &GetEndpointByType(const Endpoint::Type type);
|
2020-01-26 21:06:16 +01:00
|
|
|
void SendPacketReliably(unsigned char type, unsigned char *data, size_t len, double retryInterval, double timeout, uint8_t tries = 0xFF);
|
2020-01-23 18:06:11 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
void InitializeAudio();
|
|
|
|
void StartAudio();
|
|
|
|
void ProcessAcknowledgedOutgoingExtra(UnacknowledgedExtraData &extra);
|
|
|
|
void AddIPv6Relays();
|
|
|
|
void AddTCPRelays();
|
|
|
|
void SendUdpPings();
|
|
|
|
void EvaluateUdpPingResults();
|
|
|
|
void UpdateRTT();
|
|
|
|
void UpdateCongestion();
|
|
|
|
void UpdateAudioBitrate();
|
|
|
|
void UpdateSignalBars();
|
2020-01-26 21:06:16 +01:00
|
|
|
void UpdateReliablePackets();
|
2020-01-29 15:52:43 +01:00
|
|
|
void SendNopPacket(PacketManager &pm);
|
2020-01-23 16:52:40 +01:00
|
|
|
void TickJitterBufferAndCongestionControl();
|
|
|
|
void ResetUdpAvailability();
|
2020-01-26 10:28:30 +01:00
|
|
|
inline static std::string NetworkTypeToString(int type)
|
2020-01-25 18:11:15 +01:00
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case NET_TYPE_WIFI:
|
|
|
|
return "wifi";
|
|
|
|
case NET_TYPE_GPRS:
|
|
|
|
return "gprs";
|
|
|
|
case NET_TYPE_EDGE:
|
|
|
|
return "edge";
|
|
|
|
case NET_TYPE_3G:
|
|
|
|
return "3g";
|
|
|
|
case NET_TYPE_HSPA:
|
|
|
|
return "hspa";
|
|
|
|
case NET_TYPE_LTE:
|
|
|
|
return "lte";
|
|
|
|
case NET_TYPE_ETHERNET:
|
|
|
|
return "ethernet";
|
|
|
|
case NET_TYPE_OTHER_HIGH_SPEED:
|
|
|
|
return "other_high_speed";
|
|
|
|
case NET_TYPE_OTHER_LOW_SPEED:
|
|
|
|
return "other_low_speed";
|
|
|
|
case NET_TYPE_DIALUP:
|
|
|
|
return "dialup";
|
|
|
|
case NET_TYPE_OTHER_MOBILE:
|
|
|
|
return "other_mobile";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-26 10:28:30 +01:00
|
|
|
inline static std::string GetPacketTypeString(unsigned char type)
|
2020-01-25 18:11:15 +01:00
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case PKT_INIT:
|
|
|
|
return "init";
|
|
|
|
case PKT_INIT_ACK:
|
|
|
|
return "init_ack";
|
|
|
|
case PKT_STREAM_STATE:
|
|
|
|
return "stream_state";
|
|
|
|
case PKT_STREAM_DATA:
|
|
|
|
return "stream_data";
|
|
|
|
case PKT_PING:
|
|
|
|
return "ping";
|
|
|
|
case PKT_PONG:
|
|
|
|
return "pong";
|
|
|
|
case PKT_LAN_ENDPOINT:
|
|
|
|
return "lan_endpoint";
|
|
|
|
case PKT_NETWORK_CHANGED:
|
|
|
|
return "network_changed";
|
|
|
|
case PKT_NOP:
|
|
|
|
return "nop";
|
|
|
|
case PKT_STREAM_EC:
|
|
|
|
return "stream_ec";
|
|
|
|
}
|
|
|
|
return string("unknown(") + std::to_string(type) + ')';
|
|
|
|
}
|
|
|
|
|
2020-01-26 10:28:30 +01:00
|
|
|
// More legacy
|
2020-01-25 20:45:43 +01:00
|
|
|
bool legacyParsePacket(BufferInputStream &in, unsigned char &type, uint32_t &ackId, uint32_t &pseq, uint32_t &acks, unsigned char &pflags, size_t &packetInnerLen);
|
2020-01-28 23:45:47 +01:00
|
|
|
void legacyWritePacketHeader(uint32_t pseq, uint32_t acks, BufferOutputStream *s, unsigned char type, uint32_t length);
|
2020-01-27 16:02:59 +01:00
|
|
|
|
|
|
|
void handleReliablePackets();
|
2020-01-25 20:45:43 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
void SetupOutgoingVideoStream();
|
2020-01-27 16:02:59 +01:00
|
|
|
bool WasOutgoingPacketAcknowledged(uint32_t seq, bool checkAll = true);
|
2020-01-23 16:52:40 +01:00
|
|
|
void NetworkPacketReceived(std::shared_ptr<NetworkPacket> packet);
|
2020-01-26 21:06:16 +01:00
|
|
|
void TrySendOutgoingPackets();
|
2020-01-23 16:52:40 +01:00
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
int state = STATE_WAIT_INIT;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::map<int64_t, Endpoint> endpoints;
|
|
|
|
int64_t currentEndpoint = 0;
|
|
|
|
int64_t preferredRelay = 0;
|
|
|
|
int64_t peerPreferredRelay = 0;
|
2020-01-24 20:26:34 +01:00
|
|
|
std::atomic<bool> runReceiver = ATOMIC_VAR_INIT(false);
|
2020-01-26 11:18:18 +01:00
|
|
|
|
2020-01-27 17:18:33 +01:00
|
|
|
// Acks now handled in Ack
|
2020-01-27 16:02:59 +01:00
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
HistoricBuffer<uint32_t, 10, double> sendLossCountHistory;
|
2020-01-24 20:26:34 +01:00
|
|
|
uint32_t audioTimestampIn = 0;
|
|
|
|
|
2020-01-28 16:14:43 +01:00
|
|
|
std::shared_ptr<OpusEncoder> encoder;
|
2020-01-24 20:26:34 +01:00
|
|
|
std::unique_ptr<tgvoip::audio::AudioIO> audioIO;
|
|
|
|
|
|
|
|
// Obtained from audioIO
|
|
|
|
std::shared_ptr<tgvoip::audio::AudioInput> audioInput;
|
|
|
|
std::shared_ptr<tgvoip::audio::AudioOutput> audioOutput;
|
|
|
|
|
|
|
|
// Shared between encoder and decoder
|
|
|
|
std::shared_ptr<EchoCanceller> echoCanceller;
|
|
|
|
|
|
|
|
std::unique_ptr<Thread> recvThread;
|
|
|
|
std::unique_ptr<Thread> sendThread;
|
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
std::vector<PendingOutgoingPacket> sendQueue;
|
2020-01-24 20:26:34 +01:00
|
|
|
std::atomic<bool> stopping = ATOMIC_VAR_INIT(false);
|
|
|
|
bool audioOutStarted = false;
|
|
|
|
uint32_t packetsReceived = 0;
|
|
|
|
uint32_t recvLossCount = 0;
|
|
|
|
uint32_t prevSendLossCount = 0;
|
2020-01-23 16:52:40 +01:00
|
|
|
uint32_t firstSentPing;
|
|
|
|
HistoricBuffer<double, 32> rttHistory;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool waitingForAcks = false;
|
|
|
|
int networkType = NET_TYPE_UNKNOWN;
|
|
|
|
int dontSendPackets = 0;
|
2020-01-23 16:52:40 +01:00
|
|
|
int lastError;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool micMuted = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
uint32_t maxBitrate;
|
2020-01-24 20:26:34 +01:00
|
|
|
|
|
|
|
//
|
2020-01-23 16:52:40 +01:00
|
|
|
std::vector<std::shared_ptr<Stream>> outgoingStreams;
|
|
|
|
std::vector<std::shared_ptr<Stream>> incomingStreams;
|
2020-01-24 20:26:34 +01:00
|
|
|
|
2020-01-28 23:45:47 +01:00
|
|
|
PacketManager &getBestPacketManager();
|
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
unsigned char encryptionKey[256];
|
|
|
|
unsigned char keyFingerprint[8];
|
|
|
|
unsigned char callID[16];
|
|
|
|
double stateChangeTime;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool waitingForRelayPeerInfo = false;
|
|
|
|
bool allowP2p = true;
|
|
|
|
bool dataSavingMode = false;
|
|
|
|
bool dataSavingRequestedByPeer = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string activeNetItfName;
|
2020-01-24 20:26:34 +01:00
|
|
|
double publicEndpointsReqTime = 0;
|
2020-01-26 21:06:16 +01:00
|
|
|
std::vector<ReliableOutgoingPacket> reliablePackets;
|
2020-01-24 20:26:34 +01:00
|
|
|
double connectionInitTime = 0;
|
|
|
|
double lastRecvPacketTime = 0;
|
2020-01-23 16:52:40 +01:00
|
|
|
Config config;
|
2020-01-24 20:26:34 +01:00
|
|
|
int32_t peerVersion = 0;
|
|
|
|
CongestionControl conctl;
|
2020-01-23 16:52:40 +01:00
|
|
|
TrafficStats stats;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool receivedInit = false;
|
|
|
|
bool receivedInitAck = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
bool isOutgoing;
|
2020-01-24 20:26:34 +01:00
|
|
|
|
|
|
|
// Might point to the same or different objects
|
|
|
|
std::shared_ptr<NetworkSocket> udpSocket;
|
|
|
|
std::shared_ptr<NetworkSocket> realUdpSocket;
|
|
|
|
|
|
|
|
std::ofstream statsDump;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string currentAudioInput;
|
|
|
|
std::string currentAudioOutput;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool useTCP = false;
|
|
|
|
bool useUDP = true;
|
|
|
|
bool didAddTcpRelays = false;
|
|
|
|
std::unique_ptr<SocketSelectCanceller> selectCanceller;
|
2020-01-23 16:52:40 +01:00
|
|
|
HistoricBuffer<unsigned char, 4, int> signalBarsHistory;
|
|
|
|
bool audioStarted = false;
|
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
int udpConnectivityState = UDP_UNKNOWN;
|
|
|
|
double lastUdpPingTime = 0;
|
|
|
|
int udpPingCount = 0;
|
|
|
|
int echoCancellationStrength = 1;
|
2020-01-23 16:52:40 +01:00
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
int proxyProtocol = PROXY_NONE;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string proxyAddress;
|
2020-01-24 20:26:34 +01:00
|
|
|
uint16_t proxyPort = 0;
|
2020-01-23 16:52:40 +01:00
|
|
|
std::string proxyUsername;
|
|
|
|
std::string proxyPassword;
|
|
|
|
NetworkAddress resolvedProxyAddress = NetworkAddress::Empty();
|
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
uint32_t peerCapabilities = 0;
|
|
|
|
Callbacks callbacks{0};
|
|
|
|
bool didReceiveGroupCallKey = false;
|
|
|
|
bool didReceiveGroupCallKeyAck = false;
|
|
|
|
bool didSendGroupCallKey = false;
|
|
|
|
bool didSendUpgradeRequest = false;
|
|
|
|
bool didInvokeUpgradeCallback = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
int32_t connectionMaxLayer = 0;
|
|
|
|
bool useMTProto2 = false;
|
|
|
|
bool setCurrentEndpointToTCP = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
|
|
|
|
std::vector<UnacknowledgedExtraData> currentExtras;
|
|
|
|
std::unordered_map<uint8_t, uint64_t> lastReceivedExtrasByType;
|
2020-01-24 20:26:34 +01:00
|
|
|
bool useIPv6 = false;
|
|
|
|
bool peerIPv6Available = false;
|
2020-01-26 10:28:30 +01:00
|
|
|
NetworkAddress myIPv6{NetworkAddress::Empty()};
|
2020-01-24 20:26:34 +01:00
|
|
|
bool didAddIPv6Relays = false;
|
|
|
|
bool didSendIPv6Endpoint = false;
|
2020-01-23 16:52:40 +01:00
|
|
|
int publicEndpointsReqCount = 0;
|
|
|
|
bool wasEstablished = false;
|
|
|
|
bool receivedFirstStreamPacket = false;
|
2020-01-24 20:26:34 +01:00
|
|
|
std::atomic<unsigned int> unsentStreamPackets = ATOMIC_VAR_INIT(0);
|
2020-01-23 16:52:40 +01:00
|
|
|
HistoricBuffer<unsigned int, 5> unsentStreamPacketsHistory;
|
|
|
|
bool needReInitUdpProxy = true;
|
|
|
|
bool needRate = false;
|
|
|
|
BlockingQueue<RawPendingOutgoingPacket> rawSendQueue;
|
|
|
|
|
2020-01-28 23:45:47 +01:00
|
|
|
PacketManager packetManager;
|
|
|
|
|
2020-01-23 16:52:40 +01:00
|
|
|
uint32_t initTimeoutID = MessageThread::INVALID_ID;
|
|
|
|
uint32_t udpPingTimeoutID = MessageThread::INVALID_ID;
|
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
// Using a shared_ptr is redundant here, but it allows more flexibility in the OpusEncoder API
|
|
|
|
std::shared_ptr<effects::Volume> outputVolume = std::make_shared<effects::Volume>();
|
|
|
|
std::shared_ptr<effects::Volume> inputVolume = std::make_shared<effects::Volume>();
|
2020-01-23 16:52:40 +01:00
|
|
|
|
|
|
|
std::vector<uint32_t> peerVideoDecoders;
|
|
|
|
|
|
|
|
MessageThread messageThread;
|
|
|
|
|
|
|
|
// Locked whenever the endpoints vector is modified (but not endpoints themselves) and whenever iterated outside of messageThread.
|
|
|
|
// After the call is started, only messageThread is allowed to modify the endpoints vector.
|
|
|
|
Mutex endpointsMutex;
|
|
|
|
// Locked while audio i/o is being initialized and deinitialized so as to allow it to fully initialize before deinitialization begins.
|
|
|
|
Mutex audioIOMutex;
|
2019-02-05 12:41:00 +01:00
|
|
|
|
2018-09-01 00:59:09 +02:00
|
|
|
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
2020-01-23 16:52:40 +01:00
|
|
|
std::function<void(int16_t *, size_t)> audioInputDataCallback;
|
|
|
|
std::function<void(int16_t *, size_t)> audioOutputDataCallback;
|
2018-09-01 00:59:09 +02:00
|
|
|
#endif
|
2018-12-30 00:24:55 +01:00
|
|
|
#if defined(__APPLE__) && defined(TARGET_OS_OSX)
|
2020-01-23 16:52:40 +01:00
|
|
|
bool macAudioDuckingEnabled = true;
|
2018-12-30 00:24:55 +01:00
|
|
|
#endif
|
2020-01-22 12:43:51 +01:00
|
|
|
|
2020-01-24 20:26:34 +01:00
|
|
|
video::VideoRenderer *videoRenderer = nullptr;
|
2020-01-23 16:52:40 +01:00
|
|
|
uint32_t lastReceivedVideoFrameNumber = UINT32_MAX;
|
|
|
|
|
|
|
|
uint32_t sendLosses = 0;
|
|
|
|
uint32_t unacknowledgedIncomingPacketCount = 0;
|
|
|
|
|
2020-01-26 10:28:30 +01:00
|
|
|
ProtocolInfo protocolInfo{0};
|
2020-01-23 16:52:40 +01:00
|
|
|
|
|
|
|
/*** debug report problems ***/
|
|
|
|
bool wasReconnecting = false;
|
|
|
|
bool wasExtraEC = false;
|
|
|
|
bool wasEncoderLaggy = false;
|
|
|
|
bool wasNetworkHandover = false;
|
|
|
|
|
|
|
|
/*** persistable state values ***/
|
|
|
|
bool proxySupportsUDP = true;
|
|
|
|
bool proxySupportsTCP = true;
|
|
|
|
std::string lastTestedProxyServer = "";
|
|
|
|
|
|
|
|
/*** server config values ***/
|
|
|
|
uint32_t maxAudioBitrate;
|
|
|
|
uint32_t maxAudioBitrateEDGE;
|
|
|
|
uint32_t maxAudioBitrateGPRS;
|
|
|
|
uint32_t maxAudioBitrateSaving;
|
|
|
|
uint32_t initAudioBitrate;
|
|
|
|
uint32_t initAudioBitrateEDGE;
|
|
|
|
uint32_t initAudioBitrateGPRS;
|
|
|
|
uint32_t initAudioBitrateSaving;
|
|
|
|
uint32_t minAudioBitrate;
|
|
|
|
uint32_t audioBitrateStepIncr;
|
|
|
|
uint32_t audioBitrateStepDecr;
|
|
|
|
double relaySwitchThreshold;
|
|
|
|
double p2pToRelaySwitchThreshold;
|
|
|
|
double relayToP2pSwitchThreshold;
|
|
|
|
double reconnectingTimeout;
|
|
|
|
uint32_t needRateFlags;
|
|
|
|
double rateMaxAcceptableRTT;
|
|
|
|
double rateMaxAcceptableSendLoss;
|
|
|
|
double packetLossToEnableExtraEC;
|
|
|
|
uint32_t maxUnsentStreamPackets;
|
|
|
|
uint32_t unackNopThreshold;
|
2020-01-22 12:43:51 +01:00
|
|
|
|
|
|
|
public:
|
2017-04-17 20:57:07 +02:00
|
|
|
#ifdef __APPLE__
|
2020-01-23 16:52:40 +01:00
|
|
|
static double machTimebase;
|
2020-01-24 20:26:34 +01:00
|
|
|
static uint64_t machTimestart = 0;
|
2017-04-17 20:57:07 +02:00
|
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
2020-01-23 16:52:40 +01:00
|
|
|
static int64_t win32TimeScale;
|
|
|
|
static bool didInitWin32TimeScale;
|
2017-02-02 17:24:40 +01:00
|
|
|
#endif
|
2020-01-22 12:43:51 +01:00
|
|
|
};
|
2017-02-02 17:24:40 +01:00
|
|
|
|
2020-01-22 12:43:51 +01:00
|
|
|
}; // namespace tgvoip
|