mirror of
https://github.com/danog/libtgvoip.git
synced 2024-12-02 09:37:52 +01:00
Merge branch 'public' of https://github.com/grishka/libtgvoip into public
This commit is contained in:
commit
c5fa50730f
@ -97,12 +97,12 @@ EchoCanceller::EchoCanceller(bool enableAEC, bool enableNS, bool enableAGC){
|
||||
if(enableAGC){
|
||||
agc=WebRtcAgc_Create();
|
||||
WebRtcAgcConfig agcConfig;
|
||||
agcConfig.compressionGaindB = 9;
|
||||
agcConfig.compressionGaindB = 20;
|
||||
agcConfig.limiterEnable = 1;
|
||||
agcConfig.targetLevelDbfs = 3;
|
||||
WebRtcAgc_Init(agc, 0, 255, kAgcModeAdaptiveAnalog, 48000);
|
||||
agcConfig.targetLevelDbfs = 9;
|
||||
WebRtcAgc_Init(agc, 0, 255, kAgcModeAdaptiveDigital, 48000);
|
||||
WebRtcAgc_set_config(agc, agcConfig);
|
||||
agcMicLevel=128;
|
||||
agcMicLevel=0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -354,3 +354,75 @@ void EchoCanceller::ProcessInput(unsigned char* data, unsigned char* out, size_t
|
||||
memcpy(samplesOut, bufIn->ibuf_const()->bands(0)[0], 960*2);
|
||||
}
|
||||
|
||||
AudioEffect::~AudioEffect(){
|
||||
|
||||
}
|
||||
|
||||
void AudioEffect::SetPassThrough(bool passThrough){
|
||||
this->passThrough=passThrough;
|
||||
}
|
||||
|
||||
AutomaticGainControl::AutomaticGainControl(){
|
||||
splittingFilter=new webrtc::SplittingFilter(1, 3, 960);
|
||||
splittingFilterIn=new webrtc::IFChannelBuffer(960, 1, 1);
|
||||
splittingFilterOut=new webrtc::IFChannelBuffer(960, 1, 3);
|
||||
|
||||
agc=WebRtcAgc_Create();
|
||||
WebRtcAgcConfig agcConfig;
|
||||
agcConfig.compressionGaindB = 9;
|
||||
agcConfig.limiterEnable = 1;
|
||||
agcConfig.targetLevelDbfs = 3;
|
||||
WebRtcAgc_Init(agc, 0, 255, kAgcModeAdaptiveDigital, 48000);
|
||||
WebRtcAgc_set_config(agc, agcConfig);
|
||||
agcMicLevel=0;
|
||||
}
|
||||
|
||||
AutomaticGainControl::~AutomaticGainControl(){
|
||||
delete (webrtc::SplittingFilter*)splittingFilter;
|
||||
delete (webrtc::IFChannelBuffer*)splittingFilterIn;
|
||||
delete (webrtc::IFChannelBuffer*)splittingFilterOut;
|
||||
WebRtcAgc_Free(agc);
|
||||
}
|
||||
|
||||
void AutomaticGainControl::Process(int16_t *inOut, size_t numSamples){
|
||||
if(passThrough)
|
||||
return;
|
||||
if(numSamples!=960){
|
||||
LOGW("AutomaticGainControl only works on 960-sample buffers (got %u samples)", numSamples);
|
||||
return;
|
||||
}
|
||||
//LOGV("processing frame through AGC");
|
||||
|
||||
webrtc::IFChannelBuffer* bufIn=(webrtc::IFChannelBuffer*) splittingFilterIn;
|
||||
webrtc::IFChannelBuffer* bufOut=(webrtc::IFChannelBuffer*) splittingFilterOut;
|
||||
|
||||
memcpy(bufIn->ibuf()->bands(0)[0], inOut, 960*2);
|
||||
|
||||
((webrtc::SplittingFilter*)splittingFilter)->Analysis(bufIn, bufOut);
|
||||
|
||||
int i;
|
||||
int16_t _agcOut[3][320];
|
||||
int16_t* agcIn[3];
|
||||
int16_t* agcOut[3];
|
||||
for(i=0;i<3;i++){
|
||||
agcIn[i]=(int16_t*)bufOut->ibuf_const()->bands(0)[i];
|
||||
agcOut[i]=_agcOut[i];
|
||||
}
|
||||
uint8_t saturation;
|
||||
WebRtcAgc_AddMic(agc, agcIn, 3, 160);
|
||||
WebRtcAgc_Process(agc, (const int16_t *const *) agcIn, 3, 160, agcOut, agcMicLevel, &agcMicLevel, 0, &saturation);
|
||||
for(i=0;i<3;i++){
|
||||
agcOut[i]+=160;
|
||||
agcIn[i]+=160;
|
||||
}
|
||||
WebRtcAgc_AddMic(agc, agcIn, 3, 160);
|
||||
WebRtcAgc_Process(agc, (const int16_t *const *) agcIn, 3, 160, agcOut, agcMicLevel, &agcMicLevel, 0, &saturation);
|
||||
memcpy(bufOut->ibuf()->bands(0)[0], _agcOut[0], 320*2);
|
||||
memcpy(bufOut->ibuf()->bands(0)[1], _agcOut[1], 320*2);
|
||||
memcpy(bufOut->ibuf()->bands(0)[2], _agcOut[2], 320*2);
|
||||
|
||||
((webrtc::SplittingFilter*)splittingFilter)->Synthesis(bufOut, bufIn);
|
||||
|
||||
memcpy(inOut, bufIn->ibuf_const()->bands(0)[0], 960*2);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "threading.h"
|
||||
#include "BufferPool.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "MediaStreamItf.h"
|
||||
|
||||
namespace tgvoip{
|
||||
class EchoCanceller{
|
||||
@ -48,6 +49,29 @@ private:
|
||||
int32_t agcMicLevel;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
class AudioEffect{
|
||||
public:
|
||||
virtual ~AudioEffect()=0;
|
||||
virtual void Process(int16_t* inOut, size_t numSamples)=0;
|
||||
virtual void SetPassThrough(bool passThrough);
|
||||
protected:
|
||||
bool passThrough;
|
||||
};
|
||||
|
||||
class AutomaticGainControl : public AudioEffect{
|
||||
public:
|
||||
AutomaticGainControl();
|
||||
virtual ~AutomaticGainControl();
|
||||
virtual void Process(int16_t* inOut, size_t numSamples);
|
||||
|
||||
private:
|
||||
void* agc;
|
||||
void* splittingFilter;
|
||||
void* splittingFilterIn;
|
||||
void* splittingFilterOut;
|
||||
int32_t agcMicLevel;
|
||||
};
|
||||
};
|
||||
|
||||
#endif //LIBTGVOIP_ECHOCANCELLER_H
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "audio/Resampler.h"
|
||||
#include "logging.h"
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
#define PACKET_SIZE (960*2)
|
||||
|
||||
@ -222,6 +223,9 @@ void tgvoip::OpusDecoder::RunThread(){
|
||||
unsigned char *buf=bufferPool->Get();
|
||||
if(buf){
|
||||
if(size>0){
|
||||
for(std::vector<AudioEffect*>::iterator effect=postProcEffects.begin();effect!=postProcEffects.end();++effect){
|
||||
(*effect)->Process(reinterpret_cast<int16_t*>(processedBuffer+(PACKET_SIZE*i)), 960);
|
||||
}
|
||||
memcpy(buf, processedBuffer+(PACKET_SIZE*i), PACKET_SIZE);
|
||||
}else{
|
||||
LOGE("Error decoding, result=%d", size);
|
||||
@ -255,3 +259,13 @@ void tgvoip::OpusDecoder::ResetQueue(){
|
||||
void tgvoip::OpusDecoder::SetJitterBuffer(JitterBuffer* jitterBuffer){
|
||||
this->jitterBuffer=jitterBuffer;
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::AddAudioEffect(AudioEffect *effect){
|
||||
postProcEffects.push_back(effect);
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::RemoveAudioEffect(AudioEffect *effect){
|
||||
std::vector<AudioEffect*>::iterator i=std::find(postProcEffects.begin(), postProcEffects.end(), effect);
|
||||
if(i!=postProcEffects.end())
|
||||
postProcEffects.erase(i);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "EchoCanceller.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
class OpusDecoder {
|
||||
@ -31,6 +32,8 @@ public:
|
||||
void SetFrameDuration(uint32_t duration);
|
||||
void ResetQueue();
|
||||
void SetJitterBuffer(JitterBuffer* jitterBuffer);
|
||||
void AddAudioEffect(AudioEffect* effect);
|
||||
void RemoveAudioEffect(AudioEffect* effect);
|
||||
|
||||
private:
|
||||
static size_t Callback(unsigned char* data, size_t len, void* param);
|
||||
@ -50,6 +53,7 @@ private:
|
||||
uint32_t frameDuration;
|
||||
EchoCanceller* echoCanceller;
|
||||
JitterBuffer* jitterBuffer;
|
||||
std::vector<AudioEffect*> postProcEffects;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -234,6 +234,9 @@ VoIPController::VoIPController() : activeNetItfName(""),
|
||||
realUdpSocket=udpSocket;
|
||||
udpConnectivityState=UDP_UNKNOWN;
|
||||
|
||||
outputAGC=NULL;
|
||||
outputAGCEnabled=false;
|
||||
|
||||
maxAudioBitrate=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("audio_max_bitrate", 20000);
|
||||
maxAudioBitrateGPRS=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("audio_max_bitrate_gprs", 8000);
|
||||
maxAudioBitrateEDGE=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("audio_max_bitrate_edge", 16000);
|
||||
@ -264,6 +267,8 @@ VoIPController::VoIPController() : activeNetItfName(""),
|
||||
stm->enabled=1;
|
||||
stm->frameDuration=60;
|
||||
outgoingStreams.push_back(stm);
|
||||
|
||||
memset(signalBarsHistory, 0, sizeof(signalBarsHistory));
|
||||
}
|
||||
|
||||
VoIPController::~VoIPController(){
|
||||
@ -361,6 +366,8 @@ VoIPController::~VoIPController(){
|
||||
if(resolvedProxyAddress)
|
||||
delete resolvedProxyAddress;
|
||||
delete selectCanceller;
|
||||
if(outputAGC)
|
||||
delete outputAGC;
|
||||
LOGD("Left VoIPController::~VoIPController");
|
||||
}
|
||||
|
||||
@ -1182,10 +1189,13 @@ simpleAudioBlock random_id:long random_bytes:string raw_data:string = DecryptedA
|
||||
UpdateAudioBitrate();
|
||||
|
||||
jitterBuffer=new JitterBuffer(NULL, incomingAudioStream->frameDuration);
|
||||
outputAGC=new AutomaticGainControl();
|
||||
outputAGC->SetPassThrough(!outputAGCEnabled);
|
||||
decoder=new OpusDecoder(audioOutput);
|
||||
decoder->SetEchoCanceller(echoCanceller);
|
||||
decoder->SetJitterBuffer(jitterBuffer);
|
||||
decoder->SetFrameDuration(incomingAudioStream->frameDuration);
|
||||
decoder->AddAudioEffect(outputAGC);
|
||||
decoder->Start();
|
||||
if(incomingAudioStream->frameDuration>50)
|
||||
jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_60", 3));
|
||||
@ -1243,7 +1253,7 @@ simpleAudioBlock random_id:long random_bytes:string raw_data:string = DecryptedA
|
||||
audioOutput->Start();
|
||||
audioOutStarted=true;
|
||||
}
|
||||
if(jitterBuffer)
|
||||
if(jitterBuffer && in.Remaining()>=sdlen)
|
||||
jitterBuffer->HandleInput((unsigned char*) (buffer+in.GetOffset()), sdlen, pts);
|
||||
if(i<count-1)
|
||||
in.Seek(in.GetOffset()+sdlen);
|
||||
@ -1376,7 +1386,7 @@ void VoIPController::RunTickThread(){
|
||||
#else
|
||||
Sleep(100);
|
||||
#endif
|
||||
int prevSignalBarCount=signalBarCount;
|
||||
int prevSignalBarCount=GetSignalBarsCount();
|
||||
signalBarCount=4;
|
||||
tickCount++;
|
||||
if(connectionInitTime==0)
|
||||
@ -1559,12 +1569,12 @@ void VoIPController::RunTickThread(){
|
||||
double avgDelay=jitterBuffer->GetAverageDelay();
|
||||
double avgLateCount[3];
|
||||
jitterBuffer->GetAverageLateCount(avgLateCount);
|
||||
if(avgDelay>=5)
|
||||
/*if(avgDelay>=5)
|
||||
signalBarCount=1;
|
||||
else if(avgDelay>=4)
|
||||
signalBarCount=MIN(signalBarCount, 2);
|
||||
else if(avgDelay>=3)
|
||||
signalBarCount=MIN(signalBarCount, 3);
|
||||
signalBarCount=MIN(signalBarCount, 3);*/
|
||||
|
||||
if(avgLateCount[2]>=0.2)
|
||||
signalBarCount=1;
|
||||
@ -1711,10 +1721,12 @@ void VoIPController::RunTickThread(){
|
||||
setEstablishedAt=0;
|
||||
}
|
||||
|
||||
if(signalBarCount!=prevSignalBarCount){
|
||||
LOGD("SIGNAL BAR COUNT CHANGED: %d", signalBarCount);
|
||||
signalBarsHistory[tickCount%sizeof(signalBarsHistory)]=(unsigned char)signalBarCount;
|
||||
int _signalBarCount=GetSignalBarsCount();
|
||||
if(_signalBarCount!=prevSignalBarCount){
|
||||
LOGD("SIGNAL BAR COUNT CHANGED: %d", _signalBarCount);
|
||||
if(signalBarCountCallback)
|
||||
signalBarCountCallback(this, signalBarCount);
|
||||
signalBarCountCallback(this, _signalBarCount);
|
||||
}
|
||||
|
||||
|
||||
@ -2460,13 +2472,23 @@ void VoIPController::SendUdpPing(Endpoint *endpoint){
|
||||
}
|
||||
|
||||
int VoIPController::GetSignalBarsCount(){
|
||||
return signalBarCount;
|
||||
unsigned char avg=0;
|
||||
for(int i=0;i<sizeof(signalBarsHistory);i++)
|
||||
avg+=signalBarsHistory[i];
|
||||
return avg >> 2;
|
||||
}
|
||||
|
||||
void VoIPController::SetSignalBarsCountCallback(void (*f)(VoIPController *, int)){
|
||||
signalBarCountCallback=f;
|
||||
}
|
||||
|
||||
void VoIPController::SetAudioOutputGainControlEnabled(bool enabled){
|
||||
LOGD("New output AGC state: %d", enabled);
|
||||
outputAGCEnabled=enabled;
|
||||
if(outputAGC)
|
||||
outputAGC->SetPassThrough(!enabled);
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(int64_t id, uint16_t port, IPv4Address& _address, IPv6Address& _v6address, char type, unsigned char peerTag[16]) : address(_address), v6address(_v6address){
|
||||
this->id=id;
|
||||
this->port=port;
|
||||
|
@ -318,7 +318,7 @@ public:
|
||||
std::string GetCurrentAudioOutputID();
|
||||
/**
|
||||
* Set the proxy server to route the data through. Call this before connecting.
|
||||
* @param protocol PROXY_NONE, PROXY_SOCKS4, or PROXY_SOCKS5
|
||||
* @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
|
||||
@ -335,6 +335,13 @@ public:
|
||||
* @param f
|
||||
*/
|
||||
void SetSignalBarsCountCallback(void (*f)(VoIPController*, int));
|
||||
/**
|
||||
* 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);
|
||||
|
||||
private:
|
||||
struct PendingOutgoingPacket{
|
||||
@ -452,6 +459,7 @@ private:
|
||||
double setEstablishedAt;
|
||||
SocketSelectCanceller* selectCanceller;
|
||||
NetworkSocket* openingTcpSocket;
|
||||
unsigned char signalBarsHistory[4];
|
||||
|
||||
BufferPool outgoingPacketsBufferPool;
|
||||
int udpConnectivityState;
|
||||
@ -467,6 +475,9 @@ private:
|
||||
|
||||
int signalBarCount;
|
||||
void (*signalBarCountCallback)(VoIPController*, int);
|
||||
|
||||
AutomaticGainControl* outputAGC;
|
||||
bool outputAGCEnabled;
|
||||
|
||||
/*** server config values ***/
|
||||
uint32_t maxAudioBitrate;
|
||||
|
@ -299,6 +299,10 @@ extern "C" JNIEXPORT jstring Java_org_telegram_messenger_voip_VoIPController_nat
|
||||
return env->NewStringUTF(log.c_str());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetAudioOutputGainControlEnabled(JNIEnv* env, jclass clasz, jlong inst, jboolean enabled){
|
||||
((VoIPController*)(intptr_t)inst)->SetAudioOutputGainControlEnabled(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint Java_org_telegram_messenger_voip_Resampler_convert44to48(JNIEnv* env, jclass cls, jobject from, jobject to){
|
||||
return tgvoip::audio::Resampler::Convert44To48((int16_t *) env->GetDirectBufferAddress(from), (int16_t *) env->GetDirectBufferAddress(to), (size_t) (env->GetDirectBufferCapacity(from)/2), (size_t) (env->GetDirectBufferCapacity(to)/2));
|
||||
}
|
||||
|
@ -47,12 +47,8 @@
|
||||
692AB9201E675F7000706ACC /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 692AB91D1E675F7000706ACC /* AudioUnit.framework */; };
|
||||
692AB9211E675F7000706ACC /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 692AB91E1E675F7000706ACC /* CoreAudio.framework */; };
|
||||
695B20621EBD39FF00E31757 /* DarwinSpecific.h in Headers */ = {isa = PBXBuildFile; fileRef = 695B20601EBD39FF00E31757 /* DarwinSpecific.h */; };
|
||||
695B20631EBD39FF00E31757 /* DarwinSpecific.mm in Sources */ = {isa = PBXBuildFile; fileRef = 695B20611EBD39FF00E31757 /* DarwinSpecific.mm */; };
|
||||
698848411F4B39F700076DF0 /* AudioInputAudioUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6988483B1F4B39F700076DF0 /* AudioInputAudioUnit.cpp */; };
|
||||
698848421F4B39F700076DF0 /* AudioInputAudioUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6988483C1F4B39F700076DF0 /* AudioInputAudioUnit.h */; };
|
||||
698848431F4B39F700076DF0 /* AudioOutputAudioUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6988483D1F4B39F700076DF0 /* AudioOutputAudioUnit.cpp */; };
|
||||
698848441F4B39F700076DF0 /* AudioOutputAudioUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6988483E1F4B39F700076DF0 /* AudioOutputAudioUnit.h */; };
|
||||
698848451F4B39F700076DF0 /* AudioUnitIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6988483F1F4B39F700076DF0 /* AudioUnitIO.cpp */; };
|
||||
698848461F4B39F700076DF0 /* AudioUnitIO.h in Headers */ = {isa = PBXBuildFile; fileRef = 698848401F4B39F700076DF0 /* AudioUnitIO.h */; };
|
||||
69A6DEB91E96149300000E69 /* array_view.h in Headers */ = {isa = PBXBuildFile; fileRef = 69A6DE231E96149300000E69 /* array_view.h */; };
|
||||
69A6DEBA1E96149300000E69 /* atomicops.h in Headers */ = {isa = PBXBuildFile; fileRef = 69A6DE241E96149300000E69 /* atomicops.h */; };
|
||||
@ -189,8 +185,12 @@
|
||||
69A6DF441E9614B700000E69 /* AudioInputAudioUnitOSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 69A6DF401E9614B700000E69 /* AudioInputAudioUnitOSX.h */; };
|
||||
69A6DF451E9614B700000E69 /* AudioOutputAudioUnitOSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 69A6DF411E9614B700000E69 /* AudioOutputAudioUnitOSX.cpp */; };
|
||||
69A6DF461E9614B700000E69 /* AudioOutputAudioUnitOSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 69A6DF421E9614B700000E69 /* AudioOutputAudioUnitOSX.h */; };
|
||||
69AC14901F4B41CF00AC3173 /* Resampler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 69AC148E1F4B41CF00AC3173 /* Resampler.cpp */; };
|
||||
69AC14911F4B41CF00AC3173 /* Resampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 69AC148F1F4B41CF00AC3173 /* Resampler.h */; };
|
||||
C2A87DD81F4B6A33002D3F73 /* Resampler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DD71F4B6A33002D3F73 /* Resampler.cpp */; };
|
||||
C2A87DDA1F4B6A57002D3F73 /* DarwinSpecific.mm in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DD91F4B6A57002D3F73 /* DarwinSpecific.mm */; };
|
||||
C2A87DDF1F4B6A61002D3F73 /* AudioInputAudioUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DDB1F4B6A61002D3F73 /* AudioInputAudioUnit.cpp */; };
|
||||
C2A87DE01F4B6A61002D3F73 /* AudioOutputAudioUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DDD1F4B6A61002D3F73 /* AudioOutputAudioUnit.cpp */; };
|
||||
C2A87DE41F4B6AD3002D3F73 /* AudioUnitIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DE31F4B6AD3002D3F73 /* AudioUnitIO.cpp */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -425,6 +425,15 @@
|
||||
69AC148E1F4B41CF00AC3173 /* Resampler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Resampler.cpp; path = "../../../../Telegram-iOS/submodules/libtgvoip/audio/Resampler.cpp"; sourceTree = "<group>"; };
|
||||
69AC148F1F4B41CF00AC3173 /* Resampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Resampler.h; path = "../../../../Telegram-iOS/submodules/libtgvoip/audio/Resampler.h"; sourceTree = "<group>"; };
|
||||
69F842361E67540700C110F7 /* libtgvoip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libtgvoip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C2A87DD71F4B6A33002D3F73 /* Resampler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Resampler.cpp; path = audio/Resampler.cpp; sourceTree = "<group>"; };
|
||||
C2A87DD91F4B6A57002D3F73 /* DarwinSpecific.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = DarwinSpecific.mm; path = os/darwin/DarwinSpecific.mm; sourceTree = "<group>"; };
|
||||
C2A87DDB1F4B6A61002D3F73 /* AudioInputAudioUnit.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioInputAudioUnit.cpp; path = os/darwin/AudioInputAudioUnit.cpp; sourceTree = "<group>"; };
|
||||
C2A87DDC1F4B6A61002D3F73 /* AudioInputAudioUnitOSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioInputAudioUnitOSX.cpp; path = os/darwin/AudioInputAudioUnitOSX.cpp; sourceTree = "<group>"; };
|
||||
C2A87DDD1F4B6A61002D3F73 /* AudioOutputAudioUnit.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioOutputAudioUnit.cpp; path = os/darwin/AudioOutputAudioUnit.cpp; sourceTree = "<group>"; };
|
||||
C2A87DDE1F4B6A61002D3F73 /* AudioOutputAudioUnitOSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioOutputAudioUnitOSX.cpp; path = os/darwin/AudioOutputAudioUnitOSX.cpp; sourceTree = "<group>"; };
|
||||
C2A87DE11F4B6A89002D3F73 /* AudioInput.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioInput.cpp; path = audio/AudioInput.cpp; sourceTree = "<group>"; };
|
||||
C2A87DE21F4B6A89002D3F73 /* AudioOutput.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioOutput.cpp; path = audio/AudioOutput.cpp; sourceTree = "<group>"; };
|
||||
C2A87DE31F4B6AD3002D3F73 /* AudioUnitIO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioUnitIO.cpp; path = os/darwin/AudioUnitIO.cpp; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -840,6 +849,15 @@
|
||||
69F8422C1E67540700C110F7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2A87DE31F4B6AD3002D3F73 /* AudioUnitIO.cpp */,
|
||||
C2A87DE11F4B6A89002D3F73 /* AudioInput.cpp */,
|
||||
C2A87DE21F4B6A89002D3F73 /* AudioOutput.cpp */,
|
||||
C2A87DDB1F4B6A61002D3F73 /* AudioInputAudioUnit.cpp */,
|
||||
C2A87DDC1F4B6A61002D3F73 /* AudioInputAudioUnitOSX.cpp */,
|
||||
C2A87DDD1F4B6A61002D3F73 /* AudioOutputAudioUnit.cpp */,
|
||||
C2A87DDE1F4B6A61002D3F73 /* AudioOutputAudioUnitOSX.cpp */,
|
||||
C2A87DD91F4B6A57002D3F73 /* DarwinSpecific.mm */,
|
||||
C2A87DD71F4B6A33002D3F73 /* Resampler.cpp */,
|
||||
692AB8861E6759BF00706ACC /* libtgvoip */,
|
||||
69F842371E67540700C110F7 /* Products */,
|
||||
692AB9061E675E8700706ACC /* Frameworks */,
|
||||
@ -1072,6 +1090,7 @@
|
||||
69A6DEF41E96149300000E69 /* resample_fractional.c in Sources */,
|
||||
6915307B1E6B5BAB004F643F /* logging.cpp in Sources */,
|
||||
69A6DEE11E96149300000E69 /* get_scaling_square.c in Sources */,
|
||||
C2A87DE41F4B6AD3002D3F73 /* AudioUnitIO.cpp in Sources */,
|
||||
690725C21EBBD5F2005D860B /* NetworkSocket.cpp in Sources */,
|
||||
69A6DEC71E96149300000E69 /* channel_buffer.cc in Sources */,
|
||||
69A6DF191E96149300000E69 /* apm_data_dumper.cc in Sources */,
|
||||
@ -1086,12 +1105,10 @@
|
||||
69A6DEF71E96149300000E69 /* spl_sqrt.c in Sources */,
|
||||
69A6DEED1E96149300000E69 /* real_fft.c in Sources */,
|
||||
692AB9021E6759DD00706ACC /* VoIPController.cpp in Sources */,
|
||||
69A6DF431E9614B700000E69 /* AudioInputAudioUnitOSX.cpp in Sources */,
|
||||
69A6DF0D1E96149300000E69 /* aecm_core.cc in Sources */,
|
||||
69A6DF101E96149300000E69 /* aecm_core_neon.cc in Sources */,
|
||||
69A6DED71E96149300000E69 /* division_operations.c in Sources */,
|
||||
69A6DEDB1E96149300000E69 /* energy.c in Sources */,
|
||||
695B20631EBD39FF00E31757 /* DarwinSpecific.mm in Sources */,
|
||||
69A6DEC61E96149300000E69 /* audio_util.cc in Sources */,
|
||||
69A6DF141E96149300000E69 /* analog_agc.c in Sources */,
|
||||
69A6DEF81E96149300000E69 /* spl_sqrt_floor.c in Sources */,
|
||||
@ -1109,24 +1126,28 @@
|
||||
692AB8EB1E6759DD00706ACC /* OpusDecoder.cpp in Sources */,
|
||||
69A6DED81E96149300000E69 /* dot_product_with_scale.c in Sources */,
|
||||
69A6DF331E96149300000E69 /* ooura_fft.cc in Sources */,
|
||||
698848451F4B39F700076DF0 /* AudioUnitIO.cpp in Sources */,
|
||||
69A6DEF11E96149300000E69 /* resample_by_2.c in Sources */,
|
||||
69A6DEEC1E96149300000E69 /* randomization_functions.c in Sources */,
|
||||
69A6DEEE1E96149300000E69 /* refl_coef_to_lpc.c in Sources */,
|
||||
C2A87DDF1F4B6A61002D3F73 /* AudioInputAudioUnit.cpp in Sources */,
|
||||
69A6DF431E9614B700000E69 /* AudioInputAudioUnitOSX.cpp in Sources */,
|
||||
C2A87DE01F4B6A61002D3F73 /* AudioOutputAudioUnit.cpp in Sources */,
|
||||
69A6DF451E9614B700000E69 /* AudioOutputAudioUnitOSX.cpp in Sources */,
|
||||
69A6DEFC1E96149300000E69 /* vector_scaling_operations.c in Sources */,
|
||||
692AB8E61E6759DD00706ACC /* JitterBuffer.cpp in Sources */,
|
||||
692AB8CB1E6759DD00706ACC /* AudioInput.cpp in Sources */,
|
||||
692AB8CD1E6759DD00706ACC /* AudioOutput.cpp in Sources */,
|
||||
C2A87DDA1F4B6A57002D3F73 /* DarwinSpecific.mm in Sources */,
|
||||
C2A87DD81F4B6A33002D3F73 /* Resampler.cpp in Sources */,
|
||||
69A6DEFA1E96149300000E69 /* splitting_filter_impl.c in Sources */,
|
||||
69AC14901F4B41CF00AC3173 /* Resampler.cpp in Sources */,
|
||||
69A6DEE01E96149300000E69 /* get_hanning_window.c in Sources */,
|
||||
69A6DF161E96149300000E69 /* digital_agc.c in Sources */,
|
||||
69A6DF061E96149300000E69 /* aec_core_neon.cc in Sources */,
|
||||
69A6DF201E96149300000E69 /* ns_core.c in Sources */,
|
||||
698848431F4B39F700076DF0 /* AudioOutputAudioUnit.cpp in Sources */,
|
||||
69A6DF091E96149300000E69 /* aec_resampler.cc in Sources */,
|
||||
692AB8D11E6759DD00706ACC /* BufferInputStream.cpp in Sources */,
|
||||
692AB8E91E6759DD00706ACC /* MediaStreamItf.cpp in Sources */,
|
||||
69A6DF2C1E96149300000E69 /* block_mean_calculator.cc in Sources */,
|
||||
69A6DF451E9614B700000E69 /* AudioOutputAudioUnitOSX.cpp in Sources */,
|
||||
69A6DEBC1E96149300000E69 /* checks.cc in Sources */,
|
||||
692AB8DA1E6759DD00706ACC /* EchoCanceller.cpp in Sources */,
|
||||
69A6DF281E96149300000E69 /* splitting_filter.cc in Sources */,
|
||||
@ -1143,18 +1164,15 @@
|
||||
69A6DF1E1E96149300000E69 /* noise_suppression_x.c in Sources */,
|
||||
692AB8D51E6759DD00706ACC /* BufferPool.cpp in Sources */,
|
||||
69A6DED01E96149300000E69 /* complex_bit_reverse.c in Sources */,
|
||||
692AB8CB1E6759DD00706ACC /* AudioInput.cpp in Sources */,
|
||||
69A6DEDF1E96149300000E69 /* filter_ma_fast_q12.c in Sources */,
|
||||
69A6DEFF1E96149300000E69 /* wav_file.cc in Sources */,
|
||||
69A6DF351E96149300000E69 /* ooura_fft_neon.cc in Sources */,
|
||||
69A6DECE1E96149300000E69 /* auto_corr_to_refl_coef.c in Sources */,
|
||||
69A6DEFD1E96149300000E69 /* sparse_fir_filter.cc in Sources */,
|
||||
69A6DED91E96149300000E69 /* downsample_fast.c in Sources */,
|
||||
692AB8CD1E6759DD00706ACC /* AudioOutput.cpp in Sources */,
|
||||
69A6DF251E96149300000E69 /* nsx_core_neon.c in Sources */,
|
||||
69A6DF081E96149300000E69 /* aec_core_sse2.cc in Sources */,
|
||||
69A6DEEA1E96149300000E69 /* min_max_operations.c in Sources */,
|
||||
698848411F4B39F700076DF0 /* AudioInputAudioUnit.cpp in Sources */,
|
||||
69A6DF361E96149300000E69 /* ooura_fft_sse2.cc in Sources */,
|
||||
69A6DED51E96149300000E69 /* cross_correlation.c in Sources */,
|
||||
69A6DF3D1E96149300000E69 /* cpu_features.cc in Sources */,
|
||||
@ -1170,7 +1188,7 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
69F8423C1E67540700C110F7 /* Debug */ = {
|
||||
69F8423C1E67540700C110F7 /* Debug Hockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
@ -1219,9 +1237,9 @@
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
name = "Debug Hockeyapp";
|
||||
};
|
||||
69F8423D1E67540700C110F7 /* Release */ = {
|
||||
69F8423D1E67540700C110F7 /* Release Hockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
@ -1264,9 +1282,9 @@
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
name = "Release Hockeyapp";
|
||||
};
|
||||
69F8423F1E67540700C110F7 /* Debug */ = {
|
||||
69F8423F1E67540700C110F7 /* Debug Hockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
@ -1303,9 +1321,9 @@
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
name = "Debug Hockeyapp";
|
||||
};
|
||||
69F842401E67540700C110F7 /* Release */ = {
|
||||
69F842401E67540700C110F7 /* Release Hockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
@ -1342,7 +1360,7 @@
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
name = "Release Hockeyapp";
|
||||
};
|
||||
D04D01C31E678C0D0086DDC0 /* Debug AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
@ -1434,7 +1452,7 @@
|
||||
};
|
||||
name = "Debug AppStore";
|
||||
};
|
||||
D04D01CB1E678C230086DDC0 /* Hockeyapp */ = {
|
||||
D04D01CB1E678C230086DDC0 /* Release AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
@ -1477,9 +1495,9 @@
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Hockeyapp;
|
||||
name = "Release AppStore";
|
||||
};
|
||||
D04D01CC1E678C230086DDC0 /* Hockeyapp */ = {
|
||||
D04D01CC1E678C230086DDC0 /* Release AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
@ -1516,7 +1534,7 @@
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Hockeyapp;
|
||||
name = "Release AppStore";
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
@ -1524,24 +1542,24 @@
|
||||
69F842301E67540700C110F7 /* Build configuration list for PBXProject "libtgvoip_osx" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
69F8423C1E67540700C110F7 /* Debug */,
|
||||
69F8423C1E67540700C110F7 /* Debug Hockeyapp */,
|
||||
D04D01C31E678C0D0086DDC0 /* Debug AppStore */,
|
||||
69F8423D1E67540700C110F7 /* Release */,
|
||||
D04D01CB1E678C230086DDC0 /* Hockeyapp */,
|
||||
69F8423D1E67540700C110F7 /* Release Hockeyapp */,
|
||||
D04D01CB1E678C230086DDC0 /* Release AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = "Release Hockeyapp";
|
||||
};
|
||||
69F8423E1E67540700C110F7 /* Build configuration list for PBXNativeTarget "libtgvoip" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
69F8423F1E67540700C110F7 /* Debug */,
|
||||
69F8423F1E67540700C110F7 /* Debug Hockeyapp */,
|
||||
D04D01C41E678C0D0086DDC0 /* Debug AppStore */,
|
||||
69F842401E67540700C110F7 /* Release */,
|
||||
D04D01CC1E678C230086DDC0 /* Hockeyapp */,
|
||||
69F842401E67540700C110F7 /* Release Hockeyapp */,
|
||||
D04D01CC1E678C230086DDC0 /* Release AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = "Release Hockeyapp";
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
@ -44,10 +44,6 @@ void tgvoip_log_file_write_header();
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if !defined(snprintf) && defined(_WIN32) && defined(__cplusplus_winrt)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#define _TGVOIP_W32_LOG_PRINT(verb, msg, ...){ char __log_buf[1024]; snprintf(__log_buf, 1024, "%c/tgvoip: " msg "\n", verb, ##__VA_ARGS__); OutputDebugStringA(__log_buf); tgvoip_log_file_printf((char)verb, msg, __VA_ARGS__);}
|
||||
|
||||
#define LOGV(msg, ...) _TGVOIP_W32_LOG_PRINT('V', msg, ##__VA_ARGS__)
|
||||
@ -70,6 +66,9 @@ void tgvoip_log_file_write_header();
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(snprintf) && defined(_WIN32) && defined(__cplusplus_winrt)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#ifdef TGVOIP_LOG_VERBOSITY
|
||||
#if TGVOIP_LOG_VERBOSITY<5
|
||||
|
@ -75,12 +75,12 @@ void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
#if TARGET_OS_OSX
|
||||
while(remainingDataSize<buf.mDataByteSize/2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
#if TARGET_OS_OSX
|
||||
float* dst=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* src=reinterpret_cast<int16_t*>(remainingData);
|
||||
for(k=0;k<buf.mDataByteSize/4;k++){
|
||||
@ -89,6 +89,11 @@ void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
remainingDataSize-=buf.mDataByteSize/2;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize/2, remainingDataSize);
|
||||
#else
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
|
@ -40,6 +40,7 @@ VoIPControllerWrapper::VoIPControllerWrapper(){
|
||||
controller=new VoIPController();
|
||||
controller->implData=(void*)this;
|
||||
controller->SetStateCallback(VoIPControllerWrapper::OnStateChanged);
|
||||
controller->SetSignalBarsCountCallback(VoIPControllerWrapper::OnSignalBarsChanged);
|
||||
stateCallback=nullptr;
|
||||
}
|
||||
|
||||
@ -136,11 +137,20 @@ void VoIPControllerWrapper::OnStateChanged(VoIPController* c, int state){
|
||||
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnStateChangedInternal(state);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::OnSignalBarsChanged(VoIPController* c, int count){
|
||||
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnSignalBarsChangedInternal(count);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::OnStateChangedInternal(int state){
|
||||
if(stateCallback)
|
||||
stateCallback->OnCallStateChanged((CallState)state);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::OnSignalBarsChangedInternal(int count){
|
||||
if(stateCallback)
|
||||
stateCallback->OnSignalBarsChanged(count);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::SetConfig(double initTimeout, double recvTimeout, DataSavingMode dataSavingMode, bool enableAEC, bool enableNS, bool enableAGC, Platform::String^ logFilePath, Platform::String^ statsDumpFilePath){
|
||||
voip_config_t config{0};
|
||||
config.init_timeout=initTimeout;
|
||||
@ -170,6 +180,10 @@ void VoIPControllerWrapper::SetProxy(ProxyProtocol protocol, Platform::String^ a
|
||||
controller->SetProxy((int)protocol, _address, port, _username, _password);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::SetAudioOutputGainControlEnabled(bool enabled){
|
||||
controller->SetAudioOutputGainControlEnabled(enabled);
|
||||
}
|
||||
|
||||
void VoIPControllerWrapper::UpdateServerConfig(Platform::String^ json){
|
||||
JsonObject^ jconfig=JsonValue::Parse(json)->GetObject();
|
||||
std::map<std::string, std::string> config;
|
||||
|
@ -60,6 +60,7 @@ namespace libtgvoip{
|
||||
|
||||
public interface class IStateCallback{
|
||||
void OnCallStateChanged(CallState newState);
|
||||
void OnSignalBarsChanged(int count);
|
||||
};
|
||||
|
||||
public ref class VoIPControllerWrapper sealed{
|
||||
@ -80,12 +81,15 @@ namespace libtgvoip{
|
||||
Error GetLastError();
|
||||
static Platform::String^ GetVersion();
|
||||
int64 GetPreferredRelayID();
|
||||
void SetAudioOutputGainControlEnabled(bool enabled);
|
||||
static void UpdateServerConfig(Platform::String^ json);
|
||||
static void SwitchSpeaker(bool external);
|
||||
//static Platform::String^ TestAesIge();
|
||||
private:
|
||||
static void OnStateChanged(tgvoip::VoIPController* c, int state);
|
||||
static void OnSignalBarsChanged(tgvoip::VoIPController* c, int count);
|
||||
void OnStateChangedInternal(int state);
|
||||
void OnSignalBarsChangedInternal(int count);
|
||||
tgvoip::VoIPController* controller;
|
||||
IStateCallback^ stateCallback;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user