commit 2027dabd60cb505e631984d7cb82b0d9f7d57eb4 Author: Grishka Date: Thu Feb 2 19:24:40 2017 +0300 Initial public release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..84e7b5c836 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +.idea diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000000..189c3f6de4 --- /dev/null +++ b/Android.mk @@ -0,0 +1,54 @@ +include $(CLEAR_VARS) + +LOCAL_MODULE := voip +LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing -O3 +LOCAL_CFLAGS := -O3 -DUSE_KISS_FFT -DFIXED_POINT + +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) + LOCAL_CPPFLAGS += -mfloat-abi=softfp -mfpu=neon + LOCAL_CFLAGS += -mfloat-abi=softfp -mfpu=neon +else + ifeq ($(TARGET_ARCH_ABI),armeabi) + LOCAL_CPPFLAGS += -mfloat-abi=softfp -mfpu=neon + LOCAL_CFLAGS += -mfloat-abi=softfp -mfpu=neon + else + ifeq ($(TARGET_ARCH_ABI),x86) + + endif + endif +endif + +MY_DIR := libtgvoip + +LOCAL_C_INCLUDES := jni/opus/include jni/boringssl/include/ + +LOCAL_SRC_FILES := \ +$(MY_DIR)/external/speex_dsp/buffer.c \ +$(MY_DIR)/external/speex_dsp/fftwrap.c \ +$(MY_DIR)/external/speex_dsp/filterbank.c \ +$(MY_DIR)/external/speex_dsp/kiss_fft.c \ +$(MY_DIR)/external/speex_dsp/kiss_fftr.c \ +$(MY_DIR)/external/speex_dsp/mdf.c \ +$(MY_DIR)/external/speex_dsp/preprocess.c \ +$(MY_DIR)/external/speex_dsp/resample.c \ +$(MY_DIR)/external/speex_dsp/scal.c \ +$(MY_DIR)/external/speex_dsp/smallft.c \ +$(MY_DIR)/VoIPController.cpp \ +$(MY_DIR)/BufferInputStream.cpp \ +$(MY_DIR)/BufferOutputStream.cpp \ +$(MY_DIR)/BlockingQueue.cpp \ +$(MY_DIR)/audio/AudioInput.cpp \ +$(MY_DIR)/os/android/AudioInputOpenSLES.cpp \ +$(MY_DIR)/MediaStreamItf.cpp \ +$(MY_DIR)/audio/AudioOutput.cpp \ +$(MY_DIR)/OpusEncoder.cpp \ +$(MY_DIR)/os/android/AudioOutputOpenSLES.cpp \ +$(MY_DIR)/JitterBuffer.cpp \ +$(MY_DIR)/OpusDecoder.cpp \ +$(MY_DIR)/BufferPool.cpp \ +$(MY_DIR)/os/android/OpenSLEngineWrapper.cpp \ +$(MY_DIR)/os/android/AudioInputAndroid.cpp \ +$(MY_DIR)/EchoCanceller.cpp \ + + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/BlockingQueue.cpp b/BlockingQueue.cpp new file mode 100644 index 0000000000..f44ae2cf5c --- /dev/null +++ b/BlockingQueue.cpp @@ -0,0 +1,74 @@ +// +// 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. +// + +#include "BlockingQueue.h" + +CBlockingQueue::CBlockingQueue(size_t capacity){ + this->capacity=capacity; + init_lock(lock); + init_mutex(mutex); +} + +CBlockingQueue::~CBlockingQueue(){ + lock_mutex(mutex); + notify_lock(lock); + unlock_mutex(mutex); + lock_mutex(mutex); + unlock_mutex(mutex); + free_lock(lock); + free_mutex(mutex); +} + +void CBlockingQueue::Put(void *thing){ + lock_mutex(mutex); + if(queue.empty()){ + notify_lock(lock); + } + queue.push_back(thing); + while(queue.size()>capacity){ + queue.pop_front(); + } + unlock_mutex(mutex); +} + +void *CBlockingQueue::GetBlocking(){ + lock_mutex(mutex); + while(queue.empty()){ + wait_lock(lock, mutex); + } + void* r=GetInternal(); + unlock_mutex(mutex); + return r; +} + + +void *CBlockingQueue::Get(){ + lock_mutex(mutex); + void* r=GetInternal(); + unlock_mutex(mutex); + return r; +} + +void *CBlockingQueue::GetInternal(){ + if(queue.size()==0) + return NULL; + void* r=queue.front(); + queue.pop_front(); + return r; +} + + +unsigned int CBlockingQueue::Size(){ + return queue.size(); +} + + +void CBlockingQueue::PrepareDealloc(){ + lock_mutex(mutex); + notify_lock(lock); + unlock_mutex(mutex); +} + diff --git a/BlockingQueue.h b/BlockingQueue.h new file mode 100644 index 0000000000..673a4fdd8b --- /dev/null +++ b/BlockingQueue.h @@ -0,0 +1,35 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_BLOCKINGQUEUE_H +#define LIBTGVOIP_BLOCKINGQUEUE_H + +#include +#include +#include "threading.h" + +using namespace std; + +class CBlockingQueue{ +public: + CBlockingQueue(size_t capacity); + ~CBlockingQueue(); + void Put(void* thing); + void* GetBlocking(); + void* Get(); + unsigned int Size(); + void PrepareDealloc(); + +private: + void* GetInternal(); + list queue; + size_t capacity; + tgvoip_lock_t lock; + tgvoip_mutex_t mutex; +}; + + +#endif //LIBTGVOIP_BLOCKINGQUEUE_H diff --git a/BufferInputStream.cpp b/BufferInputStream.cpp new file mode 100644 index 0000000000..055de77ef1 --- /dev/null +++ b/BufferInputStream.cpp @@ -0,0 +1,102 @@ +// +// 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. +// + +#include "BufferInputStream.h" +#include +#include +#include +#include + +CBufferInputStream::CBufferInputStream(char* data, size_t length){ + this->buffer=data; + this->length=length; + offset=0; +} + +CBufferInputStream::~CBufferInputStream(){ + +} + + +void CBufferInputStream::Seek(size_t offset){ + assert(offset<=length); + this->offset=offset; +} + +size_t CBufferInputStream::GetLength(){ + return length; +} + +size_t CBufferInputStream::GetOffset(){ + return offset; +} + +size_t CBufferInputStream::Remaining(){ + return length-offset; +} + +unsigned char CBufferInputStream::ReadByte(){ + EnsureEnoughRemaining(1); + return (unsigned char)buffer[offset++]; +} + +int32_t CBufferInputStream::ReadInt32(){ + EnsureEnoughRemaining(4); + int32_t res=((int32_t)buffer[offset] & 0xFF) | + (((int32_t)buffer[offset+1] & 0xFF) << 8) | + (((int32_t)buffer[offset+2] & 0xFF) << 16) | + (((int32_t)buffer[offset+3] & 0xFF) << 24); + offset+=4; + return res; +} + +int64_t CBufferInputStream::ReadInt64(){ + EnsureEnoughRemaining(8); + int64_t res=((int64_t)buffer[offset] & 0xFF) | + (((int64_t)buffer[offset+1] & 0xFF) << 8) | + (((int64_t)buffer[offset+2] & 0xFF) << 16) | + (((int64_t)buffer[offset+3] & 0xFF) << 24) | + (((int64_t)buffer[offset+4] & 0xFF) << 32) | + (((int64_t)buffer[offset+5] & 0xFF) << 40) | + (((int64_t)buffer[offset+6] & 0xFF) << 48) | + (((int64_t)buffer[offset+7] & 0xFF) << 56); + offset+=8; + return res; +} + +int16_t CBufferInputStream::ReadInt16(){ + EnsureEnoughRemaining(2); + int16_t res=(uint16_t)buffer[offset] | ((uint16_t)buffer[offset+1] << 8); + offset+=2; + return res; +} + + +int32_t CBufferInputStream::ReadTlLength(){ + unsigned char l=ReadByte(); + if(l<254) + return l; + assert(length-offset>=3); + EnsureEnoughRemaining(3); + int32_t res=((int32_t)buffer[offset] & 0xFF) | + (((int32_t)buffer[offset+1] & 0xFF) << 8) | + (((int32_t)buffer[offset+2] & 0xFF) << 16); + offset+=3; + return res; +} + +void CBufferInputStream::ReadBytes(char *to, size_t count){ + EnsureEnoughRemaining(count); + memcpy(to, buffer+offset, count); + offset+=count; +} + + +void CBufferInputStream::EnsureEnoughRemaining(size_t need){ + if(length-offset +#include + +class CBufferInputStream{ + +public: + CBufferInputStream(char* data, size_t length); + ~CBufferInputStream(); + void Seek(size_t offset); + size_t GetLength(); + size_t GetOffset(); + size_t Remaining(); + unsigned char ReadByte(); + int64_t ReadInt64(); + int32_t ReadInt32(); + int16_t ReadInt16(); + int32_t ReadTlLength(); + void ReadBytes(char* to, size_t count); + +private: + void EnsureEnoughRemaining(size_t need); + char* buffer; + size_t length; + size_t offset; +}; + + +#endif //LIBTGVOIP_BUFFERINPUTSTREAM_H diff --git a/BufferOutputStream.cpp b/BufferOutputStream.cpp new file mode 100644 index 0000000000..f7e5cf4287 --- /dev/null +++ b/BufferOutputStream.cpp @@ -0,0 +1,84 @@ +// +// 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. +// + +#include "BufferOutputStream.h" +#include + +CBufferOutputStream::CBufferOutputStream(size_t size){ + buffer=(char*) malloc(size); + offset=0; + this->size=size; +} + +CBufferOutputStream::~CBufferOutputStream(){ + free(buffer); +} + +void CBufferOutputStream::WriteByte(unsigned char byte){ + this->ExpandBufferIfNeeded(1); + buffer[offset++]=byte; +} + +void CBufferOutputStream::WriteInt32(int32_t i){ + this->ExpandBufferIfNeeded(4); + buffer[offset+3]=(char)((i >> 24) & 0xFF); + buffer[offset+2]=(char)((i >> 16) & 0xFF); + buffer[offset+1]=(char)((i >> 8) & 0xFF); + buffer[offset]=(char)(i & 0xFF); + offset+=4; +} + +void CBufferOutputStream::WriteInt64(int64_t i){ + this->ExpandBufferIfNeeded(8); + buffer[offset+7]=(char)((i >> 56) & 0xFF); + buffer[offset+6]=(char)((i >> 48) & 0xFF); + buffer[offset+5]=(char)((i >> 40) & 0xFF); + buffer[offset+4]=(char)((i >> 32) & 0xFF); + buffer[offset+3]=(char)((i >> 24) & 0xFF); + buffer[offset+2]=(char)((i >> 16) & 0xFF); + buffer[offset+1]=(char)((i >> 8) & 0xFF); + buffer[offset]=(char)(i & 0xFF); + offset+=8; +} + +void CBufferOutputStream::WriteInt16(int16_t i){ + this->ExpandBufferIfNeeded(2); + buffer[offset+1]=(char)((i >> 8) & 0xFF); + buffer[offset]=(char)(i & 0xFF); + offset+=2; +} + +void CBufferOutputStream::WriteBytes(char *bytes, size_t count){ + this->ExpandBufferIfNeeded(count); + memcpy(buffer+offset, bytes, count); + offset+=count; +} + +char *CBufferOutputStream::GetBuffer(){ + return buffer; +} + +size_t CBufferOutputStream::GetLength(){ + return offset; +} + +void CBufferOutputStream::ExpandBufferIfNeeded(size_t need){ + if(offset+need>size){ + if(need<1024){ + buffer=(char *) realloc(buffer, size+1024); + size+=1024; + }else{ + buffer=(char *) realloc(buffer, size+need); + size+=need; + } + } +} + + +void CBufferOutputStream::Reset(){ + offset=0; +} + diff --git a/BufferOutputStream.h b/BufferOutputStream.h new file mode 100644 index 0000000000..22e4a74a69 --- /dev/null +++ b/BufferOutputStream.h @@ -0,0 +1,34 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_BUFFEROUTPUTSTREAM_H +#define LIBTGVOIP_BUFFEROUTPUTSTREAM_H + +#include + +class CBufferOutputStream{ + +public: + CBufferOutputStream(size_t size); + ~CBufferOutputStream(); + void WriteByte(unsigned char byte); + void WriteInt64(int64_t i); + void WriteInt32(int32_t i); + void WriteInt16(int16_t i); + void WriteBytes(char* bytes, size_t count); + char* GetBuffer(); + size_t GetLength(); + void Reset(); + +private: + void ExpandBufferIfNeeded(size_t need); + char* buffer; + size_t size; + size_t offset; +}; + + +#endif //LIBTGVOIP_BUFFEROUTPUTSTREAM_H diff --git a/BufferPool.cpp b/BufferPool.cpp new file mode 100644 index 0000000000..9e48140e23 --- /dev/null +++ b/BufferPool.cpp @@ -0,0 +1,56 @@ +// +// 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. +// + +#include "BufferPool.h" +#include "logging.h" +#include +#include + +CBufferPool::CBufferPool(unsigned int size, unsigned int count){ + assert(count<=64); + init_mutex(mutex); + buffers[0]=(unsigned char*) malloc(size*count); + bufferCount=count; + int i; + for(i=1;i> i) & 1)){ + usedBuffers|=(1LL << i); + unlock_mutex(mutex); + return buffers[i]; + } + } + unlock_mutex(mutex); + return NULL; +} + +void CBufferPool::Reuse(unsigned char* buffer){ + lock_mutex(mutex); + int i; + for(i=0;i +#include "threading.h" + +class CBufferPool{ +public: + CBufferPool(unsigned int size, unsigned int count); + ~CBufferPool(); + unsigned char* Get(); + void Reuse(unsigned char* buffer); + +private: + uint64_t usedBuffers; + int bufferCount; + unsigned char* buffers[64]; + tgvoip_mutex_t mutex; +}; + + +#endif //LIBTGVOIP_BUFFERPOOL_H diff --git a/CongestionControl.cpp b/CongestionControl.cpp new file mode 100644 index 0000000000..1ad0faf6ac --- /dev/null +++ b/CongestionControl.cpp @@ -0,0 +1,173 @@ +// +// 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. +// + +#include "CongestionControl.h" +#include "VoIPController.h" +#include "logging.h" +#include +#include + +CCongestionControl::CCongestionControl(){ + memset(rttHistory, 0, 100*sizeof(double)); + memset(inflightPackets, 0, 100*sizeof(tgvoip_congestionctl_packet_t)); + memset(inflightHistory, 0, 30*sizeof(size_t)); + tmpRtt=0; + tmpRttCount=0; + rttHistorySize=0; + rttHistoryTop=0; + lastSentSeq=0; + inflightHistoryTop=0; + state=TGVOIP_CONCTL_STARTUP; + lastActionTime=0; + lastActionRtt=0; + stateTransitionTime=0; + inflightDataSize=0; + lossCount=0; + cwnd=1024; + init_mutex(mutex); +} + +CCongestionControl::~CCongestionControl(){ + free_mutex(mutex); +} + +size_t CCongestionControl::GetAcknowledgedDataSize(){ + return 0; +} + +double CCongestionControl::GetAverageRTT(){ + if(rttHistorySize==0) + return 0; + double avg=0; + int i; + for(i=0;i<30 && i=0 ? x : (100+x)]; + //LOGV("adding [%d] %f", x>=0 ? x : (100+x), rttHistory[x>=0 ? x : (100+x)]); + } + return avg/i; +} + +size_t CCongestionControl::GetInflightDataSize(){ + size_t avg=0; + int i; + for(i=0;i<30;i++){ + avg+=inflightHistory[i]; + } + return avg/30; +} + + +size_t CCongestionControl::GetCongestionWindow(){ + return cwnd; +} + +double CCongestionControl::GetMinimumRTT(){ + int i; + double min=INFINITY; + for(i=0;i<100;i++){ + if(rttHistory[i]>0 && rttHistory[i]0){ + tmpRtt+=(CVoIPController::GetCurrentTime()-inflightPackets[i].sendTime); + tmpRttCount++; + inflightPackets[i].sendTime=0; + inflightDataSize-=inflightPackets[i].size; + break; + } + } + unlock_mutex(mutex); +} + +void CCongestionControl::PacketSent(uint32_t seq, size_t size){ + if(!seqgt(seq, lastSentSeq) || seq==lastSentSeq){ + LOGW("Duplicate outgoing seq %u", seq); + return; + } + lastSentSeq=seq; + lock_mutex(mutex); + double smallestSendTime=INFINITY; + tgvoip_congestionctl_packet_t* slot=NULL; + int i; + for(i=0;i<100;i++){ + if(inflightPackets[i].sendTime==0){ + slot=&inflightPackets[i]; + break; + } + if(smallestSendTime>inflightPackets[i].sendTime){ + slot=&inflightPackets[i]; + smallestSendTime=slot->sendTime; + } + } + assert(slot!=NULL); + if(slot->sendTime>0){ + inflightDataSize-=slot->size; + lossCount++; + LOGD("Packet with seq %u was not acknowledged", slot->seq); + } + slot->seq=seq; + slot->size=size; + slot->sendTime=CVoIPController::GetCurrentTime(); + inflightDataSize+=size; + unlock_mutex(mutex); +} + + +void CCongestionControl::Tick(){ + tickCount++; + lock_mutex(mutex); + if(tmpRttCount>0){ + rttHistory[rttHistoryTop]=tmpRtt/tmpRttCount; + rttHistoryTop=(rttHistoryTop+1)%100; + if(rttHistorySize<100) + rttHistorySize++; + tmpRtt=0; + tmpRttCount=0; + } + int i; + for(i=0;i<100;i++){ + if(inflightPackets[i].sendTime!=0 && CVoIPController::GetCurrentTime()-inflightPackets[i].sendTime>2){ + inflightPackets[i].sendTime=0; + inflightDataSize-=inflightPackets[i].size; + lossCount++; + LOGD("Packet with seq %u was not acknowledged", inflightPackets[i].seq); + } + } + inflightHistory[inflightHistoryTop]=inflightDataSize; + inflightHistoryTop=(inflightHistoryTop+1)%30; + unlock_mutex(mutex); +} + + +int CCongestionControl::GetBandwidthControlAction(){ + if(CVoIPController::GetCurrentTime()-lastActionTime<1) + return TGVOIP_CONCTL_ACT_NONE; + size_t inflightAvg=GetInflightDataSize(); + size_t max=cwnd+cwnd/10; + size_t min=cwnd-cwnd/10; + if(inflightAvgmax){ + lastActionTime=CVoIPController::GetCurrentTime(); + return TGVOIP_CONCTL_ACT_DECREASE; + } + return TGVOIP_CONCTL_ACT_NONE; +} + + +uint32_t CCongestionControl::GetSendLossCount(){ + return lossCount; +} diff --git a/CongestionControl.h b/CongestionControl.h new file mode 100644 index 0000000000..840884e485 --- /dev/null +++ b/CongestionControl.h @@ -0,0 +1,68 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_CONGESTIONCONTROL_H +#define LIBTGVOIP_CONGESTIONCONTROL_H + +#include +#include "threading.h" + +#define TGVOIP_CONCTL_STARTUP 0 +#define TGVOIP_CONCTL_DRAIN 1 +#define TGVOIP_CONCTL_PROBE_BW 2 +#define TGVOIP_CONCTL_PROBE_RTT 3 + +#define TGVOIP_CONCTL_ACT_INCREASE 1 +#define TGVOIP_CONCTL_ACT_DECREASE 2 +#define TGVOIP_CONCTL_ACT_NONE 0 + +struct tgvoip_congestionctl_packet_t{ + uint32_t seq; + double sendTime; + size_t size; +}; +typedef struct tgvoip_congestionctl_packet_t tgvoip_congestionctl_packet_t; + +class CCongestionControl{ +public: + CCongestionControl(); + ~CCongestionControl(); + + void PacketSent(uint32_t seq, size_t size); + void PacketAcknowledged(uint32_t seq); + + double GetAverageRTT(); + double GetMinimumRTT(); + size_t GetInflightDataSize(); + size_t GetCongestionWindow(); + size_t GetAcknowledgedDataSize(); + void Tick(); + int GetBandwidthControlAction(); + uint32_t GetSendLossCount(); + +private: + double rttHistory[100]; + tgvoip_congestionctl_packet_t inflightPackets[100]; + size_t inflightHistory[30]; + int state; + uint32_t lossCount; + double tmpRtt; + double lastActionTime; + double lastActionRtt; + double stateTransitionTime; + int tmpRttCount; + char rttHistorySize; + char rttHistoryTop; + char inflightHistoryTop; + uint32_t lastSentSeq; + uint32_t tickCount; + size_t inflightDataSize; + size_t cwnd; + tgvoip_mutex_t mutex; +}; + + +#endif //LIBTGVOIP_CONGESTIONCONTROL_H diff --git a/EchoCanceller.cpp b/EchoCanceller.cpp new file mode 100644 index 0000000000..94360b6ea2 --- /dev/null +++ b/EchoCanceller.cpp @@ -0,0 +1,192 @@ +// +// 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. +// + +#include "EchoCanceller.h" +#include "audio/AudioOutput.h" +#include "logging.h" +#include + +#define AEC_FRAME_SIZE 160 +#define OFFSET_STEP AEC_FRAME_SIZE*2 + +//#define CLAMP(x, min, max) (xmin ? x : min) : max) +#define CLAMP(x, min, max) x + +/*namespace webrtc{ + void WebRtcAec_enable_delay_agnostic(AecCore* self, int enable); +}*/ + +CEchoCanceller::CEchoCanceller(){ +#ifndef TGVOIP_NO_AEC + init_mutex(mutex); + state=WebRtcAecm_Create(); + WebRtcAecm_Init(state, 16000); + AecmConfig cfg; + cfg.cngMode=AecmFalse; + cfg.echoMode=1; + WebRtcAecm_set_config(state, cfg); + + //ns=WebRtcNsx_Create(); + //WebRtcNsx_Init(ns, 16000); + + /*state=webrtc::WebRtcAec_Create(); + webrtc::WebRtcAec_Init(state, 16000, 16000); + webrtc::WebRtcAec_enable_delay_agnostic(webrtc::WebRtcAec_aec_core(state), 1);*/ + splittingFilter=tgvoip_splitting_filter_create(); + splittingFilterFarend=tgvoip_splitting_filter_create(); + + farendQueue=new CBlockingQueue(10); + farendBufferPool=new CBufferPool(960*2, 10); + running=true; + + start_thread(bufferFarendThread, CEchoCanceller::StartBufferFarendThread, this); + + isOn=true; +#endif +} + +CEchoCanceller::~CEchoCanceller(){ +#ifndef TGVOIP_NO_AEC + running=false; + farendQueue->Put(NULL); + join_thread(bufferFarendThread); + delete farendQueue; + delete farendBufferPool; + WebRtcAecm_Free(state); + //WebRtcNsx_Free(ns); + //webrtc::WebRtcAec_Free(state); + tgvoip_splitting_filter_free(splittingFilter); + tgvoip_splitting_filter_free(splittingFilterFarend); + free_mutex(mutex); +#endif +} + +void CEchoCanceller::Start(){ + +} + +void CEchoCanceller::Stop(){ + +} + + +void CEchoCanceller::SpeakerOutCallback(unsigned char* data, size_t len){ +#ifndef TGVOIP_NO_AEC + if(len!=960*2 || !isOn) + return; + /*size_t offset=0; + while(offsetGet(); + if(buf){ + memcpy(buf, data, 960*2); + farendQueue->Put(buf); + } +#endif +} + +#ifndef TGVOIP_NO_AEC +void *CEchoCanceller::StartBufferFarendThread(void *arg){ + ((CEchoCanceller*)arg)->RunBufferFarendThread(); + return NULL; +} + +void CEchoCanceller::RunBufferFarendThread(){ + while(running){ + int16_t* samplesIn=(int16_t *) farendQueue->GetBlocking(); + if(samplesIn){ + int i; + for(i=0;i<960;i++){ + splittingFilterFarend->bufferIn[i]=samplesIn[i]/(float)32767; + } + farendBufferPool->Reuse((unsigned char *) samplesIn); + tgvoip_splitting_filter_analyze(splittingFilterFarend); + //webrtc::WebRtcAec_BufferFarend(state, splittingFilterFarend->bufferOut[0], 160); + //webrtc::WebRtcAec_BufferFarend(state, &splittingFilterFarend->bufferOut[0][160], 160); + int16_t farend[320]; + for(i=0;i<320;i++){ + farend[i]=(int16_t) (CLAMP(splittingFilterFarend->bufferOut[0][i], -1, 1)*32767); + } + lock_mutex(mutex); + WebRtcAecm_BufferFarend(state, farend, 160); + WebRtcAecm_BufferFarend(state, farend+160, 160); + unlock_mutex(mutex); + didBufferFarend=true; + } + } +} +#endif + +void CEchoCanceller::Enable(bool enabled){ + isOn=enabled; +} + +void CEchoCanceller::ProcessInput(unsigned char* data, unsigned char* out, size_t len){ +#ifndef TGVOIP_NO_AEC + int i; + if(!isOn){ + memcpy(out, data, len); + return; + } + int16_t* samplesIn=(int16_t*)data; + int16_t* samplesOut=(int16_t*)out; + //int16_t samplesAfterNs[320]; + //float fout[3][320]; + for(i=0;i<960;i++){ + splittingFilter->bufferIn[i]=samplesIn[i]/(float)32767; + } + + tgvoip_splitting_filter_analyze(splittingFilter); + + for(i=0;i<320;i++){ + samplesIn[i]=(int16_t) (CLAMP(splittingFilter->bufferOut[0][i], -1, 1)*32767); + } + lock_mutex(mutex); + /*float* aecIn[3]; + float* aecOut[3]; + aecIn[0]=splittingFilter->bufferOut[0]; + aecIn[1]=splittingFilter->bufferOut[1]; + aecIn[2]=splittingFilter->bufferOut[2]; + aecOut[0]=fout[0]; + aecOut[1]=fout[1]; + aecOut[2]=fout[2]; + webrtc::WebRtcAec_Process(state, (const float *const *) aecIn, 1, (float *const *) aecOut, 160, 0, 0); + aecIn[0]+=160; + aecIn[1]+=160; + aecIn[2]+=160; + aecOut[0]+=160; + aecOut[1]+=160; + aecOut[2]+=160; + webrtc::WebRtcAec_Process(state, (const float *const *) aecIn, 1, (float *const *) aecOut, 160, 0, 0);*/ + //int16_t* nsIn=samplesIn; + //int16_t* nsOut=samplesAfterNs; + //WebRtcNsx_Process(ns, (const short *const *) &nsIn, 1, (short *const *) &nsOut); + //nsIn+=160; + //nsOut+=160; + //WebRtcNsx_Process(ns, (const short *const *) &nsIn, 1, (short *const *) &nsOut); + WebRtcAecm_Process(state, samplesIn, NULL, samplesOut, AEC_FRAME_SIZE, (int16_t) CAudioOutput::GetEstimatedDelay()); + WebRtcAecm_Process(state, samplesIn+160, NULL, samplesOut+160, AEC_FRAME_SIZE, (int16_t) CAudioOutput::GetEstimatedDelay()); + unlock_mutex(mutex); + for(i=0;i<320;i++){ + splittingFilter->bufferOut[0][i]=samplesOut[i]/(float)32767; + } + + //memcpy(splittingFilter->bufferOut[0], fout[0], 320*sizeof(float)); + //memcpy(splittingFilter->bufferOut[1], fout[1], 320*sizeof(float)); + //memcpy(splittingFilter->bufferOut[2], fout[2], 320*sizeof(float)); + + tgvoip_splitting_filter_synthesize(splittingFilter); + + for(i=0;i<960;i++){ + samplesOut[i]=(int16_t) (CLAMP(splittingFilter->bufferIn[i], -1, 1)*32767); + } +#else + memcpy(out, data, len); +#endif +} + diff --git a/EchoCanceller.h b/EchoCanceller.h new file mode 100644 index 0000000000..486ca1d923 --- /dev/null +++ b/EchoCanceller.h @@ -0,0 +1,58 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_ECHOCANCELLER_H +#define LIBTGVOIP_ECHOCANCELLER_H + +#ifdef __APPLE__ +#include +#endif + +#if TARGET_OS_IPHONE +#define TGVOIP_NO_AEC +#endif + +#include "threading.h" +#include "BufferPool.h" +#include "BlockingQueue.h" +#ifndef TGVOIP_NO_AEC +#include "external/include/webrtc/echo_control_mobile.h" +//#include "external/include/webrtc/echo_cancellation.h" +#include "external/include/webrtc/splitting_filter_wrapper.h" +#include "external/include/webrtc/noise_suppression_x.h" +#endif + +class CEchoCanceller{ + +public: + CEchoCanceller(); + virtual ~CEchoCanceller(); + virtual void Start(); + virtual void Stop(); + void SpeakerOutCallback(unsigned char* data, size_t len); + void Enable(bool enabled); + void ProcessInput(unsigned char* data, unsigned char* out, size_t len); + +private: + bool isOn; +#ifndef TGVOIP_NO_AEC + static void* StartBufferFarendThread(void* arg); + void RunBufferFarendThread(); + bool didBufferFarend; + tgvoip_mutex_t mutex; + void* state; + splitting_filter_t* splittingFilter; + splitting_filter_t* splittingFilterFarend; + tgvoip_thread_t bufferFarendThread; + CBlockingQueue* farendQueue; + CBufferPool* farendBufferPool; + bool running; + NsxHandle* ns; +#endif +}; + + +#endif //LIBTGVOIP_ECHOCANCELLER_H diff --git a/JitterBuffer.cpp b/JitterBuffer.cpp new file mode 100644 index 0000000000..ab8910996f --- /dev/null +++ b/JitterBuffer.cpp @@ -0,0 +1,353 @@ +// +// 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. +// + +#include "JitterBuffer.h" +#include "logging.h" + +CJitterBuffer::CJitterBuffer(CMediaStreamItf *out, uint32_t step):bufferPool(JITTER_SLOT_SIZE, JITTER_SLOT_COUNT){ + if(out) + out->SetCallback(CJitterBuffer::CallbackOut, this); + this->step=step; + memset(slots, 0, sizeof(jitter_packet_t)*JITTER_SLOT_COUNT); + minDelay=6; + lostCount=0; + needBuffering=true; + tickCount=0; + dontIncMinDelay=0; + dontDecMinDelay=0; + lostPackets=0; + if(step<30) + minMinDelay=2; + else if(step<50) + minMinDelay=4; + else + minMinDelay=6; + Reset(); + init_mutex(mutex); +} + +CJitterBuffer::~CJitterBuffer(){ + Reset(); + free_mutex(mutex); +} + +void CJitterBuffer::SetMinPacketCount(uint32_t count){ + if(minDelay==count) + return; + minDelay=count; + Reset(); +} + +int CJitterBuffer::GetMinPacketCount(){ + return minDelay; +} + +size_t CJitterBuffer::CallbackIn(unsigned char *data, size_t len, void *param){ + //((CJitterBuffer*)param)->HandleInput(data, len); + return 0; +} + +size_t CJitterBuffer::CallbackOut(unsigned char *data, size_t len, void *param){ + return ((CJitterBuffer*)param)->HandleOutput(data, len, 0); +} + +void CJitterBuffer::HandleInput(unsigned char *data, size_t len, uint32_t timestamp){ + jitter_packet_t pkt; + pkt.size=len; + pkt.buffer=data; + pkt.timestamp=timestamp; + lock_mutex(mutex); + PutInternal(&pkt); + unlock_mutex(mutex); + //LOGV("in, ts=%d", timestamp); +} + +void CJitterBuffer::Reset(){ + wasReset=true; + needBuffering=true; + lastPutTimestamp=0; + int i; + for(i=0;isizesize); + }else{ + if(pkt) { + pkt->size = slots[i].size; + pkt->timestamp = slots[i].timestamp; + memcpy(pkt->buffer, slots[i].buffer, slots[i].size); + } + } + bufferPool.Reuse(slots[i].buffer); + slots[i].buffer=NULL; + if(offset==0) + Advance(); + lostCount=0; + needBuffering=false; + return JR_OK; + } + + LOGW("jitter: found no packet for timestamp %lld (last put = %d)", timestampToGet, lastPutTimestamp); + + if(offset==0) + Advance(); + + if(!needBuffering){ + lostCount++; + if(offset==0) + lostPackets++; + if(lostCount>=10){ + LOGW("jitter: lost %d packets in a row, resetting", lostCount); + //minDelay++; + dontIncMinDelay=16; + dontDecMinDelay+=128; + if(GetCurrentDelay()size>JITTER_SLOT_SIZE){ + LOGE("The packet is too big to fit into the jitter buffer"); + return; + } + int i; + if(wasReset){ + wasReset=false; + nextTimestamp=((int64_t)pkt->timestamp)-step*minDelay; + LOGI("jitter: resyncing, next timestamp = %lld (step=%d, minDelay=%d)", nextTimestamp, step, minDelay); + for(i=0;itimestamptimestamp); + latePacketCount++; + lostPackets--; + }else if(pkt->timestamptimestamp); + latePacketCount++; + return; + } + + if(pkt->timestamp>lastPutTimestamp) + lastPutTimestamp=pkt->timestamp; + + for(i=0;itimestamp; + slots[i].size=pkt->size; + slots[i].buffer=bufferPool.Get(); + if(slots[i].buffer) + memcpy(slots[i].buffer, pkt->buffer, pkt->size); + else + LOGE("WTF!!"); +} + + +void CJitterBuffer::Advance(){ + nextTimestamp+=step; +} + + +int CJitterBuffer::GetCurrentDelay(){ + int delay=0; + int i; + for(i=0;i0) + absolutelyNoLatePackets=false; + } + avgLate64/=64; + avgLate32/=32; + avgLate16/=16; + //LOGV("jitter: avg late=%.1f, %.1f, %.1f", avgLate16, avgLate32, avgLate64); + if(avgLate16>=0.3){ + if(dontIncMinDelay==0 && minDelay<15){ + minDelay++; + if(GetCurrentDelay()0) + dontDecMinDelay--; + if(dontDecMinDelay==0 && minDelay>minMinDelay){ + minDelay--; + dontDecMinDelay=64; + dontIncMinDelay+=16; + } + } + + if(dontIncMinDelay>0) + dontIncMinDelay--; + + memmove(&delayHistory[1], delayHistory, 63*sizeof(int)); + delayHistory[0]=GetCurrentDelay(); + + int avgDelay=0; + int min=100; + for(i=0;i<32;i++){ + avgDelay+=delayHistory[i]; + if(delayHistory[i]=minDelay/2 && delayHistory[0]>minDelay && avgLate16<=0.1 && absolutelyNoLatePackets && dontDecMinDelay<32 && min>minDelay) { + LOGI("jitter: need adjust"); + adjustingDelay=true; + } + }else{ + if(!absolutelyNoLatePackets){ + LOGI("jitter: done adjusting because we're losing packets"); + adjustingDelay=false; + }else if(tickCount%5==0){ + LOGD("jitter: removing a packet to reduce delay"); + GetInternal(NULL, 0); + if(GetCurrentDelay()<=minDelay || min<=minDelay){ + adjustingDelay = false; + LOGI("jitter: done adjusting"); + } + } + } + + tickCount++; + + unlock_mutex(mutex); +} + + +void CJitterBuffer::GetAverageLateCount(double *out){ + double avgLate64=0, avgLate32=0, avgLate16=0; + int i; + for(i=0;i<64;i++){ + avgLate64+=lateHistory[i]; + if(i<32) + avgLate32+=lateHistory[i]; + if(i<16) + avgLate16+=lateHistory[i]; + } + avgLate64/=64; + avgLate32/=32; + avgLate16/=16; + out[0]=avgLate16; + out[1]=avgLate32; + out[2]=avgLate64; +} + + +int CJitterBuffer::GetAndResetLostPacketCount(){ + lock_mutex(mutex); + int r=lostPackets; + lostPackets=0; + unlock_mutex(mutex); + return r; +} diff --git a/JitterBuffer.h b/JitterBuffer.h new file mode 100644 index 0000000000..b871a7b4fb --- /dev/null +++ b/JitterBuffer.h @@ -0,0 +1,73 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_JITTERBUFFER_H +#define LIBTGVOIP_JITTERBUFFER_H + +#include +#include +#include "MediaStreamItf.h" +#include "BlockingQueue.h" +#include "BufferPool.h" +#include "threading.h" + +#define JITTER_SLOT_COUNT 64 +#define JITTER_SLOT_SIZE 1024 +#define JR_OK 1 +#define JR_MISSING 2 +#define JR_BUFFERING 3 + +struct jitter_packet_t{ + unsigned char* buffer; + size_t size; + uint32_t timestamp; +}; +typedef struct jitter_packet_t jitter_packet_t; + +class CJitterBuffer{ +public: + CJitterBuffer(CMediaStreamItf* out, uint32_t step); + ~CJitterBuffer(); + void SetMinPacketCount(uint32_t count); + int GetMinPacketCount(); + int GetCurrentDelay(); + void Reset(); + void HandleInput(unsigned char* data, size_t len, uint32_t timestamp); + size_t HandleOutput(unsigned char* buffer, size_t len, int offsetInSteps); + void Tick(); + void GetAverageLateCount(double* out); + int GetAndResetLostPacketCount(); + +private: + static size_t CallbackIn(unsigned char* data, size_t len, void* param); + static size_t CallbackOut(unsigned char* data, size_t len, void* param); + void PutInternal(jitter_packet_t* pkt); + int GetInternal(jitter_packet_t* pkt, int offset); + void Advance(); + + CBufferPool bufferPool; + tgvoip_mutex_t mutex; + jitter_packet_t slots[JITTER_SLOT_COUNT]; + int64_t nextTimestamp; + uint32_t step; + uint32_t minDelay; + uint32_t minMinDelay; + uint32_t lastPutTimestamp; + int lostCount; + bool wasReset; + bool needBuffering; + int delayHistory[64]; + int lateHistory[64]; + bool adjustingDelay; + unsigned int tickCount; + unsigned int latePacketCount; + unsigned int dontIncMinDelay; + unsigned int dontDecMinDelay; + int lostPackets; +}; + + +#endif //LIBTGVOIP_JITTERBUFFER_H diff --git a/MediaStreamItf.cpp b/MediaStreamItf.cpp new file mode 100644 index 0000000000..94b51963d6 --- /dev/null +++ b/MediaStreamItf.cpp @@ -0,0 +1,17 @@ +// +// 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. +// + +#include "MediaStreamItf.h" + + +void CMediaStreamItf::SetCallback(size_t (*f)(unsigned char *, size_t, void*), void* param){ + callback=f; + callbackParam=param; +} + +size_t CMediaStreamItf::InvokeCallback(unsigned char *data, size_t length){ + return (*callback)(data, length, callbackParam); +} diff --git a/MediaStreamItf.h b/MediaStreamItf.h new file mode 100644 index 0000000000..1abbf28750 --- /dev/null +++ b/MediaStreamItf.h @@ -0,0 +1,27 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_MEDIASTREAMINPUT_H +#define LIBTGVOIP_MEDIASTREAMINPUT_H + +#include + +class CMediaStreamItf{ +public: + virtual void Start()=0; + virtual void Stop()=0; + void SetCallback(size_t (*f)(unsigned char*, size_t, void*), void* param); + +//protected: + size_t InvokeCallback(unsigned char* data, size_t length); + +private: + size_t (*callback)(unsigned char*, size_t, void*); + void* callbackParam; +}; + + +#endif //LIBTGVOIP_MEDIASTREAMINPUT_H diff --git a/OpusDecoder.cpp b/OpusDecoder.cpp new file mode 100644 index 0000000000..da265b49a1 --- /dev/null +++ b/OpusDecoder.cpp @@ -0,0 +1,261 @@ +// +// 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. +// + +#include "OpusDecoder.h" +#include "logging.h" +#include + +#define PACKET_SIZE (960*2) + +COpusDecoder::COpusDecoder(CMediaStreamItf *dst){ + //this->source=source; + dst->SetCallback(COpusDecoder::Callback, this); + dec=opus_decoder_create(48000, 1, NULL); + //test=fopen("/sdcard/test.raw", "wb"); + buffer=(unsigned char *) malloc(4096); + //lastDecoded=(unsigned char*) malloc(960*2); + lastDecoded=NULL; + lastDecodedLen=0; + outputBufferSize=0; + packetsNeeded=0; + lastDecodedOffset=0; + decodedQueue=new CBlockingQueue(32); + bufferPool=new CBufferPool(PACKET_SIZE, 32); + echoCanceller=NULL; + frameDuration=20; +} + +COpusDecoder::~COpusDecoder(){ + opus_decoder_destroy(dec); + free(buffer); + delete bufferPool; + delete decodedQueue; +} + + +void COpusDecoder::SetEchoCanceller(CEchoCanceller* canceller){ + echoCanceller=canceller; +} + +size_t COpusDecoder::Callback(unsigned char *data, size_t len, void *param){ + ((COpusDecoder*)param)->HandleCallback(data, len); + return 0; +} + +void COpusDecoder::HandleCallback(unsigned char *data, size_t len){ + if(!running){ + memset(data, 0, len); + return; + } + if(outputBufferSize==0){ + outputBufferSize=len; + if(len>PACKET_SIZE) + packetsNeeded=len/PACKET_SIZE; + else + packetsNeeded=1; + packetsNeeded*=2; + lock_mutex(mutex); + notify_lock(lock); + unlock_mutex(mutex); + } + assert(outputBufferSize==len && "output buffer size is supposed to be the same throughout callbacks"); + if(len>PACKET_SIZE){ + int count=len/PACKET_SIZE; + int i; + for(i=0;iGetBlocking(); + memcpy(data+(i*PACKET_SIZE), lastDecoded, PACKET_SIZE); + if(echoCanceller) + echoCanceller->SpeakerOutCallback(data, PACKET_SIZE); + bufferPool->Reuse(lastDecoded); + } + lock_mutex(mutex); + packetsNeeded+=count; + if(packetsNeeded>0) + notify_lock(lock); + unlock_mutex(mutex); + }else if(len==PACKET_SIZE){ + lastDecoded=(unsigned char*) decodedQueue->GetBlocking(); + memcpy(data, lastDecoded, PACKET_SIZE); + bufferPool->Reuse(lastDecoded); + lock_mutex(mutex); + packetsNeeded+=1; + if(packetsNeeded>0) + notify_lock(lock); + if(echoCanceller) + echoCanceller->SpeakerOutCallback(data, PACKET_SIZE); + unlock_mutex(mutex); + }else if(lenGetBlocking(); + } + + memcpy(data, lastDecoded+lastDecodedOffset, len); + lastDecodedOffset+=len; + + if(lastDecodedOffset>=PACKET_SIZE){ + if(echoCanceller) + echoCanceller->SpeakerOutCallback(lastDecoded, PACKET_SIZE); + lastDecodedOffset=0; + bufferPool->Reuse(lastDecoded); + //LOGV("before req packet, qsize=%d", decodedQueue->Size()); + lock_mutex(mutex); + if(decodedQueue->Size()==0) + packetsNeeded+=2; + else + packetsNeeded+=1; + if(packetsNeeded>0) + notify_lock(lock); + unlock_mutex(mutex); + } + } + /*if(lastDecodedLen){ + LOGV("ldl=%d, l=%d", lastDecodedLen, len); + if(len==PACKET_SIZE){ + memcpy(data, lastDecoded, len); + packetsNeeded=1; + }else if(len>PACKET_SIZE){ + memcpy(data, lastDecoded, len); + //LOGV("ldl=%d, l=%d", lastDecodedLen, len); + packetsNeeded=len/PACKET_SIZE; + }else if(len=PACKET_SIZE){ + packetsNeeded=1; + lastDecodedOffset=0; + } + } + }else{ + LOGW("skipping callback"); + if(len>PACKET_SIZE) + packetsNeeded=len/PACKET_SIZE; + else + packetsNeeded=1; + }*/ + /*if(packetsNeeded>0){ + lock_mutex(mutex); + notify_lock(lock); + unlock_mutex(mutex); + }*/ +} + + +void COpusDecoder::Start(){ + init_lock(lock); + init_mutex(mutex); + running=true; + start_thread(thread, COpusDecoder::StartThread, this); + set_thread_priority(thread, get_thread_max_priority()); + set_thread_name(thread, "opus_decoder"); +} + +void COpusDecoder::Stop(){ + if(!running) + return; + running=false; + lock_mutex(mutex); + notify_lock(lock); + unlock_mutex(mutex); + join_thread(thread); + free_lock(lock); + free_mutex(mutex); +} + + +void* COpusDecoder::StartThread(void *param){ + ((COpusDecoder*)param)->RunThread(); + return NULL; +} + +void COpusDecoder::RunThread(){ + //FILE* test=fopen("/sdcard/test.raw", "w"); + unsigned char nextBuffer[8192]; + unsigned char decodeBuffer[8192]; + int i; + int packetsPerFrame=frameDuration/20; + bool first=true; + LOGI("decoder: packets per frame %d", packetsPerFrame); + size_t nextLen=0; + while(running){ + lock_mutex(mutex); + if(packetsNeeded<=0) + wait_lock(lock, mutex); + unlock_mutex(mutex); + //LOGV("after wait, running=%d", running); + if(!running){ + //fclose(test); + //unlock_mutex(mutex); + LOGI("==== decoder exiting ===="); + return; + } + //LOGD("Will get %d packets", packetsNeeded); + //lastDecodedLen=0; + memcpy(buffer, nextBuffer, nextLen); + size_t inLen=nextLen; + //nextLen=InvokeCallback(nextBuffer, 8192); + nextLen=jitterBuffer->HandleOutput(nextBuffer, 8192, 0); + if(first){ + first=false; + continue; + } + //LOGV("Before decode, len=%d", inLen); + if(!inLen){ + LOGV("Trying to recover late packet"); + inLen=jitterBuffer->HandleOutput(buffer, 8192, -2); + if(inLen) + LOGV("Decoding late packet"); + } + int size; + if(inLen || nextLen) + size=opus_decode(dec, inLen ? buffer : nextBuffer, inLen ? inLen : nextLen, (opus_int16*) decodeBuffer, packetsPerFrame*960, inLen ? 0 : 1); + else{ // do packet loss concealment + size=opus_decode(dec, NULL, 0, (opus_int16 *) decodeBuffer, packetsPerFrame*960, 0); + LOGV("PLC"); + } + if(size<0) + LOGW("decoder: opus_decode error %d", size); + //LOGV("After decode, size=%d", size); + for(i=0;iGet(); + if(buf){ + if(size>0){ + memcpy(buf, decodeBuffer+(PACKET_SIZE*i), PACKET_SIZE); + }else{ + LOGE("Error decoding, result=%d", size); + memset(buf, 0, PACKET_SIZE); + } + decodedQueue->Put(buf); + }else{ + LOGW("decoder: no buffers left!"); + } + lock_mutex(mutex); + packetsNeeded--; + unlock_mutex(mutex); + //LOGD("packets needed: %d", packetsNeeded); + } + } +} + + +void COpusDecoder::SetFrameDuration(uint32_t duration){ + frameDuration=duration; +} + + +void COpusDecoder::ResetQueue(){ + /*lock_mutex(mutex); + packetsNeeded=0; + unlock_mutex(mutex); + while(decodedQueue->Size()>0){ + bufferPool->Reuse((unsigned char *) decodedQueue->Get()); + }*/ +} + + +void COpusDecoder::SetJitterBuffer(CJitterBuffer* jitterBuffer){ + this->jitterBuffer=jitterBuffer; +} diff --git a/OpusDecoder.h b/OpusDecoder.h new file mode 100644 index 0000000000..ce92452f9f --- /dev/null +++ b/OpusDecoder.h @@ -0,0 +1,56 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_OPUSDECODER_H +#define LIBTGVOIP_OPUSDECODER_H + + +#include "MediaStreamItf.h" +#include "opus.h" +#include "threading.h" +#include "BlockingQueue.h" +#include "BufferPool.h" +#include "EchoCanceller.h" +#include "JitterBuffer.h" +#include + +class COpusDecoder { +public: + virtual void Start(); + + virtual void Stop(); + + COpusDecoder(CMediaStreamItf* dst); + virtual ~COpusDecoder(); + void HandleCallback(unsigned char* data, size_t len); + void SetEchoCanceller(CEchoCanceller* canceller); + void SetFrameDuration(uint32_t duration); + void ResetQueue(); + void SetJitterBuffer(CJitterBuffer* jitterBuffer); + +private: + static size_t Callback(unsigned char* data, size_t len, void* param); + static void* StartThread(void* param); + void RunThread(); + OpusDecoder* dec; + CBlockingQueue* decodedQueue; + CBufferPool* bufferPool; + unsigned char* buffer; + unsigned char* lastDecoded; + size_t lastDecodedLen, lastDecodedOffset; + int packetsNeeded; + size_t outputBufferSize; + bool running; + tgvoip_thread_t thread; + tgvoip_lock_t lock; + tgvoip_mutex_t mutex; + uint32_t frameDuration; + CEchoCanceller* echoCanceller; + CJitterBuffer* jitterBuffer; +}; + + +#endif //LIBTGVOIP_OPUSDECODER_H diff --git a/OpusEncoder.cpp b/OpusEncoder.cpp new file mode 100644 index 0000000000..36e09a282b --- /dev/null +++ b/OpusEncoder.cpp @@ -0,0 +1,144 @@ +// +// 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. +// + +#include "OpusEncoder.h" +#include +#include "logging.h" + +COpusEncoder::COpusEncoder(CMediaStreamItf *source):queue(10), bufferPool(960*2, 10){ + this->source=source; + source->SetCallback(COpusEncoder::Callback, this); + enc=opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, NULL); + opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(10)); + opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(15)); + opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(1)); + opus_encoder_ctl(enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + //opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); + requestedBitrate=32000; + currentBitrate=0; + running=false; + echoCanceller=NULL; + complexity=10; + frameDuration=20; +} + +COpusEncoder::~COpusEncoder(){ + opus_encoder_destroy(enc); +} + +void COpusEncoder::Start(){ + if(running) + return; + running=true; + start_thread(thread, StartThread, this); + set_thread_priority(thread, get_thread_max_priority()); + set_thread_name(thread, "opus_encoder"); +} + +void COpusEncoder::Stop(){ + if(!running) + return; + running=false; + queue.Put(NULL); + join_thread(thread); +} + + +void COpusEncoder::SetBitrate(uint32_t bitrate){ + requestedBitrate=bitrate; +} + +void COpusEncoder::Encode(unsigned char *data, size_t len){ + if(requestedBitrate!=currentBitrate){ + opus_encoder_ctl(enc, OPUS_SET_BITRATE(requestedBitrate)); + currentBitrate=requestedBitrate; + LOGV("opus_encoder: setting bitrate to %u", currentBitrate); + } + int32_t r=opus_encode(enc, (int16_t*)data, len/2, buffer, 4096); + if(r<=0){ + LOGE("Error encoding: %d", r); + }else if(r==1){ + LOGW("DTX"); + }else if(running){ + //LOGV("Packet size = %d", r); + InvokeCallback(buffer, (size_t)r); + } +} + +size_t COpusEncoder::Callback(unsigned char *data, size_t len, void* param){ + COpusEncoder* e=(COpusEncoder*)param; + unsigned char* buf=e->bufferPool.Get(); + if(buf){ + assert(len==960*2); + memcpy(buf, data, 960*2); + e->queue.Put(buf); + }else{ + LOGW("opus_encoder: no buffer slots left"); + if(e->complexity>1){ + e->complexity--; + opus_encoder_ctl(e->enc, OPUS_SET_COMPLEXITY(e->complexity)); + } + } + return 0; +} + + +uint32_t COpusEncoder::GetBitrate(){ + return requestedBitrate; +} + +void COpusEncoder::SetEchoCanceller(CEchoCanceller* aec){ + echoCanceller=aec; +} + +void* COpusEncoder::StartThread(void* arg){ + ((COpusEncoder*)arg)->RunThread(); + return NULL; +} + +void COpusEncoder::RunThread(){ + unsigned char buf[960*2]; + uint32_t bufferedCount=0; + uint32_t packetsPerFrame=frameDuration/20; + LOGV("starting decoder, packets per frame=%d", packetsPerFrame); + unsigned char* frame; + if(packetsPerFrame>1) + frame=(unsigned char *) malloc(960*2*packetsPerFrame); + else + frame=NULL; + while(running){ + unsigned char* packet=(unsigned char*)queue.GetBlocking(); + if(packet){ + if(echoCanceller) + echoCanceller->ProcessInput(packet, buf, 960*2); + else + memcpy(buf, packet, 960*2); + if(packetsPerFrame==1){ + Encode(buf, 960*2); + }else{ + memcpy(frame+(960*2*bufferedCount), buf, 960*2); + bufferedCount++; + if(bufferedCount==packetsPerFrame){ + Encode(frame, 960*2*packetsPerFrame); + bufferedCount=0; + } + } + bufferPool.Reuse(packet); + } + } + if(frame) + free(frame); +} + + +void COpusEncoder::SetOutputFrameDuration(uint32_t duration){ + frameDuration=duration; +} + + +void COpusEncoder::SetPacketLoss(int percent){ + opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(percent)); +} diff --git a/OpusEncoder.h b/OpusEncoder.h new file mode 100644 index 0000000000..4e082926f8 --- /dev/null +++ b/OpusEncoder.h @@ -0,0 +1,52 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_OPUSENCODER_H +#define LIBTGVOIP_OPUSENCODER_H + + +#include "MediaStreamItf.h" +#include "opus.h" +#include "threading.h" +#include "BlockingQueue.h" +#include "BufferPool.h" +#include "EchoCanceller.h" + +#include + +class COpusEncoder : public CMediaStreamItf{ +public: + COpusEncoder(CMediaStreamItf* source); + virtual ~COpusEncoder(); + virtual void Start(); + virtual void Stop(); + void SetBitrate(uint32_t bitrate); + void SetEchoCanceller(CEchoCanceller* aec); + void SetOutputFrameDuration(uint32_t duration); + void SetPacketLoss(int percent); + uint32_t GetBitrate(); + +private: + static size_t Callback(unsigned char* data, size_t len, void* param); + static void* StartThread(void* arg); + void RunThread(); + void Encode(unsigned char* data, size_t len); + CMediaStreamItf* source; + OpusEncoder* enc; + unsigned char buffer[4096]; + uint32_t requestedBitrate; + uint32_t currentBitrate; + tgvoip_thread_t thread; + CBlockingQueue queue; + CBufferPool bufferPool; + CEchoCanceller* echoCanceller; + int complexity; + bool running; + uint32_t frameDuration; +}; + + +#endif //LIBTGVOIP_OPUSENCODER_H diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000000..00d2e135a7 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/VoIPController.cpp b/VoIPController.cpp new file mode 100644 index 0000000000..600b8b45bc --- /dev/null +++ b/VoIPController.cpp @@ -0,0 +1,1823 @@ +#include +#include +#include +#include +#include "VoIPController.h" +#include "logging.h" +#include "threading.h" +#include "BufferOutputStream.h" +#include "BufferInputStream.h" +#include "OpusEncoder.h" +#include "OpusDecoder.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +double CVoIPController::machTimebase=0; +uint64_t CVoIPController::machTimestart=0; +#endif + +#define SHA1_LENGTH 20 +#define SHA256_LENGTH 32 + +#ifndef USE_CUSTOM_CRYPTO +#include +#include +#include + +void tgvoip_openssl_aes_ige_encrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){ + AES_KEY akey; + AES_set_encrypt_key(key, 32*8, &akey); + AES_ige_encrypt(in, out, length, &akey, iv, AES_ENCRYPT); +} + +void tgvoip_openssl_aes_ige_decrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){ + AES_KEY akey; + AES_set_decrypt_key(key, 32*8, &akey); + AES_ige_encrypt(in, out, length, &akey, iv, AES_DECRYPT); +} + +void tgvoip_openssl_rand_bytes(uint8_t* buffer, size_t len){ + RAND_bytes(buffer, len); +} + +void tgvoip_openssl_sha1(uint8_t* msg, size_t len, uint8_t* output){ + SHA1(msg, len, output); +} + +void tgvoip_openssl_sha256(uint8_t* msg, size_t len, uint8_t* output){ + SHA256(msg, len, output); +} + +voip_crypto_functions_t CVoIPController::crypto={ + tgvoip_openssl_rand_bytes, + tgvoip_openssl_sha1, + tgvoip_openssl_sha256, + tgvoip_openssl_aes_ige_encrypt, + tgvoip_openssl_aes_ige_decrypt + +}; +#else +voip_crypto_functions_t CVoIPController::crypto; // set it yourself upon initialization +#endif + +CVoIPController::CVoIPController(){ + seq=1; + lastRemoteSeq=0; + state=STATE_WAIT_INIT; + audioInput=NULL; + audioOutput=NULL; + decoder=NULL; + encoder=NULL; + jitterBuffer=NULL; + audioOutStarted=false; + audioTimestampIn=0; + audioTimestampOut=0; + stopping=false; + int i; + for(i=0;i<20;i++){ + emptySendBuffers.push_back(new CBufferOutputStream(1024)); + } + sendQueue=new CBlockingQueue(20); + init_mutex(sendBufferMutex); + memset(remoteAcks, 0, sizeof(double)*32); + memset(sentPacketTimes, 0, sizeof(double)*32); + memset(recvPacketTimes, 0, sizeof(double)*32); + memset(rttHistory, 0, sizeof(double)*32); + memset(sendLossCountHistory, 0, sizeof(uint32_t)*32); + memset(&stats, 0, sizeof(voip_stats_t)); + lastRemoteAckSeq=0; + lastSentSeq=0; + recvLossCount=0; + packetsRecieved=0; + waitingForAcks=false; + networkType=NET_TYPE_UNKNOWN; + audioPacketGrouping=3; + audioPacketsWritten=0; + currentAudioPacket=NULL; + stateCallback=NULL; + echoCanceller=NULL; + dontSendPackets=0; + micMuted=false; + currentEndpoint=NULL; + needSendP2pPing=false; + waitingForRelayPeerInfo=false; + lastP2pPingTime=0; + p2pPingCount=0; + allowP2p=true; + dataSavingMode=false; + memset(activeNetItfName, 0, 32); + publicEndpointsReqTime=0; + init_mutex(queuedPacketsMutex); + connectionInitTime=0; + lastRecvPacketTime=0; + dataSavingRequestedByPeer=false; + peerVersion=0; + conctl=new CCongestionControl(); + prevSendLossCount=0; + enableAEC=true; + +#ifdef __APPLE__ + machTimestart=0; +#endif + + voip_stream_t* stm=(voip_stream_t *) malloc(sizeof(voip_stream_t)); + stm->id=1; + stm->type=STREAM_TYPE_AUDIO; + stm->codec=CODEC_OPUS; + stm->enabled=1; + stm->frameDuration=60; + outgoingStreams.push_back(stm); +} + +CVoIPController::~CVoIPController(){ + LOGD("Entered CVoIPController::~CVoIPController"); + if(audioInput) + audioInput->Stop(); + if(audioOutput) + audioOutput->Stop(); + stopping=true; + runReceiver=false; + LOGD("before shutdown socket"); + shutdown(udpSocket, SHUT_RDWR); + sendQueue->Put(NULL); + close(udpSocket); + LOGD("before join sendThread"); + join_thread(sendThread); + LOGD("before join recvThread"); + join_thread(recvThread); + LOGD("before join tickThread"); + join_thread(tickThread); + free_mutex(sendBufferMutex); + LOGD("before close socket"); + LOGD("before free send buffers"); + while(emptySendBuffers.size()>0){ + delete emptySendBuffers[emptySendBuffers.size()-1]; + emptySendBuffers.pop_back(); + } + while(sendQueue->Size()>0){ + void* p=sendQueue->Get(); + if(p) + delete (CBufferOutputStream*)p; + } + LOGD("before delete jitter buffer"); + if(jitterBuffer){ + delete jitterBuffer; + } + LOGD("before stop decoder"); + if(decoder){ + decoder->Stop(); + } + LOGD("before delete audio input"); + if(audioInput){ + delete audioInput; + } + LOGD("before delete encoder"); + if(encoder){ + encoder->Stop(); + delete encoder; + } + LOGD("before delete audio output"); + if(audioOutput){ + delete audioOutput; + } + LOGD("before delete decoder"); + if(decoder){ + delete decoder; + } + LOGD("before delete echo canceller"); + if(echoCanceller){ + echoCanceller->Stop(); + delete echoCanceller; + } + delete sendQueue; + int i; + for(i=0;idata) + free(queuedPackets[i]->data); + free(queuedPackets[i]); + } + delete conctl; + LOGD("Left CVoIPController::~CVoIPController"); +} + +void CVoIPController::SetRemoteEndpoints(voip_endpoint_t* endpoints, size_t count, bool allowP2p){ + LOGW("Set remote endpoints"); + assert(count>0); + preferredRelay=NULL; + size_t i; + for(i=0;i_averageRtt=0; + ep->_lastPingTime=0; + memset(ep->_rtts, 0, sizeof(double)*6); + this->endpoints.push_back(ep); + if(ep->type==EP_TYPE_UDP_RELAY && !preferredRelay) + preferredRelay=ep; + } + currentEndpoint=this->endpoints[0]; + this->allowP2p=allowP2p; +} + +void* CVoIPController::StartRecvThread(void* controller){ + ((CVoIPController*)controller)->RunRecvThread(); + return NULL; +} + +void* CVoIPController::StartSendThread(void* controller){ + ((CVoIPController*)controller)->RunSendThread(); + return NULL; +} + + +void* CVoIPController::StartTickThread(void* controller){ + ((CVoIPController*) controller)->RunTickThread(); + return NULL; +} + + +void CVoIPController::Start(){ + int res; + LOGW("Starting voip controller"); + udpSocket=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(udpSocket<0){ + LOGE("error creating socket: %d / %s", errno, strerror(errno)); + } +#ifdef __APPLE__ + int prio=NET_SERVICE_TYPE_VO; + res=setsockopt(udpSocket, SOL_SOCKET, SO_NET_SERVICE_TYPE, &prio, sizeof(prio)); + if(res<0){ + LOGE("error setting darwin-specific net priority: %d / %s", errno, strerror(errno)); + } +#else + int prio=5; + res=setsockopt(udpSocket, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)); + if(res<0){ + LOGE("error setting priority: %d / %s", errno, strerror(errno)); + } + prio<<=5; + res=setsockopt(udpSocket, SOL_IP, IP_TOS, &prio, sizeof(prio)); + if(res<0){ + LOGE("error setting ip tos: %d / %s", errno, strerror(errno)); + } +#endif + sockaddr_in addr; + addr.sin_addr.s_addr=0; + //addr.sin_port=htons(3497); + addr.sin_port=0; + addr.sin_family=AF_INET; + res=bind(udpSocket, (sockaddr*)&addr, sizeof(sockaddr_in)); + if(res<0){ + LOGE("error binding: %d / %s", errno, strerror(errno)); + } + size_t addrLen=sizeof(sockaddr_in); + getsockname(udpSocket, (sockaddr*)&addr, (socklen_t*) &addrLen); + localUdpPort=ntohs(addr.sin_port); + LOGD("Bound to local UDP port %u", addr.sin_port); + + SendPacket(NULL, 0, currentEndpoint); + + runReceiver=true; + start_thread(recvThread, StartRecvThread, this); + set_thread_priority(recvThread, get_thread_max_priority()); + set_thread_name(recvThread, "voip-recv"); + start_thread(sendThread, StartSendThread, this); + set_thread_priority(sendThread, get_thread_max_priority()); + set_thread_name(sendThread, "voip-send"); + start_thread(tickThread, StartTickThread, this); + set_thread_priority(tickThread, get_thread_max_priority()); + set_thread_name(tickThread, "voip-tick"); +} + +size_t CVoIPController::AudioInputCallback(unsigned char* data, size_t length, void* param){ + ((CVoIPController*)param)->HandleAudioInput(data, length); + return 0; +} + +void CVoIPController::HandleAudioInput(unsigned char *data, size_t len){ + if(stopping) + return; + if(waitingForAcks || dontSendPackets>0){ + LOGV("waiting for RLC, dropping outgoing audio packet"); + return; + } + int audioPacketGrouping=1; + CBufferOutputStream* pkt=NULL; + if(audioPacketsWritten==0){ + pkt=GetOutgoingPacketBuffer(); + if(!pkt){ + LOGW("Dropping data packet, queue overflow"); + return; + } + currentAudioPacket=pkt; + }else{ + pkt=currentAudioPacket; + } + unsigned char flags=(unsigned char) (len>255 ? STREAM_DATA_FLAG_LEN16 : 0); + pkt->WriteByte((unsigned char) (1 | flags)); // streamID + flags + if(len>255) + pkt->WriteInt16((int16_t)len); + else + pkt->WriteByte((unsigned char)len); + pkt->WriteInt32(audioTimestampOut); + pkt->WriteBytes((char *) data, len); + audioPacketsWritten++; + if(audioPacketsWritten>=audioPacketGrouping){ + uint32_t pl=pkt->GetLength(); + char tmp[pl]; + memcpy(tmp, pkt->GetBuffer(), pl); + pkt->Reset(); + unsigned char type; + switch(audioPacketGrouping){ + case 2: + type=PKT_STREAM_DATA_X2; + break; + case 3: + type=PKT_STREAM_DATA_X3; + break; + default: + type=PKT_STREAM_DATA; + break; + } + WritePacketHeader(pkt, type, pl); + pkt->WriteBytes(tmp, pl); + //LOGI("payload size %u", pl); + if(pl<253) + pl+=1; + for(;pl%4>0;pl++) + pkt->WriteByte(0); + sendQueue->Put(pkt); + audioPacketsWritten=0; + } + audioTimestampOut+=outgoingStreams[0]->frameDuration; +} + +void CVoIPController::Connect(){ + assert(state!=STATE_WAIT_INIT_ACK); + connectionInitTime=GetCurrentTime(); + SendInit(); +} + + +void CVoIPController::SetEncryptionKey(char *key){ + memcpy(encryptionKey, key, 256); + uint8_t sha1[SHA1_LENGTH]; + crypto.sha1((uint8_t*) encryptionKey, 256, sha1); + memcpy(keyFingerprint, sha1+(SHA1_LENGTH-8), 8); + uint8_t sha256[SHA256_LENGTH]; + crypto.sha256((uint8_t*) encryptionKey, 256, sha256); + memcpy(callID, sha256+(SHA256_LENGTH-16), 16); +} + +uint32_t CVoIPController::WritePacketHeader(CBufferOutputStream *s, unsigned char type, uint32_t length){ + uint32_t acks=0; + int i; + for(i=0;i<32;i++){ + if(recvPacketTimes[i]>0) + acks|=1; + if(i<31) + acks<<=1; + } + + uint32_t pseq=seq++; + + if(state==STATE_WAIT_INIT || state==STATE_WAIT_INIT_ACK){ + s->WriteInt32(TLID_DECRYPTED_AUDIO_BLOCK); + int64_t randomID; + crypto.rand_bytes((uint8_t *) &randomID, 8); + s->WriteInt64(randomID); + unsigned char randBytes[7]; + crypto.rand_bytes(randBytes, 7); + s->WriteByte(7); + s->WriteBytes((char *) randBytes, 7); + uint32_t pflags=PFLAG_HAS_RECENT_RECV | PFLAG_HAS_SEQ; + if(length>0) + pflags|=PFLAG_HAS_DATA; + if(state==STATE_WAIT_INIT || state==STATE_WAIT_INIT_ACK){ + pflags|=PFLAG_HAS_CALL_ID | PFLAG_HAS_PROTO; + } + pflags|=((uint32_t) type) << 24; + s->WriteInt32(pflags); + + if(pflags & PFLAG_HAS_CALL_ID){ + s->WriteBytes(callID, 16); + } + s->WriteInt32(lastRemoteSeq); + s->WriteInt32(pseq); + s->WriteInt32(acks); + if(pflags & PFLAG_HAS_PROTO){ + s->WriteInt32(PROTOCOL_NAME); + } + if(length>0){ + if(length<=253){ + s->WriteByte((unsigned char) length); + }else{ + s->WriteByte(254); + s->WriteByte((unsigned char) (length & 8)); + s->WriteByte((unsigned char) ((length >> 8) & 8)); + s->WriteByte((unsigned char) ((length >> 16) & 8)); + } + } + }else{ + s->WriteInt32(TLID_SIMPLE_AUDIO_BLOCK); + int64_t randomID; + crypto.rand_bytes((uint8_t *) &randomID, 8); + s->WriteInt64(randomID); + unsigned char randBytes[7]; + crypto.rand_bytes(randBytes, 7); + s->WriteByte(7); + s->WriteBytes((char *) randBytes, 7); + uint32_t lenWithHeader=length+13; + if(lenWithHeader>0){ + if(lenWithHeader<=253){ + s->WriteByte((unsigned char) lenWithHeader); + }else{ + s->WriteByte(254); + s->WriteByte((unsigned char) (lenWithHeader & 8)); + s->WriteByte((unsigned char) ((lenWithHeader >> 8) & 8)); + s->WriteByte((unsigned char) ((lenWithHeader >> 16) & 8)); + } + } + s->WriteByte(type); + s->WriteInt32(lastRemoteSeq); + s->WriteInt32(pseq); + s->WriteInt32(acks); + } + + if(type==PKT_STREAM_DATA || type==PKT_STREAM_DATA_X2 || type==PKT_STREAM_DATA_X3) + conctl->PacketSent(pseq, length); + + memmove(&sentPacketTimes[1], sentPacketTimes, 31*sizeof(double)); + sentPacketTimes[0]=GetCurrentTime(); + lastSentSeq=pseq; + //LOGI("packet header size %d", s->GetLength()); + + return pseq; +} + + +void CVoIPController::UpdateAudioBitrate(){ + if(encoder){ + if(networkType==NET_TYPE_GPRS || dataSavingMode || dataSavingRequestedByPeer) + encoder->SetBitrate(maxBitrate=8000); + else if(networkType==NET_TYPE_EDGE){ + maxBitrate=16000; + encoder->SetBitrate(8000); + }else{ + maxBitrate=25000; + encoder->SetBitrate(16000); + } + } +} + + +void CVoIPController::SendInit(){ + CBufferOutputStream* out=new CBufferOutputStream(1024); + WritePacketHeader(out, PKT_INIT, 15); + out->WriteInt32(PROTOCOL_VERSION); + out->WriteInt32(MIN_PROTOCOL_VERSION); + uint32_t flags=0; + if(dataSavingMode) + flags|=INIT_FLAG_DATA_SAVING_ENABLED; + out->WriteInt32(flags); + out->WriteByte(1); // audio codecs count + out->WriteByte(CODEC_OPUS); + out->WriteByte(0); // video codecs count + SendPacket(out->GetBuffer(), out->GetLength(), currentEndpoint); + SetState(STATE_WAIT_INIT_ACK); + delete out; +} + +void CVoIPController::SendInitAck(){ + +} + +void CVoIPController::RunRecvThread(){ + LOGI("Receive thread starting"); + char buffer[1024]; + sockaddr_in srcAddr; + int addrLen; + while(runReceiver){ + //LOGI("Before recv"); + addrLen=sizeof(sockaddr_in); + ssize_t len=recvfrom(udpSocket, buffer, 1024, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen); + //LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime()); + voip_endpoint_t* srcEndpoint=NULL; + int _i; + for(_i=0;_iaddress.s_addr==srcAddr.sin_addr.s_addr && endpoints[_i]->port==ntohs(srcAddr.sin_port)){ + srcEndpoint=endpoints[_i]; + break; + } + } + if(!srcEndpoint){ + LOGW("Received a packet from unknown source %s:%u", inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port)); + continue; + } + if(len<=0){ + LOGW("error receiving: %d / %s", errno, strerror(errno)); + continue; + } + if(IS_MOBILE_NETWORK(networkType)) + stats.bytesRecvdMobile+=(uint64_t)len; + else + stats.bytesRecvdWifi+=(uint64_t)len; + CBufferInputStream* in=new CBufferInputStream(buffer, (size_t)len); + if(memcmp(buffer, srcEndpoint->type==EP_TYPE_UDP_RELAY ? srcEndpoint->peerTag : callID, 16)!=0){ + LOGW("Received packet has wrong peerTag"); + delete in; + continue; + } + in->Seek(16); + if(waitingForRelayPeerInfo && in->Remaining()>=32){ + bool isPublicIpResponse=true; + int i; + for(i=0;i<12;i++){ + if((unsigned char)buffer[in->GetOffset()+i]!=0xFF){ + isPublicIpResponse=false; + break; + } + } + + if(isPublicIpResponse){ + waitingForRelayPeerInfo=false; + in->Seek(in->GetOffset()+12); + uint32_t tlid=(uint32_t) in->ReadInt32(); + if(tlid==TLID_UDP_REFLECTOR_PEER_INFO){ + uint32_t myAddr=(uint32_t) in->ReadInt32(); + uint32_t myPort=(uint32_t) in->ReadInt32(); + uint32_t peerAddr=(uint32_t) in->ReadInt32(); + uint32_t peerPort=(uint32_t) in->ReadInt32(); + voip_endpoint_t* p2pEndpoint=NULL; + for(i=0;itype==EP_TYPE_UDP_P2P_INET){ + p2pEndpoint=endpoints[i]; + break; + } + } + if(!p2pEndpoint){ + p2pEndpoint=(voip_endpoint_t *) malloc(sizeof(voip_endpoint_t)); + endpoints.push_back(p2pEndpoint); + } + memset(p2pEndpoint, 0, sizeof(voip_endpoint_t)); + p2pEndpoint->type=EP_TYPE_UDP_P2P_INET; + p2pEndpoint->port=peerPort; + p2pEndpoint->address.s_addr=peerAddr;//ntohl(peerAddr); + LOGW("Received reflector peer info, my=%08X:%u, peer=%08X:%u", myAddr, myPort, peerAddr, peerPort); + if(myAddr==peerAddr){ + LOGW("Detected LAN"); + in_addr lanAddr; + GetLocalNetworkItfInfo(&lanAddr, NULL); + CBufferOutputStream* pkt=GetOutgoingPacketBuffer(); + if(pkt){ + WritePacketHeader(pkt, PKT_LAN_ENDPOINT, 8); + pkt->WriteInt32(lanAddr.s_addr); + pkt->WriteInt32(localUdpPort); + sendQueue->Put(pkt); + } + }else{ + for(i=0;itype==EP_TYPE_UDP_P2P_LAN){ + free(endpoints[i]); + endpoints.erase(endpoints.begin()+i); + break; + } + } + } + p2pPingCount=0; + lastP2pPingTime=0; + needSendP2pPing=true; + }else{ + LOGE("It looks like a reflector response but tlid is %08X, expected %08X", tlid, TLID_UDP_REFLECTOR_PEER_INFO); + } + delete in; + continue; + } + } + if(in->Remaining()<40){ + delete in; + continue; + } + + unsigned char fingerprint[8], msgHash[16]; + in->ReadBytes((char *) fingerprint, 8); + in->ReadBytes((char *) msgHash, 16); + if(memcmp(fingerprint, keyFingerprint, 8)!=0){ + LOGW("Received packet has wrong key fingerprint"); + delete in; + continue; + } + unsigned char key[32], iv[32]; + KDF(msgHash, key, iv); + unsigned char aesOut[in->Remaining()]; + crypto.aes_ige_decrypt((unsigned char *) buffer+in->GetOffset(), aesOut, in->Remaining(), key, iv); + memcpy(buffer+in->GetOffset(), aesOut, in->Remaining()); + unsigned char sha[SHA1_LENGTH]; + int32_t _len=in->ReadInt32(); + crypto.sha1((uint8_t *) (buffer+in->GetOffset()-4), (size_t) (_len+4), sha); + if(memcmp(msgHash, sha+(SHA1_LENGTH-16), 16)!=0){ + LOGW("Received packet has wrong hash after decryption"); + delete in; + continue; + } + + lastRecvPacketTime=GetCurrentTime(); + + + try{ + /*decryptedAudioBlock random_id:long random_bytes:string flags:# voice_call_id:flags.2?int128 in_seq_no:flags.4?int out_seq_no:flags.4?int + * recent_received_mask:flags.5?int proto:flags.3?int extra:flags.1?string raw_data:flags.0?string = DecryptedAudioBlock +simpleAudioBlock random_id:long random_bytes:string raw_data:string = DecryptedAudioBlock; +*/ + uint32_t ackId, pseq, acks; + unsigned char type; + uint32_t tlid=(uint32_t) in->ReadInt32(); + uint32_t packetInnerLen; + if(tlid==TLID_DECRYPTED_AUDIO_BLOCK){ + in->ReadInt64(); // random id + uint32_t randLen=(uint32_t) in->ReadTlLength(); + in->Seek(in->GetOffset()+randLen+(randLen+(randLen<=253 ? 1 : 0))%4); + uint32_t flags=(uint32_t) in->ReadInt32(); + type=(unsigned char) ((flags >> 24) & 0xFF); + if(!(flags & PFLAG_HAS_SEQ && flags & PFLAG_HAS_RECENT_RECV)){ + LOGW("Received packet doesn't have PFLAG_HAS_SEQ, PFLAG_HAS_RECENT_RECV, or both"); + delete in; + continue; + } + if(flags & PFLAG_HAS_CALL_ID){ + unsigned char pktCallID[16]; + in->ReadBytes((char *) pktCallID, 16); + } + ackId=(uint32_t) in->ReadInt32(); + pseq=(uint32_t) in->ReadInt32(); + acks=(uint32_t) in->ReadInt32(); + if(flags & PFLAG_HAS_PROTO){ + uint32_t proto=(uint32_t) in->ReadInt32(); + if(proto!=PROTOCOL_NAME){ + LOGW("Received packet uses wrong protocol"); + delete in; + lastError=ERROR_INCOMPATIBLE; + SetState(STATE_FAILED); + return; + } + } + if(flags & PFLAG_HAS_EXTRA){ + uint32_t extraLen=(uint32_t) in->ReadTlLength(); + in->Seek(in->GetOffset()+extraLen+(extraLen+(extraLen<=253 ? 1 : 0))%4); + } + if(flags & PFLAG_HAS_DATA){ + packetInnerLen=in->ReadTlLength(); + } + }else if(tlid==TLID_SIMPLE_AUDIO_BLOCK){ + in->ReadInt64(); // random id + uint32_t randLen=(uint32_t) in->ReadTlLength(); + in->Seek(in->GetOffset()+randLen+(randLen+(randLen<=253 ? 1 : 0))%4); + packetInnerLen=in->ReadTlLength(); + type=in->ReadByte(); + ackId=(uint32_t) in->ReadInt32(); + pseq=(uint32_t) in->ReadInt32(); + acks=(uint32_t) in->ReadInt32(); + }else{ + LOGW("Received a packet of unknown type %08X", tlid); + delete in; + continue; + } + packetsRecieved++; + if(seqgt(pseq, lastRemoteSeq)){ + uint32_t diff=pseq-lastRemoteSeq; + if(diff>31){ + memset(recvPacketTimes, 0, 32*sizeof(double)); + }else{ + memmove(&recvPacketTimes[diff], recvPacketTimes, (32-diff)*sizeof(double)); + if(diff>1){ + memset(recvPacketTimes, 0, diff*sizeof(double)); + } + recvPacketTimes[0]=GetCurrentTime(); + } + lastRemoteSeq=pseq; + }else if(!seqgt(pseq, lastRemoteSeq) && lastRemoteSeq-pseq<32){ + if(recvPacketTimes[lastRemoteSeq-pseq]!=0){ + LOGW("Received duplicated packet for seq %u", pseq); + delete in; + continue; + } + recvPacketTimes[lastRemoteSeq-pseq]=GetCurrentTime(); + }else if(lastRemoteSeq-pseq>=32){ + LOGW("Packet %u is out of order and too late", pseq); + delete in; + continue; + } + if(seqgt(ackId, lastRemoteAckSeq)){ + uint32_t diff=ackId-lastRemoteAckSeq; + if(diff>31){ + memset(remoteAcks, 0, 32*sizeof(double)); + }else{ + memmove(&remoteAcks[diff], remoteAcks, (32-diff)*sizeof(double)); + if(diff>1){ + memset(remoteAcks, 0, diff*sizeof(double)); + } + remoteAcks[0]=GetCurrentTime(); + } + if(waitingForAcks && lastRemoteAckSeq>=firstSentPing){ + memset(rttHistory, 0, 32*sizeof(double)); + waitingForAcks=false; + dontSendPackets=10; + LOGI("resuming sending"); + } + lastRemoteAckSeq=ackId; + conctl->PacketAcknowledged(ackId); + int i; + for(i=0;i<31;i++){ + if(remoteAcks[i+1]==0){ + if((acks >> (31-i)) & 1){ + remoteAcks[i+1]=GetCurrentTime(); + conctl->PacketAcknowledged(ackId-(i+1)); + } + } + } + lock_mutex(queuedPacketsMutex); + for(i=0;iseqs[j]); + if(qp->seqs[j]==0) + break; + int remoteAcksIndex=lastRemoteAckSeq-qp->seqs[j]; + LOGV("remote acks index %u, value %llf", remoteAcksIndex, remoteAcksIndex>=0 && remoteAcksIndex<32 ? remoteAcks[remoteAcksIndex] : -1); + if(seqgt(lastRemoteAckSeq, qp->seqs[j]) && remoteAcksIndex>=0 && remoteAcksIndex<32 && remoteAcks[remoteAcksIndex]>0){ + LOGD("did ack seq %u, removing", qp->seqs[j]); + didAck=true; + break; + } + } + if(didAck){ + if(qp->data) + free(qp->data); + free(qp); + queuedPackets.erase(queuedPackets.begin()+i); + i--; + continue; + } + } + unlock_mutex(queuedPacketsMutex); + } + + if(srcEndpoint!=currentEndpoint && srcEndpoint->type==EP_TYPE_UDP_RELAY && currentEndpoint->type!=EP_TYPE_UDP_RELAY){ + if(seqgt(lastSentSeq-32, lastRemoteAckSeq)){ + currentEndpoint=srcEndpoint; + LOGI("Peer network address probably changed, switching to relay"); + if(allowP2p) + SendPublicEndpointsRequest(); + } + } + //LOGV("acks: %u -> %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf", lastRemoteAckSeq, remoteAcks[0], remoteAcks[1], remoteAcks[2], remoteAcks[3], remoteAcks[4], remoteAcks[5], remoteAcks[6], remoteAcks[7]); + //LOGD("recv: %u -> %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf, %.2lf", lastRemoteSeq, recvPacketTimes[0], recvPacketTimes[1], recvPacketTimes[2], recvPacketTimes[3], recvPacketTimes[4], recvPacketTimes[5], recvPacketTimes[6], recvPacketTimes[7]); + //LOGI("RTT = %.3lf", GetAverageRTT()); + //LOGV("Packet %u type is %d", pseq, type); + if(type==PKT_INIT){ + LOGD("Received init"); + + peerVersion=(uint32_t) in->ReadInt32(); + LOGI("Peer version is %d", peerVersion); + uint32_t minVer=(uint32_t) in->ReadInt32(); + if(minVer>PROTOCOL_VERSION || peerVersionReadInt32(); + if(flags & INIT_FLAG_DATA_SAVING_ENABLED){ + dataSavingRequestedByPeer=true; + UpdateDataSavingState(); + UpdateAudioBitrate(); + } + + int i; + int numSupportedAudioCodecs=in->ReadByte(); + for(i=0;iReadByte(); // ignore for now + } + int numSupportedVideoCodecs=in->ReadByte(); + for(i=0;iReadByte(); // ignore for now + } + + + CBufferOutputStream* out=new CBufferOutputStream(1024); + WritePacketHeader(out, PKT_INIT_ACK, (peerVersion>=2 ? 10 : 2)+(peerVersion>=2 ? 6 : 4)*outgoingStreams.size()); + if(peerVersion>=2){ + out->WriteInt32(PROTOCOL_VERSION); + out->WriteInt32(MIN_PROTOCOL_VERSION); + } + out->WriteByte((unsigned char) outgoingStreams.size()); + for(i=0;iWriteByte(outgoingStreams[i]->id); + out->WriteByte(outgoingStreams[i]->type); + out->WriteByte(outgoingStreams[i]->codec); + if(peerVersion>=2) + out->WriteInt16(outgoingStreams[i]->frameDuration); + else + outgoingStreams[i]->frameDuration=20; + out->WriteByte((unsigned char)(outgoingStreams[i]->enabled ? 1 : 0)); + } + SendPacket(out->GetBuffer(), out->GetLength(), currentEndpoint); + delete out; + } + if(type==PKT_INIT_ACK){ + LOGD("Received init ack"); + + if(packetInnerLen>10){ + peerVersion=in->ReadInt32(); + uint32_t minVer=(uint32_t) in->ReadInt32(); + if(minVer>PROTOCOL_VERSION || peerVersionReadByte(); + if(streamCount==0) + goto malformed_packet; + + int i; + voip_stream_t* incomingAudioStream=NULL; + for(i=0;iid=in->ReadByte(); + stm->type=in->ReadByte(); + stm->codec=in->ReadByte(); + if(peerVersion>=2) + stm->frameDuration=(uint16_t) in->ReadInt16(); + else + stm->frameDuration=20; + stm->enabled=in->ReadByte()==1; + incomingStreams.push_back(stm); + if(stm->type==STREAM_TYPE_AUDIO && !incomingAudioStream) + incomingAudioStream=stm; + } + if(!incomingAudioStream) + goto malformed_packet; + + voip_stream_t* outgoingAudioStream=outgoingStreams[0]; + + if(!audioInput){ + LOGI("before create audio io"); + audioInput=CAudioInput::Create(); + audioInput->Configure(48000, 16, 1); + audioOutput=CAudioOutput::Create(); + audioOutput->Configure(48000, 16, 1); + if(enableAEC) + echoCanceller=new CEchoCanceller(); + else + echoCanceller=NULL; + encoder=new COpusEncoder(audioInput); + encoder->SetCallback(AudioInputCallback, this); + encoder->SetOutputFrameDuration(outgoingAudioStream->frameDuration); + encoder->SetEchoCanceller(echoCanceller); + encoder->Start(); + if(!micMuted) + audioInput->Start(); + UpdateAudioBitrate(); + + jitterBuffer=new CJitterBuffer(NULL, incomingAudioStream->frameDuration); + decoder=new COpusDecoder(audioOutput); + decoder->SetEchoCanceller(echoCanceller); + decoder->SetJitterBuffer(jitterBuffer); + decoder->SetFrameDuration(incomingAudioStream->frameDuration); + decoder->Start(); + if(incomingAudioStream->frameDuration>50) + jitterBuffer->SetMinPacketCount(3); + else if(incomingAudioStream->frameDuration>30) + jitterBuffer->SetMinPacketCount(4); + //audioOutput->Start(); + } + SetState(STATE_ESTABLISHED); + if(allowP2p) + SendPublicEndpointsRequest(); + } + if(type==PKT_STREAM_DATA || type==PKT_STREAM_DATA_X2 || type==PKT_STREAM_DATA_X3){ + int count; + switch(type){ + case PKT_STREAM_DATA_X2: + count=2; + break; + case PKT_STREAM_DATA_X3: + count=3; + break; + case PKT_STREAM_DATA: + default: + count=1; + break; + } + int i; + for(i=0;iReadByte(); + unsigned char flags=(unsigned char) (streamID & 0xC0); + uint16_t sdlen=(uint16_t) (flags & STREAM_DATA_FLAG_LEN16 ? in->ReadInt16() : in->ReadByte()); + uint32_t pts=(uint32_t) in->ReadInt32(); + //LOGD("stream data, pts=%d, len=%d, rem=%d", pts, sdlen, in->Remaining()); + audioTimestampIn=pts; + if(!audioOutStarted && audioOutput){ + audioOutput->Start(); + audioOutStarted=true; + } + if(jitterBuffer) + jitterBuffer->HandleInput((unsigned char*) (buffer+in->GetOffset()), sdlen, pts); + if(iSeek(in->GetOffset()+sdlen); + } + } + if(type==PKT_PING){ + LOGD("Received ping from %s:%d", inet_ntoa(srcEndpoint->address), srcEndpoint->port); + if(srcEndpoint->type!=EP_TYPE_UDP_RELAY && !allowP2p){ + LOGW("Received p2p ping but p2p is disabled by manual override"); + delete in; + continue; + } + if(srcEndpoint==currentEndpoint){ + CBufferOutputStream *pkt=GetOutgoingPacketBuffer(); + if(!pkt){ + LOGW("Dropping pong packet, queue overflow"); + continue; + } + WritePacketHeader(pkt, PKT_PONG, 4); + pkt->WriteInt32(pseq); + sendQueue->Put(pkt); + }else{ + CBufferOutputStream pkt(32); + WritePacketHeader(&pkt, PKT_PONG, 4); + pkt.WriteInt32(pseq); + SendPacket(pkt.GetBuffer(), pkt.GetLength(), srcEndpoint); + } + } + if(type==PKT_PONG){ + if(packetInnerLen>=4){ + uint32_t pingSeq=(uint32_t) in->ReadInt32(); + if(pingSeq==srcEndpoint->_lastPingSeq){ + memmove(&srcEndpoint->_rtts[1], srcEndpoint->_rtts, sizeof(double)*5); + srcEndpoint->_rtts[0]=GetCurrentTime()-srcEndpoint->_lastPingTime; + int i; + srcEndpoint->_averageRtt=0; + for(i=0;i<6;i++){ + if(srcEndpoint->_rtts[i]==0) + break; + srcEndpoint->_averageRtt+=srcEndpoint->_rtts[i]; + } + srcEndpoint->_averageRtt/=i; + LOGD("Current RTT via %s: %.3llf, average: %.3llf", inet_ntoa(srcEndpoint->address), srcEndpoint->_rtts[0], srcEndpoint->_averageRtt); + } + } + /*if(currentEndpoint!=srcEndpoint && (srcEndpoint->type==EP_TYPE_UDP_P2P_INET || srcEndpoint->type==EP_TYPE_UDP_P2P_LAN)){ + LOGI("Switching to P2P now!"); + currentEndpoint=srcEndpoint; + needSendP2pPing=false; + }*/ + } + if(type==PKT_STREAM_STATE){ + unsigned char id=in->ReadByte(); + unsigned char enabled=in->ReadByte(); + int i; + for(i=0;iid==id){ + incomingStreams[i]->enabled=enabled==1; + UpdateAudioOutputState(); + break; + } + } + } + if(type==PKT_LAN_ENDPOINT){ + uint32_t peerAddr=(uint32_t) in->ReadInt32(); + uint16_t peerPort=(uint16_t) in->ReadInt32(); + voip_endpoint_t* p2pEndpoint=GetEndpointByType(EP_TYPE_UDP_P2P_LAN); + if(!p2pEndpoint){ + p2pEndpoint=(voip_endpoint_t *) malloc(sizeof(voip_endpoint_t)); + endpoints.push_back(p2pEndpoint); + } + memset(p2pEndpoint, 0, sizeof(voip_endpoint_t)); + p2pEndpoint->type=EP_TYPE_UDP_P2P_LAN; + p2pEndpoint->port=peerPort; + p2pEndpoint->address.s_addr=peerAddr;//ntohl(peerAddr); + } + if(type==PKT_NETWORK_CHANGED){ + currentEndpoint=preferredRelay; + if(allowP2p) + SendPublicEndpointsRequest(); + if(peerVersion>=2){ + uint32_t flags=(uint32_t) in->ReadInt32(); + dataSavingRequestedByPeer=(flags & INIT_FLAG_DATA_SAVING_ENABLED)==INIT_FLAG_DATA_SAVING_ENABLED; + UpdateDataSavingState(); + UpdateAudioBitrate(); + } + } + if(type==PKT_SWITCH_PREF_RELAY){ + uint64_t relayId=(uint64_t) in->ReadInt64(); + int i; + for(i=0;itype==EP_TYPE_UDP_RELAY && endpoints[i]->id==relayId){ + preferredRelay=endpoints[i]; + LOGD("Switching preferred relay to %s:%d", inet_ntoa(preferredRelay->address), preferredRelay->port); + break; + } + } + if(currentEndpoint->type==EP_TYPE_UDP_RELAY) + currentEndpoint=preferredRelay; + } + /*if(type==PKT_SWITCH_TO_P2P && allowP2p){ + voip_endpoint_t* p2p=GetEndpointByType(EP_TYPE_UDP_P2P_INET); + if(p2p){ + voip_endpoint_t* lan=GetEndpointByType(EP_TYPE_UDP_P2P_LAN); + if(lan && lan->_averageRtt>0){ + LOGI("Switching to p2p (LAN)"); + currentEndpoint=lan; + }else{ + if(lan) + lan->_lastPingTime=0; + if(p2p->_averageRtt>0){ + LOGI("Switching to p2p (Inet)"); + currentEndpoint=p2p; + }else{ + p2p->_lastPingTime=0; + } + } + } + }*/ + }catch(std::out_of_range x){ + LOGW("Error parsing packet: %s", x.what()); + } + malformed_packet: + delete in; + } + LOGI("=== recv thread exiting ==="); +} + +void CVoIPController::RunSendThread(){ + while(runReceiver){ + CBufferOutputStream* pkt=(CBufferOutputStream *) sendQueue->GetBlocking(); + if(pkt){ + SendPacket(pkt->GetBuffer(), pkt->GetLength(), currentEndpoint); + pkt->Reset(); + lock_mutex(sendBufferMutex); + emptySendBuffers.push_back(pkt); + unlock_mutex(sendBufferMutex); + } + } + LOGI("=== send thread exiting ==="); +} + + +void CVoIPController::RunTickThread(){ + uint32_t tickCount=0; + bool wasWaitingForAcks=false; + while(runReceiver){ + usleep(100000); + tickCount++; + if(tickCount%5==0 && state==STATE_ESTABLISHED){ + memmove(&rttHistory[1], rttHistory, 31*sizeof(double)); + rttHistory[0]=GetAverageRTT(); + /*if(rttHistory[16]>0){ + LOGI("rtt diff: %.3lf", rttHistory[0]-rttHistory[16]); + }*/ + int i; + double v=0; + for(i=1;i<32;i++){ + v+=rttHistory[i-1]-rttHistory[i]; + } + v=v/32; + if(rttHistory[0]>10.0 && rttHistory[8]>10.0 && (networkType==NET_TYPE_EDGE || networkType==NET_TYPE_GPRS)){ + waitingForAcks=true; + }else{ + waitingForAcks=false; + } + if(waitingForAcks) + wasWaitingForAcks=false; + //LOGI("%.3lf/%.3lf, rtt diff %.3lf, waiting=%d, queue=%d", rttHistory[0], rttHistory[8], v, waitingForAcks, sendQueue->Size()); + if(jitterBuffer) + recvLossCount+=jitterBuffer->GetAndResetLostPacketCount(); + } + if(dontSendPackets>0) + dontSendPackets--; + + int i; + + conctl->Tick(); + + if(state==STATE_ESTABLISHED){ + int act=conctl->GetBandwidthControlAction(); + if(act==TGVOIP_CONCTL_ACT_DECREASE){ + uint32_t bitrate=encoder->GetBitrate(); + if(bitrate>8000) + encoder->SetBitrate(bitrate<9000 ? 8000 : (bitrate-1000)); + }else if(act==TGVOIP_CONCTL_ACT_INCREASE){ + uint32_t bitrate=encoder->GetBitrate(); + if(bitrateSetBitrate(bitrate+1000); + } + + if(tickCount%10==0 && encoder){ + uint32_t sendLossCount=conctl->GetSendLossCount(); + memmove(sendLossCountHistory+1, sendLossCountHistory, 31*sizeof(uint32_t)); + sendLossCountHistory[0]=sendLossCount-prevSendLossCount; + prevSendLossCount=sendLossCount; + double avgSendLossCount=0; + for(i=0;i<10;i++){ + avgSendLossCount+=sendLossCountHistory[i]; + } + double packetsPerSec=1000/(double)outgoingStreams[0]->frameDuration; + avgSendLossCount=avgSendLossCount/10/packetsPerSec; + //LOGV("avg send loss: %.1f%%", avgSendLossCount*100); + + if(avgSendLossCount>0.1){ + encoder->SetPacketLoss(40); + }else if(avgSendLossCount>0.075){ + encoder->SetPacketLoss(35); + }else if(avgSendLossCount>0.0625){ + encoder->SetPacketLoss(30); + }else if(avgSendLossCount>0.05){ + encoder->SetPacketLoss(25); + }else if(avgSendLossCount>0.025){ + encoder->SetPacketLoss(20); + }else if(avgSendLossCount>0.01){ + encoder->SetPacketLoss(17); + }else{ + encoder->SetPacketLoss(15); + } + } + } + + bool areThereAnyEnabledStreams=false; + + for(i=0;ienabled) + areThereAnyEnabledStreams=true; + } + + if((waitingForAcks && tickCount%10==0) || (!areThereAnyEnabledStreams && tickCount%2==0)){ + CBufferOutputStream* pkt=GetOutgoingPacketBuffer(); + if(!pkt){ + LOGW("Dropping ping packet, queue overflow"); + return; + } + uint32_t seq=WritePacketHeader(pkt, PKT_NOP, 0); + firstSentPing=seq; + sendQueue->Put(pkt); + LOGV("sent ping"); + } + + if(state==STATE_WAIT_INIT_ACK && GetCurrentTime()-stateChangeTime>.5){ + SendInit(); + } + + /*if(needSendP2pPing){ + if(GetCurrentTime()-lastP2pPingTime>2){ + if(p2pPingCount<10){ // try hairpin routing first, even if we have a LAN address + SendP2pPing(EP_TYPE_UDP_P2P_INET); + } + if(p2pPingCount>=5 && p2pPingCount<15){ // last resort to get p2p + SendP2pPing(EP_TYPE_UDP_P2P_LAN); + } + p2pPingCount++; + } + }*/ + + if(waitingForRelayPeerInfo && GetCurrentTime()-publicEndpointsReqTime>5){ + LOGD("Resending peer relay info request"); + SendPublicEndpointsRequest(); + } + + lock_mutex(queuedPacketsMutex); + for(i=0;itimeout>0 && qp->firstSentTime>0 && GetCurrentTime()-qp->firstSentTime>=qp->timeout){ + LOGD("Removing queued packet because of timeout"); + if(qp->data) + free(qp->data); + free(qp); + queuedPackets.erase(queuedPackets.begin()+i); + i--; + continue; + } + if(GetCurrentTime()-qp->lastSentTime>=qp->retryInterval){ + CBufferOutputStream* pkt=GetOutgoingPacketBuffer(); + if(pkt){ + uint32_t seq=WritePacketHeader(pkt, qp->type, qp->length); + memmove(&qp->seqs[1], qp->seqs, 4*9); + qp->seqs[0]=seq; + qp->lastSentTime=GetCurrentTime(); + LOGD("Sending queued packet, seq=%u, type=%u, len=%u", seq, qp->type, qp->length); + if(qp->firstSentTime==0) + qp->firstSentTime=qp->lastSentTime; + if(qp->length) + pkt->WriteBytes(qp->data, qp->length); + sendQueue->Put(pkt); + } + } + } + unlock_mutex(queuedPacketsMutex); + + if(jitterBuffer) + jitterBuffer->Tick(); + + if(state==STATE_ESTABLISHED){ + voip_endpoint_t* minPingRelay=preferredRelay; + double minPing=preferredRelay->_averageRtt; + for(i=0;i_lastPingTime>=10){ + LOGV("Sending ping to %s", inet_ntoa(e->address)); + CBufferOutputStream pkt(32); + uint32_t seq=WritePacketHeader(&pkt, PKT_PING, 0); + e->_lastPingTime=GetCurrentTime(); + e->_lastPingSeq=seq; + SendPacket(pkt.GetBuffer(), pkt.GetLength(), e); + } + if(e->type==EP_TYPE_UDP_RELAY){ + if(e->_averageRtt>0 && e->_averageRtt_averageRtt; + minPingRelay=e; + } + } + } + if(minPingRelay!=preferredRelay){ + preferredRelay=minPingRelay; + if(currentEndpoint->type==EP_TYPE_UDP_RELAY) + currentEndpoint=preferredRelay; + /*CBufferOutputStream pkt(32); + pkt.WriteInt64(preferredRelay->id); + SendPacketReliably(PKT_SWITCH_PREF_RELAY, pkt.GetBuffer(), pkt.GetLength(), 1, 9);*/ + } + if(currentEndpoint->type==EP_TYPE_UDP_RELAY){ + voip_endpoint_t *p2p=GetEndpointByType(EP_TYPE_UDP_P2P_INET); + if(p2p){ + voip_endpoint_t *lan=GetEndpointByType(EP_TYPE_UDP_P2P_LAN); + if(lan && lan->_averageRtt>0){ + SendPacketReliably(PKT_SWITCH_TO_P2P, NULL, 0, 1, 5); + currentEndpoint=lan; + LOGI("Switching to p2p (LAN)"); + }else{ + if(p2p->_averageRtt>0 && p2p->_averageRtt0 && minPing_averageRtt*.6){ + LOGI("Switching to relay"); + currentEndpoint=preferredRelay; + } + } + } + + if(state==STATE_ESTABLISHED){ + if(GetCurrentTime()-lastRecvPacketTime>=config.recv_timeout){ + if(currentEndpoint && currentEndpoint->type!=EP_TYPE_UDP_RELAY){ + LOGW("Packet receive timeout, switching to relay"); + currentEndpoint=GetEndpointByType(EP_TYPE_UDP_RELAY); + if(allowP2p){ + SendPublicEndpointsRequest(); + } + UpdateDataSavingState(); + UpdateAudioBitrate(); + CBufferOutputStream s(4); + s.WriteInt32(dataSavingMode ? INIT_FLAG_DATA_SAVING_ENABLED : 0); + SendPacketReliably(PKT_NETWORK_CHANGED, s.GetBuffer(), s.GetLength(), 1, 20); + lastRecvPacketTime=GetCurrentTime(); + }else{ + LOGW("Packet receive timeout, disconnecting"); + lastError=ERROR_TIMEOUT; + SetState(STATE_FAILED); + } + } + }else if(state==STATE_WAIT_INIT){ + if(GetCurrentTime()-connectionInitTime>=config.init_timeout){ + LOGW("Init timeout, disconnecting"); + lastError=ERROR_TIMEOUT; + SetState(STATE_FAILED); + } + } + } + LOGI("=== tick thread exiting ==="); +} + + +voip_endpoint_t *CVoIPController::GetRemoteEndpoint(){ + //return useLan ? &remoteLanEp : &remotePublicEp; + return currentEndpoint; +} + + +void CVoIPController::SendPacket(char *data, size_t len, voip_endpoint_t* ep){ + if(stopping) + return; + sockaddr_in dst; + dst.sin_addr=ep->address; + dst.sin_port=htons(ep->port); + dst.sin_family=AF_INET; + CBufferOutputStream out(len+128); + if(ep->type==EP_TYPE_UDP_RELAY) + out.WriteBytes(ep->peerTag, 16); + else + out.WriteBytes(callID, 16); + if(len>0){ + CBufferOutputStream inner(len+128); + inner.WriteInt32(len); + inner.WriteBytes(data, len); + if(inner.GetLength()%16!=0){ + size_t padLen=16-inner.GetLength()%16; + char padding[padLen]; + crypto.rand_bytes((uint8_t *) padding, padLen); + inner.WriteBytes(padding, padLen); + } + assert(inner.GetLength()%16==0); + unsigned char key[32], iv[32], msgHash[SHA1_LENGTH]; + crypto.sha1((uint8_t *) inner.GetBuffer(), len+4, msgHash); + out.WriteBytes(keyFingerprint, 8); + out.WriteBytes((char *) (msgHash+(SHA1_LENGTH-16)), 16); + KDF(msgHash+(SHA1_LENGTH-16), key, iv); + unsigned char aesOut[inner.GetLength()]; + crypto.aes_ige_encrypt((unsigned char *) inner.GetBuffer(), aesOut, inner.GetLength(), key, iv); + out.WriteBytes((char*)aesOut, inner.GetLength()); + } + //LOGV("Sending %d bytes to %s:%d", out.GetLength(), inet_ntoa(ep->address), ep->port); + if(IS_MOBILE_NETWORK(networkType)) + stats.bytesSentMobile+=(uint64_t)out.GetLength(); + else + stats.bytesSentWifi+=(uint64_t)out.GetLength(); + int res=sendto(udpSocket, out.GetBuffer(), out.GetLength(), 0, (const sockaddr *) &dst, sizeof(sockaddr_in)); + if(res<0){ + LOGE("error sending: %d / %s", errno, strerror(errno)); + } +} + + +void CVoIPController::SetNetworkType(int type){ + networkType=type; + UpdateDataSavingState(); + UpdateAudioBitrate(); + char itfName[32]; + GetLocalNetworkItfInfo(NULL, itfName); + if(strcmp(itfName, activeNetItfName)!=0){ + LOGI("Active network interface changed: %s -> %s", activeNetItfName, itfName); + bool isFirstChange=strlen(activeNetItfName)==0; + strcpy(activeNetItfName, itfName); + if(isFirstChange) + return; + if(currentEndpoint && currentEndpoint->type!=EP_TYPE_UDP_RELAY){ + int i; + currentEndpoint=preferredRelay; + for(i=0;itype==EP_TYPE_UDP_P2P_LAN){ + free(endpoints[i]); + endpoints.erase(endpoints.begin()+i); + break; + } + } + } + if(allowP2p && currentEndpoint){ + SendPublicEndpointsRequest(); + } + CBufferOutputStream s(4); + s.WriteInt32(dataSavingMode ? INIT_FLAG_DATA_SAVING_ENABLED : 0); + SendPacketReliably(PKT_NETWORK_CHANGED, s.GetBuffer(), s.GetLength(), 1, 20); + } + LOGI("set network type: %d, active interface %s", type, activeNetItfName); + /*if(type==NET_TYPE_GPRS || type==NET_TYPE_EDGE) + audioPacketGrouping=2; + else + audioPacketGrouping=1;*/ +} + + +double CVoIPController::GetAverageRTT(){ + if(lastSentSeq>=lastRemoteAckSeq){ + uint32_t diff=lastSentSeq-lastRemoteAckSeq; + //LOGV("rtt diff=%u", diff); + if(diff<32){ + int i; + double res=0; + int count=0; + for(i=diff;i<32;i++){ + if(remoteAcks[i-diff]>0){ + res+=(remoteAcks[i-diff]-sentPacketTimes[i]); + count++; + } + } + if(count>0) + res/=count; + return res; + } + } + return 999; +} + +double CVoIPController::GetCurrentTime(){ +#if defined(__linux__) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec+(double)ts.tv_nsec/1000000000.0; +#elif defined(__APPLE__) + if(!machTimestart){ + mach_timebase_info_data_t tb = { 0, 0 }; + mach_timebase_info(&tb); + machTimebase = tb.numer; + machTimebase /= tb.denom; + machTimestart = mach_absolute_time(); + } + return (mach_absolute_time() - machTimestart) * machTimebase / 1000000000.0f; +#endif +} + +void CVoIPController::SetStateCallback(void (* f)(CVoIPController*, int)){ + stateCallback=f; + if(stateCallback){ + stateCallback(this, state); + } +} + + +void CVoIPController::SetState(int state){ + this->state=state; + stateChangeTime=GetCurrentTime(); + if(stateCallback){ + stateCallback(this, state); + } +} + + +void CVoIPController::SetMicMute(bool mute){ + micMuted=mute; + if(audioInput){ + if(mute) + audioInput->Stop(); + else + audioInput->Start(); + } + if(echoCanceller) + echoCanceller->Enable(!mute); + int i; + for(i=0;itype==STREAM_TYPE_AUDIO){ + char buf[2]; + buf[0]=outgoingStreams[i]->id; + buf[1]=(char) (mute ? 0 : 1); + SendPacketReliably(PKT_STREAM_STATE, buf, 2, .5f, 20); + outgoingStreams[i]->enabled=!mute; + } + } +} + + +void CVoIPController::UpdateAudioOutputState(){ + bool areAnyAudioStreamsEnabled=false; + int i; + for(i=0;itype==STREAM_TYPE_AUDIO && incomingStreams[i]->enabled) + areAnyAudioStreamsEnabled=true; + } + if(jitterBuffer){ + jitterBuffer->Reset(); + } + if(decoder){ + decoder->ResetQueue(); + } + if(audioOutput){ + if(audioOutput->IsPlaying()!=areAnyAudioStreamsEnabled){ + if(areAnyAudioStreamsEnabled) + audioOutput->Start(); + else + audioOutput->Stop(); + } + } +} + + +CBufferOutputStream *CVoIPController::GetOutgoingPacketBuffer(){ + CBufferOutputStream* pkt=NULL; + lock_mutex(sendBufferMutex); + if(emptySendBuffers.size()>0){ + pkt=emptySendBuffers[emptySendBuffers.size()-1]; + emptySendBuffers.pop_back(); + } + unlock_mutex(sendBufferMutex); + return pkt; +} + + +void CVoIPController::KDF(unsigned char* msgKey, unsigned char* aesKey, unsigned char* aesIv){ + uint8_t sA[SHA1_LENGTH], sB[SHA1_LENGTH], sC[SHA1_LENGTH], sD[SHA1_LENGTH]; + size_t x=0; + CBufferOutputStream buf(128); + buf.WriteBytes((char *) msgKey, 16); + buf.WriteBytes(encryptionKey+x, 32); + crypto.sha1((uint8_t *) buf.GetBuffer(), buf.GetLength(), sA); + buf.Reset(); + buf.WriteBytes(encryptionKey+32+x, 16); + buf.WriteBytes((char *) msgKey, 16); + buf.WriteBytes(encryptionKey+48+x, 16); + crypto.sha1((uint8_t *) buf.GetBuffer(), buf.GetLength(), sB); + buf.Reset(); + buf.WriteBytes(encryptionKey+64+x, 32); + buf.WriteBytes((char *) msgKey, 16); + crypto.sha1((uint8_t *) buf.GetBuffer(), buf.GetLength(), sC); + buf.Reset(); + buf.WriteBytes((char *) msgKey, 16); + buf.WriteBytes(encryptionKey+96+x, 32); + crypto.sha1(( uint8_t *) buf.GetBuffer(), buf.GetLength(), sD); + buf.Reset(); + buf.WriteBytes((char *) sA, 8); + buf.WriteBytes((char *) sB+8, 12); + buf.WriteBytes((char *) sC+4, 12); + assert(buf.GetLength()==32); + memcpy(aesKey, buf.GetBuffer(), 32); + buf.Reset(); + buf.WriteBytes((char *) sA+8, 12); + buf.WriteBytes((char *) sB, 8); + buf.WriteBytes((char *) sC+16, 4); + buf.WriteBytes((char *) sD, 8); + assert(buf.GetLength()==32); + memcpy(aesIv, buf.GetBuffer(), 32); +} + +void CVoIPController::GetDebugString(char *buffer, size_t len){ + char endpointsBuf[10240]; + memset(endpointsBuf, 0, 10240); + int i; + for(i=0;itype){ + case EP_TYPE_UDP_P2P_INET: + type="UDP_P2P_INET"; + break; + case EP_TYPE_UDP_P2P_LAN: + type="UDP_P2P_LAN"; + break; + case EP_TYPE_UDP_RELAY: + type="UDP_RELAY"; + break; + case EP_TYPE_TCP_RELAY: + type="TCP_RELAY"; + break; + default: + type="UNKNOWN"; + break; + } + if(strlen(endpointsBuf)>10240-1024) + break; + sprintf(endpointsBuf+strlen(endpointsBuf), "%s:%u %dms [%s%s]\n", inet_ntoa(endpoints[i]->address), endpoints[i]->port, (int)(endpoints[i]->_averageRtt*1000), type, currentEndpoint==endpoints[i] ? ", IN_USE" : ""); + } + double avgLate[3]; + if(jitterBuffer) + jitterBuffer->GetAverageLateCount(avgLate); + else + memset(avgLate, 0, 3*sizeof(double)); + snprintf(buffer, len, + "Remote endpoints: \n%s" + "Jitter buffer: %d/%d | %.1f, %.1f, %.1f\n" + "RTT avg/min: %d/%d\n" + "Congestion window: %d/%d bytes\n" + "Key fingerprint: %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX\n" + "Last sent/ack'd seq: %u/%u\n" + "Last recvd seq: %u\n" + "Send/recv losses: %u/%u\n" + "Audio bitrate: %d kbit\n" +// "Packet grouping: %d\n" + "Frame size out/in: %d/%d\n" + "Bytes sent/recvd: %llu/%llu", + endpointsBuf, + jitterBuffer ? jitterBuffer->GetMinPacketCount() : 0, jitterBuffer ? jitterBuffer->GetCurrentDelay() : 0, avgLate[0], avgLate[1], avgLate[2], + // (int)(GetAverageRTT()*1000), 0, + (int)(conctl->GetAverageRTT()*1000), (int)(conctl->GetMinimumRTT()*1000), + conctl->GetInflightDataSize(), conctl->GetCongestionWindow(), + keyFingerprint[0],keyFingerprint[1],keyFingerprint[2],keyFingerprint[3],keyFingerprint[4],keyFingerprint[5],keyFingerprint[6],keyFingerprint[7], + lastSentSeq, lastRemoteAckSeq, lastRemoteSeq, + conctl->GetSendLossCount(), recvLossCount, + encoder ? (encoder->GetBitrate()/1000) : 0, +// audioPacketGrouping, + outgoingStreams[0]->frameDuration, incomingStreams.size()>0 ? incomingStreams[0]->frameDuration : 0, + stats.bytesSentMobile+stats.bytesSentWifi, stats.bytesRecvdMobile+stats.bytesRecvdWifi); +} + + +void CVoIPController::SendPublicEndpointsRequest(){ + LOGI("Sending public endpoints request"); + voip_endpoint_t* relay=GetEndpointByType(EP_TYPE_UDP_RELAY); + if(!relay) + return; + publicEndpointsReqTime=GetCurrentTime(); + waitingForRelayPeerInfo=true; + char buf[32]; + memcpy(buf, relay->peerTag, 16); + memset(buf+16, 0xFF, 16); + sockaddr_in dst; + dst.sin_addr=relay->address; + dst.sin_port=htons(relay->port); + dst.sin_family=AF_INET; + int res=sendto(udpSocket, buf, 32, 0, (const sockaddr *) &dst, sizeof(sockaddr_in)); + if(res<0){ + LOGE("error sending: %d / %s", errno, strerror(errno)); + } +} + + +void CVoIPController::SendP2pPing(int endpointType){ + LOGD("Sending ping for p2p, endpoint type %d", endpointType); + voip_endpoint_t* endpoint=GetEndpointByType(endpointType); + if(!endpoint) + return; + lastP2pPingTime=GetCurrentTime(); + CBufferOutputStream pkt(32); + uint32_t seq=WritePacketHeader(&pkt, PKT_PING, 0); + SendPacket(pkt.GetBuffer(), pkt.GetLength(), endpoint); +} + + +void CVoIPController::GetLocalNetworkItfInfo(in_addr *outAddr, char *outName){ + struct ifconf ifc; + struct ifreq* ifr; + char buf[16384]; + int sd; + sd=socket(PF_INET, SOCK_DGRAM, 0); + if(sd>0){ + ifc.ifc_len=sizeof(buf); + ifc.ifc_ifcu.ifcu_buf=buf; + if(ioctl(sd, SIOCGIFCONF, &ifc)==0){ + ifr=ifc.ifc_req; + int len; + int i; + for(i=0;iifr_addr.sa_len; +#else + len=sizeof(*ifr); +#endif + if(ifr->ifr_addr.sa_family==AF_INET){ + if(ioctl(sd, SIOCGIFADDR, ifr)==0){ + struct sockaddr_in* addr=(struct sockaddr_in *)(&ifr->ifr_addr); + LOGI("Interface %s, address %s\n", ifr->ifr_name, inet_ntoa(addr->sin_addr)); + if(strcmp(ifr->ifr_name, "lo0")!=0 && strcmp(ifr->ifr_name, "lo")!=0 && addr->sin_addr.s_addr!=inet_addr("127.0.0.1")){ + if(outAddr) + memcpy(outAddr, &addr->sin_addr, sizeof(in_addr)); + if(outName) + strcpy(outName, ifr->ifr_name); + } + }else{ + LOGE("Error getting address for %s: %d\n", ifr->ifr_name, errno); + } + } + ifr=(struct ifreq*)((char*)ifr+len); + i+=len; + } + }else{ + LOGE("Error getting LAN address: %d", errno); + } + } + close(sd); +} + + +voip_endpoint_t *CVoIPController::GetEndpointByType(int type){ + if(type==EP_TYPE_UDP_RELAY && preferredRelay) + return preferredRelay; + int i; + for(i=0;itype==type) + return endpoints[i]; + } + return NULL; +} + + +float CVoIPController::GetOutputLevel(){ + if(!audioOutput || !audioOutStarted){ + return 0.0; + } + return audioOutput->GetLevel(); +} + + +char * CVoIPController::SendPacketReliably(unsigned char type, char *data, size_t len, double retryInterval, double timeout){ + LOGD("Send reliably, type=%u, len=%u, retry=%.3llf, timeout=%.3llf", type, len, retryInterval, timeout); + voip_queued_packet_t* pkt=(voip_queued_packet_t *) malloc(sizeof(voip_queued_packet_t)); + memset(pkt, 0, sizeof(voip_queued_packet_t)); + pkt->type=type; + if(data){ + pkt->data=(char *) malloc(len); + memcpy(pkt->data, data, len); + pkt->length=len; + } + pkt->retryInterval=retryInterval; + pkt->timeout=timeout; + pkt->firstSentTime=0; + pkt->lastSentTime=0; + lock_mutex(queuedPacketsMutex); + queuedPackets.push_back(pkt); + unlock_mutex(queuedPacketsMutex); +} + + +void CVoIPController::SetConfig(voip_config_t *cfg){ + memcpy(&config, cfg, sizeof(voip_config_t)); +#ifdef __ANDROID__ + enableAEC=cfg->enableAEC; +#endif + UpdateDataSavingState(); + UpdateAudioBitrate(); + if(cfg->frame_size) + outgoingStreams[0]->frameDuration=cfg->frame_size; +} + + +void CVoIPController::UpdateDataSavingState(){ + if(config.data_saving==DATA_SAVING_ALWAYS){ + dataSavingMode=true; + }else if(config.data_saving==DATA_SAVING_MOBILE){ + dataSavingMode=networkType==NET_TYPE_GPRS || networkType==NET_TYPE_EDGE || + networkType==NET_TYPE_3G || networkType==NET_TYPE_HSPA || networkType==NET_TYPE_LTE || networkType==NET_TYPE_OTHER_MOBILE; + }else{ + dataSavingMode=false; + } + LOGI("update data saving mode, config %d, enabled %d, reqd by peer %d", config.data_saving, dataSavingMode, dataSavingRequestedByPeer); +} + + +void CVoIPController::DebugCtl(int request, int param){ + if(request==1){ // set bitrate + maxBitrate=param; + if(encoder){ + encoder->SetBitrate(maxBitrate); + } + }else if(request==2){ // set packet loss + if(encoder){ + encoder->SetPacketLoss(param); + } + }else if(request==3){ // force enable/disable p2p + allowP2p=param==1; + if(!allowP2p && currentEndpoint && currentEndpoint->type!=EP_TYPE_UDP_RELAY){ + currentEndpoint=preferredRelay; + needSendP2pPing=false; + }else if(allowP2p){ + SendPublicEndpointsRequest(); + } + CBufferOutputStream s(4); + s.WriteInt32(dataSavingMode ? INIT_FLAG_DATA_SAVING_ENABLED : 0); + SendPacketReliably(PKT_NETWORK_CHANGED, s.GetBuffer(), s.GetLength(), 1, 20); + }else if(request==4){ + if(echoCanceller) + echoCanceller->Enable(param==1); + } +} + + +char* CVoIPController::GetVersion(){ + return LIBTGVOIP_VERSION; +} + + +int64_t CVoIPController::GetPreferredRelayID(){ + if(preferredRelay) + return preferredRelay->id; + return 0; +} + + +int CVoIPController::GetLastError(){ + return lastError; +} + + +void CVoIPController::GetStats(voip_stats_t *stats){ + memcpy(stats, &this->stats, sizeof(voip_stats_t)); +} + diff --git a/VoIPController.h b/VoIPController.h new file mode 100644 index 0000000000..3cddaeaceb --- /dev/null +++ b/VoIPController.h @@ -0,0 +1,295 @@ +#ifndef __VOIPCONTROLLER_H +#define __VOIPCONTROLLER_H + +#include +#include +#include +#include +#include "audio/AudioInput.h" +#include "BlockingQueue.h" +#include "BufferOutputStream.h" +#include "audio/AudioOutput.h" +#include "JitterBuffer.h" +#include "OpusDecoder.h" +#include "OpusEncoder.h" +#include "EchoCanceller.h" +#include "CongestionControl.h" + +#define LIBTGVOIP_VERSION "0.2.4" + +#define PKT_INIT 1 +#define PKT_INIT_ACK 2 +#define PKT_STREAM_STATE 3 +#define PKT_STREAM_DATA 4 +#define PKT_UPDATE_STREAMS 5 +#define PKT_PING 6 +#define PKT_PONG 7 +#define PKT_STREAM_DATA_X2 8 +#define PKT_STREAM_DATA_X3 9 +#define PKT_LAN_ENDPOINT 10 +#define PKT_NETWORK_CHANGED 11 +#define PKT_SWITCH_PREF_RELAY 12 +#define PKT_SWITCH_TO_P2P 13 +#define PKT_NOP 14 + +#define STATE_WAIT_INIT 1 +#define STATE_WAIT_INIT_ACK 2 +#define STATE_ESTABLISHED 3 +#define STATE_FAILED 4 + +#define ERROR_UNKNOWN 0 +#define ERROR_INCOMPATIBLE 1 +#define ERROR_TIMEOUT 2 + +#define NET_TYPE_UNKNOWN 0 +#define NET_TYPE_GPRS 1 +#define NET_TYPE_EDGE 2 +#define NET_TYPE_3G 3 +#define NET_TYPE_HSPA 4 +#define NET_TYPE_LTE 5 +#define NET_TYPE_WIFI 6 +#define NET_TYPE_ETHERNET 7 +#define NET_TYPE_OTHER_HIGH_SPEED 8 +#define NET_TYPE_OTHER_LOW_SPEED 9 +#define NET_TYPE_DIALUP 10 +#define NET_TYPE_OTHER_MOBILE 11 + +#define IS_MOBILE_NETWORK(x) (x==NET_TYPE_GPRS || x==NET_TYPE_EDGE || x==NET_TYPE_3G || x==NET_TYPE_HSPA || x==NET_TYPE_LTE || x==NET_TYPE_OTHER_MOBILE) + +#define PROTOCOL_NAME 0x50567247 // "GrVP" in little endian (reversed here) +#define PROTOCOL_VERSION 3 +#define MIN_PROTOCOL_VERSION 3 + +#define STREAM_DATA_FLAG_LEN16 0x40 +#define STREAM_DATA_FLAG_HAS_MORE_FLAGS 0x80 + +#define STREAM_TYPE_AUDIO 1 +#define STREAM_TYPE_VIDEO 2 + +#define CODEC_OPUS 1 + +#define EP_TYPE_UDP_P2P_INET 1 +#define EP_TYPE_UDP_P2P_LAN 2 +#define EP_TYPE_UDP_RELAY 3 +#define EP_TYPE_TCP_RELAY 4 + +/*flags:# voice_call_id:flags.2?int128 in_seq_no:flags.4?int out_seq_no:flags.4?int + * recent_received_mask:flags.5?int proto:flags.3?int extra:flags.1?string raw_data:flags.0?string*/ +#define PFLAG_HAS_DATA 1 +#define PFLAG_HAS_EXTRA 2 +#define PFLAG_HAS_CALL_ID 4 +#define PFLAG_HAS_PROTO 8 +#define PFLAG_HAS_SEQ 16 +#define PFLAG_HAS_RECENT_RECV 32 + +#define INIT_FLAG_DATA_SAVING_ENABLED 1 + +#define DATA_SAVING_NEVER 0 +#define DATA_SAVING_MOBILE 1 +#define DATA_SAVING_ALWAYS 2 + +#define TLID_DECRYPTED_AUDIO_BLOCK 0xDBF948C1 +#define TLID_SIMPLE_AUDIO_BLOCK 0xCC0D0E76 +#define TLID_UDP_REFLECTOR_PEER_INFO 0x27D9371C + +struct voip_endpoint_t{ // make this a class maybe? + int64_t id; + uint32_t port; + in_addr address; + in6_addr address6; + char type; + char peerTag[16]; + + double _lastPingTime; + uint32_t _lastPingSeq; + double _rtts[6]; + double _averageRtt; +}; +typedef struct voip_endpoint_t voip_endpoint_t; + +struct voip_stream_t{ + int32_t userID; + unsigned char id; + unsigned char type; + unsigned char codec; + bool enabled; + uint16_t frameDuration; +}; +typedef struct voip_stream_t voip_stream_t; + +struct voip_queued_packet_t{ + unsigned char type; + char* data; + size_t length; + uint32_t seqs[16]; + double firstSentTime; + double lastSentTime; + double retryInterval; + double timeout; +}; +typedef struct voip_queued_packet_t voip_queued_packet_t; + +struct voip_config_t{ + double init_timeout; + double recv_timeout; + int data_saving; + int frame_size; +#ifdef __ANDROID__ + bool enableAEC; +#endif +}; +typedef struct voip_config_t voip_config_t; + +struct voip_stats_t{ + uint64_t bytesSentWifi; + uint64_t bytesRecvdWifi; + uint64_t bytesSentMobile; + uint64_t bytesRecvdMobile; +}; +typedef struct voip_stats_t voip_stats_t; + +struct voip_crypto_functions_t{ + 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); +}; +typedef struct voip_crypto_functions_t voip_crypto_functions_t; + +#define SEQ_MAX 0xFFFFFFFF + +inline bool seqgt(uint32_t s1, uint32_t s2){ + return ((s1>s2) && (s1-s2<=SEQ_MAX/2)) || ((s1SEQ_MAX/2)); +} + +class CVoIPController +{ +public: + CVoIPController(); + ~CVoIPController(); + + void SetRemoteEndpoints(voip_endpoint_t* endpoints, size_t count, bool allowP2p); + void Start(); + void Connect(); + voip_endpoint_t* GetRemoteEndpoint(); + void GetDebugString(char* buffer, size_t len); + void SetNetworkType(int type); + double GetAverageRTT(); + void SetStateCallback(void (*f)(CVoIPController*, int)); + static double GetCurrentTime(); + void* implData; + void SetMicMute(bool mute); + void SetEncryptionKey(char* key); + void SetConfig(voip_config_t* cfg); + float GetOutputLevel(); + void DebugCtl(int request, int param); + void GetStats(voip_stats_t* stats); + int64_t GetPreferredRelayID(); + int GetLastError(); + static voip_crypto_functions_t crypto; + static char* GetVersion(); + +private: + static void* StartRecvThread(void* arg); + static void* StartSendThread(void* arg); + static void* StartTickThread(void* arg); + void RunRecvThread(); + void RunSendThread(); + void RunTickThread(); + void SendPacket(char* data, size_t len, voip_endpoint_t* ep); + void HandleAudioInput(unsigned char* data, size_t len); + void UpdateAudioBitrate(); + void SetState(int state); + void UpdateAudioOutputState(); + void SendInit(); + void SendInitAck(); + void UpdateDataSavingState(); + void SendP2pPing(int endpointType); + void KDF(unsigned char* msgKey, unsigned char* aesKey, unsigned char* aesIv); + void GetLocalNetworkItfInfo(in_addr *addr, char *outName); + CBufferOutputStream* GetOutgoingPacketBuffer(); + uint32_t WritePacketHeader(CBufferOutputStream* s, unsigned char type, uint32_t length); + static size_t AudioInputCallback(unsigned char* data, size_t length, void* param); + void SendPublicEndpointsRequest(); + voip_endpoint_t* GetEndpointByType(int type); + char * SendPacketReliably(unsigned char type, char* data, size_t len, double retryInterval, double timeout); + int state; + int udpSocket; + std::vector endpoints; + voip_endpoint_t* currentEndpoint; + voip_endpoint_t* preferredRelay; + bool runReceiver; + uint32_t seq; + uint32_t lastRemoteSeq; + uint32_t lastRemoteAckSeq; + uint32_t lastSentSeq; + double remoteAcks[32]; + double sentPacketTimes[32]; + double recvPacketTimes[32]; + uint32_t sendLossCountHistory[32]; + uint32_t audioTimestampIn; + uint32_t audioTimestampOut; + CAudioInput* audioInput; + CAudioOutput* audioOutput; + CJitterBuffer* jitterBuffer; + COpusDecoder* decoder; + COpusEncoder* encoder; + CBlockingQueue* sendQueue; + CEchoCanceller* echoCanceller; + std::vector emptySendBuffers; + tgvoip_mutex_t sendBufferMutex; + bool stopping; + bool audioOutStarted; + tgvoip_thread_t recvThread; + tgvoip_thread_t sendThread; + tgvoip_thread_t tickThread; + uint32_t packetsRecieved; + uint32_t recvLossCount; + uint32_t prevSendLossCount; + uint32_t firstSentPing; + double rttHistory[32]; + bool waitingForAcks; + int networkType; + int audioPacketGrouping; + int audioPacketsWritten; + int dontSendPackets; + int lastError; + bool micMuted; + uint32_t maxBitrate; + CBufferOutputStream* currentAudioPacket; + void (*stateCallback)(CVoIPController*, int); + std::vector outgoingStreams; + std::vector incomingStreams; + char encryptionKey[256]; + char keyFingerprint[8]; + char callID[16]; + double stateChangeTime; + bool needSendP2pPing; + bool waitingForRelayPeerInfo; + double relayPeerInfoReqTime; + double lastP2pPingTime; + int p2pPingCount; + uint16_t localUdpPort; + bool allowP2p; + bool dataSavingMode; + bool dataSavingRequestedByPeer; + char activeNetItfName[32]; + double publicEndpointsReqTime; + std::vector queuedPackets; + tgvoip_mutex_t queuedPacketsMutex; + double connectionInitTime; + double lastRecvPacketTime; + voip_config_t config; + int32_t peerVersion; + CCongestionControl* conctl; + voip_stats_t stats; + bool enableAEC; + +#ifdef __APPLE__ + static double machTimebase; + static uint64_t machTimestart; +#endif +}; + +#endif diff --git a/audio/AudioInput.cpp b/audio/AudioInput.cpp new file mode 100644 index 0000000000..6e9c356926 --- /dev/null +++ b/audio/AudioInput.cpp @@ -0,0 +1,31 @@ +// +// 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. +// + +#include "AudioInput.h" +#if defined(__ANDROID__) +#include "../os/android/AudioInputAndroid.h" +#elif defined(__APPLE__) +#include "../os/darwin/AudioInputAudioUnit.h" +#include "../os/darwin/AudioUnitIO.h" +#else +#error "Unsupported operating system" +#endif + +CAudioInput *CAudioInput::Create(){ +#if defined(__ANDROID__) + return new CAudioInputAndroid(); +#elif defined(__APPLE__) + return new CAudioInputAudioUnit(CAudioUnitIO::Get()); +#endif +} + + +CAudioInput::~CAudioInput(){ +#if defined(__APPLE__) + CAudioUnitIO::Release(); +#endif +} + diff --git a/audio/AudioInput.h b/audio/AudioInput.h new file mode 100644 index 0000000000..8d91b971f0 --- /dev/null +++ b/audio/AudioInput.h @@ -0,0 +1,22 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOINPUT_H +#define LIBTGVOIP_AUDIOINPUT_H + +#include +#include "../MediaStreamItf.h" + +class CAudioInput : public CMediaStreamItf{ +public: + virtual ~CAudioInput(); + + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels)=0; + static CAudioInput* Create(); +}; + + +#endif //LIBTGVOIP_AUDIOINPUT_H diff --git a/audio/AudioOutput.cpp b/audio/AudioOutput.cpp new file mode 100644 index 0000000000..84d799beed --- /dev/null +++ b/audio/AudioOutput.cpp @@ -0,0 +1,46 @@ +// +// 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. +// + +#include "AudioOutput.h" +#if defined(__ANDROID__) +#include "../os/android/AudioOutputOpenSLES.h" +#include "../os/android/AudioOutputAndroid.h" +#elif defined(__APPLE__) +#include "../os/darwin/AudioOutputAudioUnit.h" +#include "../os/darwin/AudioUnitIO.h" +#else +#error "Unsupported operating system" +#endif + +#if defined(__ANDROID__) +int CAudioOutput::systemVersion; +#endif + +CAudioOutput *CAudioOutput::Create(){ +#if defined(__ANDROID__) + if(systemVersion<21) + return new CAudioOutputAndroid(); + return new CAudioOutputOpenSLES(); +#elif defined(__APPLE__) + return new CAudioOutputAudioUnit(CAudioUnitIO::Get()); +#endif +} + + +CAudioOutput::~CAudioOutput(){ +#if defined(__APPLE__) + CAudioUnitIO::Release(); +#endif +} + + +int32_t CAudioOutput::GetEstimatedDelay(){ +#if defined(__ANDROID__) + return systemVersion<21 ? 150 : 50; +#endif + return 0; +} + diff --git a/audio/AudioOutput.h b/audio/AudioOutput.h new file mode 100644 index 0000000000..0d07bfb979 --- /dev/null +++ b/audio/AudioOutput.h @@ -0,0 +1,28 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOOUTPUT_H +#define LIBTGVOIP_AUDIOOUTPUT_H + +#include +#include "../MediaStreamItf.h" + +class CAudioOutput : public CMediaStreamItf{ +public: + virtual ~CAudioOutput(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels)=0; + virtual bool IsPlaying()=0; + virtual float GetLevel()=0; + static CAudioOutput* Create(); + static int32_t GetEstimatedDelay(); + +#if defined(__ANDROID__) + static int systemVersion; +#endif +}; + + +#endif //LIBTGVOIP_AUDIOOUTPUT_H diff --git a/client/android/tg_voip_jni.cpp b/client/android/tg_voip_jni.cpp new file mode 100644 index 0000000000..55a810c521 --- /dev/null +++ b/client/android/tg_voip_jni.cpp @@ -0,0 +1,208 @@ +// +// 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. +// + +#include +#include +#include +#include "../../VoIPController.h" +#include "../../os/android/AudioOutputOpenSLES.h" +#include "../../os/android/AudioInputOpenSLES.h" +#include "../../os/android/AudioInputAndroid.h" +#include "../../os/android/AudioOutputAndroid.h" + +JavaVM* sharedJVM; +jfieldID audioRecordInstanceFld=NULL; +jfieldID audioTrackInstanceFld=NULL; +jmethodID setStateMethod=NULL; + +struct impl_data_android_t{ + jobject javaObject; +}; + +void updateConnectionState(CVoIPController* cntrlr, int state){ + impl_data_android_t* impl=(impl_data_android_t*) cntrlr->implData; + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + if(setStateMethod) + env->CallVoidMethod(impl->javaObject, setStateMethod, state); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +extern "C" JNIEXPORT jlong Java_org_telegram_messenger_voip_VoIPController_nativeInit(JNIEnv* env, jobject thiz, jint systemVersion){ + CAudioOutputAndroid::systemVersion=systemVersion; + + env->GetJavaVM(&sharedJVM); + jclass cls=env->FindClass("org/telegram/messenger/voip/AudioRecordJNI"); + CAudioInputAndroid::jniClass=(jclass) env->NewGlobalRef(cls); + CAudioInputAndroid::initMethod=env->GetMethodID(cls, "init", "(IIII)V"); + CAudioInputAndroid::releaseMethod=env->GetMethodID(cls, "release", "()V"); + CAudioInputAndroid::startMethod=env->GetMethodID(cls, "start", "()V"); + CAudioInputAndroid::stopMethod=env->GetMethodID(cls, "stop", "()V"); + + cls=env->FindClass("org/telegram/messenger/voip/AudioTrackJNI"); + CAudioOutputAndroid::jniClass=(jclass) env->NewGlobalRef(cls); + CAudioOutputAndroid::initMethod=env->GetMethodID(cls, "init", "(IIII)V"); + CAudioOutputAndroid::releaseMethod=env->GetMethodID(cls, "release", "()V"); + CAudioOutputAndroid::startMethod=env->GetMethodID(cls, "start", "()V"); + CAudioOutputAndroid::stopMethod=env->GetMethodID(cls, "stop", "()V"); + + setStateMethod=env->GetMethodID(env->GetObjectClass(thiz), "handleStateChange", "(I)V"); + + impl_data_android_t* impl=(impl_data_android_t*) malloc(sizeof(impl_data_android_t)); + impl->javaObject=env->NewGlobalRef(thiz); + CVoIPController* cntrlr=new CVoIPController(); + cntrlr->implData=impl; + cntrlr->SetStateCallback(updateConnectionState); + return (jlong)(intptr_t)cntrlr; +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeStart(JNIEnv* env, jobject thiz, jlong inst){ + ((CVoIPController*)(intptr_t)inst)->Start(); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeConnect(JNIEnv* env, jobject thiz, jlong inst){ + ((CVoIPController*)(intptr_t)inst)->Connect(); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetEncryptionKey(JNIEnv* env, jobject thiz, jlong inst, jbyteArray key){ + jbyte* akey=env->GetByteArrayElements(key, NULL); + ((CVoIPController*)(intptr_t)inst)->SetEncryptionKey((char *) akey); + env->ReleaseByteArrayElements(key, akey, JNI_ABORT); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetRemoteEndpoints(JNIEnv* env, jobject thiz, jlong inst, jobjectArray endpoints, jboolean allowP2p){ + size_t len=(size_t) env->GetArrayLength(endpoints); + voip_endpoint_t* eps=(voip_endpoint_t *) malloc(sizeof(voip_endpoint_t)*len); + /*public String ip; + public String ipv6; + public int port; + public byte[] peer_tag;*/ + jclass epClass=env->GetObjectClass(env->GetObjectArrayElement(endpoints, 0)); + jfieldID ipFld=env->GetFieldID(epClass, "ip", "Ljava/lang/String;"); + jfieldID ipv6Fld=env->GetFieldID(epClass, "ipv6", "Ljava/lang/String;"); + jfieldID portFld=env->GetFieldID(epClass, "port", "I"); + jfieldID peerTagFld=env->GetFieldID(epClass, "peer_tag", "[B"); + jfieldID idFld=env->GetFieldID(epClass, "id", "J"); + int i; + for(i=0;iGetObjectArrayElement(endpoints, i); + jstring ip=(jstring) env->GetObjectField(endpoint, ipFld); + jstring ipv6=(jstring) env->GetObjectField(endpoint, ipv6Fld); + jint port=env->GetIntField(endpoint, portFld); + jlong id=env->GetLongField(endpoint, idFld); + jbyteArray peerTag=(jbyteArray) env->GetObjectField(endpoint, peerTagFld); + eps[i].id=id; + eps[i].port=(uint32_t) port; + const char* ipChars=env->GetStringUTFChars(ip, NULL); + inet_aton(ipChars, &eps[i].address); + env->ReleaseStringUTFChars(ip, ipChars); + if(ipv6 && env->GetStringLength(ipv6)){ + const char* ipv6Chars=env->GetStringUTFChars(ipv6, NULL); + inet_pton(AF_INET6, ipv6Chars, &eps[i].address6); + env->ReleaseStringUTFChars(ipv6, ipv6Chars); + } + if(peerTag && env->GetArrayLength(peerTag)){ + jbyte* peerTagBytes=env->GetByteArrayElements(peerTag, NULL); + memcpy(eps[i].peerTag, peerTagBytes, 16); + env->ReleaseByteArrayElements(peerTag, peerTagBytes, JNI_ABORT); + } + eps[i].type=EP_TYPE_UDP_RELAY; + } + ((CVoIPController*)(intptr_t)inst)->SetRemoteEndpoints(eps, len, allowP2p); + free(eps); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetNativeBufferSize(JNIEnv* env, jclass thiz, jint size){ + CAudioOutputOpenSLES::nativeBufferSize=size; + CAudioInputOpenSLES::nativeBufferSize=size; +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeRelease(JNIEnv* env, jobject thiz, jlong inst){ + env->DeleteGlobalRef(CAudioInputAndroid::jniClass); + + CVoIPController* ctlr=((CVoIPController*)(intptr_t)inst); + env->DeleteGlobalRef(((impl_data_android_t*)ctlr->implData)->javaObject); + free(ctlr->implData); + delete ctlr; +} + + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_AudioRecordJNI_nativeCallback(JNIEnv* env, jobject thiz, jobject buffer){ + if(!audioRecordInstanceFld) + audioRecordInstanceFld=env->GetFieldID(env->GetObjectClass(thiz), "nativeInst", "J"); + + jlong inst=env->GetLongField(thiz, audioRecordInstanceFld); + CAudioInputAndroid* in=(CAudioInputAndroid*)(intptr_t)inst; + in->HandleCallback(env, buffer); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_AudioTrackJNI_nativeCallback(JNIEnv* env, jobject thiz, jbyteArray buffer){ + if(!audioTrackInstanceFld) + audioTrackInstanceFld=env->GetFieldID(env->GetObjectClass(thiz), "nativeInst", "J"); + + jlong inst=env->GetLongField(thiz, audioTrackInstanceFld); + CAudioOutputAndroid* in=(CAudioOutputAndroid*)(intptr_t)inst; + in->HandleCallback(env, buffer); +} + +extern "C" JNIEXPORT jstring Java_org_telegram_messenger_voip_VoIPController_nativeGetDebugString(JNIEnv* env, jobject thiz, jlong inst){ + char buf[10240]; + ((CVoIPController*)(intptr_t)inst)->GetDebugString(buf, 10240); + return env->NewStringUTF(buf); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetNetworkType(JNIEnv* env, jobject thiz, jlong inst, jint type){ + ((CVoIPController*)(intptr_t)inst)->SetNetworkType(type); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetMicMute(JNIEnv* env, jobject thiz, jlong inst, jboolean mute){ + ((CVoIPController*)(intptr_t)inst)->SetMicMute(mute); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeSetConfig(JNIEnv* env, jobject thiz, jlong inst, jdouble recvTimeout, jdouble initTimeout, jint dataSavingMode, jint frameSize, jboolean enableAEC){ + voip_config_t cfg; + cfg.init_timeout=initTimeout; + cfg.recv_timeout=recvTimeout; + cfg.data_saving=dataSavingMode; + cfg.frame_size=frameSize; + cfg.enableAEC=enableAEC; + ((CVoIPController*)(intptr_t)inst)->SetConfig(&cfg); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeDebugCtl(JNIEnv* env, jobject thiz, jlong inst, jint request, jint param){ + ((CVoIPController*)(intptr_t)inst)->DebugCtl(request, param); +} + +extern "C" JNIEXPORT jstring Java_org_telegram_messenger_voip_VoIPController_nativeGetVersion(JNIEnv* env, jclass clasz){ + return env->NewStringUTF(CVoIPController::GetVersion()); +} + +extern "C" JNIEXPORT jlong Java_org_telegram_messenger_voip_VoIPController_nativeGetPreferredRelayID(JNIEnv* env, jclass clasz, jlong inst){ + return ((CVoIPController*)(intptr_t)inst)->GetPreferredRelayID(); +} + +extern "C" JNIEXPORT jint Java_org_telegram_messenger_voip_VoIPController_nativeGetLastError(JNIEnv* env, jclass clasz, jlong inst){ + return ((CVoIPController*)(intptr_t)inst)->GetLastError(); +} + +extern "C" JNIEXPORT void Java_org_telegram_messenger_voip_VoIPController_nativeGetStats(JNIEnv* env, jclass clasz, jlong inst, jobject stats){ + voip_stats_t _stats; + ((CVoIPController*)(intptr_t)inst)->GetStats(&_stats); + jclass cls=env->GetObjectClass(stats); + env->SetLongField(stats, env->GetFieldID(cls, "bytesSentWifi", "J"), _stats.bytesSentWifi); + env->SetLongField(stats, env->GetFieldID(cls, "bytesSentMobile", "J"), _stats.bytesSentMobile); + env->SetLongField(stats, env->GetFieldID(cls, "bytesRecvdWifi", "J"), _stats.bytesRecvdWifi); + env->SetLongField(stats, env->GetFieldID(cls, "bytesRecvdMobile", "J"), _stats.bytesRecvdMobile); +} \ No newline at end of file diff --git a/external/include/webrtc/echo_cancellation.h b/external/include/webrtc/echo_cancellation.h new file mode 100644 index 0000000000..ab6da4c6ce --- /dev/null +++ b/external/include/webrtc/echo_cancellation.h @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_ + +#include + +#include + +/*extern "C" { +#include "webrtc/common_audio/ring_buffer.h" +} +#include "webrtc/modules/audio_processing/aec/aec_core.h" +#include "webrtc/typedefs.h"*/ + +namespace webrtc { + +// Errors +#define AEC_UNSPECIFIED_ERROR 12000 +#define AEC_UNSUPPORTED_FUNCTION_ERROR 12001 +#define AEC_UNINITIALIZED_ERROR 12002 +#define AEC_NULL_POINTER_ERROR 12003 +#define AEC_BAD_PARAMETER_ERROR 12004 + +// Warnings +#define AEC_BAD_PARAMETER_WARNING 12050 + +enum { kAecNlpConservative = 0, kAecNlpModerate, kAecNlpAggressive }; + +enum { kAecFalse = 0, kAecTrue }; + +typedef struct { + int16_t nlpMode; // default kAecNlpModerate + int16_t skewMode; // default kAecFalse + int16_t metricsMode; // default kAecFalse + int delay_logging; // default kAecFalse + // float realSkew; +} AecConfig; + +typedef struct { + int instant; + int average; + int max; + int min; +} AecLevel; + +typedef struct { + AecLevel rerl; + AecLevel erl; + AecLevel erle; + AecLevel aNlp; + float divergent_filter_fraction; +} AecMetrics; + +struct AecCore; + +class ApmDataDumper; + +/*typedef struct Aec { + Aec(); + ~Aec(); + + std::unique_ptr data_dumper; + + int delayCtr; + int sampFreq; + int splitSampFreq; + int scSampFreq; + float sampFactor; // scSampRate / sampFreq + short skewMode; + int bufSizeStart; + int knownDelay; + int rate_factor; + + short initFlag; // indicates if AEC has been initialized + + // Variables used for averaging far end buffer size + short counter; + int sum; + short firstVal; + short checkBufSizeCtr; + + // Variables used for delay shifts + short msInSndCardBuf; + short filtDelay; // Filtered delay estimate. + int timeForDelayChange; + int startup_phase; + int checkBuffSize; + short lastDelayDiff; + + // Structures + void* resampler; + + int skewFrCtr; + int resample; // if the skew is small enough we don't resample + int highSkewCtr; + float skew; + + RingBuffer* far_pre_buf; // Time domain far-end pre-buffer. + + int farend_started; + + // Aec instance counter. + static int instance_count; + AecCore* aec; +} Aec;*/ + +/* + * Allocates the memory needed by the AEC. The memory needs to be initialized + * separately using the WebRtcAec_Init() function. Returns a pointer to the + * object or NULL on error. + */ +void* WebRtcAec_Create(); + +/* + * This function releases the memory allocated by WebRtcAec_Create(). + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecInst Pointer to the AEC instance + */ +void WebRtcAec_Free(void* aecInst); + +/* + * Initializes an AEC instance. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecInst Pointer to the AEC instance + * int32_t sampFreq Sampling frequency of data + * int32_t scSampFreq Soundcard sampling frequency + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * -1: error + */ +int32_t WebRtcAec_Init(void* aecInst, int32_t sampFreq, int32_t scSampFreq); + +/* + * Inserts an 80 or 160 sample block of data into the farend buffer. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecInst Pointer to the AEC instance + * const float* farend In buffer containing one frame of + * farend signal for L band + * int16_t nrOfSamples Number of samples in farend buffer + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 12000-12050: error code + */ +int32_t WebRtcAec_BufferFarend(void* aecInst, + const float* farend, + size_t nrOfSamples); + +/* + * Reports any errors that would arise if buffering a farend buffer + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecInst Pointer to the AEC instance + * const float* farend In buffer containing one frame of + * farend signal for L band + * int16_t nrOfSamples Number of samples in farend buffer + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 12000-12050: error code + */ +int32_t WebRtcAec_GetBufferFarendError(void* aecInst, + const float* farend, + size_t nrOfSamples); + +/* + * Runs the echo canceller on an 80 or 160 sample blocks of data. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecInst Pointer to the AEC instance + * float* const* nearend In buffer containing one frame of + * nearend+echo signal for each band + * int num_bands Number of bands in nearend buffer + * int16_t nrOfSamples Number of samples in nearend buffer + * int16_t msInSndCardBuf Delay estimate for sound card and + * system buffers + * int16_t skew Difference between number of samples played + * and recorded at the soundcard (for clock skew + * compensation) + * + * Outputs Description + * ------------------------------------------------------------------- + * float* const* out Out buffer, one frame of processed nearend + * for each band + * int32_t return 0: OK + * 12000-12050: error code + */ +int32_t WebRtcAec_Process(void* aecInst, + const float* const* nearend, + size_t num_bands, + float* const* out, + size_t nrOfSamples, + int16_t msInSndCardBuf, + int32_t skew); + +/* + * This function enables the user to set certain parameters on-the-fly. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* handle Pointer to the AEC instance + * AecConfig config Config instance that contains all + * properties to be set + * + * Outputs Description + * ------------------------------------------------------------------- + * int return 0: OK + * 12000-12050: error code + */ +int WebRtcAec_set_config(void* handle, AecConfig config); + +/* + * Gets the current echo status of the nearend signal. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* handle Pointer to the AEC instance + * + * Outputs Description + * ------------------------------------------------------------------- + * int* status 0: Almost certainly nearend single-talk + * 1: Might not be neared single-talk + * int return 0: OK + * 12000-12050: error code + */ +int WebRtcAec_get_echo_status(void* handle, int* status); + +/* + * Gets the current echo metrics for the session. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* handle Pointer to the AEC instance + * + * Outputs Description + * ------------------------------------------------------------------- + * AecMetrics* metrics Struct which will be filled out with the + * current echo metrics. + * int return 0: OK + * 12000-12050: error code + */ +int WebRtcAec_GetMetrics(void* handle, AecMetrics* metrics); + +/* + * Gets the current delay metrics for the session. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* handle Pointer to the AEC instance + * + * Outputs Description + * ------------------------------------------------------------------- + * int* median Delay median value. + * int* std Delay standard deviation. + * float* fraction_poor_delays Fraction of the delay estimates that may + * cause the AEC to perform poorly. + * + * int return 0: OK + * 12000-12050: error code + */ +int WebRtcAec_GetDelayMetrics(void* handle, + int* median, + int* std, + float* fraction_poor_delays); + +// Returns a pointer to the low level AEC handle. +// +// Input: +// - handle : Pointer to the AEC instance. +// +// Return value: +// - AecCore pointer : NULL for error. +// +struct AecCore* WebRtcAec_aec_core(void* handle); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_ diff --git a/external/include/webrtc/echo_control_mobile.h b/external/include/webrtc/echo_control_mobile.h new file mode 100644 index 0000000000..74733d88a5 --- /dev/null +++ b/external/include/webrtc/echo_control_mobile.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AECM_ECHO_CONTROL_MOBILE_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AECM_ECHO_CONTROL_MOBILE_H_ + +#include + +//#include "webrtc/typedefs.h" + +enum { + AecmFalse = 0, + AecmTrue +}; + +// Errors +#define AECM_UNSPECIFIED_ERROR 12000 +#define AECM_UNSUPPORTED_FUNCTION_ERROR 12001 +#define AECM_UNINITIALIZED_ERROR 12002 +#define AECM_NULL_POINTER_ERROR 12003 +#define AECM_BAD_PARAMETER_ERROR 12004 + +// Warnings +#define AECM_BAD_PARAMETER_WARNING 12100 + +typedef struct { + int16_t cngMode; // AECM_FALSE, AECM_TRUE (default) + int16_t echoMode; // 0, 1, 2, 3 (default), 4 +} AecmConfig; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Allocates the memory needed by the AECM. The memory needs to be + * initialized separately using the WebRtcAecm_Init() function. + * Returns a pointer to the instance and a nullptr at failure. + */ +void* WebRtcAecm_Create(); + +/* + * This function releases the memory allocated by WebRtcAecm_Create() + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + */ +void WebRtcAecm_Free(void* aecmInst); + +/* + * Initializes an AECM instance. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * int32_t sampFreq Sampling frequency of data + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_Init(void* aecmInst, int32_t sampFreq); + +/* + * Inserts an 80 or 160 sample block of data into the farend buffer. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * int16_t* farend In buffer containing one frame of + * farend signal + * int16_t nrOfSamples Number of samples in farend buffer + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_BufferFarend(void* aecmInst, + const int16_t* farend, + size_t nrOfSamples); + +/* + * Reports any errors that would arise when buffering a farend buffer. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * int16_t* farend In buffer containing one frame of + * farend signal + * int16_t nrOfSamples Number of samples in farend buffer + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_GetBufferFarendError(void* aecmInst, + const int16_t* farend, + size_t nrOfSamples); + +/* + * Runs the AECM on an 80 or 160 sample blocks of data. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * int16_t* nearendNoisy In buffer containing one frame of + * reference nearend+echo signal. If + * noise reduction is active, provide + * the noisy signal here. + * int16_t* nearendClean In buffer containing one frame of + * nearend+echo signal. If noise + * reduction is active, provide the + * clean signal here. Otherwise pass a + * NULL pointer. + * int16_t nrOfSamples Number of samples in nearend buffer + * int16_t msInSndCardBuf Delay estimate for sound card and + * system buffers + * + * Outputs Description + * ------------------------------------------------------------------- + * int16_t* out Out buffer, one frame of processed nearend + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_Process(void* aecmInst, + const int16_t* nearendNoisy, + const int16_t* nearendClean, + int16_t* out, + size_t nrOfSamples, + int16_t msInSndCardBuf); + +/* + * This function enables the user to set certain parameters on-the-fly + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * AecmConfig config Config instance that contains all + * properties to be set + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_set_config(void* aecmInst, AecmConfig config); + +/* + * This function enables the user to set the echo path on-the-fly. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * void* echo_path Pointer to the echo path to be set + * size_t size_bytes Size in bytes of the echo path + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_InitEchoPath(void* aecmInst, + const void* echo_path, + size_t size_bytes); + +/* + * This function enables the user to get the currently used echo path + * on-the-fly + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * void* echo_path Pointer to echo path + * size_t size_bytes Size in bytes of the echo path + * + * Outputs Description + * ------------------------------------------------------------------- + * int32_t return 0: OK + * 1200-12004,12100: error/warning + */ +int32_t WebRtcAecm_GetEchoPath(void* aecmInst, + void* echo_path, + size_t size_bytes); + +/* + * This function enables the user to get the echo path size in bytes + * + * Outputs Description + * ------------------------------------------------------------------- + * size_t return Size in bytes + */ +size_t WebRtcAecm_echo_path_size_bytes(); + + +#ifdef __cplusplus +} +#endif +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AECM_ECHO_CONTROL_MOBILE_H_ diff --git a/external/include/webrtc/noise_suppression.h b/external/include/webrtc/noise_suppression.h new file mode 100644 index 0000000000..cbd38332ce --- /dev/null +++ b/external/include/webrtc/noise_suppression.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_H_ + +#include + +//#include "webrtc/typedefs.h" + +typedef struct NsHandleT NsHandle; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This function creates an instance of the floating point Noise Suppression. + */ +NsHandle* WebRtcNs_Create(); + +/* + * This function frees the dynamic memory of a specified noise suppression + * instance. + * + * Input: + * - NS_inst : Pointer to NS instance that should be freed + */ +void WebRtcNs_Free(NsHandle* NS_inst); + +/* + * This function initializes a NS instance and has to be called before any other + * processing is made. + * + * Input: + * - NS_inst : Instance that should be initialized + * - fs : sampling frequency + * + * Output: + * - NS_inst : Initialized instance + * + * Return value : 0 - Ok + * -1 - Error + */ +int WebRtcNs_Init(NsHandle* NS_inst, uint32_t fs); + +/* + * This changes the aggressiveness of the noise suppression method. + * + * Input: + * - NS_inst : Noise suppression instance. + * - mode : 0: Mild, 1: Medium , 2: Aggressive + * + * Output: + * - NS_inst : Updated instance. + * + * Return value : 0 - Ok + * -1 - Error + */ +int WebRtcNs_set_policy(NsHandle* NS_inst, int mode); + +/* + * This functions estimates the background noise for the inserted speech frame. + * The input and output signals should always be 10ms (80 or 160 samples). + * + * Input + * - NS_inst : Noise suppression instance. + * - spframe : Pointer to speech frame buffer for L band + * + * Output: + * - NS_inst : Updated NS instance + */ +void WebRtcNs_Analyze(NsHandle* NS_inst, const float* spframe); + +/* + * This functions does Noise Suppression for the inserted speech frame. The + * input and output signals should always be 10ms (80 or 160 samples). + * + * Input + * - NS_inst : Noise suppression instance. + * - spframe : Pointer to speech frame buffer for each band + * - num_bands : Number of bands + * + * Output: + * - NS_inst : Updated NS instance + * - outframe : Pointer to output frame for each band + */ +void WebRtcNs_Process(NsHandle* NS_inst, + const float* const* spframe, + size_t num_bands, + float* const* outframe); + +/* Returns the internally used prior speech probability of the current frame. + * There is a frequency bin based one as well, with which this should not be + * confused. + * + * Input + * - handle : Noise suppression instance. + * + * Return value : Prior speech probability in interval [0.0, 1.0]. + * -1 - NULL pointer or uninitialized instance. + */ +float WebRtcNs_prior_speech_probability(NsHandle* handle); + +/* Returns a pointer to the noise estimate per frequency bin. The number of + * frequency bins can be provided using WebRtcNs_num_freq(). + * + * Input + * - handle : Noise suppression instance. + * + * Return value : Pointer to the noise estimate per frequency bin. + * Returns NULL if the input is a NULL pointer or an + * uninitialized instance. + */ +const float* WebRtcNs_noise_estimate(const NsHandle* handle); + +/* Returns the number of frequency bins, which is the length of the noise + * estimate for example. + * + * Return value : Number of frequency bins. + */ +size_t WebRtcNs_num_freq(); + +#ifdef __cplusplus +} +#endif + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_H_ diff --git a/external/include/webrtc/noise_suppression_x.h b/external/include/webrtc/noise_suppression_x.h new file mode 100644 index 0000000000..ce4c4f2234 --- /dev/null +++ b/external/include/webrtc/noise_suppression_x.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_X_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_X_H_ + +#include + +//#include "webrtc/typedefs.h" + +typedef struct NsxHandleT NsxHandle; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This function creates an instance of the fixed point Noise Suppression. + */ +NsxHandle* WebRtcNsx_Create(); + +/* + * This function frees the dynamic memory of a specified Noise Suppression + * instance. + * + * Input: + * - nsxInst : Pointer to NS instance that should be freed + */ +void WebRtcNsx_Free(NsxHandle* nsxInst); + +/* + * This function initializes a NS instance + * + * Input: + * - nsxInst : Instance that should be initialized + * - fs : sampling frequency + * + * Output: + * - nsxInst : Initialized instance + * + * Return value : 0 - Ok + * -1 - Error + */ +int WebRtcNsx_Init(NsxHandle* nsxInst, uint32_t fs); + +/* + * This changes the aggressiveness of the noise suppression method. + * + * Input: + * - nsxInst : Instance that should be initialized + * - mode : 0: Mild, 1: Medium , 2: Aggressive + * + * Output: + * - nsxInst : Initialized instance + * + * Return value : 0 - Ok + * -1 - Error + */ +int WebRtcNsx_set_policy(NsxHandle* nsxInst, int mode); + +/* + * This functions does noise suppression for the inserted speech frame. The + * input and output signals should always be 10ms (80 or 160 samples). + * + * Input + * - nsxInst : NSx instance. Needs to be initiated before call. + * - speechFrame : Pointer to speech frame buffer for each band + * - num_bands : Number of bands + * + * Output: + * - nsxInst : Updated NSx instance + * - outFrame : Pointer to output frame for each band + */ +void WebRtcNsx_Process(NsxHandle* nsxInst, + const short* const* speechFrame, + int num_bands, + short* const* outFrame); + +/* Returns a pointer to the noise estimate per frequency bin. The number of + * frequency bins can be provided using WebRtcNsx_num_freq(). + * + * Input + * - nsxInst : NSx instance. Needs to be initiated before call. + * - q_noise : Q value of the noise estimate, which is the number of + * bits that it needs to be right-shifted to be + * normalized. + * + * Return value : Pointer to the noise estimate per frequency bin. + * Returns NULL if the input is a NULL pointer or an + * uninitialized instance. + */ +const uint32_t* WebRtcNsx_noise_estimate(const NsxHandle* nsxInst, + int* q_noise); + +/* Returns the number of frequency bins, which is the length of the noise + * estimate for example. + * + * Return value : Number of frequency bins. + */ +size_t WebRtcNsx_num_freq(); + +#ifdef __cplusplus +} +#endif + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_NS_NOISE_SUPPRESSION_X_H_ diff --git a/external/include/webrtc/splitting_filter_wrapper.h b/external/include/webrtc/splitting_filter_wrapper.h new file mode 100644 index 0000000000..25eb553b99 --- /dev/null +++ b/external/include/webrtc/splitting_filter_wrapper.h @@ -0,0 +1,20 @@ +#ifndef __WEBRTC_SPLITTING_FILTER_WRAPPER +#define __WEBRTC_SPLITTING_FILTER_WRAPPER + +struct splitting_filter_t{ + /*IFCHannelBuffer*/ void* _bufferIn; + /*IFCHannelBuffer*/ void* _bufferOut; + /*SplittingFilter*/ void* _splittingFilter; + float* bufferIn; + float* bufferOut[3]; +}; + +extern "C"{ +splitting_filter_t* tgvoip_splitting_filter_create(); +void tgvoip_splitting_filter_free(splitting_filter_t* filter); + +void tgvoip_splitting_filter_analyze(splitting_filter_t* filter); +void tgvoip_splitting_filter_synthesize(splitting_filter_t* filter); +} + +#endif // __WEBRTC_SPLITTING_FILTER_WRAPPER \ No newline at end of file diff --git a/external/libWebRtcAec_android_armeabi-v7a.a b/external/libWebRtcAec_android_armeabi-v7a.a new file mode 100644 index 0000000000..253ee69515 Binary files /dev/null and b/external/libWebRtcAec_android_armeabi-v7a.a differ diff --git a/external/libWebRtcAec_android_armeabi.a b/external/libWebRtcAec_android_armeabi.a new file mode 100644 index 0000000000..8f54f588ac Binary files /dev/null and b/external/libWebRtcAec_android_armeabi.a differ diff --git a/external/libWebRtcAec_android_x86.a b/external/libWebRtcAec_android_x86.a new file mode 100644 index 0000000000..a44b9080cc Binary files /dev/null and b/external/libWebRtcAec_android_x86.a differ diff --git a/external/libWebRtcAec_darwin.a b/external/libWebRtcAec_darwin.a new file mode 100644 index 0000000000..6b8229b17e Binary files /dev/null and b/external/libWebRtcAec_darwin.a differ diff --git a/logging.h b/logging.h new file mode 100644 index 0000000000..0cb5b90ddb --- /dev/null +++ b/logging.h @@ -0,0 +1,48 @@ +// +// 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. +// + +#ifndef __LOGGING_H +#define __LOGGING_H + +#define LSTR_INT(x) LSTR_DO_INT(x) +#define LSTR_DO_INT(x) #x + +#if defined(__ANDROID__) + +#include + +//#define _LOG_WRAP(...) __BASE_FILE__":"LSTR_INT(__LINE__)": "__VA_ARGS__ +#define _LOG_WRAP(...) __VA_ARGS__ +#define TAG "tg-voip-native" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, _LOG_WRAP(__VA_ARGS__)) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, _LOG_WRAP(__VA_ARGS__)) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, _LOG_WRAP(__VA_ARGS__)) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, _LOG_WRAP(__VA_ARGS__)) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, _LOG_WRAP(__VA_ARGS__)) + +#elif defined(__APPLE__) && TARGET_OS_IPHONE + +#include "os/darwin/TGLogWrapper.h" + +#define LOGV(msg, ...) __tgvoip_call_tglog("V/tgvoip: " msg, ##__VA_ARGS__) +#define LOGD(msg, ...) __tgvoip_call_tglog("D/tgvoip: " msg, ##__VA_ARGS__) +#define LOGI(msg, ...) __tgvoip_call_tglog("I/tgvoip: " msg, ##__VA_ARGS__) +#define LOGW(msg, ...) __tgvoip_call_tglog("W/tgvoip: " msg, ##__VA_ARGS__) +#define LOGE(msg, ...) __tgvoip_call_tglog("E/tgvoip: " msg, ##__VA_ARGS__) + +#else + +#include + +#define LOGV(msg, ...) printf("V/tgvoip: " msg "\n", ##__VA_ARGS__) +#define LOGD(msg, ...) printf("D/tgvoip: " msg "\n", ##__VA_ARGS__) +#define LOGI(msg, ...) printf("I/tgvoip: " msg "\n", ##__VA_ARGS__) +#define LOGW(msg, ...) printf("W/tgvoip: " msg "\n", ##__VA_ARGS__) +#define LOGE(msg, ...) printf("E/tgvoip: " msg "\n", ##__VA_ARGS__) + +#endif + +#endif //__LOGGING_H diff --git a/os/android/AudioInputAndroid.cpp b/os/android/AudioInputAndroid.cpp new file mode 100644 index 0000000000..9674c0284b --- /dev/null +++ b/os/android/AudioInputAndroid.cpp @@ -0,0 +1,112 @@ +// +// 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. +// + +#include "AudioInputAndroid.h" +#include +#include "../../logging.h" + +extern JavaVM* sharedJVM; + +jmethodID CAudioInputAndroid::initMethod; +jmethodID CAudioInputAndroid::releaseMethod; +jmethodID CAudioInputAndroid::startMethod; +jmethodID CAudioInputAndroid::stopMethod; +jclass CAudioInputAndroid::jniClass; + +CAudioInputAndroid::CAudioInputAndroid(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + jmethodID ctor=env->GetMethodID(jniClass, "", "(J)V"); + jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this); + javaObject=env->NewGlobalRef(obj); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } + running=false; +} + +CAudioInputAndroid::~CAudioInputAndroid(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, releaseMethod); + env->DeleteGlobalRef(javaObject); + javaObject=NULL; + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioInputAndroid::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, initMethod, sampleRate, bitsPerSample, channels, 960*2); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioInputAndroid::Start(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, startMethod); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } + running=true; +} + +void CAudioInputAndroid::Stop(){ + running=false; + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, stopMethod); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioInputAndroid::HandleCallback(JNIEnv* env, jobject buffer){ + if(!running) + return; + unsigned char* buf=(unsigned char*) env->GetDirectBufferAddress(buffer); + size_t len=(size_t) env->GetDirectBufferCapacity(buffer); + InvokeCallback(buf, len); +} \ No newline at end of file diff --git a/os/android/AudioInputAndroid.h b/os/android/AudioInputAndroid.h new file mode 100644 index 0000000000..8911b799a4 --- /dev/null +++ b/os/android/AudioInputAndroid.h @@ -0,0 +1,35 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOINPUTANDROID_H +#define LIBTGVOIP_AUDIOINPUTANDROID_H + +#include +#include "../../audio/AudioInput.h" + +class CAudioInputAndroid : public CAudioInput{ + +public: + CAudioInputAndroid(); + virtual ~CAudioInputAndroid(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + void HandleCallback(JNIEnv* env, jobject buffer); + static jmethodID initMethod; + static jmethodID releaseMethod; + static jmethodID startMethod; + static jmethodID stopMethod; + static jclass jniClass; + +private: + jobject javaObject; + bool running; + +}; + + +#endif //LIBTGVOIP_AUDIOINPUTANDROID_H diff --git a/os/android/AudioInputOpenSLES.cpp b/os/android/AudioInputOpenSLES.cpp new file mode 100644 index 0000000000..0f435a39f9 --- /dev/null +++ b/os/android/AudioInputOpenSLES.cpp @@ -0,0 +1,137 @@ +// +// 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. +// + +#include +#include +#include + +#include "AudioInputOpenSLES.h" +#include "../../logging.h" +#include "OpenSLEngineWrapper.h" + +#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return; } +#define BUFFER_SIZE 960 // 20 ms + +int CAudioInputOpenSLES::nativeBufferSize; + +FILE* test; + +CAudioInputOpenSLES::CAudioInputOpenSLES(){ + slEngine=COpenSLEngineWrapper::CreateEngine(); + + LOGI("Native buffer size is %u samples", nativeBufferSize); + if(nativeBufferSizeBUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){ + LOGE("native buffer size is not multiple of 20ms!!"); + nativeBufferSize+=nativeBufferSize%BUFFER_SIZE; + } + if(nativeBufferSize==BUFFER_SIZE) + nativeBufferSize*=2; + LOGI("Adjusted native buffer size is %u", nativeBufferSize); + + buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t)); + nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t)); + //test=fopen("/sdcard/test.raw", "wb"); + slRecorderObj=NULL; +} + +CAudioInputOpenSLES::~CAudioInputOpenSLES(){ + //Stop(); + (*slBufferQueue)->Clear(slBufferQueue); + (*slRecorderObj)->Destroy(slRecorderObj); + slRecorderObj=NULL; + slRecorder=NULL; + slBufferQueue=NULL; + slEngine=NULL; + COpenSLEngineWrapper::DestroyEngine(); + free(buffer); + buffer=NULL; + free(nativeBuffer); + nativeBuffer=NULL; +} + +void CAudioInputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){ + ((CAudioInputOpenSLES*)context)->HandleSLCallback(); +} + +void CAudioInputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + assert(slRecorderObj==NULL); + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + SLDataLocator_AndroidSimpleBufferQueue loc_bq = + {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1}; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channels, sampleRate*1000, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN}; + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + SLresult result = (*slEngine)->CreateAudioRecorder(slEngine, &slRecorderObj, &audioSrc, &audioSnk, 2, id, req); + CHECK_SL_ERROR(result, "Error creating recorder"); + + SLAndroidConfigurationItf recorderConfig; + result = (*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDCONFIGURATION, &recorderConfig); + SLint32 streamType = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + result = (*recorderConfig)->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &streamType, sizeof(SLint32)); + + result=(*slRecorderObj)->Realize(slRecorderObj, SL_BOOLEAN_FALSE); + CHECK_SL_ERROR(result, "Error realizing recorder"); + + result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_RECORD, &slRecorder); + CHECK_SL_ERROR(result, "Error getting recorder interface"); + + result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue); + CHECK_SL_ERROR(result, "Error getting buffer queue"); + + result=(*slBufferQueue)->RegisterCallback(slBufferQueue, CAudioInputOpenSLES::BufferCallback, this); + CHECK_SL_ERROR(result, "Error setting buffer queue callback"); + + (*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t)); +} + +void CAudioInputOpenSLES::Start(){ + SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_RECORDING); + CHECK_SL_ERROR(result, "Error starting record"); +} + +void CAudioInputOpenSLES::Stop(){ + SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_STOPPED); + CHECK_SL_ERROR(result, "Error stopping record"); +} + + +void CAudioInputOpenSLES::HandleSLCallback(){ + //SLmillisecond pMsec = 0; + //(*slRecorder)->GetPosition(slRecorder, &pMsec); + //LOGI("Callback! pos=%lu", pMsec); + //InvokeCallback((unsigned char*)buffer, BUFFER_SIZE*sizeof(int16_t)); + //fwrite(nativeBuffer, 1, nativeBufferSize*2, test); + + if(nativeBufferSize==BUFFER_SIZE){ + //LOGV("nativeBufferSize==BUFFER_SIZE"); + InvokeCallback((unsigned char *) nativeBuffer, BUFFER_SIZE*sizeof(int16_t)); + }else if(nativeBufferSize=BUFFER_SIZE){ + InvokeCallback((unsigned char *) buffer, BUFFER_SIZE*sizeof(int16_t)); + positionInBuffer=0; + } + memcpy(((unsigned char*)buffer)+positionInBuffer*2, nativeBuffer, (size_t)nativeBufferSize*2); + positionInBuffer+=nativeBufferSize; + }else if(nativeBufferSize>BUFFER_SIZE){ + //LOGV("nativeBufferSize>BUFFER_SIZE"); + for(unsigned int offset=0;offsetEnqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t)); +} + diff --git a/os/android/AudioInputOpenSLES.h b/os/android/AudioInputOpenSLES.h new file mode 100644 index 0000000000..ac5d878dcc --- /dev/null +++ b/os/android/AudioInputOpenSLES.h @@ -0,0 +1,39 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOINPUTOPENSLES_H +#define LIBTGVOIP_AUDIOINPUTOPENSLES_H + +#include +#include + +#include "../../audio/AudioInput.h" + +class CAudioInputOpenSLES : public CAudioInput{ + +public: + CAudioInputOpenSLES(); + virtual ~CAudioInputOpenSLES(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + + static int nativeBufferSize; + +private: + static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context); + void HandleSLCallback(); + SLEngineItf slEngine; + SLObjectItf slRecorderObj; + SLRecordItf slRecorder; + SLAndroidSimpleBufferQueueItf slBufferQueue; + int16_t* buffer; + int16_t* nativeBuffer; + size_t positionInBuffer; +}; + + +#endif //LIBTGVOIP_AUDIOINPUTOPENSLES_H diff --git a/os/android/AudioOutputAndroid.cpp b/os/android/AudioOutputAndroid.cpp new file mode 100644 index 0000000000..edffe5429e --- /dev/null +++ b/os/android/AudioOutputAndroid.cpp @@ -0,0 +1,122 @@ +// +// 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. +// + +#include "AudioOutputAndroid.h" +#include +#include "../../logging.h" + +extern JavaVM* sharedJVM; + +jmethodID CAudioOutputAndroid::initMethod; +jmethodID CAudioOutputAndroid::releaseMethod; +jmethodID CAudioOutputAndroid::startMethod; +jmethodID CAudioOutputAndroid::stopMethod; +jclass CAudioOutputAndroid::jniClass; + +CAudioOutputAndroid::CAudioOutputAndroid(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + jmethodID ctor=env->GetMethodID(jniClass, "", "(J)V"); + jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this); + javaObject=env->NewGlobalRef(obj); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } + running=false; +} + +CAudioOutputAndroid::~CAudioOutputAndroid(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, releaseMethod); + env->DeleteGlobalRef(javaObject); + javaObject=NULL; + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioOutputAndroid::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, initMethod, sampleRate, bitsPerSample, channels, 960*2); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioOutputAndroid::Start(){ + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, startMethod); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } + running=true; +} + +void CAudioOutputAndroid::Stop(){ + running=false; + JNIEnv* env=NULL; + bool didAttach=false; + sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6); + if(!env){ + sharedJVM->AttachCurrentThread(&env, NULL); + didAttach=true; + } + + env->CallVoidMethod(javaObject, stopMethod); + + if(didAttach){ + sharedJVM->DetachCurrentThread(); + } +} + +void CAudioOutputAndroid::HandleCallback(JNIEnv* env, jbyteArray buffer){ + if(!running) + return; + unsigned char* buf=(unsigned char*) env->GetByteArrayElements(buffer, NULL); + size_t len=(size_t) env->GetArrayLength(buffer); + InvokeCallback(buf, len); + env->ReleaseByteArrayElements(buffer, (jbyte *) buf, 0); +} + + +bool CAudioOutputAndroid::IsPlaying(){ + return false; +} + +float CAudioOutputAndroid::GetLevel(){ + return 0; +} diff --git a/os/android/AudioOutputAndroid.h b/os/android/AudioOutputAndroid.h new file mode 100644 index 0000000000..16cd28bc8c --- /dev/null +++ b/os/android/AudioOutputAndroid.h @@ -0,0 +1,38 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOOUTPUTANDROID_H +#define LIBTGVOIP_AUDIOOUTPUTANDROID_H + +#include +#include "../../audio/AudioOutput.h" + +class CAudioOutputAndroid : public CAudioOutput{ + +public: + + CAudioOutputAndroid(); + virtual ~CAudioOutputAndroid(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + virtual bool IsPlaying() override; + virtual float GetLevel() override; + void HandleCallback(JNIEnv* env, jbyteArray buffer); + static jmethodID initMethod; + static jmethodID releaseMethod; + static jmethodID startMethod; + static jmethodID stopMethod; + static jclass jniClass; + +private: + jobject javaObject; + bool running; + +}; + + +#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H diff --git a/os/android/AudioOutputOpenSLES.cpp b/os/android/AudioOutputOpenSLES.cpp new file mode 100644 index 0000000000..0f0aab48a7 --- /dev/null +++ b/os/android/AudioOutputOpenSLES.cpp @@ -0,0 +1,168 @@ +// +// 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. +// + +#include +#include +#include +#include "AudioOutputOpenSLES.h" +#include "../../logging.h" +#include "../../VoIPController.h" +#include "OpenSLEngineWrapper.h" +#include "AudioInputAndroid.h" + +#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return; } +#define BUFFER_SIZE 960 // 20 ms + +int CAudioOutputOpenSLES::nativeBufferSize; + +CAudioOutputOpenSLES::CAudioOutputOpenSLES(){ + SLresult result; + slEngine=COpenSLEngineWrapper::CreateEngine(); + + const SLInterfaceID pOutputMixIDs[] = {}; + const SLboolean pOutputMixRequired[] = {}; + result = (*slEngine)->CreateOutputMix(slEngine, &slOutputMixObj, 0, pOutputMixIDs, pOutputMixRequired); + CHECK_SL_ERROR(result, "Error creating output mix"); + + result = (*slOutputMixObj)->Realize(slOutputMixObj, SL_BOOLEAN_FALSE); + CHECK_SL_ERROR(result, "Error realizing output mix"); + + LOGI("Native buffer size is %u samples", nativeBufferSize); + /*if(nativeBufferSizeBUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){ + LOGE("native buffer size is not multiple of 20ms!!"); + nativeBufferSize+=nativeBufferSize%BUFFER_SIZE; + } + LOGI("Adjusted native buffer size is %u", nativeBufferSize);*/ + + buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t)); + nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t)); + slPlayerObj=NULL; + remainingDataSize=0; +} + +CAudioOutputOpenSLES::~CAudioOutputOpenSLES(){ + if(!stopped) + Stop(); + (*slBufferQueue)->Clear(slBufferQueue); + LOGV("destroy slPlayerObj"); + (*slPlayerObj)->Destroy(slPlayerObj); + LOGV("destroy slOutputMixObj"); + (*slOutputMixObj)->Destroy(slOutputMixObj); + COpenSLEngineWrapper::DestroyEngine(); + free(buffer); + free(nativeBuffer); +} + + +void CAudioOutputOpenSLES::SetNativeBufferSize(int size){ + CAudioOutputOpenSLES::nativeBufferSize=size; +} + +void CAudioOutputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){ + ((CAudioOutputOpenSLES*)context)->HandleSLCallback(); +} + +void CAudioOutputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + assert(slPlayerObj==NULL); + SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue = + {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1}; + SLDataFormat_PCM formatPCM = {SL_DATAFORMAT_PCM, channels, sampleRate*1000, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN}; + SLDataSource audioSrc = {&locatorBufferQueue, &formatPCM}; + SLDataLocator_OutputMix locatorOutMix = {SL_DATALOCATOR_OUTPUTMIX, slOutputMixObj}; + SLDataSink audioSnk = {&locatorOutMix, NULL}; + + const SLInterfaceID id[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + SLresult result = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObj, &audioSrc, &audioSnk, 2, id, req); + CHECK_SL_ERROR(result, "Error creating player"); + + + SLAndroidConfigurationItf playerConfig; + result = (*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDCONFIGURATION, &playerConfig); + SLint32 streamType = SL_ANDROID_STREAM_VOICE; + result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); + + + result=(*slPlayerObj)->Realize(slPlayerObj, SL_BOOLEAN_FALSE); + CHECK_SL_ERROR(result, "Error realizing player"); + + result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_PLAY, &slPlayer); + CHECK_SL_ERROR(result, "Error getting player interface"); + + result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue); + CHECK_SL_ERROR(result, "Error getting buffer queue"); + + result=(*slBufferQueue)->RegisterCallback(slBufferQueue, CAudioOutputOpenSLES::BufferCallback, this); + CHECK_SL_ERROR(result, "Error setting buffer queue callback"); + + (*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t)); +} + +bool CAudioOutputOpenSLES::IsPhone(){ + return false; +} + +void CAudioOutputOpenSLES::EnableLoudspeaker(bool enabled){ + +} + +void CAudioOutputOpenSLES::Start(){ + stopped=false; + SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PLAYING); + CHECK_SL_ERROR(result, "Error starting player"); +} + +void CAudioOutputOpenSLES::Stop(){ + stopped=true; + LOGV("Stopping OpenSL output"); + SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PAUSED); + CHECK_SL_ERROR(result, "Error starting player"); +} + +void CAudioOutputOpenSLES::HandleSLCallback(){ + /*if(stopped){ + //LOGV("left HandleSLCallback early"); + return; + }*/ + //LOGV("before InvokeCallback"); + if(!stopped){ + while(remainingDataSize0) + memmove(remainingData, remainingData+nativeBufferSize*2, remainingDataSize); + //InvokeCallback((unsigned char *) nativeBuffer, nativeBufferSize*sizeof(int16_t)); + }else{ + memset(nativeBuffer, 0, nativeBufferSize*2); + } + + (*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t)); + //LOGV("left HandleSLCallback"); +} + + +bool CAudioOutputOpenSLES::IsPlaying(){ + if(slPlayer){ + uint32_t state; + (*slPlayer)->GetPlayState(slPlayer, &state); + return state==SL_PLAYSTATE_PLAYING; + } + return false; +} + + +float CAudioOutputOpenSLES::GetLevel(){ + return 0; // we don't use this anyway +} \ No newline at end of file diff --git a/os/android/AudioOutputOpenSLES.h b/os/android/AudioOutputOpenSLES.h new file mode 100644 index 0000000000..a9af1f9150 --- /dev/null +++ b/os/android/AudioOutputOpenSLES.h @@ -0,0 +1,46 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOOUTPUTOPENSLES_H +#define LIBTGVOIP_AUDIOOUTPUTOPENSLES_H + +#include +#include + +#include "../../audio/AudioOutput.h" + +class CAudioOutputOpenSLES : public CAudioOutput{ +public: + CAudioOutputOpenSLES(); + virtual ~CAudioOutputOpenSLES(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual bool IsPhone(); + virtual void EnableLoudspeaker(bool enabled); + virtual void Start(); + virtual void Stop(); + virtual bool IsPlaying(); + virtual float GetLevel(); + + static void SetNativeBufferSize(int size); + static int nativeBufferSize; + +private: + static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context); + void HandleSLCallback(); + SLEngineItf slEngine; + SLObjectItf slPlayerObj; + SLObjectItf slOutputMixObj; + SLPlayItf slPlayer; + SLAndroidSimpleBufferQueueItf slBufferQueue; + int16_t* buffer; + int16_t* nativeBuffer; + bool stopped; + unsigned char remainingData[10240]; + size_t remainingDataSize; +}; + + +#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H diff --git a/os/android/OpenSLEngineWrapper.cpp b/os/android/OpenSLEngineWrapper.cpp new file mode 100644 index 0000000000..9c8b754125 --- /dev/null +++ b/os/android/OpenSLEngineWrapper.cpp @@ -0,0 +1,44 @@ +// +// 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. +// + +#include +#include "OpenSLEngineWrapper.h" +#include "../../logging.h" + +#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return NULL; } + +SLObjectItf COpenSLEngineWrapper::sharedEngineObj=NULL; +SLEngineItf COpenSLEngineWrapper::sharedEngine=NULL; +int COpenSLEngineWrapper::count=0; + +void COpenSLEngineWrapper::DestroyEngine(){ + count--; + LOGI("release: engine instance count %d", count); + if(count==0){ + (*sharedEngineObj)->Destroy(sharedEngineObj); + sharedEngineObj=NULL; + sharedEngine=NULL; + } + LOGI("after release"); +} + +SLEngineItf COpenSLEngineWrapper::CreateEngine(){ + count++; + if(sharedEngine) + return sharedEngine; + const SLInterfaceID pIDs[1] = {SL_IID_ENGINE}; + const SLboolean pIDsRequired[1] = {SL_BOOLEAN_TRUE}; + SLresult result = slCreateEngine(&sharedEngineObj, 0, NULL, 1, pIDs, pIDsRequired); + CHECK_SL_ERROR(result, "Error creating engine"); + + result=(*sharedEngineObj)->Realize(sharedEngineObj, SL_BOOLEAN_FALSE); + CHECK_SL_ERROR(result, "Error realizing engine"); + + result = (*sharedEngineObj)->GetInterface(sharedEngineObj, SL_IID_ENGINE, &sharedEngine); + CHECK_SL_ERROR(result, "Error getting engine interface"); + return sharedEngine; +} + diff --git a/os/android/OpenSLEngineWrapper.h b/os/android/OpenSLEngineWrapper.h new file mode 100644 index 0000000000..f98970a2a0 --- /dev/null +++ b/os/android/OpenSLEngineWrapper.h @@ -0,0 +1,25 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_OPENSLENGINEWRAPPER_H +#define LIBTGVOIP_OPENSLENGINEWRAPPER_H + +#include +#include + +class COpenSLEngineWrapper{ +public: + static SLEngineItf CreateEngine(); + static void DestroyEngine(); + +private: + static SLObjectItf sharedEngineObj; + static SLEngineItf sharedEngine; + static int count; +}; + + +#endif //LIBTGVOIP_OPENSLENGINEWRAPPER_H diff --git a/os/darwin/AudioInputAudioUnit.cpp b/os/darwin/AudioInputAudioUnit.cpp new file mode 100644 index 0000000000..9c2cf4466b --- /dev/null +++ b/os/darwin/AudioInputAudioUnit.cpp @@ -0,0 +1,56 @@ +// +// 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. +// + +#include +#include +#include +#include "AudioUnitIO.h" +#include "AudioInputAudioUnit.h" +#include "../../logging.h" + +#define BUFFER_SIZE 960 + +CAudioInputAudioUnit::CAudioInputAudioUnit(CAudioUnitIO* io){ + remainingDataSize=0; + isRecording=false; + this->io=io; + io->AttachInput(this); +} + +CAudioInputAudioUnit::~CAudioInputAudioUnit(){ + io->DetachInput(); +} + +void CAudioInputAudioUnit::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + io->Configure(sampleRate, bitsPerSample, channels); +} + +void CAudioInputAudioUnit::Start(){ + isRecording=true; + io->EnableInput(true); +} + +void CAudioInputAudioUnit::Stop(){ + isRecording=false; + io->EnableInput(false); +} + +void CAudioInputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){ + int i; + for(i=0;imNumberBuffers;i++){ + AudioBuffer buf=ioData->mBuffers[i]; + assert(remainingDataSize+buf.mDataByteSize<10240); + memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize); + remainingDataSize+=buf.mDataByteSize; + while(remainingDataSize>=BUFFER_SIZE*2){ + InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2); + remainingDataSize-=BUFFER_SIZE*2; + if(remainingDataSize>0){ + memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize); + } + } + } +} diff --git a/os/darwin/AudioInputAudioUnit.h b/os/darwin/AudioInputAudioUnit.h new file mode 100644 index 0000000000..7e417077e4 --- /dev/null +++ b/os/darwin/AudioInputAudioUnit.h @@ -0,0 +1,33 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H +#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H + +#include +#include "../../audio/AudioInput.h" + +class CAudioUnitIO; + +class CAudioInputAudioUnit : public CAudioInput{ + +public: + CAudioInputAudioUnit(CAudioUnitIO* io); + virtual ~CAudioInputAudioUnit(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + void HandleBufferCallback(AudioBufferList* ioData); + +private: + unsigned char remainingData[10240]; + size_t remainingDataSize; + bool isRecording; + CAudioUnitIO* io; +}; + + +#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H diff --git a/os/darwin/AudioOutputAudioUnit.cpp b/os/darwin/AudioOutputAudioUnit.cpp new file mode 100644 index 0000000000..d190aeed8a --- /dev/null +++ b/os/darwin/AudioOutputAudioUnit.cpp @@ -0,0 +1,101 @@ +// +// 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. +// + +#include +#include +#include +#include "AudioOutputAudioUnit.h" +#include "../../logging.h" +#include "AudioUnitIO.h" + +#define BUFFER_SIZE 960 +const int8_t permutation[33]={0,1,2,3,4,4,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,9,9,9,9,9,9,9,9,9,9,9}; + +CAudioOutputAudioUnit::CAudioOutputAudioUnit(CAudioUnitIO* io){ + isPlaying=false; + remainingDataSize=0; + level=0.0; + this->io=io; + io->AttachOutput(this); +} + +CAudioOutputAudioUnit::~CAudioOutputAudioUnit(){ + io->DetachOutput(); +} + +void CAudioOutputAudioUnit::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + io->Configure(sampleRate, bitsPerSample, channels); +} + +bool CAudioOutputAudioUnit::IsPhone(){ + return false; +} + +void CAudioOutputAudioUnit::EnableLoudspeaker(bool enabled){ + +} + +void CAudioOutputAudioUnit::Start(){ + isPlaying=true; + io->EnableOutput(true); +} + +void CAudioOutputAudioUnit::Stop(){ + isPlaying=false; + io->EnableOutput(false); +} + +bool CAudioOutputAudioUnit::IsPlaying(){ + return isPlaying; +} + +float CAudioOutputAudioUnit::GetLevel(){ + return level / 9.0; +} + +void CAudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){ + int i; + unsigned int k; + int16_t absVal=0; + for(i=0;imNumberBuffers;i++){ + AudioBuffer buf=ioData->mBuffers[i]; + if(!isPlaying){ + memset(buf.mData, 0, buf.mDataByteSize); + return; + } + while(remainingDataSizeabsVal) + absVal=absolute; + } + + if (absVal>absMax) + absMax=absVal; + + count++; + if (count>=10) { + count=0; + + short position=absMax/1000; + if (position==0 && absMax>250) { + position=1; + } + level=permutation[position]; + absMax>>=2; + } + } +} + diff --git a/os/darwin/AudioOutputAudioUnit.h b/os/darwin/AudioOutputAudioUnit.h new file mode 100644 index 0000000000..edc7d4d1eb --- /dev/null +++ b/os/darwin/AudioOutputAudioUnit.h @@ -0,0 +1,39 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H +#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H + +#include +#include "../../audio/AudioOutput.h" + +class CAudioUnitIO; + +class CAudioOutputAudioUnit : public CAudioOutput{ +public: + CAudioOutputAudioUnit(CAudioUnitIO* io); + virtual ~CAudioOutputAudioUnit(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual bool IsPhone(); + virtual void EnableLoudspeaker(bool enabled); + virtual void Start(); + virtual void Stop(); + virtual bool IsPlaying(); + virtual float GetLevel(); + void HandleBufferCallback(AudioBufferList* ioData); + +private: + bool isPlaying; + unsigned char remainingData[10240]; + size_t remainingDataSize; + CAudioUnitIO* io; + float level; + int16_t absMax; + int count; +}; + + +#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H diff --git a/os/darwin/AudioUnitIO.cpp b/os/darwin/AudioUnitIO.cpp new file mode 100644 index 0000000000..1338e80937 --- /dev/null +++ b/os/darwin/AudioUnitIO.cpp @@ -0,0 +1,180 @@ +// +// 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. +// +#include +#include +#include "AudioUnitIO.h" +#include "AudioInputAudioUnit.h" +#include "AudioOutputAudioUnit.h" +#include "../../logging.h" + +#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); return; } +#define BUFFER_SIZE 960 // 20 ms + +#define kOutputBus 0 +#define kInputBus 1 + +int CAudioUnitIO::refCount=0; +CAudioUnitIO* CAudioUnitIO::sharedInstance=NULL; + +CAudioUnitIO::CAudioUnitIO(){ + OSStatus status; + AudioComponentDescription desc; + AudioComponent inputComponent; + desc.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + //desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; +#else + desc.componentSubType = kAudioUnitSubType_HALOutput; +#endif + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + inputComponent = AudioComponentFindNext(NULL, &desc); + status = AudioComponentInstanceNew(inputComponent, &unit); + + input=NULL; + output=NULL; + configured=false; + inputEnabled=false; + outputEnabled=false; + inBufferList.mBuffers[0].mData=malloc(10240); + inBufferList.mBuffers[0].mDataByteSize=10240; + inBufferList.mNumberBuffers=1; +} + +CAudioUnitIO::~CAudioUnitIO(){ + AudioOutputUnitStop(unit); + AudioUnitUninitialize(unit); + AudioComponentInstanceDispose(unit); + free(inBufferList.mBuffers[0].mData); +} + +CAudioUnitIO* CAudioUnitIO::Get(){ + if(refCount==0){ + sharedInstance=new CAudioUnitIO(); + } + refCount++; + assert(refCount>0); + return sharedInstance; +} + +void CAudioUnitIO::Release(){ + refCount--; + assert(refCount>=0); + if(refCount==0){ + delete sharedInstance; + sharedInstance=NULL; + } +} + +void CAudioUnitIO::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + if(configured) + return; + UInt32 flag=1; + OSStatus status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)); + CHECK_AU_ERROR(status, "Error enabling AudioUnit output"); + status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag)); + CHECK_AU_ERROR(status, "Error enabling AudioUnit input"); + + flag=1; + status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag)); + CHECK_AU_ERROR(status, "Error disabling AGC"); + + /*AudioStreamBasicDescription nativeInFormat, nativeOutFormat; + UInt32 size=sizeof(AudioStreamBasicDescription); + AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &nativeInFormat, &size); + size=sizeof(AudioStreamBasicDescription); + AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &nativeOutFormat, &size); + LOGI("native in format: %d hz, flags %08X, %d channels, %d bits", (int)nativeInFormat.mSampleRate, nativeInFormat.mFormatFlags, nativeInFormat.mChannelsPerFrame, nativeInFormat.mBitsPerChannel); + LOGI("native out format: %d hz, flags %08X, %d channels, %d bits", (int)nativeOutFormat.mSampleRate, nativeOutFormat.mFormatFlags, nativeOutFormat.mChannelsPerFrame, nativeOutFormat.mBitsPerChannel);*/ + Float64 nativeSampleRate; + UInt32 size=sizeof(Float64); + status=AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, &nativeSampleRate); + + AudioStreamBasicDescription audioFormat; + audioFormat.mSampleRate = sampleRate; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; + audioFormat.mFramesPerPacket = 1; + audioFormat.mChannelsPerFrame = channels; + audioFormat.mBitsPerChannel = bitsPerSample*channels; + audioFormat.mBytesPerPacket = bitsPerSample/8*channels; + audioFormat.mBytesPerFrame = bitsPerSample/8*channels; + + status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat)); + CHECK_AU_ERROR(status, "Error setting output format"); + status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat)); + CHECK_AU_ERROR(status, "Error setting input format"); + + AURenderCallbackStruct callbackStruct; + + callbackStruct.inputProc = CAudioUnitIO::BufferCallback; + callbackStruct.inputProcRefCon = this; + status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct)); + CHECK_AU_ERROR(status, "Error setting output buffer callback"); + status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct)); + CHECK_AU_ERROR(status, "Error setting input buffer callback"); + + status = AudioUnitInitialize(unit); + CHECK_AU_ERROR(status, "Error initializing AudioUnit"); + status=AudioOutputUnitStart(unit); + CHECK_AU_ERROR(status, "Error starting AudioUnit"); + + configured=true; +} + +OSStatus CAudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){ + ((CAudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); + return noErr; +} + +void CAudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){ + if(bus==kOutputBus){ + if(output && outputEnabled){ + output->HandleBufferCallback(ioData); + }else{ + memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); + } + }else if(bus==kInputBus){ + if(input && inputEnabled){ + inBufferList.mBuffers[0].mDataByteSize=10240; + AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList); + input->HandleBufferCallback(&inBufferList); + } + } +} + +void CAudioUnitIO::AttachInput(CAudioInputAudioUnit *i){ + assert(input==NULL); + input=i; +} + +void CAudioUnitIO::AttachOutput(CAudioOutputAudioUnit *o){ + assert(output==NULL); + output=o; +} + +void CAudioUnitIO::DetachInput(){ + assert(input!=NULL); + input=NULL; + inputEnabled=false; +} + +void CAudioUnitIO::DetachOutput(){ + assert(output!=NULL); + output=NULL; + outputEnabled=false; +} + +void CAudioUnitIO::EnableInput(bool enabled){ + inputEnabled=enabled; +} + +void CAudioUnitIO::EnableOutput(bool enabled){ + outputEnabled=enabled; +} + diff --git a/os/darwin/AudioUnitIO.h b/os/darwin/AudioUnitIO.h new file mode 100644 index 0000000000..f1ffff2a60 --- /dev/null +++ b/os/darwin/AudioUnitIO.h @@ -0,0 +1,43 @@ +// +// 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. +// + +#ifndef LIBTGVOIP_AUDIOUNITIO_H +#define LIBTGVOIP_AUDIOUNITIO_H + +#include + +class CAudioInputAudioUnit; +class CAudioOutputAudioUnit; + +class CAudioUnitIO{ +public: + CAudioUnitIO(); + ~CAudioUnitIO(); + void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + void AttachInput(CAudioInputAudioUnit* i); + void AttachOutput(CAudioOutputAudioUnit* o); + void DetachInput(); + void DetachOutput(); + void EnableInput(bool enabled); + void EnableOutput(bool enabled); + static CAudioUnitIO* Get(); + static void Release(); + +private: + static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); + void BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList* ioData); + AudioComponentInstance unit; + CAudioInputAudioUnit* input; + CAudioOutputAudioUnit* output; + AudioBufferList inBufferList; + bool configured; + bool inputEnabled; + bool outputEnabled; + static int refCount; + static CAudioUnitIO* sharedInstance; +}; + +#endif /* LIBTGVOIP_AUDIOUNITIO_H */ diff --git a/os/darwin/TGLogWrapper.h b/os/darwin/TGLogWrapper.h new file mode 100644 index 0000000000..64182dc552 --- /dev/null +++ b/os/darwin/TGLogWrapper.h @@ -0,0 +1,20 @@ +// +// 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. +// + +#ifndef TGVOIP_TGLOGWRAPPER_H +#define TGVOIP_TGLOGWRAPPER_H + +#if defined __cplusplus +extern "C" { +#endif + +void __tgvoip_call_tglog(char* format, ...); + +#if defined __cplusplus +}; +#endif + +#endif //TGVOIP_TGLOGWRAPPER_H diff --git a/os/darwin/TGLogWrapper.m b/os/darwin/TGLogWrapper.m new file mode 100644 index 0000000000..f0da01dac5 --- /dev/null +++ b/os/darwin/TGLogWrapper.m @@ -0,0 +1,8 @@ +#import + +void __tgvoip_call_tglog(char* format, ...){ + va_list args; + va_start(args, format); + TGLog([[[NSString alloc] initWithFormat:[NSString stringWithCString:format encoding:NSUTF8StringEncoding] arguments:args] stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]); + va_end(args); +} \ No newline at end of file diff --git a/threading.h b/threading.h new file mode 100644 index 0000000000..0c61ef16b8 --- /dev/null +++ b/threading.h @@ -0,0 +1,42 @@ +// +// 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. +// + +#ifndef __THREADING_H +#define __THREADING_H + +#if defined(_POSIX_THREADS) || defined(_POSIX_VERSION) || defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) + +#include +#include + +typedef pthread_t tgvoip_thread_t; +typedef pthread_mutex_t tgvoip_mutex_t; +typedef pthread_cond_t tgvoip_lock_t; + +#define start_thread(ref, entry, arg) pthread_create(&ref, NULL, entry, arg) +#define join_thread(thread) pthread_join(thread, NULL) +#ifndef __APPLE__ +#define set_thread_name(thread, name) pthread_setname_np(thread, name) +#else +#define set_thread_name(thread, name) +#endif +#define set_thread_priority(thread, priority) {sched_param __param; __param.sched_priority=priority; int __result=pthread_setschedparam(thread, SCHED_RR, &__param); if(__result!=0){LOGE("can't set thread priority: %s", strerror(__result));}}; +#define get_thread_max_priority() sched_get_priority_max(SCHED_RR) +#define get_thread_min_priority() sched_get_priority_min(SCHED_RR) +#define init_mutex(mutex) pthread_mutex_init(&mutex, NULL) +#define free_mutex(mutex) pthread_mutex_destroy(&mutex) +#define lock_mutex(mutex) pthread_mutex_lock(&mutex) +#define unlock_mutex(mutex) pthread_mutex_unlock(&mutex) +#define init_lock(lock) pthread_cond_init(&lock, NULL) +#define free_lock(lock) pthread_cond_destroy(&lock) +#define wait_lock(lock, mutex) pthread_cond_wait(&lock, &mutex) +#define notify_lock(lock) pthread_cond_broadcast(&lock) + +#else +#error "No threading implementation for your operating system" +#endif + +#endif //__THREADING_H