Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
10
TMessagesProj/jni/voip/libtgvoip/BlockingQueue.cpp
Normal file
10
TMessagesProj/jni/voip/libtgvoip/BlockingQueue.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
90
TMessagesProj/jni/voip/libtgvoip/BlockingQueue.h
Normal file
90
TMessagesProj/jni/voip/libtgvoip/BlockingQueue.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <list>
|
||||
#include "threading.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
template<typename T>
|
||||
class BlockingQueue{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(BlockingQueue);
|
||||
BlockingQueue(size_t capacity) : semaphore(capacity, 0){
|
||||
this->capacity=capacity;
|
||||
overflowCallback=NULL;
|
||||
};
|
||||
|
||||
~BlockingQueue(){
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
void Put(T thing){
|
||||
MutexGuard sync(mutex);
|
||||
queue.push_back(std::move(thing));
|
||||
bool didOverflow=false;
|
||||
while(queue.size()>capacity){
|
||||
didOverflow=true;
|
||||
if(overflowCallback){
|
||||
overflowCallback(std::move(queue.front()));
|
||||
queue.pop_front();
|
||||
}else{
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if(!didOverflow)
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
T GetBlocking(){
|
||||
semaphore.Acquire();
|
||||
MutexGuard sync(mutex);
|
||||
return GetInternal();
|
||||
}
|
||||
|
||||
T Get(){
|
||||
MutexGuard sync(mutex);
|
||||
if(queue.size()>0)
|
||||
semaphore.Acquire();
|
||||
return GetInternal();
|
||||
}
|
||||
|
||||
unsigned int Size(){
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
void PrepareDealloc(){
|
||||
|
||||
}
|
||||
|
||||
void SetOverflowCallback(void (*overflowCallback)(T)){
|
||||
this->overflowCallback=overflowCallback;
|
||||
}
|
||||
|
||||
private:
|
||||
T GetInternal(){
|
||||
//if(queue.size()==0)
|
||||
// return NULL;
|
||||
T r=std::move(queue.front());
|
||||
queue.pop_front();
|
||||
return r;
|
||||
}
|
||||
|
||||
std::list<T> queue;
|
||||
size_t capacity;
|
||||
//tgvoip_lock_t lock;
|
||||
Semaphore semaphore;
|
||||
Mutex mutex;
|
||||
void (*overflowCallback)(T);
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_BLOCKINGQUEUE_H
|
||||
290
TMessagesProj/jni/voip/libtgvoip/Buffers.cpp
Normal file
290
TMessagesProj/jni/voip/libtgvoip/Buffers.cpp
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
//
|
||||
// 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 "Buffers.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include "logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
#pragma mark - BufferInputStream
|
||||
|
||||
BufferInputStream::BufferInputStream(const unsigned char* data, size_t length){
|
||||
this->buffer=data;
|
||||
this->length=length;
|
||||
offset=0;
|
||||
}
|
||||
|
||||
BufferInputStream::BufferInputStream(const Buffer &buffer){
|
||||
this->buffer=*buffer;
|
||||
this->length=buffer.Length();
|
||||
offset=0;
|
||||
}
|
||||
|
||||
BufferInputStream::~BufferInputStream(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
void BufferInputStream::Seek(size_t offset){
|
||||
if(offset>length){
|
||||
throw std::out_of_range("Not enough bytes in buffer");
|
||||
}
|
||||
this->offset=offset;
|
||||
}
|
||||
|
||||
size_t BufferInputStream::GetLength(){
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t BufferInputStream::GetOffset(){
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t BufferInputStream::Remaining(){
|
||||
return length-offset;
|
||||
}
|
||||
|
||||
unsigned char BufferInputStream::ReadByte(){
|
||||
EnsureEnoughRemaining(1);
|
||||
return (unsigned char)buffer[offset++];
|
||||
}
|
||||
|
||||
int32_t BufferInputStream::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 BufferInputStream::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 BufferInputStream::ReadInt16(){
|
||||
EnsureEnoughRemaining(2);
|
||||
int16_t res=(uint16_t)buffer[offset] | ((uint16_t)buffer[offset+1] << 8);
|
||||
offset+=2;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
int32_t BufferInputStream::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 BufferInputStream::ReadBytes(unsigned char *to, size_t count){
|
||||
EnsureEnoughRemaining(count);
|
||||
memcpy(to, buffer+offset, count);
|
||||
offset+=count;
|
||||
}
|
||||
|
||||
void BufferInputStream::ReadBytes(Buffer &to){
|
||||
ReadBytes(*to, to.Length());
|
||||
}
|
||||
|
||||
BufferInputStream BufferInputStream::GetPartBuffer(size_t length, bool advance){
|
||||
EnsureEnoughRemaining(length);
|
||||
BufferInputStream s=BufferInputStream(buffer+offset, length);
|
||||
if(advance)
|
||||
offset+=length;
|
||||
return s;
|
||||
}
|
||||
|
||||
void BufferInputStream::EnsureEnoughRemaining(size_t need){
|
||||
if(length-offset<need){
|
||||
throw std::out_of_range("Not enough bytes in buffer");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - BufferOutputStream
|
||||
|
||||
BufferOutputStream::BufferOutputStream(size_t size){
|
||||
buffer=(unsigned char*) malloc(size);
|
||||
if(!buffer)
|
||||
throw std::bad_alloc();
|
||||
offset=0;
|
||||
this->size=size;
|
||||
bufferProvided=false;
|
||||
}
|
||||
|
||||
BufferOutputStream::BufferOutputStream(unsigned char *buffer, size_t size){
|
||||
this->buffer=buffer;
|
||||
this->size=size;
|
||||
offset=0;
|
||||
bufferProvided=true;
|
||||
}
|
||||
|
||||
BufferOutputStream::~BufferOutputStream(){
|
||||
if(!bufferProvided && buffer)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteByte(unsigned char byte){
|
||||
this->ExpandBufferIfNeeded(1);
|
||||
buffer[offset++]=byte;
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteInt32(int32_t i){
|
||||
this->ExpandBufferIfNeeded(4);
|
||||
buffer[offset+3]=(unsigned char)((i >> 24) & 0xFF);
|
||||
buffer[offset+2]=(unsigned char)((i >> 16) & 0xFF);
|
||||
buffer[offset+1]=(unsigned char)((i >> 8) & 0xFF);
|
||||
buffer[offset]=(unsigned char)(i & 0xFF);
|
||||
offset+=4;
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteInt64(int64_t i){
|
||||
this->ExpandBufferIfNeeded(8);
|
||||
buffer[offset+7]=(unsigned char)((i >> 56) & 0xFF);
|
||||
buffer[offset+6]=(unsigned char)((i >> 48) & 0xFF);
|
||||
buffer[offset+5]=(unsigned char)((i >> 40) & 0xFF);
|
||||
buffer[offset+4]=(unsigned char)((i >> 32) & 0xFF);
|
||||
buffer[offset+3]=(unsigned char)((i >> 24) & 0xFF);
|
||||
buffer[offset+2]=(unsigned char)((i >> 16) & 0xFF);
|
||||
buffer[offset+1]=(unsigned char)((i >> 8) & 0xFF);
|
||||
buffer[offset]=(unsigned char)(i & 0xFF);
|
||||
offset+=8;
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteInt16(int16_t i){
|
||||
this->ExpandBufferIfNeeded(2);
|
||||
buffer[offset+1]=(unsigned char)((i >> 8) & 0xFF);
|
||||
buffer[offset]=(unsigned char)(i & 0xFF);
|
||||
offset+=2;
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteBytes(const unsigned char *bytes, size_t count){
|
||||
this->ExpandBufferIfNeeded(count);
|
||||
memcpy(buffer+offset, bytes, count);
|
||||
offset+=count;
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteBytes(const Buffer &buffer){
|
||||
WriteBytes(*buffer, buffer.Length());
|
||||
}
|
||||
|
||||
void BufferOutputStream::WriteBytes(const Buffer &buffer, size_t offset, size_t count){
|
||||
if(offset+count>buffer.Length())
|
||||
throw std::out_of_range("offset out of buffer bounds");
|
||||
WriteBytes(*buffer+offset, count);
|
||||
}
|
||||
|
||||
unsigned char *BufferOutputStream::GetBuffer(){
|
||||
return buffer;
|
||||
}
|
||||
|
||||
size_t BufferOutputStream::GetLength(){
|
||||
return offset;
|
||||
}
|
||||
|
||||
void BufferOutputStream::ExpandBufferIfNeeded(size_t need){
|
||||
if(offset+need>size){
|
||||
if(bufferProvided){
|
||||
throw std::out_of_range("buffer overflow");
|
||||
}
|
||||
if(need<1024){
|
||||
buffer=(unsigned char *) realloc(buffer, size+1024);
|
||||
size+=1024;
|
||||
}else{
|
||||
buffer=(unsigned char *) realloc(buffer, size+need);
|
||||
size+=need;
|
||||
}
|
||||
if(!buffer)
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BufferOutputStream::Reset(){
|
||||
offset=0;
|
||||
}
|
||||
|
||||
void BufferOutputStream::Rewind(size_t numBytes){
|
||||
if(numBytes>offset)
|
||||
throw std::out_of_range("buffer underflow");
|
||||
offset-=numBytes;
|
||||
}
|
||||
|
||||
#pragma mark - BufferPool
|
||||
|
||||
BufferPool::BufferPool(unsigned int size, unsigned int count){
|
||||
assert(count<=64);
|
||||
buffers[0]=(unsigned char*) malloc(size*count);
|
||||
bufferCount=count;
|
||||
unsigned int i;
|
||||
for(i=1;i<count;i++){
|
||||
buffers[i]=buffers[0]+i*size;
|
||||
}
|
||||
usedBuffers=0;
|
||||
this->size=size;
|
||||
}
|
||||
|
||||
BufferPool::~BufferPool(){
|
||||
free(buffers[0]);
|
||||
}
|
||||
|
||||
unsigned char* BufferPool::Get(){
|
||||
MutexGuard m(mutex);
|
||||
int i;
|
||||
for(i=0;i<bufferCount;i++){
|
||||
if(!((usedBuffers >> i) & 1)){
|
||||
usedBuffers|=(1LL << i);
|
||||
return buffers[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void BufferPool::Reuse(unsigned char* buffer){
|
||||
MutexGuard m(mutex);
|
||||
int i;
|
||||
for(i=0;i<bufferCount;i++){
|
||||
if(buffers[i]==buffer){
|
||||
usedBuffers&= ~(1LL << i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOGE("pointer passed isn't a valid buffer from this pool");
|
||||
abort();
|
||||
}
|
||||
|
||||
size_t BufferPool::GetSingleBufferSize(){
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t BufferPool::GetBufferCount(){
|
||||
return (size_t) bufferCount;
|
||||
}
|
||||
|
||||
#pragma mark - Buffer
|
||||
|
||||
278
TMessagesProj/jni/voip/libtgvoip/Buffers.h
Normal file
278
TMessagesProj/jni/voip/libtgvoip/Buffers.h
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
//
|
||||
// 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_BUFFERINPUTSTREAM_H
|
||||
#define LIBTGVOIP_BUFFERINPUTSTREAM_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <stddef.h>
|
||||
#include "threading.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace tgvoip{
|
||||
class Buffer;
|
||||
|
||||
class BufferInputStream{
|
||||
|
||||
public:
|
||||
BufferInputStream(const unsigned char* data, size_t length);
|
||||
BufferInputStream(const Buffer& buffer);
|
||||
~BufferInputStream();
|
||||
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(unsigned char* to, size_t count);
|
||||
void ReadBytes(Buffer& to);
|
||||
BufferInputStream GetPartBuffer(size_t length, bool advance);
|
||||
|
||||
private:
|
||||
void EnsureEnoughRemaining(size_t need);
|
||||
const unsigned char* buffer;
|
||||
size_t length;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
class BufferOutputStream{
|
||||
friend class Buffer;
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(BufferOutputStream);
|
||||
BufferOutputStream(size_t size);
|
||||
BufferOutputStream(unsigned char* buffer, size_t size);
|
||||
~BufferOutputStream();
|
||||
void WriteByte(unsigned char byte);
|
||||
void WriteInt64(int64_t i);
|
||||
void WriteInt32(int32_t i);
|
||||
void WriteInt16(int16_t i);
|
||||
void WriteBytes(const unsigned char* bytes, size_t count);
|
||||
void WriteBytes(const Buffer& buffer);
|
||||
void WriteBytes(const Buffer& buffer, size_t offset, size_t count);
|
||||
unsigned char* GetBuffer();
|
||||
size_t GetLength();
|
||||
void Reset();
|
||||
void Rewind(size_t numBytes);
|
||||
|
||||
BufferOutputStream& operator=(BufferOutputStream&& other){
|
||||
if(this!=&other){
|
||||
if(!bufferProvided && buffer)
|
||||
free(buffer);
|
||||
buffer=other.buffer;
|
||||
offset=other.offset;
|
||||
size=other.size;
|
||||
bufferProvided=other.bufferProvided;
|
||||
other.buffer=NULL;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void ExpandBufferIfNeeded(size_t need);
|
||||
unsigned char* buffer=NULL;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
bool bufferProvided;
|
||||
};
|
||||
|
||||
class BufferPool{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(BufferPool);
|
||||
BufferPool(unsigned int size, unsigned int count);
|
||||
~BufferPool();
|
||||
unsigned char* Get();
|
||||
void Reuse(unsigned char* buffer);
|
||||
size_t GetSingleBufferSize();
|
||||
size_t GetBufferCount();
|
||||
|
||||
private:
|
||||
uint64_t usedBuffers;
|
||||
int bufferCount;
|
||||
size_t size;
|
||||
unsigned char* buffers[64];
|
||||
Mutex mutex;
|
||||
};
|
||||
|
||||
class Buffer{
|
||||
public:
|
||||
Buffer(size_t capacity){
|
||||
if(capacity>0)
|
||||
data=(unsigned char *) malloc(capacity);
|
||||
else
|
||||
data=NULL;
|
||||
length=capacity;
|
||||
};
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(Buffer); // use Buffer::CopyOf to copy contents explicitly
|
||||
Buffer(Buffer&& other) noexcept {
|
||||
data=other.data;
|
||||
length=other.length;
|
||||
other.data=NULL;
|
||||
};
|
||||
Buffer(BufferOutputStream&& stream){
|
||||
data=stream.buffer;
|
||||
length=stream.offset;
|
||||
stream.buffer=NULL;
|
||||
}
|
||||
Buffer(){
|
||||
data=NULL;
|
||||
length=0;
|
||||
}
|
||||
~Buffer(){
|
||||
if(data)
|
||||
free(data);
|
||||
data=NULL;
|
||||
};
|
||||
Buffer& operator=(Buffer&& other){
|
||||
if(this!=&other){
|
||||
if(data)
|
||||
free(data);
|
||||
data=other.data;
|
||||
length=other.length;
|
||||
other.data=NULL;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
unsigned char& operator[](size_t i){
|
||||
if(i>=length)
|
||||
throw std::out_of_range("");
|
||||
return data[i];
|
||||
}
|
||||
const unsigned char& operator[](size_t i) const{
|
||||
if(i>=length)
|
||||
throw std::out_of_range("");
|
||||
return data[i];
|
||||
}
|
||||
unsigned char* operator*(){
|
||||
return data;
|
||||
}
|
||||
const unsigned char* operator*() const{
|
||||
return data;
|
||||
}
|
||||
void CopyFrom(const Buffer& other, size_t count, size_t srcOffset=0, size_t dstOffset=0){
|
||||
if(!other.data)
|
||||
throw std::invalid_argument("CopyFrom can't copy from NULL");
|
||||
if(other.length<srcOffset+count || length<dstOffset+count)
|
||||
throw std::out_of_range("Out of offset+count bounds of either buffer");
|
||||
memcpy(data+dstOffset, other.data+srcOffset, count);
|
||||
}
|
||||
void CopyFrom(const void* ptr, size_t dstOffset, size_t count){
|
||||
if(length<dstOffset+count)
|
||||
throw std::out_of_range("Offset+count is out of bounds");
|
||||
memcpy(data+dstOffset, ptr, count);
|
||||
}
|
||||
void Resize(size_t newSize){
|
||||
data=(unsigned char *) realloc(data, newSize);
|
||||
length=newSize;
|
||||
}
|
||||
size_t Length() const{
|
||||
return length;
|
||||
}
|
||||
bool IsEmpty() const{
|
||||
return length==0;
|
||||
}
|
||||
static Buffer CopyOf(const Buffer& other){
|
||||
Buffer buf(other.length);
|
||||
buf.CopyFrom(other, other.length);
|
||||
return buf;
|
||||
}
|
||||
private:
|
||||
unsigned char* data;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
template <typename T, size_t size, typename AVG_T=T> class HistoricBuffer{
|
||||
public:
|
||||
HistoricBuffer(){
|
||||
std::fill(data.begin(), data.end(), (T)0);
|
||||
}
|
||||
|
||||
AVG_T Average(){
|
||||
AVG_T avg=(AVG_T)0;
|
||||
for(T& i:data){
|
||||
avg+=i;
|
||||
}
|
||||
return avg/(AVG_T)size;
|
||||
}
|
||||
|
||||
AVG_T Average(size_t firstN){
|
||||
AVG_T avg=(AVG_T)0;
|
||||
for(size_t i=0;i<firstN;i++){
|
||||
avg+=(*this)[i];
|
||||
}
|
||||
return avg/(AVG_T)firstN;
|
||||
}
|
||||
|
||||
AVG_T NonZeroAverage(){
|
||||
AVG_T avg=(AVG_T)0;
|
||||
int nonZeroCount=0;
|
||||
for(T& i:data){
|
||||
if(i!=0){
|
||||
nonZeroCount++;
|
||||
avg+=i;
|
||||
}
|
||||
}
|
||||
if(nonZeroCount==0)
|
||||
return (AVG_T)0;
|
||||
return avg/(AVG_T)nonZeroCount;
|
||||
}
|
||||
|
||||
void Add(T el){
|
||||
data[offset]=el;
|
||||
offset=(offset+1)%size;
|
||||
}
|
||||
|
||||
T Min(){
|
||||
T min=std::numeric_limits<T>::max();
|
||||
for(T& i:data){
|
||||
if(i<min)
|
||||
min=i;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
T Max(){
|
||||
T max=std::numeric_limits<T>::min();
|
||||
for(T& i:data){
|
||||
if(i>max)
|
||||
max=i;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
void Reset(){
|
||||
std::fill(data.begin(), data.end(), (T)0);
|
||||
offset=0;
|
||||
}
|
||||
|
||||
T& operator[](size_t i){
|
||||
assert(i<size);
|
||||
// [0] should return the most recent entry, [1] the one before it, and so on
|
||||
ptrdiff_t _i=offset-i-1;
|
||||
if(_i<0)
|
||||
_i=size+_i;
|
||||
return data[_i];
|
||||
}
|
||||
|
||||
size_t Size(){
|
||||
return size;
|
||||
}
|
||||
private:
|
||||
std::array<T, size> data;
|
||||
ptrdiff_t offset=0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_BUFFERINPUTSTREAM_H
|
||||
142
TMessagesProj/jni/voip/libtgvoip/CongestionControl.cpp
Normal file
142
TMessagesProj/jni/voip/libtgvoip/CongestionControl.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// 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 "VoIPServerConfig.h"
|
||||
#include "PrivateDefines.h"
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
CongestionControl::CongestionControl(){
|
||||
memset(inflightPackets, 0, sizeof(inflightPackets));
|
||||
tmpRtt=0;
|
||||
tmpRttCount=0;
|
||||
lastSentSeq=0;
|
||||
lastActionTime=0;
|
||||
lastActionRtt=0;
|
||||
stateTransitionTime=0;
|
||||
inflightDataSize=0;
|
||||
lossCount=0;
|
||||
cwnd=(size_t) ServerConfig::GetSharedInstance()->GetInt("audio_congestion_window", 1024);
|
||||
}
|
||||
|
||||
CongestionControl::~CongestionControl(){
|
||||
}
|
||||
|
||||
size_t CongestionControl::GetAcknowledgedDataSize(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
double CongestionControl::GetAverageRTT(){
|
||||
return rttHistory.NonZeroAverage();
|
||||
}
|
||||
|
||||
size_t CongestionControl::GetInflightDataSize(){
|
||||
return inflightHistory.Average();
|
||||
}
|
||||
|
||||
|
||||
size_t CongestionControl::GetCongestionWindow(){
|
||||
return cwnd;
|
||||
}
|
||||
|
||||
double CongestionControl::GetMinimumRTT(){
|
||||
return rttHistory.Min();
|
||||
}
|
||||
|
||||
void CongestionControl::PacketAcknowledged(uint32_t seq){
|
||||
MutexGuard sync(mutex);
|
||||
int i;
|
||||
for(i=0;i<100;i++){
|
||||
if(inflightPackets[i].seq==seq && inflightPackets[i].sendTime>0){
|
||||
tmpRtt+=(VoIPController::GetCurrentTime()-inflightPackets[i].sendTime);
|
||||
tmpRttCount++;
|
||||
inflightPackets[i].sendTime=0;
|
||||
inflightDataSize-=inflightPackets[i].size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CongestionControl::PacketSent(uint32_t seq, size_t size){
|
||||
if(!seqgt(seq, lastSentSeq) || seq==lastSentSeq){
|
||||
LOGW("Duplicate outgoing seq %u", seq);
|
||||
return;
|
||||
}
|
||||
lastSentSeq=seq;
|
||||
MutexGuard sync(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=VoIPController::GetCurrentTime();
|
||||
inflightDataSize+=size;
|
||||
}
|
||||
|
||||
|
||||
void CongestionControl::Tick(){
|
||||
tickCount++;
|
||||
MutexGuard sync(mutex);
|
||||
if(tmpRttCount>0){
|
||||
rttHistory.Add(tmpRtt/tmpRttCount);
|
||||
tmpRtt=0;
|
||||
tmpRttCount=0;
|
||||
}
|
||||
int i;
|
||||
for(i=0;i<100;i++){
|
||||
if(inflightPackets[i].sendTime!=0 && VoIPController::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.Add(inflightDataSize);
|
||||
}
|
||||
|
||||
|
||||
int CongestionControl::GetBandwidthControlAction(){
|
||||
if(VoIPController::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(inflightAvg<min){
|
||||
lastActionTime=VoIPController::GetCurrentTime();
|
||||
return TGVOIP_CONCTL_ACT_INCREASE;
|
||||
}
|
||||
if(inflightAvg>max){
|
||||
lastActionTime=VoIPController::GetCurrentTime();
|
||||
return TGVOIP_CONCTL_ACT_DECREASE;
|
||||
}
|
||||
return TGVOIP_CONCTL_ACT_NONE;
|
||||
}
|
||||
|
||||
|
||||
uint32_t CongestionControl::GetSendLossCount(){
|
||||
return lossCount;
|
||||
}
|
||||
63
TMessagesProj/jni/voip/libtgvoip/CongestionControl.h
Normal file
63
TMessagesProj/jni/voip/libtgvoip/CongestionControl.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "threading.h"
|
||||
#include "Buffers.h"
|
||||
|
||||
#define TGVOIP_CONCTL_ACT_INCREASE 1
|
||||
#define TGVOIP_CONCTL_ACT_DECREASE 2
|
||||
#define TGVOIP_CONCTL_ACT_NONE 0
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
struct tgvoip_congestionctl_packet_t{
|
||||
uint32_t seq;
|
||||
double sendTime;
|
||||
size_t size;
|
||||
};
|
||||
typedef struct tgvoip_congestionctl_packet_t tgvoip_congestionctl_packet_t;
|
||||
|
||||
class CongestionControl{
|
||||
public:
|
||||
CongestionControl();
|
||||
~CongestionControl();
|
||||
|
||||
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:
|
||||
HistoricBuffer<double, 100> rttHistory;
|
||||
HistoricBuffer<size_t, 30> inflightHistory;
|
||||
tgvoip_congestionctl_packet_t inflightPackets[100];
|
||||
uint32_t lossCount;
|
||||
double tmpRtt;
|
||||
double lastActionTime;
|
||||
double lastActionRtt;
|
||||
double stateTransitionTime;
|
||||
int tmpRttCount;
|
||||
uint32_t lastSentSeq;
|
||||
uint32_t tickCount;
|
||||
size_t inflightDataSize;
|
||||
size_t cwnd;
|
||||
Mutex mutex;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_CONGESTIONCONTROL_H
|
||||
249
TMessagesProj/jni/voip/libtgvoip/EchoCanceller.cpp
Executable file
249
TMessagesProj/jni/voip/libtgvoip/EchoCanceller.cpp
Executable file
|
|
@ -0,0 +1,249 @@
|
|||
//
|
||||
// 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_NO_DSP
|
||||
#include "modules/audio_processing/include/audio_processing.h"
|
||||
#include "api/audio/audio_frame.h"
|
||||
#endif
|
||||
|
||||
#include "EchoCanceller.h"
|
||||
#include "audio/AudioOutput.h"
|
||||
#include "audio/AudioInput.h"
|
||||
#include "logging.h"
|
||||
#include "VoIPServerConfig.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
EchoCanceller::EchoCanceller(bool enableAEC, bool enableNS, bool enableAGC){
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
this->enableAEC=enableAEC;
|
||||
this->enableAGC=enableAGC;
|
||||
this->enableNS=enableNS;
|
||||
isOn=true;
|
||||
|
||||
apm=webrtc::AudioProcessingBuilder().Create();
|
||||
|
||||
webrtc::AudioProcessing::Config config;
|
||||
config.echo_canceller.enabled = enableAEC;
|
||||
#ifndef TGVOIP_USE_DESKTOP_DSP
|
||||
config.echo_canceller.mobile_mode = true;
|
||||
#else
|
||||
config.echo_canceller.mobile_mode = false;
|
||||
#endif
|
||||
config.high_pass_filter.enabled = enableAEC;
|
||||
config.gain_controller2.enabled = enableAGC;
|
||||
|
||||
using Level = webrtc::AudioProcessing::Config::NoiseSuppression::Level;
|
||||
Level nsLevel;
|
||||
#ifdef __APPLE__
|
||||
switch(ServerConfig::GetSharedInstance()->GetInt("webrtc_ns_level_vpio", 0)){
|
||||
#else
|
||||
switch (ServerConfig::GetSharedInstance()->GetInt("webrtc_ns_level", 2)) {
|
||||
#endif
|
||||
case 0:
|
||||
nsLevel=Level::kLow;
|
||||
break;
|
||||
case 1:
|
||||
nsLevel=Level::kModerate;
|
||||
break;
|
||||
case 3:
|
||||
nsLevel=Level::kVeryHigh;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
nsLevel=Level::kHigh;
|
||||
break;
|
||||
}
|
||||
config.noise_suppression.level = nsLevel;
|
||||
config.noise_suppression.enabled = enableNS;
|
||||
if (enableAGC) {
|
||||
config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveDigital;
|
||||
config.gain_controller1.target_level_dbfs = ServerConfig::GetSharedInstance()->GetInt("webrtc_agc_target_level", 9);
|
||||
config.gain_controller1.enable_limiter = ServerConfig::GetSharedInstance()->GetBoolean("webrtc_agc_enable_limiter", true);
|
||||
config.gain_controller1.compression_gain_db = ServerConfig::GetSharedInstance()->GetInt("webrtc_agc_compression_gain", 20);
|
||||
}
|
||||
|
||||
apm->ApplyConfig(config);
|
||||
|
||||
audioFrame = new webrtc::AudioFrame();
|
||||
audioFrame->samples_per_channel_ = 480;
|
||||
audioFrame->sample_rate_hz_ = 48000;
|
||||
audioFrame->num_channels_ = 1;
|
||||
|
||||
farendQueue = new BlockingQueue<int16_t *>(11);
|
||||
farendBufferPool = new BufferPool(960 * 2, 10);
|
||||
running = true;
|
||||
bufferFarendThread = new Thread(std::bind(&EchoCanceller::RunBufferFarendThread, this));
|
||||
bufferFarendThread->Start();
|
||||
|
||||
#else
|
||||
this->enableAEC=this->enableAGC=enableAGC=this->enableNS=enableNS=false;
|
||||
isOn=true;
|
||||
#endif
|
||||
}
|
||||
|
||||
EchoCanceller::~EchoCanceller() {
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
delete apm;
|
||||
delete audioFrame;
|
||||
delete farendBufferPool;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EchoCanceller::Start() {
|
||||
|
||||
}
|
||||
|
||||
void EchoCanceller::Stop() {
|
||||
|
||||
}
|
||||
|
||||
void EchoCanceller::SpeakerOutCallback(unsigned char* data, size_t len) {
|
||||
if (len != 960 * 2 || !enableAEC || !isOn)
|
||||
return;
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
int16_t *buf = (int16_t *) farendBufferPool->Get();
|
||||
if (buf) {
|
||||
memcpy(buf, data, 960 * 2);
|
||||
farendQueue->Put(buf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
void EchoCanceller::RunBufferFarendThread() {
|
||||
webrtc::AudioFrame frame;
|
||||
frame.num_channels_ = 1;
|
||||
frame.sample_rate_hz_ = 48000;
|
||||
frame.samples_per_channel_ = 480;
|
||||
|
||||
webrtc::StreamConfig input_config(frame.sample_rate_hz_, frame.num_channels_);
|
||||
webrtc::StreamConfig output_config(frame.sample_rate_hz_, frame.num_channels_);
|
||||
|
||||
while (running) {
|
||||
int16_t *samplesIn = farendQueue->GetBlocking();
|
||||
if (samplesIn) {
|
||||
memcpy(frame.mutable_data(), samplesIn, 480 * 2);
|
||||
apm->ProcessReverseStream(frame.data(), input_config,
|
||||
output_config, frame.mutable_data());
|
||||
memcpy(frame.mutable_data(), samplesIn + 480, 480 * 2);
|
||||
apm->ProcessReverseStream(frame.data(), input_config,
|
||||
output_config, frame.mutable_data());
|
||||
didBufferFarend = true;
|
||||
farendBufferPool->Reuse(reinterpret_cast<unsigned char *>(samplesIn));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void EchoCanceller::Enable(bool enabled) {
|
||||
isOn = enabled;
|
||||
}
|
||||
|
||||
void EchoCanceller::ProcessInput(int16_t* inOut, size_t numSamples, bool& hasVoice) {
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
if (!isOn || (!enableAEC && !enableAGC && !enableNS)) {
|
||||
return;
|
||||
}
|
||||
int delay = audio::AudioInput::GetEstimatedDelay() + audio::AudioOutput::GetEstimatedDelay();
|
||||
assert(numSamples == 960);
|
||||
|
||||
webrtc::StreamConfig input_config(audioFrame->sample_rate_hz_, audioFrame->num_channels_);
|
||||
webrtc::StreamConfig output_config(audioFrame->sample_rate_hz_, audioFrame->num_channels_);
|
||||
|
||||
memcpy(audioFrame->mutable_data(), inOut, 480 * 2);
|
||||
if (enableAEC)
|
||||
apm->set_stream_delay_ms(delay);
|
||||
apm->ProcessStream(audioFrame->data(), input_config,
|
||||
output_config, audioFrame->mutable_data());
|
||||
if (enableVAD)
|
||||
hasVoice= apm->GetStatistics().voice_detected.value_or(false);
|
||||
memcpy(inOut, audioFrame->data(), 480 * 2);
|
||||
memcpy(audioFrame->mutable_data(), inOut + 480, 480 * 2);
|
||||
if (enableAEC)
|
||||
apm->set_stream_delay_ms(delay);
|
||||
apm->ProcessStream(audioFrame->data(), input_config,
|
||||
output_config, audioFrame->mutable_data());
|
||||
if (enableVAD) {
|
||||
hasVoice=hasVoice || apm->GetStatistics().voice_detected.value_or(false);
|
||||
}
|
||||
memcpy(inOut + 480, audioFrame->data(), 480 * 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EchoCanceller::SetAECStrength(int strength) {
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
/*if(aec){
|
||||
#ifndef TGVOIP_USE_DESKTOP_DSP
|
||||
AecmConfig cfg;
|
||||
cfg.cngMode=AecmFalse;
|
||||
cfg.echoMode=(int16_t) strength;
|
||||
WebRtcAecm_set_config(aec, cfg);
|
||||
#endif
|
||||
}*/
|
||||
#endif
|
||||
}
|
||||
|
||||
void EchoCanceller::SetVoiceDetectionEnabled(bool enabled) {
|
||||
enableVAD = enabled;
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
auto config = apm->GetConfig();
|
||||
// config.voice_detection.enabled = enabled;
|
||||
apm->ApplyConfig(config);
|
||||
#endif
|
||||
}
|
||||
|
||||
using namespace tgvoip::effects;
|
||||
|
||||
AudioEffect::~AudioEffect() {
|
||||
|
||||
}
|
||||
|
||||
void AudioEffect::SetPassThrough(bool passThrough) {
|
||||
this->passThrough = passThrough;
|
||||
}
|
||||
|
||||
Volume::Volume() {
|
||||
|
||||
}
|
||||
|
||||
Volume::~Volume() {
|
||||
|
||||
}
|
||||
|
||||
void Volume::Process(int16_t* inOut, size_t numSamples) {
|
||||
if (level == 1.0f || passThrough) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < numSamples; i++) {
|
||||
float sample = (float) inOut[i] * multiplier;
|
||||
if (sample > 32767.0f)
|
||||
inOut[i] = INT16_MAX;
|
||||
else if (sample < -32768.0f)
|
||||
inOut[i] = INT16_MIN;
|
||||
else
|
||||
inOut[i] = (int16_t) sample;
|
||||
}
|
||||
}
|
||||
|
||||
void Volume::SetLevel(float level) {
|
||||
this->level = level;
|
||||
float db;
|
||||
if (level < 1.0f)
|
||||
db = -50.0f * (1.0f - level);
|
||||
else if (level > 1.0f && level <= 2.0f)
|
||||
db = 10.0f * (level - 1.0f);
|
||||
else
|
||||
db = 0.0f;
|
||||
multiplier = expf(db / 20.0f * logf(10.0f));
|
||||
}
|
||||
|
||||
float Volume::GetLevel() {
|
||||
return level;
|
||||
}
|
||||
83
TMessagesProj/jni/voip/libtgvoip/EchoCanceller.h
Executable file
83
TMessagesProj/jni/voip/libtgvoip/EchoCanceller.h
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
#include "threading.h"
|
||||
#include "Buffers.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "MediaStreamItf.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace webrtc{
|
||||
class AudioProcessing;
|
||||
class AudioFrame;
|
||||
}
|
||||
|
||||
namespace tgvoip{
|
||||
class EchoCanceller{
|
||||
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(EchoCanceller);
|
||||
EchoCanceller(bool enableAEC, bool enableNS, bool enableAGC);
|
||||
virtual ~EchoCanceller();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void SpeakerOutCallback(unsigned char* data, size_t len);
|
||||
void Enable(bool enabled);
|
||||
void ProcessInput(int16_t* inOut, size_t numSamples, bool& hasVoice);
|
||||
void SetAECStrength(int strength);
|
||||
void SetVoiceDetectionEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
bool enableAEC;
|
||||
bool enableAGC;
|
||||
bool enableNS;
|
||||
bool enableVAD=false;
|
||||
bool isOn;
|
||||
#ifndef TGVOIP_NO_DSP
|
||||
webrtc::AudioProcessing* apm=NULL;
|
||||
webrtc::AudioFrame* audioFrame=NULL;
|
||||
void RunBufferFarendThread();
|
||||
bool didBufferFarend;
|
||||
Thread* bufferFarendThread;
|
||||
BlockingQueue<int16_t*>* farendQueue;
|
||||
BufferPool* farendBufferPool;
|
||||
bool running;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace effects{
|
||||
|
||||
class AudioEffect{
|
||||
public:
|
||||
virtual ~AudioEffect()=0;
|
||||
virtual void Process(int16_t* inOut, size_t numSamples)=0;
|
||||
virtual void SetPassThrough(bool passThrough);
|
||||
protected:
|
||||
bool passThrough=false;
|
||||
};
|
||||
|
||||
class Volume : public AudioEffect{
|
||||
public:
|
||||
Volume();
|
||||
virtual ~Volume();
|
||||
virtual void Process(int16_t* inOut, size_t numSamples);
|
||||
/**
|
||||
* Level is (0.0, 2.0]
|
||||
*/
|
||||
void SetLevel(float level);
|
||||
float GetLevel();
|
||||
private:
|
||||
float level=1.0f;
|
||||
float multiplier=1.0f;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_ECHOCANCELLER_H
|
||||
463
TMessagesProj/jni/voip/libtgvoip/JitterBuffer.cpp
Executable file
463
TMessagesProj/jni/voip/libtgvoip/JitterBuffer.cpp
Executable file
|
|
@ -0,0 +1,463 @@
|
|||
//
|
||||
// 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 "VoIPController.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include "logging.h"
|
||||
#include "VoIPServerConfig.h"
|
||||
#include <math.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
JitterBuffer::JitterBuffer(MediaStreamItf *out, uint32_t step):bufferPool(JITTER_SLOT_SIZE, JITTER_SLOT_COUNT){
|
||||
if(out)
|
||||
out->SetCallback(JitterBuffer::CallbackOut, this);
|
||||
this->step=step;
|
||||
memset(slots, 0, sizeof(jitter_packet_t)*JITTER_SLOT_COUNT);
|
||||
if(step<30){
|
||||
minMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_min_delay_20", 6);
|
||||
maxMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_delay_20", 25);
|
||||
maxUsedSlots=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_slots_20", 50);
|
||||
}else if(step<50){
|
||||
minMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_min_delay_40", 4);
|
||||
maxMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_delay_40", 15);
|
||||
maxUsedSlots=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_slots_40", 30);
|
||||
}else{
|
||||
minMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_min_delay_60", 2);
|
||||
maxMinDelay=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_delay_60", 10);
|
||||
maxUsedSlots=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_max_slots_60", 20);
|
||||
}
|
||||
lossesToReset=(uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_losses_to_reset", 20);
|
||||
resyncThreshold=ServerConfig::GetSharedInstance()->GetDouble("jitter_resync_threshold", 1.0);
|
||||
#ifdef TGVOIP_DUMP_JITTER_STATS
|
||||
#ifdef TGVOIP_JITTER_DUMP_FILE
|
||||
dump=fopen(TGVOIP_JITTER_DUMP_FILE, "w");
|
||||
#elif defined(__ANDROID__)
|
||||
dump=fopen("/sdcard/tgvoip_jitter_dump.txt", "w");
|
||||
#else
|
||||
dump=fopen("tgvoip_jitter_dump.txt", "w");
|
||||
#endif
|
||||
tgvoip_log_file_write_header(dump);
|
||||
fprintf(dump, "PTS\tRTS\tNumInBuf\tAJitter\tADelay\tTDelay\n");
|
||||
#endif
|
||||
Reset();
|
||||
}
|
||||
|
||||
JitterBuffer::~JitterBuffer(){
|
||||
Reset();
|
||||
}
|
||||
|
||||
void JitterBuffer::SetMinPacketCount(uint32_t count){
|
||||
LOGI("jitter: set min packet count %u", count);
|
||||
minDelay=count;
|
||||
minMinDelay=count;
|
||||
//Reset();
|
||||
}
|
||||
|
||||
int JitterBuffer::GetMinPacketCount(){
|
||||
return (int)minDelay;
|
||||
}
|
||||
|
||||
size_t JitterBuffer::CallbackIn(unsigned char *data, size_t len, void *param){
|
||||
//((JitterBuffer*)param)->HandleInput(data, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t JitterBuffer::CallbackOut(unsigned char *data, size_t len, void *param){
|
||||
return 0; //((JitterBuffer*)param)->HandleOutput(data, len, 0, NULL);
|
||||
}
|
||||
|
||||
void JitterBuffer::HandleInput(unsigned char *data, size_t len, uint32_t timestamp, bool isEC){
|
||||
MutexGuard m(mutex);
|
||||
jitter_packet_t pkt;
|
||||
pkt.size=len;
|
||||
pkt.buffer=data;
|
||||
pkt.timestamp=timestamp;
|
||||
pkt.isEC=isEC;
|
||||
PutInternal(&pkt, !isEC);
|
||||
//LOGV("in, ts=%d, ec=%d", timestamp, isEC);
|
||||
|
||||
}
|
||||
|
||||
void JitterBuffer::Reset(){
|
||||
wasReset=true;
|
||||
needBuffering=true;
|
||||
lastPutTimestamp=0;
|
||||
int i;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer){
|
||||
bufferPool.Reuse(slots[i].buffer);
|
||||
slots[i].buffer=NULL;
|
||||
}
|
||||
}
|
||||
delayHistory.Reset();
|
||||
lateHistory.Reset();
|
||||
adjustingDelay=false;
|
||||
lostSinceReset=0;
|
||||
gotSinceReset=0;
|
||||
expectNextAtTime=0;
|
||||
deviationHistory.Reset();
|
||||
outstandingDelayChange=0;
|
||||
dontChangeDelay=0;
|
||||
}
|
||||
|
||||
|
||||
size_t JitterBuffer::HandleOutput(unsigned char *buffer, size_t len, int offsetInSteps, bool advance, int& playbackScaledDuration, bool& isEC){
|
||||
jitter_packet_t pkt;
|
||||
pkt.buffer=buffer;
|
||||
pkt.size=len;
|
||||
MutexGuard m(mutex);
|
||||
if(first){
|
||||
first=false;
|
||||
unsigned int delay=GetCurrentDelay();
|
||||
if(GetCurrentDelay()>5){
|
||||
LOGW("jitter: delay too big upon start (%u), dropping packets", delay);
|
||||
while(delay>GetMinPacketCount()){
|
||||
for(int i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].timestamp==nextTimestamp){
|
||||
if(slots[i].buffer){
|
||||
bufferPool.Reuse(slots[i].buffer);
|
||||
slots[i].buffer=NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Advance();
|
||||
delay--;
|
||||
}
|
||||
}
|
||||
}
|
||||
int result=GetInternal(&pkt, offsetInSteps, advance);
|
||||
if(outstandingDelayChange!=0){
|
||||
if(outstandingDelayChange<0){
|
||||
playbackScaledDuration=40;
|
||||
outstandingDelayChange+=20;
|
||||
}else{
|
||||
playbackScaledDuration=80;
|
||||
outstandingDelayChange-=20;
|
||||
}
|
||||
//LOGV("outstanding delay change: %d", outstandingDelayChange);
|
||||
}else if(advance && GetCurrentDelay()==0){
|
||||
//LOGV("stretching packet because the next one is late");
|
||||
playbackScaledDuration=80;
|
||||
}else{
|
||||
playbackScaledDuration=60;
|
||||
}
|
||||
if(result==JR_OK){
|
||||
isEC=pkt.isEC;
|
||||
return pkt.size;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int JitterBuffer::GetInternal(jitter_packet_t* pkt, int offset, bool advance){
|
||||
/*if(needBuffering && lastPutTimestamp<nextTimestamp){
|
||||
LOGV("jitter: don't have timestamp %lld, buffering", (long long int)nextTimestamp);
|
||||
Advance();
|
||||
return JR_BUFFERING;
|
||||
}*/
|
||||
|
||||
//needBuffering=false;
|
||||
|
||||
int64_t timestampToGet=nextTimestamp+offset*(int32_t)step;
|
||||
|
||||
int i;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer!=NULL && slots[i].timestamp==timestampToGet){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i<JITTER_SLOT_COUNT){
|
||||
if(pkt && pkt->size<slots[i].size){
|
||||
LOGE("jitter: packet won't fit into provided buffer of %d (need %d)", int(slots[i].size), int(pkt->size));
|
||||
}else{
|
||||
if(pkt) {
|
||||
pkt->size = slots[i].size;
|
||||
pkt->timestamp = slots[i].timestamp;
|
||||
memcpy(pkt->buffer, slots[i].buffer, slots[i].size);
|
||||
pkt->isEC=slots[i].isEC;
|
||||
}
|
||||
}
|
||||
bufferPool.Reuse(slots[i].buffer);
|
||||
slots[i].buffer=NULL;
|
||||
if(offset==0)
|
||||
Advance();
|
||||
lostCount=0;
|
||||
needBuffering=false;
|
||||
return JR_OK;
|
||||
}
|
||||
|
||||
LOGV("jitter: found no packet for timestamp %lld (last put = %d, lost = %d)", (long long int)timestampToGet, lastPutTimestamp, lostCount);
|
||||
|
||||
if(advance)
|
||||
Advance();
|
||||
|
||||
if(!needBuffering){
|
||||
lostCount++;
|
||||
if(offset==0){
|
||||
lostPackets++;
|
||||
lostSinceReset++;
|
||||
}
|
||||
if(lostCount>=lossesToReset || (gotSinceReset>minDelay*25 && lostSinceReset>gotSinceReset/2)){
|
||||
LOGW("jitter: lost %d packets in a row, resetting", lostCount);
|
||||
//minDelay++;
|
||||
dontIncMinDelay=16;
|
||||
dontDecMinDelay+=128;
|
||||
if(GetCurrentDelay()<minDelay)
|
||||
nextTimestamp-=(int64_t)(minDelay-GetCurrentDelay());
|
||||
lostCount=0;
|
||||
Reset();
|
||||
}
|
||||
|
||||
return JR_MISSING;
|
||||
}
|
||||
return JR_BUFFERING;
|
||||
}
|
||||
|
||||
void JitterBuffer::PutInternal(jitter_packet_t* pkt, bool overwriteExisting){
|
||||
if(pkt->size>JITTER_SLOT_SIZE){
|
||||
LOGE("The packet is too big to fit into the jitter buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer && slots[i].timestamp==pkt->timestamp){
|
||||
//LOGV("Found existing packet for timestamp %u, overwrite %d", pkt->timestamp, overwriteExisting);
|
||||
if(overwriteExisting){
|
||||
memcpy(slots[i].buffer, pkt->buffer, pkt->size);
|
||||
slots[i].size=pkt->size;
|
||||
slots[i].isEC=pkt->isEC;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
gotSinceReset++;
|
||||
if(wasReset){
|
||||
wasReset=false;
|
||||
outstandingDelayChange=0;
|
||||
nextTimestamp=(int64_t)(((int64_t)pkt->timestamp)-step*minDelay);
|
||||
first=true;
|
||||
LOGI("jitter: resyncing, next timestamp = %lld (step=%d, minDelay=%f)", (long long int)nextTimestamp, step, minDelay);
|
||||
}
|
||||
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer!=NULL){
|
||||
if(slots[i].timestamp<nextTimestamp-1){
|
||||
bufferPool.Reuse(slots[i].buffer);
|
||||
slots[i].buffer=NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*double prevTime=0;
|
||||
uint32_t closestTime=0;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer!=NULL && pkt->timestamp-slots[i].timestamp<pkt->timestamp-closestTime){
|
||||
closestTime=slots[i].timestamp;
|
||||
prevTime=slots[i].recvTime;
|
||||
}
|
||||
}*/
|
||||
double time=VoIPController::GetCurrentTime();
|
||||
if(expectNextAtTime!=0){
|
||||
double dev=expectNextAtTime-time;
|
||||
//LOGV("packet dev %f", dev);
|
||||
deviationHistory.Add(dev);
|
||||
expectNextAtTime+=step/1000.0;
|
||||
}else{
|
||||
expectNextAtTime=time+step/1000.0;
|
||||
}
|
||||
|
||||
if(pkt->timestamp<nextTimestamp){
|
||||
//LOGW("jitter: would drop packet with timestamp %d because it is late but not hopelessly", pkt->timestamp);
|
||||
latePacketCount++;
|
||||
lostPackets--;
|
||||
}else if(pkt->timestamp<nextTimestamp-1){
|
||||
//LOGW("jitter: dropping packet with timestamp %d because it is too late", pkt->timestamp);
|
||||
latePacketCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
if(pkt->timestamp>lastPutTimestamp)
|
||||
lastPutTimestamp=pkt->timestamp;
|
||||
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer==NULL)
|
||||
break;
|
||||
}
|
||||
if(i==JITTER_SLOT_COUNT || GetCurrentDelay()>=maxUsedSlots){
|
||||
int toRemove=JITTER_SLOT_COUNT;
|
||||
uint32_t bestTimestamp=0xFFFFFFFF;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer!=NULL && slots[i].timestamp<bestTimestamp){
|
||||
toRemove=i;
|
||||
bestTimestamp=slots[i].timestamp;
|
||||
}
|
||||
}
|
||||
Advance();
|
||||
bufferPool.Reuse(slots[toRemove].buffer);
|
||||
slots[toRemove].buffer=NULL;
|
||||
i=toRemove;
|
||||
}
|
||||
slots[i].timestamp=pkt->timestamp;
|
||||
slots[i].size=pkt->size;
|
||||
slots[i].buffer=bufferPool.Get();
|
||||
slots[i].recvTimeDiff=time-prevRecvTime;
|
||||
slots[i].isEC=pkt->isEC;
|
||||
if(slots[i].buffer)
|
||||
memcpy(slots[i].buffer, pkt->buffer, pkt->size);
|
||||
else
|
||||
LOGE("WTF!!");
|
||||
#ifdef TGVOIP_DUMP_JITTER_STATS
|
||||
fprintf(dump, "%u\t%.03f\t%d\t%.03f\t%.03f\t%.03f\n", pkt->timestamp, time, GetCurrentDelay(), lastMeasuredJitter, lastMeasuredDelay, minDelay);
|
||||
#endif
|
||||
prevRecvTime=time;
|
||||
}
|
||||
|
||||
|
||||
void JitterBuffer::Advance(){
|
||||
nextTimestamp+=step;
|
||||
}
|
||||
|
||||
|
||||
unsigned int JitterBuffer::GetCurrentDelay(){
|
||||
unsigned int delay=0;
|
||||
int i;
|
||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
||||
if(slots[i].buffer!=NULL)
|
||||
delay++;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
void JitterBuffer::Tick(){
|
||||
MutexGuard m(mutex);
|
||||
int i;
|
||||
|
||||
lateHistory.Add(latePacketCount);
|
||||
latePacketCount=0;
|
||||
bool absolutelyNoLatePackets=lateHistory.Max()==0;
|
||||
|
||||
double avgLate16=lateHistory.Average(16);
|
||||
//LOGV("jitter: avg late=%.1f, %.1f, %.1f", avgLate16, avgLate32, avgLate64);
|
||||
if(avgLate16>=resyncThreshold){
|
||||
LOGV("resyncing: avgLate16=%f, resyncThreshold=%f", avgLate16, resyncThreshold);
|
||||
wasReset=true;
|
||||
}
|
||||
|
||||
if(absolutelyNoLatePackets){
|
||||
if(dontDecMinDelay>0)
|
||||
dontDecMinDelay--;
|
||||
}
|
||||
|
||||
delayHistory.Add(GetCurrentDelay());
|
||||
avgDelay=delayHistory.Average(32);
|
||||
|
||||
double stddev=0;
|
||||
double avgdev=deviationHistory.Average();
|
||||
for(i=0;i<64;i++){
|
||||
double d=(deviationHistory[i]-avgdev);
|
||||
stddev+=(d*d);
|
||||
}
|
||||
stddev=sqrt(stddev/64);
|
||||
uint32_t stddevDelay=(uint32_t)ceil(stddev*2*1000/step);
|
||||
if(stddevDelay<minMinDelay)
|
||||
stddevDelay=minMinDelay;
|
||||
if(stddevDelay>maxMinDelay)
|
||||
stddevDelay=maxMinDelay;
|
||||
if(stddevDelay!=minDelay){
|
||||
int32_t diff=(int32_t)(stddevDelay-minDelay);
|
||||
if(diff>0){
|
||||
dontDecMinDelay=100;
|
||||
}
|
||||
if(diff<-1)
|
||||
diff=-1;
|
||||
if(diff>1)
|
||||
diff=1;
|
||||
if((diff>0 && dontIncMinDelay==0) || (diff<0 && dontDecMinDelay==0)){
|
||||
//nextTimestamp+=diff*(int32_t)step;
|
||||
minDelay+=diff;
|
||||
outstandingDelayChange+=diff*60;
|
||||
dontChangeDelay+=32;
|
||||
//LOGD("new delay from stddev %f", minDelay);
|
||||
if(diff<0){
|
||||
dontDecMinDelay+=25;
|
||||
}
|
||||
if(diff>0){
|
||||
dontIncMinDelay=25;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastMeasuredJitter=stddev;
|
||||
lastMeasuredDelay=stddevDelay;
|
||||
//LOGV("stddev=%.3f, avg=%.3f, ndelay=%d, dontDec=%u", stddev, avgdev, stddevDelay, dontDecMinDelay);
|
||||
if(dontChangeDelay==0){
|
||||
if(avgDelay>minDelay+0.5){
|
||||
outstandingDelayChange-=avgDelay>minDelay+2 ? 60 : 20;
|
||||
dontChangeDelay+=10;
|
||||
}else if(avgDelay<minDelay-0.3){
|
||||
outstandingDelayChange+=20;
|
||||
dontChangeDelay+=10;
|
||||
}
|
||||
}
|
||||
if(dontChangeDelay>0)
|
||||
dontChangeDelay--;
|
||||
|
||||
//LOGV("jitter: avg delay=%d, delay=%d, late16=%.1f, dontDecMinDelay=%d", avgDelay, delayHistory[0], avgLate16, dontDecMinDelay);
|
||||
/*if(!adjustingDelay) {
|
||||
if (((minDelay==1 ? (avgDelay>=3) : (avgDelay>=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);
|
||||
expectNextAtTime=0;
|
||||
if(GetCurrentDelay()<=minDelay || min<=minDelay){
|
||||
adjustingDelay = false;
|
||||
LOGI("jitter: done adjusting");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
tickCount++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void JitterBuffer::GetAverageLateCount(double *out){
|
||||
double avgLate64=lateHistory.Average(), avgLate32=lateHistory.Average(32), avgLate16=lateHistory.Average(16);
|
||||
out[0]=avgLate16;
|
||||
out[1]=avgLate32;
|
||||
out[2]=avgLate64;
|
||||
}
|
||||
|
||||
|
||||
int JitterBuffer::GetAndResetLostPacketCount(){
|
||||
MutexGuard m(mutex);
|
||||
int r=lostPackets;
|
||||
lostPackets=0;
|
||||
return r;
|
||||
}
|
||||
|
||||
double JitterBuffer::GetLastMeasuredJitter(){
|
||||
return lastMeasuredJitter;
|
||||
}
|
||||
|
||||
double JitterBuffer::GetLastMeasuredDelay(){
|
||||
return lastMeasuredDelay;
|
||||
}
|
||||
|
||||
double JitterBuffer::GetAverageDelay(){
|
||||
return avgDelay;
|
||||
}
|
||||
97
TMessagesProj/jni/voip/libtgvoip/JitterBuffer.h
Normal file
97
TMessagesProj/jni/voip/libtgvoip/JitterBuffer.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include "MediaStreamItf.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Buffers.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
|
||||
|
||||
|
||||
namespace tgvoip{
|
||||
class JitterBuffer{
|
||||
public:
|
||||
JitterBuffer(MediaStreamItf* out, uint32_t step);
|
||||
~JitterBuffer();
|
||||
void SetMinPacketCount(uint32_t count);
|
||||
int GetMinPacketCount();
|
||||
unsigned int GetCurrentDelay();
|
||||
double GetAverageDelay();
|
||||
void Reset();
|
||||
void HandleInput(unsigned char* data, size_t len, uint32_t timestamp, bool isEC);
|
||||
size_t HandleOutput(unsigned char* buffer, size_t len, int offsetInSteps, bool advance, int& playbackScaledDuration, bool& isEC);
|
||||
void Tick();
|
||||
void GetAverageLateCount(double* out);
|
||||
int GetAndResetLostPacketCount();
|
||||
double GetLastMeasuredJitter();
|
||||
double GetLastMeasuredDelay();
|
||||
|
||||
private:
|
||||
struct jitter_packet_t{
|
||||
unsigned char* buffer=NULL;
|
||||
size_t size;
|
||||
uint32_t timestamp;
|
||||
bool isEC;
|
||||
double recvTimeDiff;
|
||||
};
|
||||
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, bool overwriteExisting);
|
||||
int GetInternal(jitter_packet_t* pkt, int offset, bool advance);
|
||||
void Advance();
|
||||
|
||||
BufferPool bufferPool;
|
||||
Mutex mutex;
|
||||
jitter_packet_t slots[JITTER_SLOT_COUNT];
|
||||
int64_t nextTimestamp=0;
|
||||
uint32_t step;
|
||||
double minDelay=6;
|
||||
uint32_t minMinDelay;
|
||||
uint32_t maxMinDelay;
|
||||
uint32_t maxUsedSlots;
|
||||
uint32_t lastPutTimestamp;
|
||||
uint32_t lossesToReset;
|
||||
double resyncThreshold;
|
||||
unsigned int lostCount=0;
|
||||
unsigned int lostSinceReset=0;
|
||||
unsigned int gotSinceReset=0;
|
||||
bool wasReset=true;
|
||||
bool needBuffering=true;
|
||||
HistoricBuffer<int, 64, double> delayHistory;
|
||||
HistoricBuffer<int, 64, double> lateHistory;
|
||||
bool adjustingDelay=false;
|
||||
unsigned int tickCount=0;
|
||||
unsigned int latePacketCount=0;
|
||||
unsigned int dontIncMinDelay=0;
|
||||
unsigned int dontDecMinDelay=0;
|
||||
int lostPackets=0;
|
||||
double prevRecvTime=0;
|
||||
double expectNextAtTime=0;
|
||||
HistoricBuffer<double, 64> deviationHistory;
|
||||
double lastMeasuredJitter=0;
|
||||
double lastMeasuredDelay=0;
|
||||
int outstandingDelayChange=0;
|
||||
unsigned int dontChangeDelay=0;
|
||||
double avgDelay=0;
|
||||
bool first=true;
|
||||
#ifdef TGVOIP_DUMP_JITTER_STATS
|
||||
FILE* dump;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_JITTERBUFFER_H
|
||||
209
TMessagesProj/jni/voip/libtgvoip/MediaStreamItf.cpp
Normal file
209
TMessagesProj/jni/voip/libtgvoip/MediaStreamItf.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// 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 "logging.h"
|
||||
#include "MediaStreamItf.h"
|
||||
#include "EchoCanceller.h"
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
void MediaStreamItf::SetCallback(size_t (*f)(unsigned char *, size_t, void*), void* param){
|
||||
callback=f;
|
||||
callbackParam=param;
|
||||
}
|
||||
|
||||
size_t MediaStreamItf::InvokeCallback(unsigned char *data, size_t length){
|
||||
if(callback)
|
||||
return (*callback)(data, length, callbackParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AudioMixer::AudioMixer() : bufferPool(960*2, 16), processedQueue(16), semaphore(16, 0){
|
||||
running=false;
|
||||
}
|
||||
|
||||
AudioMixer::~AudioMixer(){
|
||||
}
|
||||
|
||||
void AudioMixer::SetOutput(MediaStreamItf* output){
|
||||
output->SetCallback(OutputCallback, this);
|
||||
}
|
||||
|
||||
void AudioMixer::Start(){
|
||||
assert(!running);
|
||||
running=true;
|
||||
thread=new Thread(std::bind(&AudioMixer::RunThread, this));
|
||||
thread->SetName("AudioMixer");
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void AudioMixer::Stop(){
|
||||
if(!running){
|
||||
LOGE("Tried to stop AudioMixer that wasn't started");
|
||||
return;
|
||||
}
|
||||
running=false;
|
||||
semaphore.Release();
|
||||
thread->Join();
|
||||
delete thread;
|
||||
thread=NULL;
|
||||
}
|
||||
|
||||
void AudioMixer::DoCallback(unsigned char *data, size_t length){
|
||||
//memset(data, 0, 960*2);
|
||||
//LOGD("audio mixer callback, %d inputs", inputs.size());
|
||||
if(processedQueue.Size()==0)
|
||||
semaphore.Release(2);
|
||||
else
|
||||
semaphore.Release();
|
||||
unsigned char* buf=processedQueue.GetBlocking();
|
||||
memcpy(data, buf, 960*2);
|
||||
bufferPool.Reuse(buf);
|
||||
}
|
||||
|
||||
size_t AudioMixer::OutputCallback(unsigned char *data, size_t length, void *arg){
|
||||
((AudioMixer*)arg)->DoCallback(data, length);
|
||||
return 960*2;
|
||||
}
|
||||
|
||||
void AudioMixer::AddInput(std::shared_ptr<MediaStreamItf> input){
|
||||
MutexGuard m(inputsMutex);
|
||||
MixerInput in;
|
||||
in.multiplier=1;
|
||||
in.source=input;
|
||||
inputs.push_back(in);
|
||||
}
|
||||
|
||||
void AudioMixer::RemoveInput(std::shared_ptr<MediaStreamItf> input){
|
||||
MutexGuard m(inputsMutex);
|
||||
for(std::vector<MixerInput>::iterator i=inputs.begin();i!=inputs.end();++i){
|
||||
if(i->source==input){
|
||||
inputs.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::SetInputVolume(std::shared_ptr<MediaStreamItf> input, float volumeDB){
|
||||
MutexGuard m(inputsMutex);
|
||||
for(std::vector<MixerInput>::iterator i=inputs.begin();i!=inputs.end();++i){
|
||||
if(i->source==input){
|
||||
if(volumeDB==-INFINITY)
|
||||
i->multiplier=0;
|
||||
else
|
||||
i->multiplier=expf(volumeDB/20.0f * logf(10.0f));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::RunThread(){
|
||||
LOGV("AudioMixer thread started");
|
||||
while(running){
|
||||
semaphore.Acquire();
|
||||
if(!running)
|
||||
break;
|
||||
|
||||
unsigned char* data=bufferPool.Get();
|
||||
//LOGV("Audio mixer processing a frame");
|
||||
if(!data){
|
||||
LOGE("AudioMixer: no buffers left");
|
||||
continue;
|
||||
}
|
||||
MutexGuard m(inputsMutex);
|
||||
int16_t* buf=reinterpret_cast<int16_t*>(data);
|
||||
int16_t input[960];
|
||||
float out[960];
|
||||
memset(out, 0, 960*4);
|
||||
int usedInputs=0;
|
||||
for(std::vector<MixerInput>::iterator in=inputs.begin();in!=inputs.end();++in){
|
||||
size_t res=in->source->InvokeCallback(reinterpret_cast<unsigned char*>(input), 960*2);
|
||||
if(!res || in->multiplier==0){
|
||||
//LOGV("AudioMixer: skipping silent packet");
|
||||
continue;
|
||||
}
|
||||
usedInputs++;
|
||||
float k=in->multiplier;
|
||||
if(k!=1){
|
||||
for(size_t i=0; i<960; i++){
|
||||
out[i]+=(float)input[i]*k;
|
||||
}
|
||||
}else{
|
||||
for(size_t i=0;i<960;i++){
|
||||
out[i]+=(float)input[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(usedInputs>0){
|
||||
for(size_t i=0; i<960; i++){
|
||||
if(out[i]>32767.0f)
|
||||
buf[i]=INT16_MAX;
|
||||
else if(out[i]<-32768.0f)
|
||||
buf[i]=INT16_MIN;
|
||||
else
|
||||
buf[i]=(int16_t)out[i];
|
||||
}
|
||||
}else{
|
||||
memset(data, 0, 960*2);
|
||||
}
|
||||
if(echoCanceller)
|
||||
echoCanceller->SpeakerOutCallback(data, 960*2);
|
||||
processedQueue.Put(data);
|
||||
}
|
||||
LOGI("======== audio mixer thread exiting =========");
|
||||
}
|
||||
|
||||
void AudioMixer::SetEchoCanceller(EchoCanceller *aec){
|
||||
echoCanceller=aec;
|
||||
}
|
||||
|
||||
AudioLevelMeter::AudioLevelMeter(){
|
||||
absMax=0;
|
||||
count=0;
|
||||
currentLevel=0;
|
||||
currentLevelFullRange=0;
|
||||
}
|
||||
|
||||
float AudioLevelMeter::GetLevel(){
|
||||
return currentLevel/9.0f;
|
||||
}
|
||||
|
||||
void AudioLevelMeter::Update(int16_t *samples, size_t count){
|
||||
// Number of bars on the indicator.
|
||||
// Note that the number of elements is specified because we are indexing it
|
||||
// in the range of 0-32
|
||||
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};
|
||||
int16_t absValue=0;
|
||||
for(unsigned int k=0;k<count;k++){
|
||||
int16_t absolute=(int16_t)abs(samples[k]);
|
||||
if (absolute>absValue)
|
||||
absValue=absolute;
|
||||
}
|
||||
|
||||
if(absValue>absMax)
|
||||
absMax = absValue;
|
||||
// Update level approximately 10 times per second
|
||||
if (this->count++==10){
|
||||
currentLevelFullRange=absMax;
|
||||
this->count=0;
|
||||
// Highest value for a int16_t is 0x7fff = 32767
|
||||
// Divide with 1000 to get in the range of 0-32 which is the range of
|
||||
// the permutation vector
|
||||
int32_t position=absMax/1000;
|
||||
// Make it less likely that the bar stays at position 0. I.e. only if
|
||||
// its in the range 0-250 (instead of 0-1000)
|
||||
/*if ((position==0) && (absMax>250)){
|
||||
position=1;
|
||||
}*/
|
||||
currentLevel=permutation[position];
|
||||
// Decay the absolute maximum (divide by 4)
|
||||
absMax >>= 2;
|
||||
}
|
||||
}
|
||||
88
TMessagesProj/jni/voip/libtgvoip/MediaStreamItf.h
Normal file
88
TMessagesProj/jni/voip/libtgvoip/MediaStreamItf.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// 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 <string.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include "threading.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Buffers.h"
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
class EchoCanceller;
|
||||
|
||||
class MediaStreamItf{
|
||||
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*)=NULL;
|
||||
void* callbackParam;
|
||||
};
|
||||
|
||||
class AudioMixer : public MediaStreamItf{
|
||||
public:
|
||||
AudioMixer();
|
||||
virtual ~AudioMixer();
|
||||
void SetOutput(MediaStreamItf* output);
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void AddInput(std::shared_ptr<MediaStreamItf> input);
|
||||
void RemoveInput(std::shared_ptr<MediaStreamItf> input);
|
||||
void SetInputVolume(std::shared_ptr<MediaStreamItf> input, float volumeDB);
|
||||
void SetEchoCanceller(EchoCanceller* aec);
|
||||
private:
|
||||
void RunThread();
|
||||
struct MixerInput{
|
||||
std::shared_ptr<MediaStreamItf> source;
|
||||
float multiplier;
|
||||
};
|
||||
Mutex inputsMutex;
|
||||
void DoCallback(unsigned char* data, size_t length);
|
||||
static size_t OutputCallback(unsigned char* data, size_t length, void* arg);
|
||||
std::vector<MixerInput> inputs;
|
||||
Thread* thread;
|
||||
BufferPool bufferPool;
|
||||
BlockingQueue<unsigned char*> processedQueue;
|
||||
Semaphore semaphore;
|
||||
EchoCanceller* echoCanceller;
|
||||
bool running;
|
||||
};
|
||||
|
||||
class CallbackWrapper : public MediaStreamItf{
|
||||
public:
|
||||
CallbackWrapper(){};
|
||||
virtual ~CallbackWrapper(){};
|
||||
virtual void Start(){};
|
||||
virtual void Stop(){};
|
||||
|
||||
};
|
||||
|
||||
class AudioLevelMeter{
|
||||
public:
|
||||
AudioLevelMeter();
|
||||
float GetLevel();
|
||||
void Update(int16_t* samples, size_t count);
|
||||
private:
|
||||
int16_t absMax;
|
||||
int16_t count;
|
||||
int8_t currentLevel;
|
||||
int16_t currentLevelFullRange;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#endif //LIBTGVOIP_MEDIASTREAMINPUT_H
|
||||
183
TMessagesProj/jni/voip/libtgvoip/MessageThread.cpp
Executable file
183
TMessagesProj/jni/voip/libtgvoip/MessageThread.cpp
Executable file
|
|
@ -0,0 +1,183 @@
|
|||
//
|
||||
// Created by Grishka on 17.06.2018.
|
||||
//
|
||||
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include "MessageThread.h"
|
||||
#include "VoIPController.h"
|
||||
#include "logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
MessageThread::MessageThread() : Thread(std::bind(&MessageThread::Run, this)){
|
||||
|
||||
SetName("MessageThread");
|
||||
|
||||
#ifdef _WIN32
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
|
||||
event=CreateEvent(NULL, false, false, NULL);
|
||||
#else
|
||||
event=CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
|
||||
#endif
|
||||
#else
|
||||
pthread_cond_init(&cond, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
MessageThread::~MessageThread(){
|
||||
Stop();
|
||||
#ifdef _WIN32
|
||||
CloseHandle(event);
|
||||
#else
|
||||
pthread_cond_destroy(&cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MessageThread::Stop(){
|
||||
if(running){
|
||||
running=false;
|
||||
#ifdef _WIN32
|
||||
SetEvent(event);
|
||||
#else
|
||||
pthread_cond_signal(&cond);
|
||||
#endif
|
||||
Join();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageThread::Run(){
|
||||
queueMutex.Lock();
|
||||
while(running){
|
||||
double currentTime=VoIPController::GetCurrentTime();
|
||||
double waitTimeout=queue.empty() ? DBL_MAX : (queue[0].deliverAt-currentTime);
|
||||
//LOGW("MessageThread wait timeout %f", waitTimeout);
|
||||
if(waitTimeout>0.0){
|
||||
#ifdef _WIN32
|
||||
queueMutex.Unlock();
|
||||
DWORD actualWaitTimeout=waitTimeout==DBL_MAX ? INFINITE : ((DWORD)round(waitTimeout*1000.0));
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
|
||||
WaitForSingleObject(event, actualWaitTimeout);
|
||||
#else
|
||||
WaitForSingleObjectEx(event, actualWaitTimeout, false);
|
||||
#endif
|
||||
// we don't really care if a context switch happens here and anything gets added to the queue by another thread
|
||||
// since any new no-delay messages will get delivered on this iteration anyway
|
||||
queueMutex.Lock();
|
||||
#else
|
||||
if(waitTimeout!=DBL_MAX){
|
||||
struct timeval now;
|
||||
struct timespec timeout;
|
||||
gettimeofday(&now, NULL);
|
||||
waitTimeout+=now.tv_sec;
|
||||
waitTimeout+=(now.tv_usec/1000000.0);
|
||||
timeout.tv_sec=(time_t)(floor(waitTimeout));
|
||||
timeout.tv_nsec=(long)((waitTimeout-floor(waitTimeout))*1000000000.0);
|
||||
pthread_cond_timedwait(&cond, queueMutex.NativeHandle(), &timeout);
|
||||
}else{
|
||||
pthread_cond_wait(&cond, queueMutex.NativeHandle());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if(!running){
|
||||
queueMutex.Unlock();
|
||||
return;
|
||||
}
|
||||
currentTime=VoIPController::GetCurrentTime();
|
||||
std::vector<Message> msgsToDeliverNow;
|
||||
for(std::vector<Message>::iterator m=queue.begin();m!=queue.end();){
|
||||
if(m->deliverAt==0.0 || currentTime>=m->deliverAt){
|
||||
msgsToDeliverNow.push_back(*m);
|
||||
m=queue.erase(m);
|
||||
continue;
|
||||
}
|
||||
++m;
|
||||
}
|
||||
|
||||
for(Message& m:msgsToDeliverNow){
|
||||
//LOGI("MessageThread delivering %u", m.msg);
|
||||
cancelCurrent=false;
|
||||
if(m.deliverAt==0.0)
|
||||
m.deliverAt=VoIPController::GetCurrentTime();
|
||||
if(m.func!=nullptr){
|
||||
m.func();
|
||||
}
|
||||
if(!cancelCurrent && m.interval>0.0){
|
||||
m.deliverAt+=m.interval;
|
||||
InsertMessageInternal(m);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
queueMutex.Unlock();
|
||||
}
|
||||
|
||||
uint32_t MessageThread::Post(std::function<void()> func, double delay, double interval){
|
||||
assert(delay>=0);
|
||||
//LOGI("MessageThread post [function] delay %f", delay);
|
||||
if(!IsCurrent()){
|
||||
queueMutex.Lock();
|
||||
}
|
||||
double currentTime=VoIPController::GetCurrentTime();
|
||||
Message m{lastMessageID++, delay==0.0 ? 0.0 : (currentTime+delay), interval, func};
|
||||
InsertMessageInternal(m);
|
||||
if(!IsCurrent()){
|
||||
#ifdef _WIN32
|
||||
SetEvent(event);
|
||||
#else
|
||||
pthread_cond_signal(&cond);
|
||||
#endif
|
||||
queueMutex.Unlock();
|
||||
}
|
||||
return m.id;
|
||||
}
|
||||
|
||||
void MessageThread::InsertMessageInternal(MessageThread::Message &m){
|
||||
if(queue.empty()){
|
||||
queue.push_back(m);
|
||||
}else{
|
||||
if(queue[0].deliverAt>m.deliverAt){
|
||||
queue.insert(queue.begin(), m);
|
||||
}else{
|
||||
std::vector<Message>::iterator insertAfter=queue.begin();
|
||||
for(; insertAfter!=queue.end(); ++insertAfter){
|
||||
std::vector<Message>::iterator next=std::next(insertAfter);
|
||||
if(next==queue.end() || (next->deliverAt>m.deliverAt && insertAfter->deliverAt<=m.deliverAt)){
|
||||
queue.insert(next, m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageThread::Cancel(uint32_t id){
|
||||
if(!IsCurrent()){
|
||||
queueMutex.Lock();
|
||||
}
|
||||
|
||||
for(std::vector<Message>::iterator m=queue.begin();m!=queue.end();){
|
||||
if(m->id==id){
|
||||
m=queue.erase(m);
|
||||
}else{
|
||||
++m;
|
||||
}
|
||||
}
|
||||
|
||||
if(!IsCurrent()){
|
||||
queueMutex.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageThread::CancelSelf(){
|
||||
assert(IsCurrent());
|
||||
cancelCurrent=true;
|
||||
}
|
||||
52
TMessagesProj/jni/voip/libtgvoip/MessageThread.h
Executable file
52
TMessagesProj/jni/voip/libtgvoip/MessageThread.h
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Created by Grishka on 17.06.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_MESSAGETHREAD_H
|
||||
#define LIBTGVOIP_MESSAGETHREAD_H
|
||||
|
||||
#include "threading.h"
|
||||
#include "utils.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace tgvoip{
|
||||
class MessageThread : public Thread{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(MessageThread);
|
||||
MessageThread();
|
||||
virtual ~MessageThread();
|
||||
uint32_t Post(std::function<void()> func, double delay=0, double interval=0);
|
||||
void Cancel(uint32_t id);
|
||||
void CancelSelf();
|
||||
void Stop();
|
||||
|
||||
enum{
|
||||
INVALID_ID=0
|
||||
};
|
||||
private:
|
||||
struct Message{
|
||||
uint32_t id;
|
||||
double deliverAt;
|
||||
double interval;
|
||||
std::function<void()> func;
|
||||
};
|
||||
|
||||
void Run();
|
||||
void InsertMessageInternal(Message& m);
|
||||
|
||||
bool running=true;
|
||||
std::vector<Message> queue;
|
||||
Mutex queueMutex;
|
||||
uint32_t lastMessageID=1;
|
||||
bool cancelCurrent=false;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE event;
|
||||
#else
|
||||
pthread_cond_t cond;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_MESSAGETHREAD_H
|
||||
682
TMessagesProj/jni/voip/libtgvoip/NetworkSocket.cpp
Executable file
682
TMessagesProj/jni/voip/libtgvoip/NetworkSocket.cpp
Executable file
|
|
@ -0,0 +1,682 @@
|
|||
//
|
||||
// Created by Grishka on 29.03.17.
|
||||
//
|
||||
|
||||
#include "NetworkSocket.h"
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#if defined(_WIN32)
|
||||
#include "os/windows/NetworkSocketWinsock.h"
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include "os/posix/NetworkSocketPosix.h"
|
||||
#endif
|
||||
#include "logging.h"
|
||||
#include "VoIPServerConfig.h"
|
||||
#include "VoIPController.h"
|
||||
#include "Buffers.h"
|
||||
|
||||
#define MIN_UDP_PORT 16384
|
||||
#define MAX_UDP_PORT 32768
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
NetworkSocket::NetworkSocket(NetworkProtocol protocol) : protocol(protocol){
|
||||
ipv6Timeout=ServerConfig::GetSharedInstance()->GetDouble("nat64_fallback_timeout", 3);
|
||||
failed=false;
|
||||
}
|
||||
|
||||
NetworkSocket::~NetworkSocket(){
|
||||
|
||||
}
|
||||
|
||||
std::string NetworkSocket::GetLocalInterfaceInfo(IPv4Address *inet4addr, IPv6Address *inet6addr){
|
||||
std::string r="not implemented";
|
||||
return r;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocket::GenerateLocalPort(){
|
||||
uint16_t rnd;
|
||||
VoIPController::crypto.rand_bytes(reinterpret_cast<uint8_t*>(&rnd), 2);
|
||||
return (uint16_t) ((rnd%(MAX_UDP_PORT-MIN_UDP_PORT))+MIN_UDP_PORT);
|
||||
}
|
||||
|
||||
void NetworkSocket::SetMaxPriority(){
|
||||
}
|
||||
|
||||
bool NetworkSocket::IsFailed(){
|
||||
return failed;
|
||||
}
|
||||
|
||||
NetworkSocket *NetworkSocket::Create(NetworkProtocol protocol){
|
||||
#ifndef _WIN32
|
||||
return new NetworkSocketPosix(protocol);
|
||||
#else
|
||||
return new NetworkSocketWinsock(protocol);
|
||||
#endif
|
||||
}
|
||||
|
||||
IPv4Address *NetworkSocket::ResolveDomainName(std::string name){
|
||||
#ifndef _WIN32
|
||||
return NetworkSocketPosix::ResolveDomainName(name);
|
||||
#else
|
||||
return NetworkSocketWinsock::ResolveDomainName(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkSocket::GenerateTCPO2States(unsigned char* buffer, TCPO2State* recvState, TCPO2State* sendState){
|
||||
memset(recvState, 0, sizeof(TCPO2State));
|
||||
memset(sendState, 0, sizeof(TCPO2State));
|
||||
unsigned char nonce[64];
|
||||
uint32_t *first = reinterpret_cast<uint32_t*>(nonce), *second = first + 1;
|
||||
uint32_t first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU;
|
||||
uint32_t second1 = 0;
|
||||
do {
|
||||
VoIPController::crypto.rand_bytes(nonce, sizeof(nonce));
|
||||
} while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || *second == second1 || *reinterpret_cast<unsigned char*>(nonce) == 0xef);
|
||||
|
||||
// prepare encryption key/iv
|
||||
memcpy(sendState->key, nonce + 8, 32);
|
||||
memcpy(sendState->iv, nonce + 8 + 32, 16);
|
||||
|
||||
// prepare decryption key/iv
|
||||
char reversed[48];
|
||||
memcpy(reversed, nonce + 8, sizeof(reversed));
|
||||
std::reverse(reversed, reversed + sizeof(reversed));
|
||||
memcpy(recvState->key, reversed, 32);
|
||||
memcpy(recvState->iv, reversed + 32, 16);
|
||||
|
||||
// write protocol identifier
|
||||
*reinterpret_cast<uint32_t*>(nonce + 56) = 0xefefefefU;
|
||||
memcpy(buffer, nonce, 56);
|
||||
EncryptForTCPO2(nonce, sizeof(nonce), sendState);
|
||||
memcpy(buffer+56, nonce+56, 8);
|
||||
}
|
||||
|
||||
void NetworkSocket::EncryptForTCPO2(unsigned char *buffer, size_t len, TCPO2State *state){
|
||||
VoIPController::crypto.aes_ctr_encrypt(buffer, len, state->key, state->iv, state->ecount, &state->num);
|
||||
}
|
||||
|
||||
size_t NetworkSocket::Receive(unsigned char *buffer, size_t len){
|
||||
NetworkPacket pkt={0};
|
||||
pkt.data=buffer;
|
||||
pkt.length=len;
|
||||
Receive(&pkt);
|
||||
return pkt.length;
|
||||
}
|
||||
|
||||
size_t NetworkSocket::Send(unsigned char *buffer, size_t len){
|
||||
NetworkPacket pkt={0};
|
||||
pkt.data=buffer;
|
||||
pkt.length=len;
|
||||
Send(&pkt);
|
||||
return pkt.length;
|
||||
}
|
||||
|
||||
bool NetworkAddress::operator==(const NetworkAddress &other) const{
|
||||
const IPv4Address* self4=dynamic_cast<const IPv4Address*>(this);
|
||||
const IPv4Address* other4=dynamic_cast<const IPv4Address*>((NetworkAddress*)&other);
|
||||
if(self4 && other4){
|
||||
return self4->GetAddress()==other4->GetAddress();
|
||||
}
|
||||
const IPv6Address* self6=dynamic_cast<const IPv6Address*>(this);
|
||||
const IPv6Address* other6=dynamic_cast<const IPv6Address*>((NetworkAddress*)&other);
|
||||
if(self6 && other6){
|
||||
return memcmp(self6->GetAddress(), other6->GetAddress(), 16)==0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkAddress::operator!=(const NetworkAddress &other) const{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
IPv4Address::IPv4Address(std::string addr){
|
||||
#ifndef _WIN32
|
||||
this->address=NetworkSocketPosix::StringToV4Address(addr);
|
||||
#else
|
||||
this->address=NetworkSocketWinsock::StringToV4Address(addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
IPv4Address::IPv4Address(uint32_t addr){
|
||||
this->address=addr;
|
||||
}
|
||||
|
||||
IPv4Address::IPv4Address(){
|
||||
this->address=0;
|
||||
}
|
||||
|
||||
|
||||
std::string IPv4Address::ToString() const{
|
||||
#ifndef _WIN32
|
||||
return NetworkSocketPosix::V4AddressToString(address);
|
||||
#else
|
||||
return NetworkSocketWinsock::V4AddressToString(address);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IPv4Address::PrefixMatches(const unsigned int prefix, const NetworkAddress &other) const{
|
||||
const IPv4Address* v4=dynamic_cast<const IPv4Address*>(&other);
|
||||
if(v4){
|
||||
uint32_t mask=0xFFFFFFFF << (32-prefix);
|
||||
return (address & mask) == (v4->address & mask);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*sockaddr &IPv4Address::ToSockAddr(uint16_t port){
|
||||
sockaddr_in sa;
|
||||
sa.sin_family=AF_INET;
|
||||
sa.sin_addr=addr;
|
||||
sa.sin_port=port;
|
||||
return *((sockaddr *) &sa);
|
||||
}*/
|
||||
|
||||
uint32_t IPv4Address::GetAddress() const{
|
||||
return address;
|
||||
}
|
||||
|
||||
bool IPv4Address::IsEmpty() const{
|
||||
return address==0;
|
||||
}
|
||||
|
||||
IPv6Address::IPv6Address(std::string addr){
|
||||
#ifndef _WIN32
|
||||
NetworkSocketPosix::StringToV6Address(addr, this->address);
|
||||
#else
|
||||
NetworkSocketWinsock::StringToV6Address(addr, this->address);
|
||||
#endif
|
||||
}
|
||||
|
||||
IPv6Address::IPv6Address(const uint8_t* addr){
|
||||
memcpy(address, addr, 16);
|
||||
}
|
||||
|
||||
IPv6Address::IPv6Address(){
|
||||
memset(address, 0, 16);
|
||||
}
|
||||
|
||||
std::string IPv6Address::ToString() const{
|
||||
#ifndef _WIN32
|
||||
return NetworkSocketPosix::V6AddressToString(address);
|
||||
#else
|
||||
return NetworkSocketWinsock::V6AddressToString(address);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IPv6Address::PrefixMatches(const unsigned int prefix, const NetworkAddress &other) const{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IPv6Address::IsEmpty() const{
|
||||
const uint64_t* a=reinterpret_cast<const uint64_t*>(address);
|
||||
return a[0]==0LL && a[1]==0LL;
|
||||
}
|
||||
|
||||
/*sockaddr &IPv6Address::ToSockAddr(uint16_t port){
|
||||
sockaddr_in6 sa;
|
||||
sa.sin6_family=AF_INET6;
|
||||
sa.sin6_addr=addr;
|
||||
sa.sin6_port=port;
|
||||
return *((sockaddr *) &sa);
|
||||
}*/
|
||||
|
||||
const uint8_t *IPv6Address::GetAddress() const{
|
||||
return address;
|
||||
}
|
||||
|
||||
bool NetworkSocket::Select(std::vector<NetworkSocket *> &readFds, std::vector<NetworkSocket*> &writeFds, std::vector<NetworkSocket *> &errorFds, SocketSelectCanceller *canceller){
|
||||
#ifndef _WIN32
|
||||
return NetworkSocketPosix::Select(readFds, writeFds, errorFds, canceller);
|
||||
#else
|
||||
return NetworkSocketWinsock::Select(readFds, writeFds, errorFds, canceller);
|
||||
#endif
|
||||
}
|
||||
|
||||
SocketSelectCanceller::~SocketSelectCanceller(){
|
||||
|
||||
}
|
||||
|
||||
SocketSelectCanceller *SocketSelectCanceller::Create(){
|
||||
#ifndef _WIN32
|
||||
return new SocketSelectCancellerPosix();
|
||||
#else
|
||||
return new SocketSelectCancellerWin32();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
NetworkSocketTCPObfuscated::NetworkSocketTCPObfuscated(NetworkSocket *wrapped) : NetworkSocketWrapper(PROTO_TCP){
|
||||
this->wrapped=wrapped;
|
||||
}
|
||||
|
||||
NetworkSocketTCPObfuscated::~NetworkSocketTCPObfuscated(){
|
||||
if(wrapped)
|
||||
delete wrapped;
|
||||
}
|
||||
|
||||
NetworkSocket *NetworkSocketTCPObfuscated::GetWrapped(){
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::InitConnection(){
|
||||
unsigned char buf[64];
|
||||
GenerateTCPO2States(buf, &recvState, &sendState);
|
||||
wrapped->Send(buf, 64);
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::Send(NetworkPacket *packet){
|
||||
BufferOutputStream os(packet->length+4);
|
||||
size_t len=packet->length/4;
|
||||
if(len<0x7F){
|
||||
os.WriteByte((unsigned char)len);
|
||||
}else{
|
||||
os.WriteByte(0x7F);
|
||||
os.WriteByte((unsigned char)(len & 0xFF));
|
||||
os.WriteByte((unsigned char)((len >> 8) & 0xFF));
|
||||
os.WriteByte((unsigned char)((len >> 16) & 0xFF));
|
||||
}
|
||||
os.WriteBytes(packet->data, packet->length);
|
||||
EncryptForTCPO2(os.GetBuffer(), os.GetLength(), &sendState);
|
||||
wrapped->Send(os.GetBuffer(), os.GetLength());
|
||||
//LOGD("Sent %u bytes", os.GetLength());
|
||||
}
|
||||
|
||||
bool NetworkSocketTCPObfuscated::OnReadyToSend(){
|
||||
if(!initialized){
|
||||
//LOGV("Initializing TCPO2 connection");
|
||||
initialized=true;
|
||||
InitConnection();
|
||||
readyToSend=true;
|
||||
return false;
|
||||
}
|
||||
return wrapped->OnReadyToSend();
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::Receive(NetworkPacket *packet){
|
||||
unsigned char len1;
|
||||
size_t packetLen=0;
|
||||
size_t offset=0;
|
||||
size_t len;
|
||||
len=wrapped->Receive(&len1, 1);
|
||||
if(len<=0){
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
EncryptForTCPO2(&len1, 1, &recvState);
|
||||
|
||||
if(len1<0x7F){
|
||||
packetLen=(size_t)len1*4;
|
||||
}else{
|
||||
unsigned char len2[3];
|
||||
len=wrapped->Receive(len2, 3);
|
||||
if(len<=0){
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
EncryptForTCPO2(len2, 3, &recvState);
|
||||
packetLen=((size_t)len2[0] | ((size_t)len2[1] << 8) | ((size_t)len2[2] << 16))*4;
|
||||
}
|
||||
|
||||
if(packetLen>packet->length){
|
||||
LOGW("packet too big to fit into buffer (%u vs %u)", (unsigned int)packetLen, (unsigned int)packet->length);
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
|
||||
while(offset<packetLen){
|
||||
len=wrapped->Receive(packet->data+offset, packetLen-offset);
|
||||
if(len<=0){
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
offset+=len;
|
||||
}
|
||||
EncryptForTCPO2(packet->data, packetLen, &recvState);
|
||||
//packet->address=&itr->address;
|
||||
packet->length=packetLen;
|
||||
//packet->port=itr->port;
|
||||
packet->protocol=PROTO_TCP;
|
||||
packet->address=wrapped->GetConnectedAddress();
|
||||
packet->port=wrapped->GetConnectedPort();
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::Open(){
|
||||
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::Close(){
|
||||
wrapped->Close();
|
||||
}
|
||||
|
||||
void NetworkSocketTCPObfuscated::Connect(const NetworkAddress *address, uint16_t port){
|
||||
wrapped->Connect(address, port);
|
||||
}
|
||||
|
||||
bool NetworkSocketTCPObfuscated::IsFailed(){
|
||||
return wrapped->IsFailed();
|
||||
}
|
||||
|
||||
NetworkSocketSOCKS5Proxy::NetworkSocketSOCKS5Proxy(NetworkSocket *tcp, NetworkSocket *udp, std::string username, std::string password) : NetworkSocketWrapper(udp ? PROTO_UDP : PROTO_TCP){
|
||||
this->tcp=tcp;
|
||||
this->udp=udp;
|
||||
this->username=std::move(username);
|
||||
this->password=std::move(password);
|
||||
connectedAddress=NULL;
|
||||
}
|
||||
|
||||
NetworkSocketSOCKS5Proxy::~NetworkSocketSOCKS5Proxy(){
|
||||
delete tcp;
|
||||
if(connectedAddress)
|
||||
delete connectedAddress;
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::Send(NetworkPacket *packet){
|
||||
if(protocol==PROTO_TCP){
|
||||
tcp->Send(packet);
|
||||
}else if(protocol==PROTO_UDP){
|
||||
unsigned char buf[1500];
|
||||
BufferOutputStream out(buf, sizeof(buf));
|
||||
out.WriteInt16(0); // RSV
|
||||
out.WriteByte(0); // FRAG
|
||||
const IPv4Address* v4=dynamic_cast<IPv4Address*>(packet->address);
|
||||
const IPv6Address* v6=dynamic_cast<IPv6Address*>(packet->address);
|
||||
if(v4){
|
||||
out.WriteByte(1); // ATYP (IPv4)
|
||||
out.WriteInt32(v4->GetAddress());
|
||||
}else{
|
||||
out.WriteByte(4); // ATYP (IPv6)
|
||||
out.WriteBytes((unsigned char *) v6->GetAddress(), 16);
|
||||
}
|
||||
out.WriteInt16(htons(packet->port));
|
||||
out.WriteBytes(packet->data, packet->length);
|
||||
NetworkPacket p={0};
|
||||
p.data=buf;
|
||||
p.length=out.GetLength();
|
||||
p.address=connectedAddress;
|
||||
p.port=connectedPort;
|
||||
p.protocol=PROTO_UDP;
|
||||
udp->Send(&p);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::Receive(NetworkPacket *packet){
|
||||
if(protocol==PROTO_TCP){
|
||||
tcp->Receive(packet);
|
||||
packet->address=connectedAddress;
|
||||
packet->port=connectedPort;
|
||||
}else if(protocol==PROTO_UDP){
|
||||
unsigned char buf[1500];
|
||||
NetworkPacket p={0};
|
||||
p.data=buf;
|
||||
p.length=sizeof(buf);
|
||||
udp->Receive(&p);
|
||||
if(p.length && p.address && *p.address==*connectedAddress && p.port==connectedPort){
|
||||
BufferInputStream in(buf, p.length);
|
||||
in.ReadInt16(); // RSV
|
||||
in.ReadByte(); // FRAG
|
||||
unsigned char atyp=in.ReadByte();
|
||||
if(atyp==1){ // IPv4
|
||||
lastRecvdV4=IPv4Address((uint32_t) in.ReadInt32());
|
||||
packet->address=&lastRecvdV4;
|
||||
}else if(atyp==4){ // IPv6
|
||||
unsigned char addr[16];
|
||||
in.ReadBytes(addr, 16);
|
||||
lastRecvdV6=IPv6Address(addr);
|
||||
packet->address=&lastRecvdV6;
|
||||
}
|
||||
packet->port=ntohs(in.ReadInt16());
|
||||
if(packet->length>=in.Remaining()){
|
||||
packet->length=in.Remaining();
|
||||
in.ReadBytes(packet->data, in.Remaining());
|
||||
}else{
|
||||
packet->length=0;
|
||||
LOGW("socks5: received packet too big");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::Open(){
|
||||
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::Close(){
|
||||
tcp->Close();
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::Connect(const NetworkAddress *address, uint16_t port){
|
||||
const IPv4Address* v4=dynamic_cast<const IPv4Address*>(address);
|
||||
const IPv6Address* v6=dynamic_cast<const IPv6Address*>(address);
|
||||
connectedAddress=v4 ? (NetworkAddress*)new IPv4Address(*v4) : (NetworkAddress*)new IPv6Address(*v6);
|
||||
connectedPort=port;
|
||||
}
|
||||
|
||||
NetworkSocket *NetworkSocketSOCKS5Proxy::GetWrapped(){
|
||||
return protocol==PROTO_TCP ? tcp : udp;
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::InitConnection(){
|
||||
}
|
||||
|
||||
bool NetworkSocketSOCKS5Proxy::IsFailed(){
|
||||
return NetworkSocket::IsFailed() || tcp->IsFailed();
|
||||
}
|
||||
|
||||
NetworkAddress *NetworkSocketSOCKS5Proxy::GetConnectedAddress(){
|
||||
return connectedAddress;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocketSOCKS5Proxy::GetConnectedPort(){
|
||||
return connectedPort;
|
||||
}
|
||||
|
||||
bool NetworkSocketSOCKS5Proxy::OnReadyToSend(){
|
||||
//LOGV("on ready to send, state=%d", state);
|
||||
unsigned char buf[1024];
|
||||
if(state==ConnectionState::Initial){
|
||||
BufferOutputStream p(buf, sizeof(buf));
|
||||
p.WriteByte(5); // VER
|
||||
if(!username.empty()){
|
||||
p.WriteByte(2); // NMETHODS
|
||||
p.WriteByte(0); // no auth
|
||||
p.WriteByte(2); // user/pass
|
||||
}else{
|
||||
p.WriteByte(1); // NMETHODS
|
||||
p.WriteByte(0); // no auth
|
||||
}
|
||||
tcp->Send(buf, p.GetLength());
|
||||
state=ConnectionState::WaitingForAuthMethod;
|
||||
return false;
|
||||
}
|
||||
return udp ? udp->OnReadyToSend() : tcp->OnReadyToSend();
|
||||
}
|
||||
|
||||
bool NetworkSocketSOCKS5Proxy::OnReadyToReceive(){
|
||||
//LOGV("on ready to receive state=%d", state);
|
||||
unsigned char buf[1024];
|
||||
if(state==ConnectionState::WaitingForAuthMethod){
|
||||
size_t l=tcp->Receive(buf, sizeof(buf));
|
||||
if(l<2 || tcp->IsFailed()){
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
BufferInputStream in(buf, l);
|
||||
unsigned char ver=in.ReadByte();
|
||||
unsigned char chosenMethod=in.ReadByte();
|
||||
LOGV("socks5: VER=%02X, METHOD=%02X", ver, chosenMethod);
|
||||
if(ver!=5){
|
||||
LOGW("socks5: incorrect VER in response");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
if(chosenMethod==0){
|
||||
// connected, no further auth needed
|
||||
SendConnectionCommand();
|
||||
}else if(chosenMethod==2 && !username.empty()){
|
||||
BufferOutputStream p(buf, sizeof(buf));
|
||||
p.WriteByte(1); // VER
|
||||
p.WriteByte((unsigned char)(username.length()>255 ? 255 : username.length())); // ULEN
|
||||
p.WriteBytes((unsigned char*)username.c_str(), username.length()>255 ? 255 : username.length()); // UNAME
|
||||
p.WriteByte((unsigned char)(password.length()>255 ? 255 : password.length())); // PLEN
|
||||
p.WriteBytes((unsigned char*)password.c_str(), password.length()>255 ? 255 : password.length()); // PASSWD
|
||||
tcp->Send(buf, p.GetLength());
|
||||
state=ConnectionState::WaitingForAuthResult;
|
||||
}else{
|
||||
LOGW("socks5: unsupported auth method");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}else if(state==ConnectionState::WaitingForAuthResult){
|
||||
size_t l=tcp->Receive(buf, sizeof(buf));
|
||||
if(l<2 || tcp->IsFailed()){
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
BufferInputStream in(buf, l);
|
||||
uint8_t ver=in.ReadByte();
|
||||
unsigned char status=in.ReadByte();
|
||||
LOGV("socks5: auth response VER=%02X, STATUS=%02X", ver, status);
|
||||
if(ver!=1){
|
||||
LOGW("socks5: auth response VER is incorrect");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
if(status!=0){
|
||||
LOGW("socks5: username/password auth failed");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
LOGV("socks5: authentication succeeded");
|
||||
SendConnectionCommand();
|
||||
return false;
|
||||
}else if(state==ConnectionState::WaitingForCommandResult){
|
||||
size_t l=tcp->Receive(buf, sizeof(buf));
|
||||
if(protocol==PROTO_TCP){
|
||||
if(l<2 || tcp->IsFailed()){
|
||||
LOGW("socks5: connect failed")
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
BufferInputStream in(buf, l);
|
||||
unsigned char ver=in.ReadByte();
|
||||
if(ver!=5){
|
||||
LOGW("socks5: connect: wrong ver in response");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
unsigned char rep=in.ReadByte();
|
||||
if(rep!=0){
|
||||
LOGW("socks5: connect: failed with error %02X", rep);
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
LOGV("socks5: connect succeeded");
|
||||
state=ConnectionState::Connected;
|
||||
tcp=new NetworkSocketTCPObfuscated(tcp);
|
||||
readyToSend=true;
|
||||
return tcp->OnReadyToSend();
|
||||
}else if(protocol==PROTO_UDP){
|
||||
if(l<2 || tcp->IsFailed()){
|
||||
LOGW("socks5: udp associate failed");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
try{
|
||||
BufferInputStream in(buf, l);
|
||||
unsigned char ver=in.ReadByte();
|
||||
unsigned char rep=in.ReadByte();
|
||||
if(ver!=5){
|
||||
LOGW("socks5: udp associate: wrong ver in response");
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
if(rep!=0){
|
||||
LOGW("socks5: udp associate failed with error %02X", rep);
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
in.ReadByte(); // RSV
|
||||
unsigned char atyp=in.ReadByte();
|
||||
if(atyp==1){
|
||||
uint32_t addr=(uint32_t) in.ReadInt32();
|
||||
connectedAddress=new IPv4Address(addr);
|
||||
}else if(atyp==3){
|
||||
unsigned char len=in.ReadByte();
|
||||
char domain[256];
|
||||
memset(domain, 0, sizeof(domain));
|
||||
in.ReadBytes((unsigned char*)domain, len);
|
||||
LOGD("address type is domain, address=%s", domain);
|
||||
connectedAddress=ResolveDomainName(std::string(domain));
|
||||
if(!connectedAddress){
|
||||
LOGW("socks5: failed to resolve domain name '%s'", domain);
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
}else if(atyp==4){
|
||||
unsigned char addr[16];
|
||||
in.ReadBytes(addr, 16);
|
||||
connectedAddress=new IPv6Address(addr);
|
||||
}else{
|
||||
LOGW("socks5: unknown address type %d", atyp);
|
||||
failed=true;
|
||||
return false;
|
||||
}
|
||||
connectedPort=(uint16_t)ntohs(in.ReadInt16());
|
||||
state=ConnectionState::Connected;
|
||||
readyToSend=true;
|
||||
LOGV("socks5: udp associate successful, given endpoint %s:%d", connectedAddress->ToString().c_str(), connectedPort);
|
||||
}catch(std::out_of_range& x){
|
||||
LOGW("socks5: udp associate response parse failed");
|
||||
failed=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return udp ? udp->OnReadyToReceive() : tcp->OnReadyToReceive();
|
||||
}
|
||||
|
||||
void NetworkSocketSOCKS5Proxy::SendConnectionCommand(){
|
||||
unsigned char buf[1024];
|
||||
if(protocol==PROTO_TCP){
|
||||
BufferOutputStream out(buf, sizeof(buf));
|
||||
out.WriteByte(5); // VER
|
||||
out.WriteByte(1); // CMD (CONNECT)
|
||||
out.WriteByte(0); // RSV
|
||||
const IPv4Address* v4=dynamic_cast<const IPv4Address*>(connectedAddress);
|
||||
const IPv6Address* v6=dynamic_cast<const IPv6Address*>(connectedAddress);
|
||||
if(v4){
|
||||
out.WriteByte(1); // ATYP (IPv4)
|
||||
out.WriteInt32(v4->GetAddress());
|
||||
}else if(v6){
|
||||
out.WriteByte(4); // ATYP (IPv6)
|
||||
out.WriteBytes((unsigned char*)v6->GetAddress(), 16);
|
||||
}else{
|
||||
LOGW("socks5: unknown address type");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
out.WriteInt16(htons(connectedPort)); // DST.PORT
|
||||
tcp->Send(buf, out.GetLength());
|
||||
state=ConnectionState::WaitingForCommandResult;
|
||||
}else if(protocol==PROTO_UDP){
|
||||
LOGV("Sending udp associate");
|
||||
BufferOutputStream out(buf, sizeof(buf));
|
||||
out.WriteByte(5); // VER
|
||||
out.WriteByte(3); // CMD (UDP ASSOCIATE)
|
||||
out.WriteByte(0); // RSV
|
||||
out.WriteByte(1); // ATYP (IPv4)
|
||||
out.WriteInt32(0); // DST.ADDR
|
||||
out.WriteInt16(0); // DST.PORT
|
||||
tcp->Send(buf, out.GetLength());
|
||||
state=ConnectionState::WaitingForCommandResult;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSocketSOCKS5Proxy::NeedSelectForSending(){
|
||||
return state==ConnectionState::Initial || state==ConnectionState::Connected;
|
||||
}
|
||||
210
TMessagesProj/jni/voip/libtgvoip/NetworkSocket.h
Executable file
210
TMessagesProj/jni/voip/libtgvoip/NetworkSocket.h
Executable file
|
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// Created by Grishka on 29.03.17.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_NETWORKSOCKET_H
|
||||
#define LIBTGVOIP_NETWORKSOCKET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "utils.h"
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
enum NetworkProtocol{
|
||||
PROTO_UDP=0,
|
||||
PROTO_TCP
|
||||
};
|
||||
|
||||
struct TCPO2State{
|
||||
unsigned char key[32];
|
||||
unsigned char iv[16];
|
||||
unsigned char ecount[16];
|
||||
uint32_t num;
|
||||
};
|
||||
|
||||
class NetworkAddress{
|
||||
public:
|
||||
virtual std::string ToString() const =0;
|
||||
bool operator==(const NetworkAddress& other) const;
|
||||
bool operator!=(const NetworkAddress& other) const;
|
||||
virtual ~NetworkAddress()=default;
|
||||
virtual bool IsEmpty() const =0;
|
||||
virtual bool PrefixMatches(const unsigned int prefix, const NetworkAddress& other) const =0;
|
||||
};
|
||||
|
||||
class IPv4Address : public NetworkAddress{
|
||||
public:
|
||||
IPv4Address(std::string addr);
|
||||
IPv4Address(uint32_t addr);
|
||||
IPv4Address();
|
||||
virtual std::string ToString() const override;
|
||||
uint32_t GetAddress() const;
|
||||
virtual bool IsEmpty() const override;
|
||||
virtual bool PrefixMatches(const unsigned int prefix, const NetworkAddress& other) const override;
|
||||
|
||||
static const IPv4Address Broadcast(){
|
||||
return IPv4Address(0xFFFFFFFF);
|
||||
}
|
||||
private:
|
||||
uint32_t address;
|
||||
};
|
||||
|
||||
class IPv6Address : public NetworkAddress{
|
||||
public:
|
||||
IPv6Address(std::string addr);
|
||||
IPv6Address(const uint8_t* addr);
|
||||
IPv6Address();
|
||||
virtual std::string ToString() const override;
|
||||
const uint8_t* GetAddress() const;
|
||||
virtual bool IsEmpty() const override;
|
||||
virtual bool PrefixMatches(const unsigned int prefix, const NetworkAddress& other) const override;
|
||||
private:
|
||||
uint8_t address[16];
|
||||
};
|
||||
|
||||
struct NetworkPacket{
|
||||
unsigned char* data;
|
||||
size_t length;
|
||||
NetworkAddress* address;
|
||||
uint16_t port;
|
||||
NetworkProtocol protocol;
|
||||
};
|
||||
typedef struct NetworkPacket NetworkPacket;
|
||||
|
||||
class SocketSelectCanceller{
|
||||
public:
|
||||
virtual ~SocketSelectCanceller();
|
||||
virtual void CancelSelect()=0;
|
||||
static SocketSelectCanceller* Create();
|
||||
};
|
||||
|
||||
class NetworkSocket{
|
||||
public:
|
||||
friend class NetworkSocketPosix;
|
||||
friend class NetworkSocketWinsock;
|
||||
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(NetworkSocket);
|
||||
NetworkSocket(NetworkProtocol protocol);
|
||||
virtual ~NetworkSocket();
|
||||
virtual void Send(NetworkPacket* packet)=0;
|
||||
virtual void Receive(NetworkPacket* packet)=0;
|
||||
size_t Receive(unsigned char* buffer, size_t len);
|
||||
size_t Send(unsigned char* buffer, size_t len);
|
||||
virtual void Open()=0;
|
||||
virtual void Close()=0;
|
||||
virtual uint16_t GetLocalPort(){ return 0; };
|
||||
virtual void Connect(const NetworkAddress* address, uint16_t port)=0;
|
||||
virtual std::string GetLocalInterfaceInfo(IPv4Address* inet4addr, IPv6Address* inet6addr);
|
||||
virtual void OnActiveInterfaceChanged(){};
|
||||
virtual NetworkAddress* GetConnectedAddress(){ return NULL; };
|
||||
virtual uint16_t GetConnectedPort(){ return 0; };
|
||||
virtual void SetTimeouts(int sendTimeout, int recvTimeout){};
|
||||
|
||||
virtual bool IsFailed();
|
||||
virtual bool IsReadyToSend(){
|
||||
return readyToSend;
|
||||
}
|
||||
virtual bool OnReadyToSend(){ readyToSend=true; return true; };
|
||||
virtual bool OnReadyToReceive(){ return true; };
|
||||
void SetTimeout(double timeout){
|
||||
this->timeout=timeout;
|
||||
};
|
||||
|
||||
static NetworkSocket* Create(NetworkProtocol protocol);
|
||||
static IPv4Address* ResolveDomainName(std::string name);
|
||||
static bool Select(std::vector<NetworkSocket*>& readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*>& errorFds, SocketSelectCanceller* canceller);
|
||||
|
||||
protected:
|
||||
virtual uint16_t GenerateLocalPort();
|
||||
virtual void SetMaxPriority();
|
||||
|
||||
static void GenerateTCPO2States(unsigned char* buffer, TCPO2State* recvState, TCPO2State* sendState);
|
||||
static void EncryptForTCPO2(unsigned char* buffer, size_t len, TCPO2State* state);
|
||||
double ipv6Timeout;
|
||||
unsigned char nat64Prefix[12];
|
||||
bool failed;
|
||||
bool readyToSend=false;
|
||||
double lastSuccessfulOperationTime=0.0;
|
||||
double timeout=0.0;
|
||||
NetworkProtocol protocol;
|
||||
};
|
||||
|
||||
class NetworkSocketWrapper : public NetworkSocket{
|
||||
public:
|
||||
NetworkSocketWrapper(NetworkProtocol protocol) : NetworkSocket(protocol){};
|
||||
virtual ~NetworkSocketWrapper(){};
|
||||
virtual NetworkSocket* GetWrapped()=0;
|
||||
virtual void InitConnection()=0;
|
||||
virtual void SetNonBlocking(bool){};
|
||||
};
|
||||
|
||||
class NetworkSocketTCPObfuscated : public NetworkSocketWrapper{
|
||||
public:
|
||||
NetworkSocketTCPObfuscated(NetworkSocket* wrapped);
|
||||
virtual ~NetworkSocketTCPObfuscated();
|
||||
virtual NetworkSocket* GetWrapped();
|
||||
virtual void InitConnection();
|
||||
virtual void Send(NetworkPacket *packet);
|
||||
virtual void Receive(NetworkPacket *packet);
|
||||
virtual void Open();
|
||||
virtual void Close();
|
||||
virtual void Connect(const NetworkAddress *address, uint16_t port);
|
||||
virtual bool OnReadyToSend();
|
||||
|
||||
virtual bool IsFailed();
|
||||
virtual bool IsReadyToSend(){
|
||||
return readyToSend && wrapped->IsReadyToSend();
|
||||
};
|
||||
|
||||
private:
|
||||
NetworkSocket* wrapped;
|
||||
TCPO2State recvState;
|
||||
TCPO2State sendState;
|
||||
bool initialized=false;
|
||||
};
|
||||
|
||||
class NetworkSocketSOCKS5Proxy : public NetworkSocketWrapper{
|
||||
public:
|
||||
NetworkSocketSOCKS5Proxy(NetworkSocket* tcp, NetworkSocket* udp, std::string username, std::string password);
|
||||
virtual ~NetworkSocketSOCKS5Proxy();
|
||||
virtual void Send(NetworkPacket *packet);
|
||||
virtual void Receive(NetworkPacket *packet);
|
||||
virtual void Open();
|
||||
virtual void Close();
|
||||
virtual void Connect(const NetworkAddress *address, uint16_t port);
|
||||
virtual NetworkSocket *GetWrapped();
|
||||
virtual void InitConnection();
|
||||
virtual bool IsFailed();
|
||||
virtual NetworkAddress *GetConnectedAddress();
|
||||
virtual uint16_t GetConnectedPort();
|
||||
virtual bool OnReadyToSend();
|
||||
virtual bool OnReadyToReceive();
|
||||
|
||||
bool NeedSelectForSending();
|
||||
|
||||
private:
|
||||
void SendConnectionCommand();
|
||||
enum ConnectionState{
|
||||
Initial,
|
||||
WaitingForAuthMethod,
|
||||
WaitingForAuthResult,
|
||||
WaitingForCommandResult,
|
||||
Connected
|
||||
};
|
||||
NetworkSocket* tcp;
|
||||
NetworkSocket* udp;
|
||||
std::string username;
|
||||
std::string password;
|
||||
NetworkAddress* connectedAddress;
|
||||
uint16_t connectedPort;
|
||||
ConnectionState state=ConnectionState::Initial;
|
||||
|
||||
IPv4Address lastRecvdV4;
|
||||
IPv6Address lastRecvdV6;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_NETWORKSOCKET_H
|
||||
290
TMessagesProj/jni/voip/libtgvoip/OpusDecoder.cpp
Executable file
290
TMessagesProj/jni/voip/libtgvoip/OpusDecoder.cpp
Executable file
|
|
@ -0,0 +1,290 @@
|
|||
//
|
||||
// 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 "audio/Resampler.h"
|
||||
#include "logging.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <opus/opus.h>
|
||||
#else
|
||||
#include "opus.h"
|
||||
#endif
|
||||
|
||||
#include "VoIPController.h"
|
||||
|
||||
#define PACKET_SIZE (960*2)
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
tgvoip::OpusDecoder::OpusDecoder(const std::shared_ptr<MediaStreamItf>& dst, bool isAsync, bool needEC){
|
||||
dst->SetCallback(OpusDecoder::Callback, this);
|
||||
Initialize(isAsync, needEC);
|
||||
}
|
||||
|
||||
tgvoip::OpusDecoder::OpusDecoder(const std::unique_ptr<MediaStreamItf>& dst, bool isAsync, bool needEC){
|
||||
dst->SetCallback(OpusDecoder::Callback, this);
|
||||
Initialize(isAsync, needEC);
|
||||
}
|
||||
|
||||
tgvoip::OpusDecoder::OpusDecoder(MediaStreamItf* dst, bool isAsync, bool needEC){
|
||||
dst->SetCallback(OpusDecoder::Callback, this);
|
||||
Initialize(isAsync, needEC);
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::Initialize(bool isAsync, bool needEC){
|
||||
async=isAsync;
|
||||
if(async){
|
||||
decodedQueue=new BlockingQueue<unsigned char*>(33);
|
||||
bufferPool=new BufferPool(PACKET_SIZE, 32);
|
||||
semaphore=new Semaphore(32, 0);
|
||||
}else{
|
||||
decodedQueue=NULL;
|
||||
bufferPool=NULL;
|
||||
semaphore=NULL;
|
||||
}
|
||||
dec=opus_decoder_create(48000, 1, NULL);
|
||||
if(needEC)
|
||||
ecDec=opus_decoder_create(48000, 1, NULL);
|
||||
else
|
||||
ecDec=NULL;
|
||||
buffer=(unsigned char *) malloc(8192);
|
||||
lastDecoded=NULL;
|
||||
outputBufferSize=0;
|
||||
echoCanceller=NULL;
|
||||
frameDuration=20;
|
||||
consecutiveLostPackets=0;
|
||||
enableDTX=false;
|
||||
silentPacketCount=0;
|
||||
levelMeter=NULL;
|
||||
nextLen=0;
|
||||
running=false;
|
||||
remainingDataLen=0;
|
||||
processedBuffer=NULL;
|
||||
prevWasEC=false;
|
||||
prevLastSample=0;
|
||||
}
|
||||
|
||||
tgvoip::OpusDecoder::~OpusDecoder(){
|
||||
opus_decoder_destroy(dec);
|
||||
if(ecDec)
|
||||
opus_decoder_destroy(ecDec);
|
||||
free(buffer);
|
||||
if(bufferPool)
|
||||
delete bufferPool;
|
||||
if(decodedQueue)
|
||||
delete decodedQueue;
|
||||
if(semaphore)
|
||||
delete semaphore;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusDecoder::SetEchoCanceller(EchoCanceller* canceller){
|
||||
echoCanceller=canceller;
|
||||
}
|
||||
|
||||
size_t tgvoip::OpusDecoder::Callback(unsigned char *data, size_t len, void *param){
|
||||
return ((OpusDecoder*)param)->HandleCallback(data, len);
|
||||
}
|
||||
|
||||
size_t tgvoip::OpusDecoder::HandleCallback(unsigned char *data, size_t len){
|
||||
if(async){
|
||||
if(!running){
|
||||
memset(data, 0, len);
|
||||
return 0;
|
||||
}
|
||||
if(outputBufferSize==0){
|
||||
outputBufferSize=len;
|
||||
int packetsNeeded;
|
||||
if(len>PACKET_SIZE)
|
||||
packetsNeeded=len/PACKET_SIZE;
|
||||
else
|
||||
packetsNeeded=1;
|
||||
packetsNeeded*=2;
|
||||
semaphore->Release(packetsNeeded);
|
||||
}
|
||||
assert(outputBufferSize==len && "output buffer size is supposed to be the same throughout callbacks");
|
||||
if(len==PACKET_SIZE){
|
||||
lastDecoded=(unsigned char *) decodedQueue->GetBlocking();
|
||||
if(!lastDecoded)
|
||||
return 0;
|
||||
memcpy(data, lastDecoded, PACKET_SIZE);
|
||||
bufferPool->Reuse(lastDecoded);
|
||||
semaphore->Release();
|
||||
if(silentPacketCount>0){
|
||||
silentPacketCount--;
|
||||
if(levelMeter)
|
||||
levelMeter->Update(reinterpret_cast<int16_t *>(data), 0);
|
||||
return 0;
|
||||
}
|
||||
if(echoCanceller){
|
||||
echoCanceller->SpeakerOutCallback(data, PACKET_SIZE);
|
||||
}
|
||||
}else{
|
||||
LOGE("Opus decoder buffer length != 960 samples");
|
||||
abort();
|
||||
}
|
||||
}else{
|
||||
if(remainingDataLen==0 && silentPacketCount==0){
|
||||
int duration=DecodeNextFrame();
|
||||
remainingDataLen=(size_t) (duration/20*960*2);
|
||||
}
|
||||
if(silentPacketCount>0 || remainingDataLen==0 || !processedBuffer){
|
||||
if(silentPacketCount>0)
|
||||
silentPacketCount--;
|
||||
memset(data, 0, 960*2);
|
||||
if(levelMeter)
|
||||
levelMeter->Update(reinterpret_cast<int16_t *>(data), 0);
|
||||
return 0;
|
||||
}
|
||||
memcpy(data, processedBuffer, 960*2);
|
||||
remainingDataLen-=960*2;
|
||||
if(remainingDataLen>0){
|
||||
memmove(processedBuffer, processedBuffer+960*2, remainingDataLen);
|
||||
}
|
||||
}
|
||||
if(levelMeter)
|
||||
levelMeter->Update(reinterpret_cast<int16_t *>(data), len/2);
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusDecoder::Start(){
|
||||
if(!async)
|
||||
return;
|
||||
running=true;
|
||||
thread=new Thread(std::bind(&tgvoip::OpusDecoder::RunThread, this));
|
||||
thread->SetName("opus_decoder");
|
||||
thread->SetMaxPriority();
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::Stop(){
|
||||
if(!running || !async)
|
||||
return;
|
||||
running=false;
|
||||
semaphore->Release();
|
||||
thread->Join();
|
||||
delete thread;
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::RunThread(){
|
||||
int i;
|
||||
LOGI("decoder: packets per frame %d", packetsPerFrame);
|
||||
while(running){
|
||||
int playbackDuration=DecodeNextFrame();
|
||||
for(i=0;i<playbackDuration/20;i++){
|
||||
semaphore->Acquire();
|
||||
if(!running){
|
||||
LOGI("==== decoder exiting ====");
|
||||
return;
|
||||
}
|
||||
unsigned char *buf=bufferPool->Get();
|
||||
if(buf){
|
||||
if(remainingDataLen>0){
|
||||
for(effects::AudioEffect*& effect:postProcEffects){
|
||||
effect->Process(reinterpret_cast<int16_t*>(processedBuffer+(PACKET_SIZE*i)), 960);
|
||||
}
|
||||
memcpy(buf, processedBuffer+(PACKET_SIZE*i), PACKET_SIZE);
|
||||
}else{
|
||||
//LOGE("Error decoding, result=%d", size);
|
||||
memset(buf, 0, PACKET_SIZE);
|
||||
}
|
||||
decodedQueue->Put(buf);
|
||||
}else{
|
||||
LOGW("decoder: no buffers left!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int tgvoip::OpusDecoder::DecodeNextFrame(){
|
||||
int playbackDuration=0;
|
||||
bool isEC=false;
|
||||
size_t len=jitterBuffer->HandleOutput(buffer, 8192, 0, true, playbackDuration, isEC);
|
||||
bool fec=false;
|
||||
if(!len){
|
||||
fec=true;
|
||||
len=jitterBuffer->HandleOutput(buffer, 8192, 0, false, playbackDuration, isEC);
|
||||
//if(len)
|
||||
// LOGV("Trying FEC...");
|
||||
}
|
||||
int size;
|
||||
if(len){
|
||||
size=opus_decode(isEC ? ecDec : dec, buffer, len, (opus_int16 *) decodeBuffer, packetsPerFrame*960, fec ? 1 : 0);
|
||||
consecutiveLostPackets=0;
|
||||
if(prevWasEC!=isEC && size){
|
||||
// It turns out the waveforms generated by the PLC feature are also great to help smooth out the
|
||||
// otherwise audible transition between the frames from different decoders. Those are basically an extrapolation
|
||||
// of the previous successfully decoded data -- which is exactly what we need here.
|
||||
size=opus_decode(prevWasEC ? ecDec : dec, NULL, 0, (opus_int16*)nextBuffer, packetsPerFrame*960, 0);
|
||||
if(size){
|
||||
int16_t* plcSamples=reinterpret_cast<int16_t*>(nextBuffer);
|
||||
int16_t* samples=reinterpret_cast<int16_t*>(decodeBuffer);
|
||||
constexpr float coeffs[]={0.999802, 0.995062, 0.984031, 0.966778, 0.943413, 0.914084, 0.878975, 0.838309, 0.792344,
|
||||
0.741368, 0.685706, 0.625708, 0.561754, 0.494249, 0.423619, 0.350311, 0.274788, 0.197527, 0.119018, 0.039757};
|
||||
for(int i=0;i<20;i++){
|
||||
samples[i]=(int16_t)round((plcSamples[i]*coeffs[i]+(float)samples[i]*(1.0-coeffs[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
prevWasEC=isEC;
|
||||
prevLastSample=decodeBuffer[size-1];
|
||||
}else{ // do packet loss concealment
|
||||
consecutiveLostPackets++;
|
||||
if(consecutiveLostPackets>2 && enableDTX){
|
||||
silentPacketCount+=packetsPerFrame;
|
||||
size=packetsPerFrame*960;
|
||||
}else{
|
||||
size=opus_decode(prevWasEC ? ecDec : dec, NULL, 0, (opus_int16 *) decodeBuffer, packetsPerFrame*960, 0);
|
||||
//LOGV("PLC");
|
||||
}
|
||||
}
|
||||
if(size<0)
|
||||
LOGW("decoder: opus_decode error %d", size);
|
||||
remainingDataLen=size;
|
||||
if(playbackDuration==80){
|
||||
processedBuffer=buffer;
|
||||
audio::Resampler::Rescale60To80((int16_t*) decodeBuffer, (int16_t*) processedBuffer);
|
||||
}else if(playbackDuration==40){
|
||||
processedBuffer=buffer;
|
||||
audio::Resampler::Rescale60To40((int16_t*) decodeBuffer, (int16_t*) processedBuffer);
|
||||
}else{
|
||||
processedBuffer=decodeBuffer;
|
||||
}
|
||||
return playbackDuration;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusDecoder::SetFrameDuration(uint32_t duration){
|
||||
frameDuration=duration;
|
||||
packetsPerFrame=frameDuration/20;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusDecoder::SetJitterBuffer(std::shared_ptr<JitterBuffer> jitterBuffer){
|
||||
this->jitterBuffer=jitterBuffer;
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::SetDTX(bool enable){
|
||||
enableDTX=enable;
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::SetLevelMeter(AudioLevelMeter *levelMeter){
|
||||
this->levelMeter=levelMeter;
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::AddAudioEffect(effects::AudioEffect *effect){
|
||||
postProcEffects.push_back(effect);
|
||||
}
|
||||
|
||||
void tgvoip::OpusDecoder::RemoveAudioEffect(effects::AudioEffect *effect){
|
||||
std::vector<effects::AudioEffect*>::iterator i=std::find(postProcEffects.begin(), postProcEffects.end(), effect);
|
||||
if(i!=postProcEffects.end())
|
||||
postProcEffects.erase(i);
|
||||
}
|
||||
80
TMessagesProj/jni/voip/libtgvoip/OpusDecoder.h
Executable file
80
TMessagesProj/jni/voip/libtgvoip/OpusDecoder.h
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// 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 "threading.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Buffers.h"
|
||||
#include "EchoCanceller.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include "utils.h"
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
struct OpusDecoder;
|
||||
|
||||
namespace tgvoip{
|
||||
class OpusDecoder {
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(OpusDecoder);
|
||||
virtual void Start();
|
||||
|
||||
virtual void Stop();
|
||||
|
||||
OpusDecoder(const std::shared_ptr<MediaStreamItf>& dst, bool isAsync, bool needEC);
|
||||
OpusDecoder(const std::unique_ptr<MediaStreamItf>& dst, bool isAsync, bool needEC);
|
||||
OpusDecoder(MediaStreamItf* dst, bool isAsync, bool needEC);
|
||||
virtual ~OpusDecoder();
|
||||
size_t HandleCallback(unsigned char* data, size_t len);
|
||||
void SetEchoCanceller(EchoCanceller* canceller);
|
||||
void SetFrameDuration(uint32_t duration);
|
||||
void SetJitterBuffer(std::shared_ptr<JitterBuffer> jitterBuffer);
|
||||
void SetDTX(bool enable);
|
||||
void SetLevelMeter(AudioLevelMeter* levelMeter);
|
||||
void AddAudioEffect(effects::AudioEffect* effect);
|
||||
void RemoveAudioEffect(effects::AudioEffect* effect);
|
||||
|
||||
private:
|
||||
void Initialize(bool isAsync, bool needEC);
|
||||
static size_t Callback(unsigned char* data, size_t len, void* param);
|
||||
void RunThread();
|
||||
int DecodeNextFrame();
|
||||
::OpusDecoder* dec;
|
||||
::OpusDecoder* ecDec;
|
||||
BlockingQueue<unsigned char*>* decodedQueue;
|
||||
BufferPool* bufferPool;
|
||||
unsigned char* buffer;
|
||||
unsigned char* lastDecoded;
|
||||
unsigned char* processedBuffer;
|
||||
size_t outputBufferSize;
|
||||
bool running;
|
||||
Thread* thread;
|
||||
Semaphore* semaphore;
|
||||
uint32_t frameDuration;
|
||||
EchoCanceller* echoCanceller;
|
||||
std::shared_ptr<JitterBuffer> jitterBuffer;
|
||||
AudioLevelMeter* levelMeter;
|
||||
int consecutiveLostPackets;
|
||||
bool enableDTX;
|
||||
size_t silentPacketCount;
|
||||
std::vector<effects::AudioEffect*> postProcEffects;
|
||||
bool async;
|
||||
unsigned char nextBuffer[8192];
|
||||
unsigned char decodeBuffer[8192];
|
||||
size_t nextLen;
|
||||
unsigned int packetsPerFrame;
|
||||
ptrdiff_t remainingDataLen;
|
||||
bool prevWasEC;
|
||||
int16_t prevLastSample;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_OPUSDECODER_H
|
||||
270
TMessagesProj/jni/voip/libtgvoip/OpusEncoder.cpp
Executable file
270
TMessagesProj/jni/voip/libtgvoip/OpusEncoder.cpp
Executable file
|
|
@ -0,0 +1,270 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include <algorithm>
|
||||
#include "logging.h"
|
||||
#include "VoIPServerConfig.h"
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <opus/opus.h>
|
||||
#else
|
||||
#include "opus.h"
|
||||
#endif
|
||||
|
||||
namespace{
|
||||
int serverConfigValueToBandwidth(int config){
|
||||
switch(config){
|
||||
case 0:
|
||||
return OPUS_BANDWIDTH_NARROWBAND;
|
||||
case 1:
|
||||
return OPUS_BANDWIDTH_MEDIUMBAND;
|
||||
case 2:
|
||||
return OPUS_BANDWIDTH_WIDEBAND;
|
||||
case 3:
|
||||
return OPUS_BANDWIDTH_SUPERWIDEBAND;
|
||||
case 4:
|
||||
default:
|
||||
return OPUS_BANDWIDTH_FULLBAND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tgvoip::OpusEncoder::OpusEncoder(MediaStreamItf *source, bool needSecondary):queue(11), bufferPool(960*2, 10){
|
||||
this->source=source;
|
||||
source->SetCallback(tgvoip::OpusEncoder::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(1));
|
||||
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=20000;
|
||||
currentBitrate=0;
|
||||
running=false;
|
||||
echoCanceller=NULL;
|
||||
complexity=10;
|
||||
frameDuration=20;
|
||||
levelMeter=NULL;
|
||||
vadNoVoiceBitrate=static_cast<uint32_t>(ServerConfig::GetSharedInstance()->GetInt("audio_vad_no_voice_bitrate", 6000));
|
||||
vadModeVoiceBandwidth=serverConfigValueToBandwidth(ServerConfig::GetSharedInstance()->GetInt("audio_vad_bandwidth", 3));
|
||||
vadModeNoVoiceBandwidth=serverConfigValueToBandwidth(ServerConfig::GetSharedInstance()->GetInt("audio_vad_no_voice_bandwidth", 0));
|
||||
secondaryEnabledBandwidth=serverConfigValueToBandwidth(ServerConfig::GetSharedInstance()->GetInt("audio_extra_ec_bandwidth", 2));
|
||||
secondaryEncoderEnabled=false;
|
||||
|
||||
if(needSecondary){
|
||||
secondaryEncoder=opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, NULL);
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_COMPLEXITY(10));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
|
||||
//opus_encoder_ctl(secondaryEncoder, OPUS_SET_VBR(0));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(8000));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BANDWIDTH(secondaryEnabledBandwidth));
|
||||
}else{
|
||||
secondaryEncoder=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
tgvoip::OpusEncoder::~OpusEncoder(){
|
||||
opus_encoder_destroy(enc);
|
||||
if(secondaryEncoder)
|
||||
opus_encoder_destroy(secondaryEncoder);
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::Start(){
|
||||
if(running)
|
||||
return;
|
||||
running=true;
|
||||
thread=new Thread(std::bind(&tgvoip::OpusEncoder::RunThread, this));
|
||||
thread->SetName("OpusEncoder");
|
||||
thread->Start();
|
||||
thread->SetMaxPriority();
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::Stop(){
|
||||
if(!running)
|
||||
return;
|
||||
running=false;
|
||||
queue.Put(NULL);
|
||||
thread->Join();
|
||||
delete thread;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusEncoder::SetBitrate(uint32_t bitrate){
|
||||
requestedBitrate=bitrate;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::Encode(int16_t* 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);
|
||||
}
|
||||
if(levelMeter)
|
||||
levelMeter->Update(data, len);
|
||||
if(secondaryEncoderEnabled!=wasSecondaryEncoderEnabled){
|
||||
wasSecondaryEncoderEnabled=secondaryEncoderEnabled;
|
||||
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(secondaryEncoderEnabled ? secondaryEnabledBandwidth : OPUS_BANDWIDTH_FULLBAND));
|
||||
}
|
||||
int32_t r=opus_encode(enc, data, static_cast<int>(len), buffer, 4096);
|
||||
if(r<=0){
|
||||
LOGE("Error encoding: %d", r);
|
||||
}else if(r==1){
|
||||
LOGW("DTX");
|
||||
}else if(running){
|
||||
//LOGV("Packet size = %d", r);
|
||||
int32_t secondaryLen=0;
|
||||
unsigned char secondaryBuffer[128];
|
||||
if(secondaryEncoderEnabled && secondaryEncoder){
|
||||
secondaryLen=opus_encode(secondaryEncoder, data, static_cast<int>(len), secondaryBuffer, sizeof(secondaryBuffer));
|
||||
//LOGV("secondaryLen %d", secondaryLen);
|
||||
}
|
||||
InvokeCallback(buffer, (size_t)r, secondaryBuffer, (size_t)secondaryLen);
|
||||
}
|
||||
}
|
||||
|
||||
size_t tgvoip::OpusEncoder::Callback(unsigned char *data, size_t len, void* param){
|
||||
OpusEncoder* e=(OpusEncoder*)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 tgvoip::OpusEncoder::GetBitrate(){
|
||||
return requestedBitrate;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetEchoCanceller(EchoCanceller* aec){
|
||||
echoCanceller=aec;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::RunThread(){
|
||||
uint32_t bufferedCount=0;
|
||||
uint32_t packetsPerFrame=frameDuration/20;
|
||||
LOGV("starting encoder, packets per frame=%d", packetsPerFrame);
|
||||
int16_t* frame;
|
||||
if(packetsPerFrame>1)
|
||||
frame=(int16_t*) malloc(960*2*packetsPerFrame);
|
||||
else
|
||||
frame=NULL;
|
||||
bool frameHasVoice=false;
|
||||
bool wasVadMode=false;
|
||||
while(running){
|
||||
int16_t* packet=(int16_t*)queue.GetBlocking();
|
||||
if(packet){
|
||||
bool hasVoice=true;
|
||||
if(echoCanceller)
|
||||
echoCanceller->ProcessInput(packet, 960, hasVoice);
|
||||
if(!postProcEffects.empty()){
|
||||
for(effects::AudioEffect* effect:postProcEffects){
|
||||
effect->Process(packet, 960);
|
||||
}
|
||||
}
|
||||
if(packetsPerFrame==1){
|
||||
Encode(packet, 960);
|
||||
}else{
|
||||
memcpy(frame+(960*bufferedCount), packet, 960*2);
|
||||
frameHasVoice=frameHasVoice || hasVoice;
|
||||
bufferedCount++;
|
||||
if(bufferedCount==packetsPerFrame){
|
||||
if(vadMode){
|
||||
if(frameHasVoice){
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(currentBitrate));
|
||||
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(vadModeVoiceBandwidth));
|
||||
if(secondaryEncoder){
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(currentBitrate));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BANDWIDTH(vadModeVoiceBandwidth));
|
||||
}
|
||||
}else{
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(vadNoVoiceBitrate));
|
||||
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(vadModeNoVoiceBandwidth));
|
||||
if(secondaryEncoder){
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(vadNoVoiceBitrate));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BANDWIDTH(vadModeNoVoiceBandwidth));
|
||||
}
|
||||
}
|
||||
wasVadMode=true;
|
||||
}else if(wasVadMode){
|
||||
wasVadMode=false;
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(currentBitrate));
|
||||
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(secondaryEncoderEnabled ? secondaryEnabledBandwidth : OPUS_AUTO));
|
||||
if(secondaryEncoder){
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(currentBitrate));
|
||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BANDWIDTH(secondaryEnabledBandwidth));
|
||||
}
|
||||
}
|
||||
Encode(frame, 960*packetsPerFrame);
|
||||
bufferedCount=0;
|
||||
frameHasVoice=false;
|
||||
}
|
||||
}
|
||||
bufferPool.Reuse(reinterpret_cast<unsigned char *>(packet));
|
||||
}
|
||||
}
|
||||
if(frame)
|
||||
free(frame);
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusEncoder::SetOutputFrameDuration(uint32_t duration){
|
||||
frameDuration=duration;
|
||||
}
|
||||
|
||||
|
||||
void tgvoip::OpusEncoder::SetPacketLoss(int percent){
|
||||
packetLossPercent=std::min(20, percent);
|
||||
opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packetLossPercent));
|
||||
opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(percent>0 && !secondaryEncoderEnabled ? 1 : 0));
|
||||
}
|
||||
|
||||
int tgvoip::OpusEncoder::GetPacketLoss(){
|
||||
return packetLossPercent;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetDTX(bool enable){
|
||||
opus_encoder_ctl(enc, OPUS_SET_DTX(enable ? 1 : 0));
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetLevelMeter(tgvoip::AudioLevelMeter *levelMeter){
|
||||
this->levelMeter=levelMeter;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetCallback(void (*f)(unsigned char *, size_t, unsigned char *, size_t, void *), void *param){
|
||||
callback=f;
|
||||
callbackParam=param;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::InvokeCallback(unsigned char *data, size_t length, unsigned char *secondaryData, size_t secondaryLength){
|
||||
callback(data, length, secondaryData, secondaryLength, callbackParam);
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetSecondaryEncoderEnabled(bool enabled){
|
||||
secondaryEncoderEnabled=enabled;
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::SetVadMode(bool vad){
|
||||
vadMode=vad;
|
||||
}
|
||||
void tgvoip::OpusEncoder::AddAudioEffect(effects::AudioEffect *effect){
|
||||
postProcEffects.push_back(effect);
|
||||
}
|
||||
|
||||
void tgvoip::OpusEncoder::RemoveAudioEffect(effects::AudioEffect *effect){
|
||||
std::vector<effects::AudioEffect*>::iterator i=std::find(postProcEffects.begin(), postProcEffects.end(), effect);
|
||||
if(i!=postProcEffects.end())
|
||||
postProcEffects.erase(i);
|
||||
}
|
||||
82
TMessagesProj/jni/voip/libtgvoip/OpusEncoder.h
Executable file
82
TMessagesProj/jni/voip/libtgvoip/OpusEncoder.h
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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 "threading.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Buffers.h"
|
||||
#include "EchoCanceller.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct OpusEncoder;
|
||||
|
||||
namespace tgvoip{
|
||||
class OpusEncoder{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(OpusEncoder);
|
||||
OpusEncoder(MediaStreamItf* source, bool needSecondary);
|
||||
virtual ~OpusEncoder();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void SetBitrate(uint32_t bitrate);
|
||||
void SetEchoCanceller(EchoCanceller* aec);
|
||||
void SetOutputFrameDuration(uint32_t duration);
|
||||
void SetPacketLoss(int percent);
|
||||
int GetPacketLoss();
|
||||
uint32_t GetBitrate();
|
||||
void SetDTX(bool enable);
|
||||
void SetLevelMeter(AudioLevelMeter* levelMeter);
|
||||
void SetCallback(void (*f)(unsigned char*, size_t, unsigned char*, size_t, void*), void* param);
|
||||
void SetSecondaryEncoderEnabled(bool enabled);
|
||||
void SetVadMode(bool vad);
|
||||
void AddAudioEffect(effects::AudioEffect* effect);
|
||||
void RemoveAudioEffect(effects::AudioEffect* effect);
|
||||
int GetComplexity(){
|
||||
return complexity;
|
||||
}
|
||||
|
||||
private:
|
||||
static size_t Callback(unsigned char* data, size_t len, void* param);
|
||||
void RunThread();
|
||||
void Encode(int16_t* data, size_t len);
|
||||
void InvokeCallback(unsigned char* data, size_t length, unsigned char* secondaryData, size_t secondaryLength);
|
||||
MediaStreamItf* source;
|
||||
::OpusEncoder* enc;
|
||||
::OpusEncoder* secondaryEncoder;
|
||||
unsigned char buffer[4096];
|
||||
uint32_t requestedBitrate;
|
||||
uint32_t currentBitrate;
|
||||
Thread* thread;
|
||||
BlockingQueue<unsigned char*> queue;
|
||||
BufferPool bufferPool;
|
||||
EchoCanceller* echoCanceller;
|
||||
int complexity;
|
||||
bool running;
|
||||
uint32_t frameDuration;
|
||||
int packetLossPercent;
|
||||
AudioLevelMeter* levelMeter;
|
||||
bool secondaryEncoderEnabled;
|
||||
bool vadMode=false;
|
||||
uint32_t vadNoVoiceBitrate;
|
||||
std::vector<effects::AudioEffect*> postProcEffects;
|
||||
int secondaryEnabledBandwidth;
|
||||
int vadModeVoiceBandwidth;
|
||||
int vadModeNoVoiceBandwidth;
|
||||
|
||||
bool wasSecondaryEncoderEnabled=false;
|
||||
|
||||
void (*callback)(unsigned char*, size_t, unsigned char*, size_t, void*);
|
||||
void* callbackParam;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_OPUSENCODER_H
|
||||
92
TMessagesProj/jni/voip/libtgvoip/PacketReassembler.cpp
Normal file
92
TMessagesProj/jni/voip/libtgvoip/PacketReassembler.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// Created by Grishka on 19.03.2018.
|
||||
//
|
||||
|
||||
#include "PacketReassembler.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
PacketReassembler::PacketReassembler(){
|
||||
|
||||
}
|
||||
|
||||
PacketReassembler::~PacketReassembler(){
|
||||
|
||||
}
|
||||
|
||||
void PacketReassembler::Reset(){
|
||||
|
||||
}
|
||||
|
||||
void PacketReassembler::AddFragment(Buffer pkt, unsigned int fragmentIndex, unsigned int fragmentCount, uint32_t pts, bool keyframe, uint16_t rotation){
|
||||
for(Packet& packet:packets){
|
||||
if(packet.timestamp==pts){
|
||||
if(fragmentCount!=packet.partCount){
|
||||
LOGE("Received fragment total count %u inconsistent with previous %u", fragmentCount, packet.partCount);
|
||||
return;
|
||||
}
|
||||
packet.AddFragment(std::move(pkt), fragmentIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(pts<maxTimestamp){
|
||||
LOGW("Received fragment doesn't belong here (ts=%u < maxTs=%u)", pts, maxTimestamp);
|
||||
return;
|
||||
}
|
||||
if(fragmentIndex>=fragmentCount){
|
||||
LOGE("Received fragment index %u is out of bounds %u", fragmentIndex, fragmentCount);
|
||||
return;
|
||||
}
|
||||
if(fragmentCount>255){
|
||||
LOGE("Received fragment total count too big %u", fragmentCount);
|
||||
return;
|
||||
}
|
||||
|
||||
maxTimestamp=std::max(maxTimestamp, pts);
|
||||
|
||||
Packet packet(fragmentCount);
|
||||
packet.timestamp=pts;
|
||||
packet.isKeyframe=keyframe;
|
||||
packet.receivedPartCount=0;
|
||||
packet.rotation=rotation;
|
||||
packet.AddFragment(std::move(pkt), fragmentIndex);
|
||||
|
||||
packets.push_back(std::move(packet));
|
||||
while(packets.size()>3){
|
||||
Packet&& old=std::move(packets[0]);
|
||||
packets.erase(packets.begin());
|
||||
if(old.receivedPartCount==old.partCount){
|
||||
Buffer buffer=old.Reassemble();
|
||||
callback(std::move(buffer), old.timestamp, old.isKeyframe, old.rotation);
|
||||
//LOGV("Packet %u reassembled", old.timestamp);
|
||||
}else{
|
||||
LOGW("Packet %u not reassembled (%u of %u)", old.timestamp, old.receivedPartCount, old.partCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PacketReassembler::SetCallback(std::function<void(Buffer packet, uint32_t pts, bool keyframe, uint16_t rotation)> callback){
|
||||
this->callback=callback;
|
||||
}
|
||||
|
||||
void PacketReassembler::Packet::AddFragment(Buffer pkt, uint32_t fragmentIndex){
|
||||
//LOGV("Add fragment %u/%u to packet %u", fragmentIndex, partCount, timestamp);
|
||||
parts[fragmentIndex]=std::move(pkt);
|
||||
receivedPartCount++;
|
||||
}
|
||||
|
||||
Buffer PacketReassembler::Packet::Reassemble(){
|
||||
if(partCount==1){
|
||||
return std::move(parts[0]);
|
||||
}
|
||||
BufferOutputStream out(10240);
|
||||
for(unsigned int i=0;i<partCount;i++){
|
||||
out.WriteBytes(parts[i]);
|
||||
parts[i]=Buffer();
|
||||
}
|
||||
return Buffer(std::move(out));
|
||||
}
|
||||
72
TMessagesProj/jni/voip/libtgvoip/PacketReassembler.h
Normal file
72
TMessagesProj/jni/voip/libtgvoip/PacketReassembler.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// Created by Grishka on 19.03.2018.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_PACKETREASSEMBLER_H
|
||||
#define TGVOIP_PACKETREASSEMBLER_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Buffers.h"
|
||||
|
||||
namespace tgvoip {
|
||||
class PacketReassembler{
|
||||
public:
|
||||
PacketReassembler();
|
||||
virtual ~PacketReassembler();
|
||||
|
||||
void Reset();
|
||||
void AddFragment(Buffer pkt, unsigned int fragmentIndex, unsigned int fragmentCount, uint32_t pts, bool keyframe, uint16_t rotation);
|
||||
void SetCallback(std::function<void(Buffer packet, uint32_t pts, bool keyframe, uint16_t rotation)> callback);
|
||||
|
||||
private:
|
||||
struct Packet{
|
||||
uint32_t timestamp;
|
||||
uint32_t partCount;
|
||||
uint32_t receivedPartCount;
|
||||
bool isKeyframe;
|
||||
uint16_t rotation;
|
||||
Buffer* parts;
|
||||
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(Packet);
|
||||
|
||||
Packet(Packet&& other) : timestamp(other.timestamp), partCount(other.partCount), receivedPartCount(other.receivedPartCount), isKeyframe(other.isKeyframe), rotation(other.rotation){
|
||||
parts=other.parts;
|
||||
other.parts=NULL;
|
||||
}
|
||||
Packet& operator=(Packet&& other){
|
||||
if(&other!=this){
|
||||
if(parts)
|
||||
delete[] parts;
|
||||
parts=other.parts;
|
||||
other.parts=NULL;
|
||||
timestamp=other.timestamp;
|
||||
partCount=other.partCount;
|
||||
receivedPartCount=other.receivedPartCount;
|
||||
isKeyframe=other.isKeyframe;
|
||||
rotation=other.rotation;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet(uint32_t partCount) : partCount(partCount){
|
||||
parts=new Buffer[partCount];
|
||||
}
|
||||
~Packet(){
|
||||
if(parts)
|
||||
delete[] parts;
|
||||
}
|
||||
|
||||
|
||||
void AddFragment(Buffer pkt, uint32_t fragmentIndex);
|
||||
Buffer Reassemble();
|
||||
};
|
||||
std::function<void(Buffer, uint32_t, bool, uint16_t)> callback;
|
||||
std::vector<Packet> packets;
|
||||
uint32_t maxTimestamp=0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TGVOIP_PACKETREASSEMBLER_H
|
||||
141
TMessagesProj/jni/voip/libtgvoip/PrivateDefines.h
Normal file
141
TMessagesProj/jni/voip/libtgvoip/PrivateDefines.h
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// Created by Grishka on 20.04.2018.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_PRIVATEDEFINES_H
|
||||
#define TGVOIP_PRIVATEDEFINES_H
|
||||
|
||||
#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 PKT_GROUP_CALL_KEY 15 // replaced with 'extra' in 2.1 (protocol v6)
|
||||
//#define PKT_REQUEST_GROUP 16
|
||||
#define PKT_STREAM_EC 17
|
||||
|
||||
#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 9
|
||||
#define MIN_PROTOCOL_VERSION 3
|
||||
|
||||
#define STREAM_DATA_FLAG_LEN16 0x40
|
||||
#define STREAM_DATA_FLAG_HAS_MORE_FLAGS 0x80
|
||||
// Since the data can't be larger than the MTU anyway,
|
||||
// 5 top bits of data length are allocated for these flags
|
||||
#define STREAM_DATA_XFLAG_KEYFRAME (1 << 15)
|
||||
#define STREAM_DATA_XFLAG_FRAGMENTED (1 << 14)
|
||||
#define STREAM_DATA_XFLAG_EXTRA_FEC (1 << 13)
|
||||
|
||||
#define STREAM_TYPE_AUDIO 1
|
||||
#define STREAM_TYPE_VIDEO 2
|
||||
|
||||
#define FOURCC(a,b,c,d) ((uint32_t)d | ((uint32_t)c << 8) | ((uint32_t)b << 16) | ((uint32_t)a << 24))
|
||||
#define PRINT_FOURCC(x) (char)(x >> 24), (char)(x >> 16), (char)(x >> 8), (char)x
|
||||
|
||||
#define CODEC_OPUS_OLD 1
|
||||
#define CODEC_OPUS FOURCC('O','P','U','S')
|
||||
|
||||
#define CODEC_AVC FOURCC('A','V','C',' ')
|
||||
#define CODEC_HEVC FOURCC('H','E','V','C')
|
||||
#define CODEC_VP8 FOURCC('V','P','8','0')
|
||||
#define CODEC_VP9 FOURCC('V','P','9','0')
|
||||
#define CODEC_AV1 FOURCC('A','V','0','1')
|
||||
|
||||
#define DEFAULT_MTU 1100
|
||||
|
||||
/*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 PFLAG_HAS_SENDER_TAG_HASH 64
|
||||
|
||||
#define XPFLAG_HAS_EXTRA 1
|
||||
#define XPFLAG_HAS_RECV_TS 2
|
||||
|
||||
#define EXTRA_TYPE_STREAM_FLAGS 1
|
||||
#define EXTRA_TYPE_STREAM_CSD 2
|
||||
#define EXTRA_TYPE_LAN_ENDPOINT 3
|
||||
#define EXTRA_TYPE_NETWORK_CHANGED 4
|
||||
#define EXTRA_TYPE_GROUP_CALL_KEY 5
|
||||
#define EXTRA_TYPE_REQUEST_GROUP 6
|
||||
#define EXTRA_TYPE_IPV6_ENDPOINT 7
|
||||
|
||||
#define STREAM_FLAG_ENABLED 1
|
||||
#define STREAM_FLAG_DTX 2
|
||||
#define STREAM_FLAG_EXTRA_EC 4
|
||||
|
||||
#define STREAM_RFLAG_SUPPORTED 1
|
||||
|
||||
#define INIT_FLAG_DATA_SAVING_ENABLED 1
|
||||
#define INIT_FLAG_GROUP_CALLS_SUPPORTED 2
|
||||
#define INIT_FLAG_VIDEO_SEND_SUPPORTED 4
|
||||
#define INIT_FLAG_VIDEO_RECV_SUPPORTED 8
|
||||
|
||||
#define INIT_VIDEO_RES_NONE 0
|
||||
#define INIT_VIDEO_RES_240 1
|
||||
#define INIT_VIDEO_RES_360 2
|
||||
#define INIT_VIDEO_RES_480 3
|
||||
#define INIT_VIDEO_RES_720 4
|
||||
#define INIT_VIDEO_RES_1080 5
|
||||
#define INIT_VIDEO_RES_1440 6
|
||||
#define INIT_VIDEO_RES_4K 7
|
||||
|
||||
#define TLID_DECRYPTED_AUDIO_BLOCK 0xDBF948C1
|
||||
#define TLID_SIMPLE_AUDIO_BLOCK 0xCC0D0E76
|
||||
#define TLID_UDP_REFLECTOR_PEER_INFO 0x27D9371C
|
||||
#define TLID_UDP_REFLECTOR_PEER_INFO_IPV6 0x83fc73b1
|
||||
#define TLID_UDP_REFLECTOR_SELF_INFO 0xc01572c7
|
||||
#define TLID_UDP_REFLECTOR_REQUEST_PACKETS_INFO 0x1a06fc96
|
||||
#define TLID_UDP_REFLECTOR_LAST_PACKETS_INFO 0x0e107305
|
||||
#define TLID_VECTOR 0x1cb5c415
|
||||
#define PAD4(x) (4-(x+(x<=253 ? 1 : 0))%4)
|
||||
|
||||
#define MAX_RECENT_PACKETS 128
|
||||
|
||||
#define MAX(a,b) (a>b ? a : b)
|
||||
#define MIN(a,b) (a<b ? a : b)
|
||||
|
||||
#define SHA1_LENGTH 20
|
||||
#define SHA256_LENGTH 32
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define MSC_STACK_FALLBACK(a, b) (b)
|
||||
#else
|
||||
#define MSC_STACK_FALLBACK(a, b) (a)
|
||||
#endif
|
||||
|
||||
#define SEQ_MAX 0xFFFFFFFF
|
||||
|
||||
inline bool seqgt(uint32_t s1, uint32_t s2){
|
||||
return ((s1>s2) && (s1-s2<=SEQ_MAX/2)) || ((s1<s2) && (s2-s1>SEQ_MAX/2));
|
||||
}
|
||||
|
||||
#define NEED_RATE_FLAG_SHITTY_INTERNET_MODE 1
|
||||
#define NEED_RATE_FLAG_UDP_NA 2
|
||||
#define NEED_RATE_FLAG_UDP_BAD 4
|
||||
#define NEED_RATE_FLAG_RECONNECTING 8
|
||||
|
||||
#define VIDEO_FRAME_FLAG_KEYFRAME 1
|
||||
|
||||
#define VIDEO_ROTATION_MASK 3
|
||||
#define VIDEO_ROTATION_0 0
|
||||
#define VIDEO_ROTATION_90 1
|
||||
#define VIDEO_ROTATION_180 2
|
||||
#define VIDEO_ROTATION_270 3
|
||||
|
||||
#endif //TGVOIP_PRIVATEDEFINES_H
|
||||
24
TMessagesProj/jni/voip/libtgvoip/UNLICENSE
Normal file
24
TMessagesProj/jni/voip/libtgvoip/UNLICENSE
Normal file
|
|
@ -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 <http://unlicense.org/>
|
||||
4019
TMessagesProj/jni/voip/libtgvoip/VoIPController.cpp
Executable file
4019
TMessagesProj/jni/voip/libtgvoip/VoIPController.cpp
Executable file
File diff suppressed because it is too large
Load diff
882
TMessagesProj/jni/voip/libtgvoip/VoIPController.h
Executable file
882
TMessagesProj/jni/voip/libtgvoip/VoIPController.h
Executable file
|
|
@ -0,0 +1,882 @@
|
|||
//
|
||||
// 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 __VOIPCONTROLLER_H
|
||||
#define __VOIPCONTROLLER_H
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#include "os/darwin/AudioUnitIO.h"
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include "video/VideoSource.h"
|
||||
#include "video/VideoRenderer.h"
|
||||
#include <atomic>
|
||||
#include "video/ScreamCongestionController.h"
|
||||
#include "audio/AudioInput.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "audio/AudioOutput.h"
|
||||
#include "audio/AudioIO.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include "OpusDecoder.h"
|
||||
#include "OpusEncoder.h"
|
||||
#include "EchoCanceller.h"
|
||||
#include "CongestionControl.h"
|
||||
#include "NetworkSocket.h"
|
||||
#include "Buffers.h"
|
||||
#include "PacketReassembler.h"
|
||||
#include "MessageThread.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define LIBTGVOIP_VERSION "2.4.4"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef GetCurrentTime
|
||||
#undef ERROR_TIMEOUT
|
||||
#endif
|
||||
|
||||
#define TGVOIP_PEER_CAP_GROUP_CALLS 1
|
||||
#define TGVOIP_PEER_CAP_VIDEO_CAPTURE 2
|
||||
#define TGVOIP_PEER_CAP_VIDEO_DISPLAY 4
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
enum{
|
||||
PROXY_NONE=0,
|
||||
PROXY_SOCKS5,
|
||||
//PROXY_HTTP
|
||||
};
|
||||
|
||||
enum{
|
||||
STATE_WAIT_INIT=1,
|
||||
STATE_WAIT_INIT_ACK,
|
||||
STATE_ESTABLISHED,
|
||||
STATE_FAILED,
|
||||
STATE_RECONNECTING
|
||||
};
|
||||
|
||||
enum{
|
||||
ERROR_UNKNOWN=0,
|
||||
ERROR_INCOMPATIBLE,
|
||||
ERROR_TIMEOUT,
|
||||
ERROR_AUDIO_IO,
|
||||
ERROR_PROXY
|
||||
};
|
||||
|
||||
enum{
|
||||
NET_TYPE_UNKNOWN=0,
|
||||
NET_TYPE_GPRS,
|
||||
NET_TYPE_EDGE,
|
||||
NET_TYPE_3G,
|
||||
NET_TYPE_HSPA,
|
||||
NET_TYPE_LTE,
|
||||
NET_TYPE_WIFI,
|
||||
NET_TYPE_ETHERNET,
|
||||
NET_TYPE_OTHER_HIGH_SPEED,
|
||||
NET_TYPE_OTHER_LOW_SPEED,
|
||||
NET_TYPE_DIALUP,
|
||||
NET_TYPE_OTHER_MOBILE
|
||||
};
|
||||
|
||||
enum{
|
||||
DATA_SAVING_NEVER=0,
|
||||
DATA_SAVING_MOBILE,
|
||||
DATA_SAVING_ALWAYS
|
||||
};
|
||||
|
||||
struct CryptoFunctions{
|
||||
void (*rand_bytes)(uint8_t* buffer, size_t length);
|
||||
void (*sha1)(uint8_t* msg, size_t length, uint8_t* output);
|
||||
void (*sha256)(uint8_t* msg, size_t length, uint8_t* output);
|
||||
void (*aes_ige_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv);
|
||||
void (*aes_ige_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv);
|
||||
void (*aes_ctr_encrypt)(uint8_t* inout, size_t length, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num);
|
||||
void (*aes_cbc_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv);
|
||||
void (*aes_cbc_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv);
|
||||
};
|
||||
|
||||
struct CellularCarrierInfo{
|
||||
std::string name;
|
||||
std::string mcc;
|
||||
std::string mnc;
|
||||
std::string countryCode;
|
||||
};
|
||||
|
||||
class Endpoint{
|
||||
friend class VoIPController;
|
||||
friend class VoIPGroupController;
|
||||
public:
|
||||
|
||||
enum Type{
|
||||
UDP_P2P_INET=1,
|
||||
UDP_P2P_LAN,
|
||||
UDP_RELAY,
|
||||
TCP_RELAY
|
||||
};
|
||||
|
||||
Endpoint(int64_t id, uint16_t port, const IPv4Address& address, const IPv6Address& v6address, Type type, unsigned char* peerTag);
|
||||
Endpoint();
|
||||
~Endpoint();
|
||||
const NetworkAddress& GetAddress() const;
|
||||
NetworkAddress& GetAddress();
|
||||
bool IsIPv6Only() const;
|
||||
int64_t id;
|
||||
uint16_t port;
|
||||
IPv4Address address;
|
||||
IPv6Address v6address;
|
||||
Type type;
|
||||
unsigned char peerTag[16];
|
||||
|
||||
private:
|
||||
double lastPingTime;
|
||||
uint32_t lastPingSeq;
|
||||
HistoricBuffer<double, 6> rtts;
|
||||
double averageRTT;
|
||||
NetworkSocket* socket;
|
||||
int udpPongCount;
|
||||
};
|
||||
|
||||
class AudioDevice{
|
||||
public:
|
||||
std::string id;
|
||||
std::string displayName;
|
||||
};
|
||||
|
||||
class AudioOutputDevice : public AudioDevice{
|
||||
|
||||
};
|
||||
|
||||
class AudioInputDevice : public AudioDevice{
|
||||
|
||||
};
|
||||
|
||||
class AudioInputTester{
|
||||
public:
|
||||
AudioInputTester(const std::string deviceID);
|
||||
~AudioInputTester();
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioInputTester);
|
||||
float GetAndResetLevel();
|
||||
bool Failed(){
|
||||
return io && io->Failed();
|
||||
}
|
||||
private:
|
||||
void Update(int16_t* samples, size_t count);
|
||||
audio::AudioIO* io=NULL;
|
||||
audio::AudioInput* input=NULL;
|
||||
int16_t maxSample=0;
|
||||
std::string deviceID;
|
||||
};
|
||||
|
||||
class VoIPController{
|
||||
friend class VoIPGroupController;
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(VoIPController);
|
||||
struct Config{
|
||||
Config(double initTimeout=30.0, double recvTimeout=20.0, int dataSaving=DATA_SAVING_NEVER, bool enableAEC=false, bool enableNS=false, bool enableAGC=false, bool enableCallUpgrade=false){
|
||||
this->initTimeout=initTimeout;
|
||||
this->recvTimeout=recvTimeout;
|
||||
this->dataSaving=dataSaving;
|
||||
this->enableAEC=enableAEC;
|
||||
this->enableNS=enableNS;
|
||||
this->enableAGC=enableAGC;
|
||||
this->enableCallUpgrade=enableCallUpgrade;
|
||||
}
|
||||
|
||||
double initTimeout;
|
||||
double recvTimeout;
|
||||
int dataSaving;
|
||||
#ifndef _WIN32
|
||||
std::string logFilePath="";
|
||||
std::string statsDumpFilePath="";
|
||||
#else
|
||||
std::wstring logFilePath=L"";
|
||||
std::wstring statsDumpFilePath=L"";
|
||||
#endif
|
||||
|
||||
bool enableAEC;
|
||||
bool enableNS;
|
||||
bool enableAGC;
|
||||
|
||||
bool enableCallUpgrade;
|
||||
|
||||
bool logPacketStats=false;
|
||||
bool enableVolumeControl=false;
|
||||
|
||||
bool enableVideoSend=false;
|
||||
bool enableVideoReceive=false;
|
||||
};
|
||||
|
||||
struct TrafficStats{
|
||||
uint64_t bytesSentWifi;
|
||||
uint64_t bytesRecvdWifi;
|
||||
uint64_t bytesSentMobile;
|
||||
uint64_t bytesRecvdMobile;
|
||||
};
|
||||
|
||||
|
||||
VoIPController();
|
||||
virtual ~VoIPController();
|
||||
|
||||
/**
|
||||
* Set the initial endpoints (relays)
|
||||
* @param endpoints Endpoints converted from phone.PhoneConnection TL objects
|
||||
* @param allowP2p Whether p2p connectivity is allowed
|
||||
* @param connectionMaxLayer The max_layer field from the phoneCallProtocol object returned by Telegram server.
|
||||
* DO NOT HARDCODE THIS VALUE, it's extremely important for backwards compatibility.
|
||||
*/
|
||||
void SetRemoteEndpoints(std::vector<Endpoint> endpoints, bool allowP2p, int32_t connectionMaxLayer);
|
||||
/**
|
||||
* Initialize and start all the internal threads
|
||||
*/
|
||||
void Start();
|
||||
/**
|
||||
* Stop any internal threads. Don't call any other methods after this.
|
||||
*/
|
||||
void Stop();
|
||||
/**
|
||||
* Initiate connection
|
||||
*/
|
||||
void Connect();
|
||||
Endpoint& GetRemoteEndpoint();
|
||||
/**
|
||||
* Get the debug info string to be displayed in client UI
|
||||
*/
|
||||
virtual std::string GetDebugString();
|
||||
/**
|
||||
* Notify the library of network type change
|
||||
* @param type The new network type
|
||||
*/
|
||||
virtual void SetNetworkType(int type);
|
||||
/**
|
||||
* Get the average round-trip time for network packets
|
||||
* @return
|
||||
*/
|
||||
double GetAverageRTT();
|
||||
static double GetCurrentTime();
|
||||
/**
|
||||
* Use this field to store any of your context data associated with this call
|
||||
*/
|
||||
void* implData;
|
||||
/**
|
||||
*
|
||||
* @param mute
|
||||
*/
|
||||
virtual void SetMicMute(bool mute);
|
||||
/**
|
||||
*
|
||||
* @param key
|
||||
* @param isOutgoing
|
||||
*/
|
||||
void SetEncryptionKey(char* key, bool isOutgoing);
|
||||
/**
|
||||
*
|
||||
* @param cfg
|
||||
*/
|
||||
void SetConfig(const Config& cfg);
|
||||
void DebugCtl(int request, int param);
|
||||
/**
|
||||
*
|
||||
* @param stats
|
||||
*/
|
||||
void GetStats(TrafficStats* stats);
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int64_t GetPreferredRelayID();
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int GetLastError();
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static CryptoFunctions crypto;
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static const char* GetVersion();
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string GetDebugLog();
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::vector<AudioInputDevice> EnumerateAudioInputs();
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::vector<AudioOutputDevice> EnumerateAudioOutputs();
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
void SetCurrentAudioInput(std::string id);
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
void SetCurrentAudioOutput(std::string id);
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string GetCurrentAudioInputID();
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string GetCurrentAudioOutputID();
|
||||
/**
|
||||
* Set the proxy server to route the data through. Call this before connecting.
|
||||
* @param protocol PROXY_NONE or PROXY_SOCKS5
|
||||
* @param address IP address or domain name of the server
|
||||
* @param port Port of the server
|
||||
* @param username Username; empty string for anonymous
|
||||
* @param password Password; empty string if none
|
||||
*/
|
||||
void SetProxy(int protocol, std::string address, uint16_t port, std::string username, std::string password);
|
||||
/**
|
||||
* Get the number of signal bars to display in the client UI.
|
||||
* @return the number of signal bars, from 1 to 4
|
||||
*/
|
||||
int GetSignalBarsCount();
|
||||
/**
|
||||
* Enable or disable AGC (automatic gain control) on audio output. Should only be enabled on phones when the earpiece speaker is being used.
|
||||
* The audio output will be louder with this on.
|
||||
* AGC with speakerphone or other kinds of loud speakers has detrimental effects on some echo cancellation implementations.
|
||||
* @param enabled I usually pick argument names to be self-explanatory
|
||||
*/
|
||||
void SetAudioOutputGainControlEnabled(bool enabled);
|
||||
/**
|
||||
* Get the additional capabilities of the peer client app
|
||||
* @return corresponding TGVOIP_PEER_CAP_* flags OR'ed together
|
||||
*/
|
||||
uint32_t GetPeerCapabilities();
|
||||
/**
|
||||
* Send the peer the key for the group call to prepare this private call to an upgrade to a E2E group call.
|
||||
* The peer must have the TGVOIP_PEER_CAP_GROUP_CALLS capability. After the peer acknowledges the key, Callbacks::groupCallKeySent will be called.
|
||||
* @param key newly-generated group call key, must be exactly 265 bytes long
|
||||
*/
|
||||
void SendGroupCallKey(unsigned char* key);
|
||||
/**
|
||||
* In an incoming call, request the peer to generate a new encryption key, send it to you and upgrade this call to a E2E group call.
|
||||
*/
|
||||
void RequestCallUpgrade();
|
||||
void SetEchoCancellationStrength(int strength);
|
||||
int GetConnectionState();
|
||||
bool NeedRate();
|
||||
/**
|
||||
* Get the maximum connection layer supported by this libtgvoip version.
|
||||
* Pass this as <code>max_layer</code> in the phone.phoneConnection TL object when requesting and accepting calls.
|
||||
*/
|
||||
static int32_t GetConnectionMaxLayer(){
|
||||
return 92;
|
||||
};
|
||||
/**
|
||||
* Get the persistable state of the library, like proxy capabilities, to save somewhere on the disk. Call this at the end of the call.
|
||||
* Using this will speed up the connection establishment in some cases.
|
||||
*/
|
||||
std::vector<uint8_t> GetPersistentState();
|
||||
/**
|
||||
* Load the persistable state. Call this before starting the call.
|
||||
*/
|
||||
void SetPersistentState(std::vector<uint8_t> state);
|
||||
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
void SetAudioDataCallbacks(std::function<void(int16_t*, size_t)> input, std::function<void(int16_t*, size_t)> output, std::function<void(int16_t*, size_t)> preprocessed);
|
||||
#endif
|
||||
|
||||
void SetVideoCodecSpecificData(const std::vector<Buffer>& data);
|
||||
|
||||
struct Callbacks{
|
||||
void (*connectionStateChanged)(VoIPController*, int);
|
||||
void (*signalBarCountChanged)(VoIPController*, int);
|
||||
void (*groupCallKeySent)(VoIPController*);
|
||||
void (*groupCallKeyReceived)(VoIPController*, const unsigned char*);
|
||||
void (*upgradeToGroupCallRequested)(VoIPController*);
|
||||
};
|
||||
void SetCallbacks(Callbacks callbacks);
|
||||
|
||||
float GetOutputLevel(){
|
||||
return 0.0f;
|
||||
};
|
||||
int GetVideoResolutionForCurrentBitrate();
|
||||
void SetVideoSource(video::VideoSource* source);
|
||||
void SetVideoRenderer(video::VideoRenderer* renderer);
|
||||
|
||||
void SetInputVolume(float level);
|
||||
void SetOutputVolume(float level);
|
||||
#if defined(__APPLE__) && defined(TARGET_OS_OSX)
|
||||
void SetAudioOutputDuckingEnabled(bool enabled);
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Stream;
|
||||
struct UnacknowledgedExtraData;
|
||||
|
||||
protected:
|
||||
struct RecentOutgoingPacket{
|
||||
uint32_t seq;
|
||||
uint16_t id; // for group calls only
|
||||
double sendTime;
|
||||
double ackTime;
|
||||
uint8_t type;
|
||||
uint32_t size;
|
||||
};
|
||||
struct PendingOutgoingPacket{
|
||||
PendingOutgoingPacket(uint32_t seq, unsigned char type, size_t len, Buffer&& data, int64_t endpoint){
|
||||
this->seq=seq;
|
||||
this->type=type;
|
||||
this->len=len;
|
||||
this->data=std::move(data);
|
||||
this->endpoint=endpoint;
|
||||
}
|
||||
PendingOutgoingPacket(PendingOutgoingPacket&& other){
|
||||
seq=other.seq;
|
||||
type=other.type;
|
||||
len=other.len;
|
||||
data=std::move(other.data);
|
||||
endpoint=other.endpoint;
|
||||
}
|
||||
PendingOutgoingPacket& operator=(PendingOutgoingPacket&& other){
|
||||
if(this!=&other){
|
||||
seq=other.seq;
|
||||
type=other.type;
|
||||
len=other.len;
|
||||
data=std::move(other.data);
|
||||
endpoint=other.endpoint;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(PendingOutgoingPacket);
|
||||
uint32_t seq;
|
||||
unsigned char type;
|
||||
size_t len;
|
||||
Buffer data;
|
||||
int64_t endpoint;
|
||||
};
|
||||
struct QueuedPacket{
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 // VS2013 doesn't support auto-generating move constructors
|
||||
//TGVOIP_DISALLOW_COPY_AND_ASSIGN(QueuedPacket);
|
||||
QueuedPacket(QueuedPacket&& other){
|
||||
data=std::move(other.data);
|
||||
type=other.type;
|
||||
seqs=other.seqs;
|
||||
firstSentTime=other.firstSentTime;
|
||||
lastSentTime=other.lastSentTime;
|
||||
retryInterval=other.retryInterval;
|
||||
timeout=other.timeout;
|
||||
}
|
||||
QueuedPacket(){
|
||||
|
||||
}
|
||||
#endif
|
||||
Buffer data;
|
||||
unsigned char type;
|
||||
HistoricBuffer<uint32_t, 16> seqs;
|
||||
double firstSentTime;
|
||||
double lastSentTime;
|
||||
double retryInterval;
|
||||
double timeout;
|
||||
};
|
||||
virtual void ProcessIncomingPacket(NetworkPacket& packet, Endpoint& srcEndpoint);
|
||||
virtual void ProcessExtraData(Buffer& data);
|
||||
virtual void WritePacketHeader(uint32_t seq, BufferOutputStream* s, unsigned char type, uint32_t length);
|
||||
virtual void SendPacket(unsigned char* data, size_t len, Endpoint& ep, PendingOutgoingPacket& srcPacket);
|
||||
virtual void SendInit();
|
||||
virtual void SendUdpPing(Endpoint& endpoint);
|
||||
virtual void SendRelayPings();
|
||||
virtual void OnAudioOutputReady();
|
||||
virtual void SendExtra(Buffer& data, unsigned char type);
|
||||
void SendStreamFlags(Stream& stream);
|
||||
void SendStreamCSD(Stream& stream);
|
||||
void InitializeTimers();
|
||||
void ResetEndpointPingStats();
|
||||
void SendVideoFrame(const Buffer& frame, uint32_t flags, uint32_t rotation);
|
||||
void ProcessIncomingVideoFrame(Buffer frame, uint32_t pts, bool keyframe, uint16_t rotation);
|
||||
std::shared_ptr<Stream> GetStreamByType(int type, bool outgoing);
|
||||
Endpoint* GetEndpointForPacket(const PendingOutgoingPacket& pkt);
|
||||
bool SendOrEnqueuePacket(PendingOutgoingPacket pkt, bool enqueue=true);
|
||||
static std::string NetworkTypeToString(int type);
|
||||
CellularCarrierInfo GetCarrierInfo();
|
||||
|
||||
private:
|
||||
struct Stream{
|
||||
int32_t userID;
|
||||
unsigned char id;
|
||||
unsigned char type;
|
||||
uint32_t codec;
|
||||
bool enabled;
|
||||
bool extraECEnabled;
|
||||
uint16_t frameDuration;
|
||||
std::shared_ptr<JitterBuffer> jitterBuffer;
|
||||
std::shared_ptr<OpusDecoder> decoder;
|
||||
std::shared_ptr<PacketReassembler> packetReassembler;
|
||||
std::shared_ptr<CallbackWrapper> callbackWrapper;
|
||||
std::vector<Buffer> codecSpecificData;
|
||||
bool csdIsValid=false;
|
||||
int resolution;
|
||||
unsigned int width=0;
|
||||
unsigned int height=0;
|
||||
uint16_t rotation=0;
|
||||
};
|
||||
struct UnacknowledgedExtraData{
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 // VS2013 doesn't support auto-generating move constructors
|
||||
UnacknowledgedExtraData(UnacknowledgedExtraData&& other){
|
||||
type=other.type;
|
||||
data=std::move(other.data);
|
||||
firstContainingSeq=other.firstContainingSeq;
|
||||
}
|
||||
UnacknowledgedExtraData(unsigned char _type, Buffer&& _data, uint32_t _firstContainingSeq){
|
||||
type=_type;
|
||||
data=_data;
|
||||
firstContainingSeq=_firstContainingSeq;
|
||||
}
|
||||
#endif
|
||||
unsigned char type;
|
||||
Buffer data;
|
||||
uint32_t firstContainingSeq;
|
||||
};
|
||||
enum{
|
||||
UDP_UNKNOWN=0,
|
||||
UDP_PING_PENDING,
|
||||
UDP_PING_SENT,
|
||||
UDP_AVAILABLE,
|
||||
UDP_NOT_AVAILABLE,
|
||||
UDP_BAD
|
||||
};
|
||||
struct DebugLoggedPacket{
|
||||
int32_t seq;
|
||||
double timestamp;
|
||||
int32_t length;
|
||||
};
|
||||
struct SentVideoFrame{
|
||||
uint32_t num;
|
||||
uint32_t fragmentCount;
|
||||
std::vector<uint32_t> unacknowledgedPackets;
|
||||
uint32_t fragmentsInQueue;
|
||||
};
|
||||
struct PendingVideoFrameFragment{
|
||||
uint32_t pts;
|
||||
Buffer data;
|
||||
};
|
||||
|
||||
void RunRecvThread();
|
||||
void RunSendThread();
|
||||
void HandleAudioInput(unsigned char* data, size_t len, unsigned char* secondaryData, size_t secondaryLen);
|
||||
void UpdateAudioBitrateLimit();
|
||||
void SetState(int state);
|
||||
void UpdateAudioOutputState();
|
||||
void InitUDPProxy();
|
||||
void UpdateDataSavingState();
|
||||
void KDF(unsigned char* msgKey, size_t x, unsigned char* aesKey, unsigned char* aesIv);
|
||||
void KDF2(unsigned char* msgKey, size_t x, unsigned char* aesKey, unsigned char* aesIv);
|
||||
static void AudioInputCallback(unsigned char* data, size_t length, unsigned char* secondaryData, size_t secondaryLength, void* param);
|
||||
void SendPublicEndpointsRequest();
|
||||
void SendPublicEndpointsRequest(const Endpoint& relay);
|
||||
Endpoint& GetEndpointByType(int type);
|
||||
void SendPacketReliably(unsigned char type, unsigned char* data, size_t len, double retryInterval, double timeout);
|
||||
uint32_t GenerateOutSeq();
|
||||
void ActuallySendPacket(NetworkPacket& pkt, Endpoint& ep);
|
||||
void InitializeAudio();
|
||||
void StartAudio();
|
||||
void ProcessAcknowledgedOutgoingExtra(UnacknowledgedExtraData& extra);
|
||||
void AddIPv6Relays();
|
||||
void AddTCPRelays();
|
||||
void SendUdpPings();
|
||||
void EvaluateUdpPingResults();
|
||||
void UpdateRTT();
|
||||
void UpdateCongestion();
|
||||
void UpdateAudioBitrate();
|
||||
void UpdateSignalBars();
|
||||
void UpdateQueuedPackets();
|
||||
void SendNopPacket();
|
||||
void TickJitterBufferAngCongestionControl();
|
||||
void ResetUdpAvailability();
|
||||
std::string GetPacketTypeString(unsigned char type);
|
||||
void SetupOutgoingVideoStream();
|
||||
bool WasOutgoingPacketAcknowledged(uint32_t seq);
|
||||
RecentOutgoingPacket* GetRecentOutgoingPacket(uint32_t seq);
|
||||
|
||||
int state;
|
||||
std::map<int64_t, Endpoint> endpoints;
|
||||
int64_t currentEndpoint=0;
|
||||
int64_t preferredRelay=0;
|
||||
int64_t peerPreferredRelay=0;
|
||||
bool runReceiver;
|
||||
std::atomic<uint32_t> seq;
|
||||
uint32_t lastRemoteSeq;
|
||||
uint32_t lastRemoteAckSeq;
|
||||
uint32_t lastSentSeq;
|
||||
std::vector<RecentOutgoingPacket> recentOutgoingPackets;
|
||||
double recvPacketTimes[32];
|
||||
HistoricBuffer<uint32_t, 10, double> sendLossCountHistory;
|
||||
uint32_t audioTimestampIn;
|
||||
uint32_t audioTimestampOut;
|
||||
tgvoip::audio::AudioIO* audioIO=NULL;
|
||||
tgvoip::audio::AudioInput* audioInput=NULL;
|
||||
tgvoip::audio::AudioOutput* audioOutput=NULL;
|
||||
OpusEncoder* encoder;
|
||||
std::vector<PendingOutgoingPacket> sendQueue;
|
||||
EchoCanceller* echoCanceller;
|
||||
Mutex sendBufferMutex;
|
||||
Mutex endpointsMutex;
|
||||
Mutex socketSelectMutex;
|
||||
bool stopping;
|
||||
bool audioOutStarted;
|
||||
Thread* recvThread;
|
||||
Thread* sendThread;
|
||||
uint32_t packetsReceived;
|
||||
uint32_t recvLossCount;
|
||||
uint32_t prevSendLossCount;
|
||||
uint32_t firstSentPing;
|
||||
HistoricBuffer<double, 32> rttHistory;
|
||||
bool waitingForAcks;
|
||||
int networkType;
|
||||
int dontSendPackets;
|
||||
int lastError;
|
||||
bool micMuted;
|
||||
uint32_t maxBitrate;
|
||||
std::vector<std::shared_ptr<Stream>> outgoingStreams;
|
||||
std::vector<std::shared_ptr<Stream>> incomingStreams;
|
||||
unsigned char encryptionKey[256];
|
||||
unsigned char keyFingerprint[8];
|
||||
unsigned char callID[16];
|
||||
double stateChangeTime;
|
||||
bool waitingForRelayPeerInfo;
|
||||
bool allowP2p;
|
||||
bool dataSavingMode;
|
||||
bool dataSavingRequestedByPeer;
|
||||
std::string activeNetItfName;
|
||||
double publicEndpointsReqTime;
|
||||
std::vector<QueuedPacket> queuedPackets;
|
||||
Mutex audioIOMutex;
|
||||
Mutex queuedPacketsMutex;
|
||||
double connectionInitTime;
|
||||
double lastRecvPacketTime;
|
||||
Config config;
|
||||
int32_t peerVersion;
|
||||
CongestionControl* conctl;
|
||||
TrafficStats stats;
|
||||
bool receivedInit;
|
||||
bool receivedInitAck;
|
||||
bool isOutgoing;
|
||||
NetworkSocket* udpSocket;
|
||||
NetworkSocket* realUdpSocket;
|
||||
FILE* statsDump;
|
||||
std::string currentAudioInput;
|
||||
std::string currentAudioOutput;
|
||||
bool useTCP;
|
||||
bool useUDP;
|
||||
bool didAddTcpRelays;
|
||||
SocketSelectCanceller* selectCanceller;
|
||||
HistoricBuffer<unsigned char, 4, int> signalBarsHistory;
|
||||
bool audioStarted=false;
|
||||
|
||||
int udpConnectivityState;
|
||||
double lastUdpPingTime;
|
||||
int udpPingCount;
|
||||
int echoCancellationStrength;
|
||||
|
||||
int proxyProtocol;
|
||||
std::string proxyAddress;
|
||||
uint16_t proxyPort;
|
||||
std::string proxyUsername;
|
||||
std::string proxyPassword;
|
||||
IPv4Address* resolvedProxyAddress;
|
||||
|
||||
uint32_t peerCapabilities;
|
||||
Callbacks callbacks;
|
||||
bool didReceiveGroupCallKey;
|
||||
bool didReceiveGroupCallKeyAck;
|
||||
bool didSendGroupCallKey;
|
||||
bool didSendUpgradeRequest;
|
||||
bool didInvokeUpgradeCallback;
|
||||
|
||||
int32_t connectionMaxLayer;
|
||||
bool useMTProto2;
|
||||
bool setCurrentEndpointToTCP;
|
||||
|
||||
std::vector<UnacknowledgedExtraData> currentExtras;
|
||||
std::unordered_map<uint8_t, uint64_t> lastReceivedExtrasByType;
|
||||
bool useIPv6;
|
||||
bool peerIPv6Available;
|
||||
IPv6Address myIPv6;
|
||||
bool shittyInternetMode;
|
||||
int extraEcLevel=0;
|
||||
std::vector<Buffer> ecAudioPackets;
|
||||
bool didAddIPv6Relays;
|
||||
bool didSendIPv6Endpoint;
|
||||
int publicEndpointsReqCount=0;
|
||||
MessageThread messageThread;
|
||||
bool wasEstablished=false;
|
||||
bool receivedFirstStreamPacket=false;
|
||||
std::atomic<unsigned int> unsentStreamPackets;
|
||||
HistoricBuffer<unsigned int, 5> unsentStreamPacketsHistory;
|
||||
bool needReInitUdpProxy=true;
|
||||
bool needRate=false;
|
||||
std::vector<DebugLoggedPacket> debugLoggedPackets;
|
||||
|
||||
uint32_t initTimeoutID=MessageThread::INVALID_ID;
|
||||
uint32_t noStreamsNopID=MessageThread::INVALID_ID;
|
||||
uint32_t udpPingTimeoutID=MessageThread::INVALID_ID;
|
||||
|
||||
effects::Volume outputVolume;
|
||||
effects::Volume inputVolume;
|
||||
|
||||
std::vector<uint32_t> peerVideoDecoders;
|
||||
int peerMaxVideoResolution=0;
|
||||
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
std::function<void(int16_t*, size_t)> audioInputDataCallback;
|
||||
std::function<void(int16_t*, size_t)> audioOutputDataCallback;
|
||||
std::function<void(int16_t*, size_t)> audioPreprocDataCallback;
|
||||
::OpusDecoder* preprocDecoder=nullptr;
|
||||
int16_t preprocBuffer[4096];
|
||||
#endif
|
||||
#if defined(__APPLE__) && defined(TARGET_OS_OSX)
|
||||
bool macAudioDuckingEnabled=true;
|
||||
#endif
|
||||
|
||||
video::VideoSource* videoSource=NULL;
|
||||
video::VideoRenderer* videoRenderer=NULL;
|
||||
double firstVideoFrameTime=0.0;
|
||||
uint32_t videoFrameCount=0;
|
||||
uint32_t lastReceivedVideoFrameNumber=UINT32_MAX;
|
||||
std::vector<SentVideoFrame> sentVideoFrames;
|
||||
Mutex sentVideoFramesMutex;
|
||||
bool videoKeyframeRequested=false;
|
||||
video::ScreamCongestionController videoCongestionControl;
|
||||
std::vector<PendingVideoFrameFragment> videoPacingQueue;
|
||||
uint32_t sendVideoPacketID=MessageThread::INVALID_ID;
|
||||
uint32_t videoPacketLossCount=0;
|
||||
uint32_t currentVideoBitrate=0;
|
||||
double lastVideoResolutionChangeTime=0.0;
|
||||
|
||||
/*** debug report problems ***/
|
||||
bool wasReconnecting=false;
|
||||
bool wasExtraEC=false;
|
||||
bool wasEncoderLaggy=false;
|
||||
bool wasNetworkHandover=false;
|
||||
|
||||
/*** persistable state values ***/
|
||||
bool proxySupportsUDP=true;
|
||||
bool proxySupportsTCP=true;
|
||||
std::string lastTestedProxyServer="";
|
||||
|
||||
/*** server config values ***/
|
||||
uint32_t maxAudioBitrate;
|
||||
uint32_t maxAudioBitrateEDGE;
|
||||
uint32_t maxAudioBitrateGPRS;
|
||||
uint32_t maxAudioBitrateSaving;
|
||||
uint32_t initAudioBitrate;
|
||||
uint32_t initAudioBitrateEDGE;
|
||||
uint32_t initAudioBitrateGPRS;
|
||||
uint32_t initAudioBitrateSaving;
|
||||
uint32_t minAudioBitrate;
|
||||
uint32_t audioBitrateStepIncr;
|
||||
uint32_t audioBitrateStepDecr;
|
||||
double relaySwitchThreshold;
|
||||
double p2pToRelaySwitchThreshold;
|
||||
double relayToP2pSwitchThreshold;
|
||||
double reconnectingTimeout;
|
||||
uint32_t needRateFlags;
|
||||
double rateMaxAcceptableRTT;
|
||||
double rateMaxAcceptableSendLoss;
|
||||
double packetLossToEnableExtraEC;
|
||||
uint32_t maxUnsentStreamPackets;
|
||||
|
||||
public:
|
||||
#ifdef __APPLE__
|
||||
static double machTimebase;
|
||||
static uint64_t machTimestart;
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
static int64_t win32TimeScale;
|
||||
static bool didInitWin32TimeScale;
|
||||
#endif
|
||||
};
|
||||
|
||||
class VoIPGroupController : public VoIPController{
|
||||
public:
|
||||
VoIPGroupController(int32_t timeDifference);
|
||||
virtual ~VoIPGroupController();
|
||||
void SetGroupCallInfo(unsigned char* encryptionKey, unsigned char* reflectorGroupTag, unsigned char* reflectorSelfTag, unsigned char* reflectorSelfSecret, unsigned char* reflectorSelfTagHash, int32_t selfUserID, IPv4Address reflectorAddress, IPv6Address reflectorAddressV6, uint16_t reflectorPort);
|
||||
void AddGroupCallParticipant(int32_t userID, unsigned char* memberTagHash, unsigned char* serializedStreams, size_t streamsLength);
|
||||
void RemoveGroupCallParticipant(int32_t userID);
|
||||
float GetParticipantAudioLevel(int32_t userID);
|
||||
virtual void SetMicMute(bool mute);
|
||||
void SetParticipantVolume(int32_t userID, float volume);
|
||||
void SetParticipantStreams(int32_t userID, unsigned char* serializedStreams, size_t length);
|
||||
static size_t GetInitialStreams(unsigned char* buf, size_t size);
|
||||
|
||||
struct Callbacks : public VoIPController::Callbacks{
|
||||
void (*updateStreams)(VoIPGroupController*, unsigned char*, size_t);
|
||||
void (*participantAudioStateChanged)(VoIPGroupController*, int32_t, bool);
|
||||
|
||||
};
|
||||
void SetCallbacks(Callbacks callbacks);
|
||||
virtual std::string GetDebugString();
|
||||
virtual void SetNetworkType(int type);
|
||||
protected:
|
||||
virtual void ProcessIncomingPacket(NetworkPacket& packet, Endpoint& srcEndpoint);
|
||||
virtual void SendInit();
|
||||
virtual void SendUdpPing(Endpoint& endpoint);
|
||||
virtual void SendRelayPings();
|
||||
virtual void SendPacket(unsigned char* data, size_t len, Endpoint& ep, PendingOutgoingPacket& srcPacket);
|
||||
virtual void WritePacketHeader(uint32_t seq, BufferOutputStream* s, unsigned char type, uint32_t length);
|
||||
virtual void OnAudioOutputReady();
|
||||
private:
|
||||
int32_t GetCurrentUnixtime();
|
||||
std::vector<std::shared_ptr<Stream>> DeserializeStreams(BufferInputStream& in);
|
||||
void SendRecentPacketsRequest();
|
||||
void SendSpecialReflectorRequest(unsigned char* data, size_t len);
|
||||
void SerializeAndUpdateOutgoingStreams();
|
||||
struct GroupCallParticipant{
|
||||
int32_t userID;
|
||||
unsigned char memberTagHash[32];
|
||||
std::vector<std::shared_ptr<Stream>> streams;
|
||||
AudioLevelMeter* levelMeter;
|
||||
};
|
||||
std::vector<GroupCallParticipant> participants;
|
||||
unsigned char reflectorSelfTag[16];
|
||||
unsigned char reflectorSelfSecret[16];
|
||||
unsigned char reflectorSelfTagHash[32];
|
||||
int32_t userSelfID;
|
||||
Endpoint groupReflector;
|
||||
AudioMixer* audioMixer;
|
||||
AudioLevelMeter selfLevelMeter;
|
||||
Callbacks groupCallbacks;
|
||||
struct PacketIdMapping{
|
||||
uint32_t seq;
|
||||
uint16_t id;
|
||||
double ackTime;
|
||||
};
|
||||
std::vector<PacketIdMapping> recentSentPackets;
|
||||
Mutex sentPacketsMutex;
|
||||
Mutex participantsMutex;
|
||||
int32_t timeDifference;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
816
TMessagesProj/jni/voip/libtgvoip/VoIPGroupController.cpp
Normal file
816
TMessagesProj/jni/voip/libtgvoip/VoIPGroupController.cpp
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
//
|
||||
// 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 "VoIPController.h"
|
||||
#include "logging.h"
|
||||
#include "VoIPServerConfig.h"
|
||||
#include "PrivateDefines.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace std;
|
||||
|
||||
VoIPGroupController::VoIPGroupController(int32_t timeDifference){
|
||||
audioMixer=new AudioMixer();
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
userSelfID=0;
|
||||
this->timeDifference=timeDifference;
|
||||
LOGV("Created VoIPGroupController; timeDifference=%d", timeDifference);
|
||||
}
|
||||
|
||||
VoIPGroupController::~VoIPGroupController(){
|
||||
if(audioOutput){
|
||||
audioOutput->Stop();
|
||||
}
|
||||
LOGD("before stop audio mixer");
|
||||
audioMixer->Stop();
|
||||
delete audioMixer;
|
||||
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();p++){
|
||||
if(p->levelMeter)
|
||||
delete p->levelMeter;
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetGroupCallInfo(unsigned char *encryptionKey, unsigned char *reflectorGroupTag, unsigned char *reflectorSelfTag, unsigned char *reflectorSelfSecret, unsigned char* reflectorSelfTagHash, int32_t selfUserID, IPv4Address reflectorAddress, IPv6Address reflectorAddressV6, uint16_t reflectorPort){
|
||||
Endpoint e;
|
||||
e.address=reflectorAddress;
|
||||
e.v6address=reflectorAddressV6;
|
||||
e.port=reflectorPort;
|
||||
memcpy(e.peerTag, reflectorGroupTag, 16);
|
||||
e.type=Endpoint::Type::UDP_RELAY;
|
||||
e.id=FOURCC('G','R','P','R');
|
||||
endpoints[e.id]=e;
|
||||
groupReflector=e;
|
||||
currentEndpoint=e.id;
|
||||
|
||||
memcpy(this->encryptionKey, encryptionKey, 256);
|
||||
memcpy(this->reflectorSelfTag, reflectorSelfTag, 16);
|
||||
memcpy(this->reflectorSelfSecret, reflectorSelfSecret, 16);
|
||||
memcpy(this->reflectorSelfTagHash, reflectorSelfTagHash, 16);
|
||||
uint8_t sha256[SHA256_LENGTH];
|
||||
crypto.sha256((uint8_t*) encryptionKey, 256, sha256);
|
||||
memcpy(callID, sha256+(SHA256_LENGTH-16), 16);
|
||||
memcpy(keyFingerprint, sha256+(SHA256_LENGTH-16), 8);
|
||||
this->userSelfID=selfUserID;
|
||||
|
||||
//LOGD("reflectorSelfTag = %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", reflectorSelfTag[0], reflectorSelfTag[1], reflectorSelfTag[2], reflectorSelfTag[3], reflectorSelfTag[4], reflectorSelfTag[5], reflectorSelfTag[6], reflectorSelfTag[7], reflectorSelfTag[8], reflectorSelfTag[9], reflectorSelfTag[10], reflectorSelfTag[11], reflectorSelfTag[12], reflectorSelfTag[13], reflectorSelfTag[14], reflectorSelfTag[15]);
|
||||
//LOGD("reflectorSelfSecret = %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", reflectorSelfSecret[0], reflectorSelfSecret[1], reflectorSelfSecret[2], reflectorSelfSecret[3], reflectorSelfSecret[4], reflectorSelfSecret[5], reflectorSelfSecret[6], reflectorSelfSecret[7], reflectorSelfSecret[8], reflectorSelfSecret[9], reflectorSelfSecret[10], reflectorSelfSecret[11], reflectorSelfSecret[12], reflectorSelfSecret[13], reflectorSelfSecret[14], reflectorSelfSecret[15]);
|
||||
//LOGD("reflectorSelfTagHash = %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", reflectorSelfTagHash[0], reflectorSelfTagHash[1], reflectorSelfTagHash[2], reflectorSelfTagHash[3], reflectorSelfTagHash[4], reflectorSelfTagHash[5], reflectorSelfTagHash[6], reflectorSelfTagHash[7], reflectorSelfTagHash[8], reflectorSelfTagHash[9], reflectorSelfTagHash[10], reflectorSelfTagHash[11], reflectorSelfTagHash[12], reflectorSelfTagHash[13], reflectorSelfTagHash[14], reflectorSelfTagHash[15]);
|
||||
}
|
||||
|
||||
void VoIPGroupController::AddGroupCallParticipant(int32_t userID, unsigned char *memberTagHash, unsigned char* serializedStreams, size_t streamsLength){
|
||||
if(userID==userSelfID)
|
||||
return;
|
||||
if(userSelfID==0)
|
||||
return;
|
||||
//if(streamsLength==0)
|
||||
// return;
|
||||
MutexGuard m(participantsMutex);
|
||||
LOGV("Adding group call user %d, streams length %u", userID, (unsigned int)streamsLength);
|
||||
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
if(p->userID==userID){
|
||||
LOGE("user %d already added", userID);
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GroupCallParticipant p;
|
||||
p.userID=userID;
|
||||
memcpy(p.memberTagHash, memberTagHash, sizeof(p.memberTagHash));
|
||||
p.levelMeter=new AudioLevelMeter();
|
||||
|
||||
BufferInputStream ss(serializedStreams, streamsLength);
|
||||
vector<shared_ptr<Stream>> streams=DeserializeStreams(ss);
|
||||
|
||||
unsigned char audioStreamID=0;
|
||||
|
||||
for(vector<shared_ptr<Stream>>::iterator _s=streams.begin();_s!=streams.end();++_s){
|
||||
shared_ptr<Stream>& s=*_s;
|
||||
s->userID=userID;
|
||||
if(s->type==STREAM_TYPE_AUDIO && s->codec==CODEC_OPUS && !audioStreamID){
|
||||
audioStreamID=s->id;
|
||||
s->jitterBuffer=make_shared<JitterBuffer>(nullptr, s->frameDuration);
|
||||
if(s->frameDuration>50)
|
||||
s->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_60", 2));
|
||||
else if(s->frameDuration>30)
|
||||
s->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_40", 4));
|
||||
else
|
||||
s->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_20", 6));
|
||||
s->callbackWrapper=make_shared<CallbackWrapper>();
|
||||
s->decoder=make_shared<OpusDecoder>(s->callbackWrapper, false, false);
|
||||
s->decoder->SetJitterBuffer(s->jitterBuffer);
|
||||
s->decoder->SetFrameDuration(s->frameDuration);
|
||||
s->decoder->SetDTX(true);
|
||||
s->decoder->SetLevelMeter(p.levelMeter);
|
||||
audioMixer->AddInput(s->callbackWrapper);
|
||||
}
|
||||
incomingStreams.push_back(s);
|
||||
}
|
||||
|
||||
if(!audioStreamID){
|
||||
LOGW("User %d has no usable audio stream", userID);
|
||||
}
|
||||
|
||||
p.streams.insert(p.streams.end(), streams.begin(), streams.end());
|
||||
participants.push_back(p);
|
||||
LOGI("Added group call participant %d", userID);
|
||||
}
|
||||
|
||||
void VoIPGroupController::RemoveGroupCallParticipant(int32_t userID){
|
||||
MutexGuard m(participantsMutex);
|
||||
vector<shared_ptr<Stream>>::iterator stm=incomingStreams.begin();
|
||||
while(stm!=incomingStreams.end()){
|
||||
if((*stm)->userID==userID){
|
||||
LOGI("Removed stream %d belonging to user %d", (*stm)->id, userID);
|
||||
audioMixer->RemoveInput((*stm)->callbackWrapper);
|
||||
(*stm)->decoder->Stop();
|
||||
//delete (*stm)->decoder;
|
||||
//delete (*stm)->jitterBuffer;
|
||||
//delete (*stm)->callbackWrapper;
|
||||
stm=incomingStreams.erase(stm);
|
||||
continue;
|
||||
}
|
||||
++stm;
|
||||
}
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
if(p->userID==userID){
|
||||
if(p->levelMeter)
|
||||
delete p->levelMeter;
|
||||
participants.erase(p);
|
||||
LOGI("Removed group call participant %d", userID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<VoIPController::Stream>> VoIPGroupController::DeserializeStreams(BufferInputStream& in){
|
||||
vector<shared_ptr<Stream>> res;
|
||||
try{
|
||||
unsigned char count=in.ReadByte();
|
||||
for(unsigned char i=0;i<count;i++){
|
||||
uint16_t len=(uint16_t) in.ReadInt16();
|
||||
BufferInputStream inner=in.GetPartBuffer(len, true);
|
||||
shared_ptr<Stream> s=make_shared<Stream>();
|
||||
s->id=inner.ReadByte();
|
||||
s->type=inner.ReadByte();
|
||||
s->codec=(uint32_t) inner.ReadInt32();
|
||||
uint32_t flags=(uint32_t) inner.ReadInt32();
|
||||
s->enabled=(flags & STREAM_FLAG_ENABLED)==STREAM_FLAG_ENABLED;
|
||||
s->frameDuration=(uint16_t) inner.ReadInt16();
|
||||
res.push_back(s);
|
||||
}
|
||||
}catch(out_of_range& x){
|
||||
LOGW("Error deserializing streams: %s", x.what());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetParticipantStreams(int32_t userID, unsigned char *serializedStreams, size_t length){
|
||||
LOGD("Set participant streams for %d", userID);
|
||||
MutexGuard m(participantsMutex);
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
if(p->userID==userID){
|
||||
BufferInputStream in(serializedStreams, length);
|
||||
vector<shared_ptr<Stream>> streams=DeserializeStreams(in);
|
||||
for(vector<shared_ptr<Stream>>::iterator ns=streams.begin();ns!=streams.end();++ns){
|
||||
bool found=false;
|
||||
for(vector<shared_ptr<Stream>>::iterator s=p->streams.begin();s!=p->streams.end();++s){
|
||||
if((*s)->id==(*ns)->id){
|
||||
(*s)->enabled=(*ns)->enabled;
|
||||
if(groupCallbacks.participantAudioStateChanged)
|
||||
groupCallbacks.participantAudioStateChanged(this, userID, (*s)->enabled);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found){
|
||||
LOGW("Tried to add stream %d for user %d but adding/removing streams is not supported", (*ns)->id, userID);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t VoIPGroupController::GetInitialStreams(unsigned char *buf, size_t size){
|
||||
BufferOutputStream s(buf, size);
|
||||
s.WriteByte(1); // streams count
|
||||
|
||||
s.WriteInt16(12); // this object length
|
||||
s.WriteByte(1); // stream id
|
||||
s.WriteByte(STREAM_TYPE_AUDIO);
|
||||
s.WriteInt32(CODEC_OPUS);
|
||||
s.WriteInt32(STREAM_FLAG_ENABLED | STREAM_FLAG_DTX); // flags
|
||||
s.WriteInt16(60); // frame duration
|
||||
|
||||
return s.GetLength();
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendInit(){
|
||||
SendRecentPacketsRequest();
|
||||
}
|
||||
|
||||
void VoIPGroupController::ProcessIncomingPacket(NetworkPacket &packet, Endpoint& srcEndpoint){
|
||||
//LOGD("Received incoming packet from %s:%u, %u bytes", packet.address->ToString().c_str(), packet.port, packet.length);
|
||||
if(packet.length<17 || packet.length>2000){
|
||||
LOGW("Received packet has wrong length %d", (int)packet.length);
|
||||
return;
|
||||
}
|
||||
BufferOutputStream sigData(packet.length);
|
||||
sigData.WriteBytes(packet.data, packet.length-16);
|
||||
sigData.WriteBytes(reflectorSelfSecret, 16);
|
||||
unsigned char sig[32];
|
||||
crypto.sha256(sigData.GetBuffer(), sigData.GetLength(), sig);
|
||||
if(memcmp(sig, packet.data+(packet.length-16), 16)!=0){
|
||||
LOGW("Received packet has incorrect signature");
|
||||
return;
|
||||
}
|
||||
|
||||
// reflector special response
|
||||
if(memcmp(packet.data, reflectorSelfTagHash, 16)==0 && packet.length>60){
|
||||
//LOGI("possible reflector special response");
|
||||
unsigned char firstBlock[16];
|
||||
unsigned char iv[16];
|
||||
memcpy(iv, packet.data+16, 16);
|
||||
unsigned char key[32];
|
||||
crypto.sha256(reflectorSelfSecret, 16, key);
|
||||
crypto.aes_cbc_decrypt(packet.data+32, firstBlock, 16, key, iv);
|
||||
BufferInputStream in(firstBlock, 16);
|
||||
in.Seek(8);
|
||||
size_t len=(size_t) in.ReadInt32();
|
||||
int32_t tlid=in.ReadInt32();
|
||||
//LOGD("special response: len=%d, tlid=0x%08X", len, tlid);
|
||||
if(len%4==0 && len+60<=packet.length && packet.length<=1500){
|
||||
lastRecvPacketTime=GetCurrentTime();
|
||||
memcpy(iv, packet.data+16, 16);
|
||||
unsigned char buf[1500];
|
||||
crypto.aes_cbc_decrypt(packet.data+32, buf, len+16, key, iv);
|
||||
try{
|
||||
if(tlid==TLID_UDP_REFLECTOR_LAST_PACKETS_INFO){
|
||||
MutexGuard m(sentPacketsMutex);
|
||||
//LOGV("received udpReflector.lastPacketsInfo");
|
||||
in=BufferInputStream(buf, len+16);
|
||||
in.Seek(16);
|
||||
/*int32_t date=*/in.ReadInt32();
|
||||
/*int64_t queryID=*/in.ReadInt64();
|
||||
int32_t vectorMagic=in.ReadInt32();
|
||||
if(vectorMagic!=TLID_VECTOR){
|
||||
LOGW("last packets info: expected vector, got %08X", vectorMagic);
|
||||
return;
|
||||
}
|
||||
int32_t recvCount=in.ReadInt32();
|
||||
//LOGV("%d received packets", recvCount);
|
||||
for(int i=0;i<recvCount;i++){
|
||||
uint32_t p=(uint32_t) in.ReadInt32();
|
||||
//LOGV("Relay received packet: %08X", p);
|
||||
uint16_t id=(uint16_t) (p & 0xFFFF);
|
||||
//LOGV("ack id %04X", id);
|
||||
for(vector<PacketIdMapping>::iterator pkt=recentSentPackets.begin();pkt!=recentSentPackets.end();++pkt){
|
||||
//LOGV("== sent id %04X", pkt->id);
|
||||
if(pkt->id==id){
|
||||
if(!pkt->ackTime){
|
||||
pkt->ackTime=GetCurrentTime();
|
||||
conctl->PacketAcknowledged(pkt->seq);
|
||||
//LOGV("relay acknowledged packet %u", pkt->seq);
|
||||
if(seqgt(pkt->seq, lastRemoteAckSeq))
|
||||
lastRemoteAckSeq=pkt->seq;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vectorMagic=in.ReadInt32();
|
||||
if(vectorMagic!=TLID_VECTOR){
|
||||
LOGW("last packets info: expected vector, got %08X", vectorMagic);
|
||||
return;
|
||||
}
|
||||
int32_t sentCount=in.ReadInt32();
|
||||
//LOGV("%d sent packets", sentCount);
|
||||
for(int i=0;i<sentCount;i++){
|
||||
/*int32_t p=*/in.ReadInt32();
|
||||
//LOGV("Sent packet: %08X", p);
|
||||
}
|
||||
if(udpConnectivityState!=UDP_AVAILABLE)
|
||||
udpConnectivityState=UDP_AVAILABLE;
|
||||
if(state!=STATE_ESTABLISHED)
|
||||
SetState(STATE_ESTABLISHED);
|
||||
if(!audioInput){
|
||||
InitializeAudio();
|
||||
if(state!=STATE_FAILED){
|
||||
// audioOutput->Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(out_of_range& x){
|
||||
LOGE("Error parsing special response: %s", x.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(packet.length<32)
|
||||
return;
|
||||
|
||||
// it's a packet relayed from another participant - find the sender
|
||||
MutexGuard m(participantsMutex);
|
||||
GroupCallParticipant* sender=NULL;
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
if(memcmp(packet.data, p->memberTagHash, 16)==0){
|
||||
//LOGV("received data packet from user %d", p->userID);
|
||||
sender=&*p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!sender){
|
||||
LOGV("Received data packet is from unknown user");
|
||||
return;
|
||||
}
|
||||
|
||||
if(memcmp(packet.data+16, keyFingerprint, 8)!=0){
|
||||
LOGW("received packet has wrong key fingerprint");
|
||||
return;
|
||||
}
|
||||
|
||||
BufferInputStream in(packet.data, packet.length-16);
|
||||
in.Seek(16+8); // peer tag + key fingerprint
|
||||
|
||||
unsigned char msgKey[16];
|
||||
in.ReadBytes(msgKey, 16);
|
||||
|
||||
unsigned char decrypted[1500];
|
||||
unsigned char aesKey[32], aesIv[32];
|
||||
KDF2(msgKey, 0, aesKey, aesIv);
|
||||
size_t decryptedLen=in.Remaining()-16;
|
||||
if(decryptedLen>sizeof(decrypted))
|
||||
return;
|
||||
//LOGV("-> MSG KEY: %08x %08x %08x %08x, hashed %u", *reinterpret_cast<int32_t*>(msgKey), *reinterpret_cast<int32_t*>(msgKey+4), *reinterpret_cast<int32_t*>(msgKey+8), *reinterpret_cast<int32_t*>(msgKey+12), decryptedLen-4);
|
||||
uint8_t *decryptOffset = packet.data + in.GetOffset();
|
||||
if ((((intptr_t)decryptOffset) % sizeof(long)) != 0) {
|
||||
LOGE("alignment2 packet.data+in.GetOffset()");
|
||||
}
|
||||
if (decryptedLen % sizeof(long) != 0) {
|
||||
LOGE("alignment2 decryptedLen");
|
||||
}
|
||||
crypto.aes_ige_decrypt(packet.data+in.GetOffset(), decrypted, decryptedLen, aesKey, aesIv);
|
||||
|
||||
in=BufferInputStream(decrypted, decryptedLen);
|
||||
//LOGD("received packet length: %d", in.ReadInt32());
|
||||
|
||||
BufferOutputStream buf(decryptedLen+32);
|
||||
size_t x=0;
|
||||
buf.WriteBytes(encryptionKey+88+x, 32);
|
||||
buf.WriteBytes(decrypted+4, decryptedLen-4);
|
||||
unsigned char msgKeyLarge[32];
|
||||
crypto.sha256(buf.GetBuffer(), buf.GetLength(), msgKeyLarge);
|
||||
|
||||
if(memcmp(msgKey, msgKeyLarge+8, 16)!=0){
|
||||
LOGW("Received packet from user %d has wrong hash", sender->userID);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t innerLen=(uint32_t) in.ReadInt32();
|
||||
if(innerLen>decryptedLen-4){
|
||||
LOGW("Received packet has wrong inner length (%d with total of %u)", (int)innerLen, (unsigned int)decryptedLen);
|
||||
return;
|
||||
}
|
||||
if(decryptedLen-innerLen<12){
|
||||
LOGW("Received packet has too little padding (%u)", (unsigned int)(decryptedLen-innerLen));
|
||||
return;
|
||||
}
|
||||
in=BufferInputStream(decrypted+4, (size_t) innerLen);
|
||||
|
||||
uint32_t tlid=(uint32_t) in.ReadInt32();
|
||||
if(tlid!=TLID_DECRYPTED_AUDIO_BLOCK){
|
||||
LOGW("Received packet has unknown TL ID 0x%08x", tlid);
|
||||
return;
|
||||
}
|
||||
in.Seek(in.GetOffset()+16); // random bytes
|
||||
int32_t flags=in.ReadInt32();
|
||||
if(!(flags & PFLAG_HAS_SEQ) || !(flags & PFLAG_HAS_SENDER_TAG_HASH)){
|
||||
LOGW("Received packet has wrong flags");
|
||||
return;
|
||||
}
|
||||
/*uint32_t seq=(uint32_t) */in.ReadInt32();
|
||||
unsigned char senderTagHash[16];
|
||||
in.ReadBytes(senderTagHash, 16);
|
||||
if(memcmp(senderTagHash, sender->memberTagHash, 16)!=0){
|
||||
LOGW("Received packet has wrong inner sender tag hash");
|
||||
return;
|
||||
}
|
||||
|
||||
//int32_t oneMoreInnerLengthWhyDoWeEvenNeedThis;
|
||||
if(flags & PFLAG_HAS_DATA){
|
||||
/*oneMoreInnerLengthWhyDoWeEvenNeedThis=*/in.ReadTlLength();
|
||||
}
|
||||
unsigned char type=(unsigned char) ((flags >> 24) & 0xFF);
|
||||
lastRecvPacketTime=GetCurrentTime();
|
||||
|
||||
if(type==PKT_STREAM_DATA || type==PKT_STREAM_DATA_X2 || type==PKT_STREAM_DATA_X3){
|
||||
if(state!=STATE_ESTABLISHED && receivedInitAck)
|
||||
SetState(STATE_ESTABLISHED);
|
||||
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;
|
||||
//if(srcEndpoint->type==Endpoint::Type::UDP_RELAY && srcEndpoint!=peerPreferredRelay){
|
||||
// peerPreferredRelay=srcEndpoint;
|
||||
//}
|
||||
for(i=0;i<count;i++){
|
||||
unsigned char streamID=in.ReadByte();
|
||||
unsigned char sflags=(unsigned char) (streamID & 0xC0);
|
||||
uint16_t sdlen=(uint16_t) (sflags & 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(in.GetOffset()+sdlen>in.GetLength()){
|
||||
return;
|
||||
}
|
||||
for(vector<shared_ptr<Stream>>::iterator stm=sender->streams.begin();stm!=sender->streams.end();++stm){
|
||||
if((*stm)->id==streamID){
|
||||
if((*stm)->jitterBuffer){
|
||||
(*stm)->jitterBuffer->HandleInput(decrypted+4+in.GetOffset(), sdlen, pts, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i<count-1)
|
||||
in.Seek(in.GetOffset()+sdlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendUdpPing(Endpoint& endpoint){
|
||||
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetNetworkType(int type){
|
||||
networkType=type;
|
||||
UpdateDataSavingState();
|
||||
UpdateAudioBitrateLimit();
|
||||
string itfName=udpSocket->GetLocalInterfaceInfo(NULL, NULL);
|
||||
if(itfName!=activeNetItfName){
|
||||
udpSocket->OnActiveInterfaceChanged();
|
||||
LOGI("Active network interface changed: %s -> %s", activeNetItfName.c_str(), itfName.c_str());
|
||||
bool isFirstChange=activeNetItfName.length()==0;
|
||||
activeNetItfName=itfName;
|
||||
if(isFirstChange)
|
||||
return;
|
||||
udpConnectivityState=UDP_UNKNOWN;
|
||||
udpPingCount=0;
|
||||
lastUdpPingTime=0;
|
||||
if(proxyProtocol==PROXY_SOCKS5)
|
||||
InitUDPProxy();
|
||||
selectCanceller->CancelSelect();
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendRecentPacketsRequest(){
|
||||
BufferOutputStream out(1024);
|
||||
out.WriteInt32(TLID_UDP_REFLECTOR_REQUEST_PACKETS_INFO); // TL function
|
||||
out.WriteInt32(GetCurrentUnixtime()); // date:int
|
||||
out.WriteInt64(0); // query_id:long
|
||||
out.WriteInt32(64); // recv_num:int
|
||||
out.WriteInt32(0); // sent_num:int
|
||||
SendSpecialReflectorRequest(out.GetBuffer(), out.GetLength());
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendSpecialReflectorRequest(unsigned char *data, size_t len){
|
||||
BufferOutputStream out(1024);
|
||||
unsigned char buf[1500];
|
||||
crypto.rand_bytes(buf, 8);
|
||||
out.WriteBytes(buf, 8);
|
||||
out.WriteInt32((int32_t)len);
|
||||
out.WriteBytes(data, len);
|
||||
if(out.GetLength()%16!=0){
|
||||
size_t paddingLen=16-(out.GetLength()%16);
|
||||
crypto.rand_bytes(buf, paddingLen);
|
||||
out.WriteBytes(buf, paddingLen);
|
||||
}
|
||||
unsigned char iv[16];
|
||||
crypto.rand_bytes(iv, 16);
|
||||
unsigned char key[32];
|
||||
crypto.sha256(reflectorSelfSecret, 16, key);
|
||||
unsigned char _iv[16];
|
||||
memcpy(_iv, iv, 16);
|
||||
size_t encryptedLen=out.GetLength();
|
||||
crypto.aes_cbc_encrypt(out.GetBuffer(), buf, encryptedLen, key, _iv);
|
||||
out.Reset();
|
||||
out.WriteBytes(reflectorSelfTag, 16);
|
||||
out.WriteBytes(iv, 16);
|
||||
out.WriteBytes(buf, encryptedLen);
|
||||
out.WriteBytes(reflectorSelfSecret, 16);
|
||||
crypto.sha256(out.GetBuffer(), out.GetLength(), buf);
|
||||
out.Rewind(16);
|
||||
out.WriteBytes(buf, 16);
|
||||
|
||||
NetworkPacket pkt={0};
|
||||
pkt.address=&groupReflector.address;
|
||||
pkt.port=groupReflector.port;
|
||||
pkt.protocol=PROTO_UDP;
|
||||
pkt.data=out.GetBuffer();
|
||||
pkt.length=out.GetLength();
|
||||
ActuallySendPacket(pkt, groupReflector);
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendRelayPings(){
|
||||
//LOGV("Send relay pings 2");
|
||||
double currentTime=GetCurrentTime();
|
||||
if(currentTime-groupReflector.lastPingTime>=0.25){
|
||||
SendRecentPacketsRequest();
|
||||
groupReflector.lastPingTime=currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::OnAudioOutputReady(){
|
||||
encoder->SetDTX(true);
|
||||
audioMixer->SetOutput(audioOutput);
|
||||
audioMixer->SetEchoCanceller(echoCanceller);
|
||||
audioMixer->Start();
|
||||
audioOutput->Start();
|
||||
audioOutStarted=true;
|
||||
encoder->SetLevelMeter(&selfLevelMeter);
|
||||
}
|
||||
|
||||
void VoIPGroupController::WritePacketHeader(uint32_t seq, BufferOutputStream *s, unsigned char type, uint32_t length){
|
||||
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(randBytes, 7);
|
||||
uint32_t pflags=PFLAG_HAS_SEQ | PFLAG_HAS_SENDER_TAG_HASH;
|
||||
if(length>0)
|
||||
pflags|=PFLAG_HAS_DATA;
|
||||
pflags|=((uint32_t) type) << 24;
|
||||
s->WriteInt32(pflags);
|
||||
|
||||
if(type==PKT_STREAM_DATA || type==PKT_STREAM_DATA_X2 || type==PKT_STREAM_DATA_X3){
|
||||
conctl->PacketSent(seq, length);
|
||||
}
|
||||
|
||||
/*if(pflags & PFLAG_HAS_CALL_ID){
|
||||
s->WriteBytes(callID, 16);
|
||||
}*/
|
||||
//s->WriteInt32(lastRemoteSeq);
|
||||
s->WriteInt32(seq);
|
||||
s->WriteBytes(reflectorSelfTagHash, 16);
|
||||
if(length>0){
|
||||
if(length<=253){
|
||||
s->WriteByte((unsigned char) length);
|
||||
}else{
|
||||
s->WriteByte(254);
|
||||
s->WriteByte((unsigned char) (length & 0xFF));
|
||||
s->WriteByte((unsigned char) ((length >> 8) & 0xFF));
|
||||
s->WriteByte((unsigned char) ((length >> 16) & 0xFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::SendPacket(unsigned char *data, size_t len, Endpoint& ep, PendingOutgoingPacket& srcPacket){
|
||||
if(stopping)
|
||||
return;
|
||||
if(ep.type==Endpoint::Type::TCP_RELAY && !useTCP)
|
||||
return;
|
||||
BufferOutputStream out(len+128);
|
||||
//LOGV("send group packet %u", len);
|
||||
|
||||
out.WriteBytes(reflectorSelfTag, 16);
|
||||
|
||||
if(len>0){
|
||||
BufferOutputStream inner(len+128);
|
||||
inner.WriteInt32((uint32_t)len);
|
||||
inner.WriteBytes(data, len);
|
||||
size_t padLen=16-inner.GetLength()%16;
|
||||
if(padLen<12)
|
||||
padLen+=16;
|
||||
unsigned char padding[28];
|
||||
crypto.rand_bytes((uint8_t *) padding, padLen);
|
||||
inner.WriteBytes(padding, padLen);
|
||||
assert(inner.GetLength()%16==0);
|
||||
|
||||
unsigned char key[32], iv[32], msgKey[16];
|
||||
out.WriteBytes(keyFingerprint, 8);
|
||||
BufferOutputStream buf(len+32);
|
||||
size_t x=0;
|
||||
buf.WriteBytes(encryptionKey+88+x, 32);
|
||||
buf.WriteBytes(inner.GetBuffer()+4, inner.GetLength()-4);
|
||||
unsigned char msgKeyLarge[32];
|
||||
crypto.sha256(buf.GetBuffer(), buf.GetLength(), msgKeyLarge);
|
||||
memcpy(msgKey, msgKeyLarge+8, 16);
|
||||
KDF2(msgKey, 0, key, iv);
|
||||
out.WriteBytes(msgKey, 16);
|
||||
//LOGV("<- MSG KEY: %08x %08x %08x %08x, hashed %u", *reinterpret_cast<int32_t*>(msgKey), *reinterpret_cast<int32_t*>(msgKey+4), *reinterpret_cast<int32_t*>(msgKey+8), *reinterpret_cast<int32_t*>(msgKey+12), inner.GetLength()-4);
|
||||
|
||||
unsigned char aesOut[MSC_STACK_FALLBACK(inner.GetLength(), 1500)];
|
||||
crypto.aes_ige_encrypt(inner.GetBuffer(), aesOut, inner.GetLength(), key, iv);
|
||||
out.WriteBytes(aesOut, inner.GetLength());
|
||||
}
|
||||
|
||||
// relay signature
|
||||
out.WriteBytes(reflectorSelfSecret, 16);
|
||||
unsigned char sig[32];
|
||||
crypto.sha256(out.GetBuffer(), out.GetLength(), sig);
|
||||
out.Rewind(16);
|
||||
out.WriteBytes(sig, 16);
|
||||
|
||||
if(srcPacket.type==PKT_STREAM_DATA || srcPacket.type==PKT_STREAM_DATA_X2 || srcPacket.type==PKT_STREAM_DATA_X3){
|
||||
PacketIdMapping mapping={srcPacket.seq, *reinterpret_cast<uint16_t*>(sig+14), 0};
|
||||
MutexGuard m(sentPacketsMutex);
|
||||
recentSentPackets.push_back(mapping);
|
||||
//LOGD("sent packet with id: %04X", mapping.id);
|
||||
while(recentSentPackets.size()>64)
|
||||
recentSentPackets.erase(recentSentPackets.begin());
|
||||
}
|
||||
lastSentSeq=srcPacket.seq;
|
||||
|
||||
if(IS_MOBILE_NETWORK(networkType))
|
||||
stats.bytesSentMobile+=(uint64_t)out.GetLength();
|
||||
else
|
||||
stats.bytesSentWifi+=(uint64_t)out.GetLength();
|
||||
|
||||
NetworkPacket pkt={0};
|
||||
pkt.address=(NetworkAddress*)&ep.address;
|
||||
pkt.port=ep.port;
|
||||
pkt.length=out.GetLength();
|
||||
pkt.data=out.GetBuffer();
|
||||
pkt.protocol=ep.type==Endpoint::Type::TCP_RELAY ? PROTO_TCP : PROTO_UDP;
|
||||
ActuallySendPacket(pkt, ep);
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetCallbacks(VoIPGroupController::Callbacks callbacks){
|
||||
VoIPController::SetCallbacks(callbacks);
|
||||
this->groupCallbacks=callbacks;
|
||||
}
|
||||
|
||||
int32_t VoIPGroupController::GetCurrentUnixtime(){
|
||||
return time(NULL)+timeDifference;
|
||||
}
|
||||
|
||||
float VoIPGroupController::GetParticipantAudioLevel(int32_t userID){
|
||||
if(userID==userSelfID)
|
||||
return selfLevelMeter.GetLevel();
|
||||
MutexGuard m(participantsMutex);
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin(); p!=participants.end(); ++p){
|
||||
if(p->userID==userID){
|
||||
return p->levelMeter->GetLevel();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetMicMute(bool mute){
|
||||
micMuted=mute;
|
||||
if(audioInput){
|
||||
if(mute)
|
||||
audioInput->Stop();
|
||||
else
|
||||
audioInput->Start();
|
||||
if(!audioInput->IsInitialized()){
|
||||
lastError=ERROR_AUDIO_IO;
|
||||
SetState(STATE_FAILED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
outgoingStreams[0]->enabled=!mute;
|
||||
SerializeAndUpdateOutgoingStreams();
|
||||
}
|
||||
|
||||
void VoIPGroupController::SetParticipantVolume(int32_t userID, float volume){
|
||||
MutexGuard m(participantsMutex);
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
if(p->userID==userID){
|
||||
for(vector<shared_ptr<Stream>>::iterator s=p->streams.begin();s!=p->streams.end();++s){
|
||||
if((*s)->type==STREAM_TYPE_AUDIO){
|
||||
if((*s)->decoder){
|
||||
float db;
|
||||
if(volume==0.0f)
|
||||
db=-INFINITY;
|
||||
else if(volume<1.0f)
|
||||
db=-50.0f*(1.0f-volume);
|
||||
else if(volume>1.0f && volume<=2.0f)
|
||||
db=10.0f*(volume-1.0f);
|
||||
else
|
||||
db=0.0f;
|
||||
//LOGV("Setting user %u audio volume to %.2f dB", userID, db);
|
||||
audioMixer->SetInputVolume((*s)->callbackWrapper, db);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoIPGroupController::SerializeAndUpdateOutgoingStreams(){
|
||||
BufferOutputStream out(1024);
|
||||
out.WriteByte((unsigned char) outgoingStreams.size());
|
||||
|
||||
for(vector<shared_ptr<Stream>>::iterator s=outgoingStreams.begin(); s!=outgoingStreams.end(); ++s){
|
||||
BufferOutputStream o(128);
|
||||
o.WriteByte((*s)->id);
|
||||
o.WriteByte((*s)->type);
|
||||
o.WriteInt32((*s)->codec);
|
||||
o.WriteInt32((unsigned char) (((*s)->enabled ? STREAM_FLAG_ENABLED : 0) | STREAM_FLAG_DTX));
|
||||
o.WriteInt16((*s)->frameDuration);
|
||||
out.WriteInt16((int16_t) o.GetLength());
|
||||
out.WriteBytes(o.GetBuffer(), o.GetLength());
|
||||
}
|
||||
if(groupCallbacks.updateStreams)
|
||||
groupCallbacks.updateStreams(this, out.GetBuffer(), out.GetLength());
|
||||
}
|
||||
|
||||
std::string VoIPGroupController::GetDebugString(){
|
||||
std::string r="Remote endpoints: \n";
|
||||
char buffer[2048];
|
||||
for(pair<const int64_t, Endpoint>& _endpoint:endpoints){
|
||||
Endpoint& endpoint=_endpoint.second;
|
||||
const char* type;
|
||||
switch(endpoint.type){
|
||||
case Endpoint::Type::UDP_P2P_INET:
|
||||
type="UDP_P2P_INET";
|
||||
break;
|
||||
case Endpoint::Type::UDP_P2P_LAN:
|
||||
type="UDP_P2P_LAN";
|
||||
break;
|
||||
case Endpoint::Type::UDP_RELAY:
|
||||
type="UDP_RELAY";
|
||||
break;
|
||||
case Endpoint::Type::TCP_RELAY:
|
||||
type="TCP_RELAY";
|
||||
break;
|
||||
default:
|
||||
type="UNKNOWN";
|
||||
break;
|
||||
}
|
||||
snprintf(buffer, sizeof(buffer), "%s:%u %dms [%s%s]\n", endpoint.address.ToString().c_str(), endpoint.port, (int)(endpoint.averageRTT*1000), type, currentEndpoint==endpoint.id ? ", IN_USE" : "");
|
||||
r+=buffer;
|
||||
}
|
||||
double avgLate[3];
|
||||
shared_ptr<JitterBuffer> jitterBuffer=incomingStreams.size()==1 ? incomingStreams[0]->jitterBuffer : NULL;
|
||||
if(jitterBuffer)
|
||||
jitterBuffer->GetAverageLateCount(avgLate);
|
||||
else
|
||||
memset(avgLate, 0, 3*sizeof(double));
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"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"
|
||||
"Send/recv losses: %u/%u (%d%%)\n"
|
||||
"Audio bitrate: %d kbit\n"
|
||||
"Bytes sent/recvd: %llu/%llu\n\n",
|
||||
(int)(conctl->GetAverageRTT()*1000), (int)(conctl->GetMinimumRTT()*1000),
|
||||
int(conctl->GetInflightDataSize()), int(conctl->GetCongestionWindow()),
|
||||
keyFingerprint[0],keyFingerprint[1],keyFingerprint[2],keyFingerprint[3],keyFingerprint[4],keyFingerprint[5],keyFingerprint[6],keyFingerprint[7],
|
||||
lastSentSeq, lastRemoteAckSeq,
|
||||
conctl->GetSendLossCount(), recvLossCount, encoder ? encoder->GetPacketLoss() : 0,
|
||||
encoder ? (encoder->GetBitrate()/1000) : 0,
|
||||
(long long unsigned int)(stats.bytesSentMobile+stats.bytesSentWifi),
|
||||
(long long unsigned int)(stats.bytesRecvdMobile+stats.bytesRecvdWifi));
|
||||
|
||||
MutexGuard m(participantsMutex);
|
||||
for(vector<GroupCallParticipant>::iterator p=participants.begin();p!=participants.end();++p){
|
||||
snprintf(buffer, sizeof(buffer), "Participant id: %d\n", p->userID);
|
||||
r+=buffer;
|
||||
for(vector<shared_ptr<Stream>>::iterator stm=p->streams.begin();stm!=p->streams.end();++stm){
|
||||
char* codec=reinterpret_cast<char*>(&(*stm)->codec);
|
||||
snprintf(buffer, sizeof(buffer), "Stream %d (type %d, codec '%c%c%c%c', %sabled)\n",
|
||||
(*stm)->id, (*stm)->type, codec[3], codec[2], codec[1], codec[0], (*stm)->enabled ? "en" : "dis");
|
||||
r+=buffer;
|
||||
if((*stm)->enabled){
|
||||
if((*stm)->jitterBuffer){
|
||||
snprintf(buffer, sizeof(buffer), "Jitter buffer: %d/%.2f\n",
|
||||
(*stm)->jitterBuffer->GetMinPacketCount(), (*stm)->jitterBuffer->GetAverageDelay());
|
||||
r+=buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
r+="\n";
|
||||
}
|
||||
return r;
|
||||
}
|
||||
70
TMessagesProj/jni/voip/libtgvoip/VoIPServerConfig.cpp
Normal file
70
TMessagesProj/jni/voip/libtgvoip/VoIPServerConfig.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// 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 "VoIPServerConfig.h"
|
||||
#include <stdlib.h>
|
||||
#include "logging.h"
|
||||
#include <sstream>
|
||||
#include <locale>
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
ServerConfig* ServerConfig::sharedInstance=NULL;
|
||||
|
||||
ServerConfig::ServerConfig(){
|
||||
}
|
||||
|
||||
ServerConfig::~ServerConfig(){
|
||||
}
|
||||
|
||||
ServerConfig *ServerConfig::GetSharedInstance(){
|
||||
if(!sharedInstance)
|
||||
sharedInstance=new ServerConfig();
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
bool ServerConfig::GetBoolean(std::string name, bool fallback){
|
||||
MutexGuard sync(mutex);
|
||||
if(ContainsKey(name) && config[name].is_bool())
|
||||
return config[name].bool_value();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
double ServerConfig::GetDouble(std::string name, double fallback){
|
||||
MutexGuard sync(mutex);
|
||||
if(ContainsKey(name) && config[name].is_number())
|
||||
return config[name].number_value();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int32_t ServerConfig::GetInt(std::string name, int32_t fallback){
|
||||
MutexGuard sync(mutex);
|
||||
if(ContainsKey(name) && config[name].is_number())
|
||||
return config[name].int_value();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
std::string ServerConfig::GetString(std::string name, std::string fallback){
|
||||
MutexGuard sync(mutex);
|
||||
if(ContainsKey(name) && config[name].is_string())
|
||||
return config[name].string_value();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void ServerConfig::Update(std::string jsonString){
|
||||
MutexGuard sync(mutex);
|
||||
LOGD("=== Updating voip config ===");
|
||||
LOGD("%s", jsonString.c_str());
|
||||
std::string jsonError;
|
||||
config=json11::Json::parse(jsonString, jsonError);
|
||||
if(!jsonError.empty())
|
||||
LOGE("Error parsing server config: %s", jsonError.c_str());
|
||||
}
|
||||
|
||||
|
||||
bool ServerConfig::ContainsKey(std::string key){
|
||||
return config.object_items().find(key)!=config.object_items().end();
|
||||
}
|
||||
37
TMessagesProj/jni/voip/libtgvoip/VoIPServerConfig.h
Normal file
37
TMessagesProj/jni/voip/libtgvoip/VoIPServerConfig.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// 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_VOIPSERVERCONFIG_H
|
||||
#define TGVOIP_VOIPSERVERCONFIG_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include "threading.h"
|
||||
#include "json11.hpp"
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
class ServerConfig{
|
||||
public:
|
||||
ServerConfig();
|
||||
~ServerConfig();
|
||||
static ServerConfig* GetSharedInstance();
|
||||
int32_t GetInt(std::string name, int32_t fallback);
|
||||
double GetDouble(std::string name, double fallback);
|
||||
std::string GetString(std::string name, std::string fallback);
|
||||
bool GetBoolean(std::string name, bool fallback);
|
||||
void Update(std::string jsonString);
|
||||
|
||||
private:
|
||||
static ServerConfig* sharedInstance;
|
||||
bool ContainsKey(std::string key);
|
||||
json11::Json config;
|
||||
Mutex mutex;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TGVOIP_VOIPSERVERCONFIG_H
|
||||
92
TMessagesProj/jni/voip/libtgvoip/audio/AudioIO.cpp
Normal file
92
TMessagesProj/jni/voip/libtgvoip/audio/AudioIO.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// 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 "AudioIO.h"
|
||||
#include "../logging.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
#include "AudioIOCallback.h"
|
||||
#elif defined(__ANDROID__)
|
||||
#include "../os/android/AudioInputAndroid.h"
|
||||
#include "../os/android/AudioOutputAndroid.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#include "../os/darwin/AudioUnitIO.h"
|
||||
#if TARGET_OS_OSX
|
||||
#include "../os/darwin/AudioInputAudioUnitOSX.h"
|
||||
#include "../os/darwin/AudioOutputAudioUnitOSX.h"
|
||||
#endif
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
#include "../os/windows/AudioInputWave.h"
|
||||
#include "../os/windows/AudioOutputWave.h"
|
||||
#endif
|
||||
#include "../os/windows/AudioInputWASAPI.h"
|
||||
#include "../os/windows/AudioOutputWASAPI.h"
|
||||
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__gnu_hurd__)
|
||||
#ifndef WITHOUT_ALSA
|
||||
#include "../os/linux/AudioInputALSA.h"
|
||||
#include "../os/linux/AudioOutputALSA.h"
|
||||
#endif
|
||||
#ifndef WITHOUT_PULSE
|
||||
#include "../os/linux/AudioPulse.h"
|
||||
#endif
|
||||
#else
|
||||
#error "Unsupported operating system"
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
using namespace std;
|
||||
|
||||
AudioIO* AudioIO::Create(std::string inputDevice, std::string outputDevice){
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
return new AudioIOCallback();
|
||||
#elif defined(__ANDROID__)
|
||||
return new ContextlessAudioIO<AudioInputAndroid, AudioOutputAndroid>();
|
||||
#elif defined(__APPLE__)
|
||||
#if TARGET_OS_OSX
|
||||
if(kCFCoreFoundationVersionNumber<kCFCoreFoundationVersionNumber10_7)
|
||||
return new ContextlessAudioIO<AudioInputAudioUnitLegacy, AudioOutputAudioUnitLegacy>(inputDevice, outputDevice);
|
||||
|
||||
#endif
|
||||
return new AudioUnitIO(inputDevice, outputDevice);
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
if(LOBYTE(LOWORD(GetVersion()))<6)
|
||||
return new ContextlessAudioIO<AudioInputWave, AudioOutputWave>(inputDevice, outputDevice);
|
||||
#endif
|
||||
return new ContextlessAudioIO<AudioInputWASAPI, AudioOutputWASAPI>(inputDevice, outputDevice);
|
||||
#elif defined(__linux__)
|
||||
#ifndef WITHOUT_ALSA
|
||||
#ifndef WITHOUT_PULSE
|
||||
if(AudioPulse::Load()){
|
||||
AudioIO* io=new AudioPulse(inputDevice, outputDevice);
|
||||
if(!io->Failed() && io->GetInput()->IsInitialized() && io->GetOutput()->IsInitialized())
|
||||
return io;
|
||||
LOGW("PulseAudio available but not working; trying ALSA");
|
||||
delete io;
|
||||
}
|
||||
#endif
|
||||
return new ContextlessAudioIO<AudioInputALSA, AudioOutputALSA>(inputDevice, outputDevice);
|
||||
#else
|
||||
return new AudioPulse(inputDevice, outputDevice);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioIO::Failed(){
|
||||
return failed;
|
||||
}
|
||||
|
||||
std::string AudioIO::GetErrorDescription(){
|
||||
return error;
|
||||
}
|
||||
65
TMessagesProj/jni/voip/libtgvoip/audio/AudioIO.h
Normal file
65
TMessagesProj/jni/voip/libtgvoip/audio/AudioIO.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// 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_AUDIOIO_H
|
||||
#define LIBTGVOIP_AUDIOIO_H
|
||||
|
||||
#include "AudioInput.h"
|
||||
#include "AudioOutput.h"
|
||||
#include "../utils.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio {
|
||||
class AudioIO{
|
||||
public:
|
||||
AudioIO(){};
|
||||
virtual ~AudioIO(){};
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioIO);
|
||||
static AudioIO* Create(std::string inputDevice, std::string outputDevice);
|
||||
virtual AudioInput* GetInput()=0;
|
||||
virtual AudioOutput* GetOutput()=0;
|
||||
bool Failed();
|
||||
std::string GetErrorDescription();
|
||||
protected:
|
||||
bool failed=false;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
template<class I, class O> class ContextlessAudioIO : public AudioIO{
|
||||
public:
|
||||
ContextlessAudioIO(){
|
||||
input=new I();
|
||||
output=new O();
|
||||
}
|
||||
|
||||
ContextlessAudioIO(std::string inputDeviceID, std::string outputDeviceID){
|
||||
input=new I(inputDeviceID);
|
||||
output=new O(outputDeviceID);
|
||||
}
|
||||
|
||||
virtual ~ContextlessAudioIO(){
|
||||
delete input;
|
||||
delete output;
|
||||
}
|
||||
|
||||
virtual AudioInput* GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
virtual AudioOutput* GetOutput(){
|
||||
return output;
|
||||
}
|
||||
private:
|
||||
I* input;
|
||||
O* output;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOIO_H
|
||||
121
TMessagesProj/jni/voip/libtgvoip/audio/AudioIOCallback.cpp
Normal file
121
TMessagesProj/jni/voip/libtgvoip/audio/AudioIOCallback.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// 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 "AudioIOCallback.h"
|
||||
#include "../VoIPController.h"
|
||||
#include "../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
#pragma mark - IO
|
||||
|
||||
AudioIOCallback::AudioIOCallback(){
|
||||
input=new AudioInputCallback();
|
||||
output=new AudioOutputCallback();
|
||||
}
|
||||
|
||||
AudioIOCallback::~AudioIOCallback(){
|
||||
delete input;
|
||||
delete output;
|
||||
}
|
||||
|
||||
AudioInput* AudioIOCallback::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
AudioOutput* AudioIOCallback::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
#pragma mark - Input
|
||||
|
||||
AudioInputCallback::AudioInputCallback(){
|
||||
thread=new Thread(std::bind(&AudioInputCallback::RunThread, this));
|
||||
thread->SetName("AudioInputCallback");
|
||||
}
|
||||
|
||||
AudioInputCallback::~AudioInputCallback(){
|
||||
running=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
}
|
||||
|
||||
void AudioInputCallback::Start(){
|
||||
if(!running){
|
||||
running=true;
|
||||
thread->Start();
|
||||
}
|
||||
recording=true;
|
||||
}
|
||||
|
||||
void AudioInputCallback::Stop(){
|
||||
recording=false;
|
||||
}
|
||||
|
||||
void AudioInputCallback::SetDataCallback(std::function<void(int16_t*, size_t)> c){
|
||||
dataCallback=c;
|
||||
}
|
||||
|
||||
void AudioInputCallback::RunThread(){
|
||||
int16_t buf[960];
|
||||
while(running){
|
||||
double t=VoIPController::GetCurrentTime();
|
||||
memset(buf, 0, sizeof(buf));
|
||||
dataCallback(buf, 960);
|
||||
InvokeCallback(reinterpret_cast<unsigned char*>(buf), 960*2);
|
||||
double sl=0.02-(VoIPController::GetCurrentTime()-t);
|
||||
if(sl>0)
|
||||
Thread::Sleep(sl);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Output
|
||||
|
||||
AudioOutputCallback::AudioOutputCallback(){
|
||||
thread=new Thread(std::bind(&AudioOutputCallback::RunThread, this));
|
||||
thread->SetName("AudioOutputCallback");
|
||||
}
|
||||
|
||||
AudioOutputCallback::~AudioOutputCallback(){
|
||||
running=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
}
|
||||
|
||||
void AudioOutputCallback::Start(){
|
||||
if(!running){
|
||||
running=true;
|
||||
thread->Start();
|
||||
}
|
||||
playing=true;
|
||||
}
|
||||
|
||||
void AudioOutputCallback::Stop(){
|
||||
playing=false;
|
||||
}
|
||||
|
||||
bool AudioOutputCallback::IsPlaying(){
|
||||
return playing;
|
||||
}
|
||||
|
||||
void AudioOutputCallback::SetDataCallback(std::function<void(int16_t*, size_t)> c){
|
||||
dataCallback=c;
|
||||
}
|
||||
|
||||
void AudioOutputCallback::RunThread(){
|
||||
int16_t buf[960];
|
||||
while(running){
|
||||
double t=VoIPController::GetCurrentTime();
|
||||
InvokeCallback(reinterpret_cast<unsigned char*>(buf), 960*2);
|
||||
dataCallback(buf, 960);
|
||||
double sl=0.02-(VoIPController::GetCurrentTime()-t);
|
||||
if(sl>0)
|
||||
Thread::Sleep(sl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
62
TMessagesProj/jni/voip/libtgvoip/audio/AudioIOCallback.h
Normal file
62
TMessagesProj/jni/voip/libtgvoip/audio/AudioIOCallback.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// 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_AUDIO_IO_CALLBACK
|
||||
#define LIBTGVOIP_AUDIO_IO_CALLBACK
|
||||
|
||||
#include "AudioIO.h"
|
||||
#include <functional>
|
||||
|
||||
#include "../threading.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
class AudioInputCallback : public AudioInput{
|
||||
public:
|
||||
AudioInputCallback();
|
||||
virtual ~AudioInputCallback();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
void SetDataCallback(std::function<void(int16_t*, size_t)> c);
|
||||
private:
|
||||
void RunThread();
|
||||
bool running=false;
|
||||
bool recording=false;
|
||||
Thread* thread;
|
||||
std::function<void(int16_t*, size_t)> dataCallback;
|
||||
};
|
||||
|
||||
class AudioOutputCallback : public AudioOutput{
|
||||
public:
|
||||
AudioOutputCallback();
|
||||
virtual ~AudioOutputCallback();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual bool IsPlaying() override;
|
||||
void SetDataCallback(std::function<void(int16_t*, size_t)> c);
|
||||
private:
|
||||
void RunThread();
|
||||
bool running=false;
|
||||
bool playing=false;
|
||||
Thread* thread;
|
||||
std::function<void(int16_t*, size_t)> dataCallback;
|
||||
};
|
||||
|
||||
class AudioIOCallback : public AudioIO{
|
||||
public:
|
||||
AudioIOCallback();
|
||||
virtual ~AudioIOCallback();
|
||||
virtual AudioInput* GetInput() override;
|
||||
virtual AudioOutput* GetOutput() override;
|
||||
private:
|
||||
AudioInputCallback* input;
|
||||
AudioOutputCallback* output;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* LIBTGVOIP_AUDIO_IO_CALLBACK */
|
||||
97
TMessagesProj/jni/voip/libtgvoip/audio/AudioInput.cpp
Normal file
97
TMessagesProj/jni/voip/libtgvoip/audio/AudioInput.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// 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"
|
||||
#include "../logging.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
// nothing
|
||||
#elif defined(__ANDROID__)
|
||||
#include "../os/android/AudioInputAndroid.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#include "../os/darwin/AudioInputAudioUnit.h"
|
||||
#if TARGET_OS_OSX
|
||||
#include "../os/darwin/AudioInputAudioUnitOSX.h"
|
||||
#endif
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
#include "../os/windows/AudioInputWave.h"
|
||||
#endif
|
||||
#include "../os/windows/AudioInputWASAPI.h"
|
||||
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__gnu_hurd__)
|
||||
#ifndef WITHOUT_ALSA
|
||||
#include "../os/linux/AudioInputALSA.h"
|
||||
#endif
|
||||
#ifndef WITHOUT_PULSE
|
||||
#include "../os/linux/AudioPulse.h"
|
||||
#endif
|
||||
#else
|
||||
#error "Unsupported operating system"
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
int32_t AudioInput::estimatedDelay=60;
|
||||
|
||||
AudioInput::AudioInput() : currentDevice("default"){
|
||||
failed=false;
|
||||
}
|
||||
|
||||
AudioInput::AudioInput(std::string deviceID) : currentDevice(deviceID){
|
||||
failed=false;
|
||||
}
|
||||
|
||||
|
||||
AudioInput::~AudioInput(){
|
||||
|
||||
}
|
||||
|
||||
bool AudioInput::IsInitialized(){
|
||||
return !failed;
|
||||
}
|
||||
|
||||
void AudioInput::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
// not supported
|
||||
#elif defined(__APPLE__) && TARGET_OS_OSX
|
||||
AudioInputAudioUnitLegacy::EnumerateDevices(devs);
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
if(LOBYTE(LOWORD(GetVersion()))<6){
|
||||
AudioInputWave::EnumerateDevices(devs);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
AudioInputWASAPI::EnumerateDevices(devs);
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
#if !defined(WITHOUT_PULSE) && !defined(WITHOUT_ALSA)
|
||||
if(!AudioInputPulse::EnumerateDevices(devs))
|
||||
AudioInputALSA::EnumerateDevices(devs);
|
||||
#elif defined(WITHOUT_PULSE)
|
||||
AudioInputALSA::EnumerateDevices(devs);
|
||||
#else
|
||||
AudioInputPulse::EnumerateDevices(devs)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string AudioInput::GetCurrentDevice(){
|
||||
return currentDevice;
|
||||
}
|
||||
|
||||
void AudioInput::SetCurrentDevice(std::string deviceID){
|
||||
|
||||
}
|
||||
|
||||
int32_t AudioInput::GetEstimatedDelay(){
|
||||
return estimatedDelay;
|
||||
}
|
||||
41
TMessagesProj/jni/voip/libtgvoip/audio/AudioInput.h
Normal file
41
TMessagesProj/jni/voip/libtgvoip/audio/AudioInput.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// 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 <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "../MediaStreamItf.h"
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
class AudioInputDevice;
|
||||
class AudioOutputDevice;
|
||||
|
||||
namespace audio{
|
||||
class AudioInput : public MediaStreamItf{
|
||||
public:
|
||||
AudioInput();
|
||||
AudioInput(std::string deviceID);
|
||||
virtual ~AudioInput();
|
||||
|
||||
bool IsInitialized();
|
||||
virtual std::string GetCurrentDevice();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
//static AudioInput* Create(std::string deviceID, void* platformSpecific);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
static int32_t GetEstimatedDelay();
|
||||
|
||||
protected:
|
||||
std::string currentDevice;
|
||||
bool failed;
|
||||
static int32_t estimatedDelay;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUT_H
|
||||
109
TMessagesProj/jni/voip/libtgvoip/audio/AudioOutput.cpp
Normal file
109
TMessagesProj/jni/voip/libtgvoip/audio/AudioOutput.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// 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"
|
||||
#include "../logging.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
// nothing
|
||||
#elif defined(__ANDROID__)
|
||||
#include "../os/android/AudioOutputOpenSLES.h"
|
||||
#include "../os/android/AudioOutputAndroid.h"
|
||||
#include <sys/system_properties.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#include "../os/darwin/AudioOutputAudioUnit.h"
|
||||
#if TARGET_OS_OSX
|
||||
#include "../os/darwin/AudioOutputAudioUnitOSX.h"
|
||||
#endif
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
#include "../os/windows/AudioOutputWave.h"
|
||||
#endif
|
||||
#include "../os/windows/AudioOutputWASAPI.h"
|
||||
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__gnu_hurd__)
|
||||
#ifndef WITHOUT_ALSA
|
||||
#include "../os/linux/AudioOutputALSA.h"
|
||||
#endif
|
||||
#ifndef WITHOUT_PULSE
|
||||
#include "../os/linux/AudioOutputPulse.h"
|
||||
#include "../os/linux/AudioPulse.h"
|
||||
#endif
|
||||
#else
|
||||
#error "Unsupported operating system"
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
int32_t AudioOutput::estimatedDelay=60;
|
||||
|
||||
AudioOutput::AudioOutput() : currentDevice("default"){
|
||||
failed=false;
|
||||
}
|
||||
|
||||
AudioOutput::AudioOutput(std::string deviceID) : currentDevice(deviceID){
|
||||
failed=false;
|
||||
}
|
||||
|
||||
AudioOutput::~AudioOutput(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
int32_t AudioOutput::GetEstimatedDelay(){
|
||||
#if defined(__ANDROID__)
|
||||
char sdkNum[PROP_VALUE_MAX];
|
||||
__system_property_get("ro.build.version.sdk", sdkNum);
|
||||
int systemVersion=atoi(sdkNum);
|
||||
return systemVersion<21 ? 150 : 50;
|
||||
#endif
|
||||
return estimatedDelay;
|
||||
}
|
||||
|
||||
|
||||
void AudioOutput::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
#if defined(TGVOIP_USE_CALLBACK_AUDIO_IO)
|
||||
// not supported
|
||||
#elif defined(__APPLE__) && TARGET_OS_OSX
|
||||
AudioOutputAudioUnitLegacy::EnumerateDevices(devs);
|
||||
#elif defined(_WIN32)
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
if(LOBYTE(LOWORD(GetVersion()))<6){
|
||||
AudioOutputWave::EnumerateDevices(devs);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
AudioOutputWASAPI::EnumerateDevices(devs);
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
#if !defined(WITHOUT_PULSE) && !defined(WITHOUT_ALSA)
|
||||
if(!AudioOutputPulse::EnumerateDevices(devs))
|
||||
AudioOutputALSA::EnumerateDevices(devs);
|
||||
#elif defined(WITHOUT_PULSE)
|
||||
AudioOutputALSA::EnumerateDevices(devs);
|
||||
#else
|
||||
AudioOutputPulse::EnumerateDevices(devs)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
std::string AudioOutput::GetCurrentDevice(){
|
||||
return currentDevice;
|
||||
}
|
||||
|
||||
void AudioOutput::SetCurrentDevice(std::string deviceID){
|
||||
|
||||
}
|
||||
|
||||
bool AudioOutput::IsInitialized(){
|
||||
return !failed;
|
||||
}
|
||||
42
TMessagesProj/jni/voip/libtgvoip/audio/AudioOutput.h
Normal file
42
TMessagesProj/jni/voip/libtgvoip/audio/AudioOutput.h
Normal file
|
|
@ -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 LIBTGVOIP_AUDIOOUTPUT_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "../MediaStreamItf.h"
|
||||
|
||||
namespace tgvoip{
|
||||
|
||||
class AudioInputDevice;
|
||||
class AudioOutputDevice;
|
||||
|
||||
namespace audio{
|
||||
class AudioOutput : public MediaStreamItf{
|
||||
public:
|
||||
AudioOutput();
|
||||
AudioOutput(std::string deviceID);
|
||||
virtual ~AudioOutput();
|
||||
virtual bool IsPlaying()=0;
|
||||
static int32_t GetEstimatedDelay();
|
||||
virtual std::string GetCurrentDevice();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
//static std::unique_ptr<AudioOutput> Create(std::string deviceID, void* platformSpecific);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
bool IsInitialized();
|
||||
|
||||
protected:
|
||||
std::string currentDevice;
|
||||
bool failed;
|
||||
static int32_t estimatedDelay;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUT_H
|
||||
117
TMessagesProj/jni/voip/libtgvoip/audio/Resampler.cpp
Normal file
117
TMessagesProj/jni/voip/libtgvoip/audio/Resampler.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
//
|
||||
// Created by Grishka on 01.04.17.
|
||||
//
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "Resampler.h"
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
static const int16_t hann[960]={
|
||||
0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0002, 0x0003, 0x0004, 0x0006, 0x0007, 0x0009, 0x000B, 0x000D, 0x000F, 0x0011, 0x0014, 0x0016, 0x0019, 0x001C, 0x0020,
|
||||
0x0023, 0x0027, 0x002A, 0x002E, 0x0033, 0x0037, 0x003B, 0x0040, 0x0045, 0x004A, 0x004F, 0x0054, 0x005A, 0x0060, 0x0065, 0x006B, 0x0072, 0x0078, 0x007F, 0x0085,
|
||||
0x008C, 0x0093, 0x009B, 0x00A2, 0x00AA, 0x00B2, 0x00B9, 0x00C2, 0x00CA, 0x00D2, 0x00DB, 0x00E4, 0x00ED, 0x00F6, 0x00FF, 0x0109, 0x0113, 0x011C, 0x0127, 0x0131,
|
||||
0x013B, 0x0146, 0x0150, 0x015B, 0x0166, 0x0172, 0x017D, 0x0189, 0x0194, 0x01A0, 0x01AC, 0x01B9, 0x01C5, 0x01D2, 0x01DF, 0x01EC, 0x01F9, 0x0206, 0x0213, 0x0221,
|
||||
0x022F, 0x023D, 0x024B, 0x0259, 0x0268, 0x0276, 0x0285, 0x0294, 0x02A3, 0x02B3, 0x02C2, 0x02D2, 0x02E2, 0x02F2, 0x0302, 0x0312, 0x0323, 0x0333, 0x0344, 0x0355,
|
||||
0x0366, 0x0378, 0x0389, 0x039B, 0x03AD, 0x03BF, 0x03D1, 0x03E3, 0x03F6, 0x0408, 0x041B, 0x042E, 0x0441, 0x0455, 0x0468, 0x047C, 0x0490, 0x04A4, 0x04B8, 0x04CC,
|
||||
0x04E0, 0x04F5, 0x050A, 0x051F, 0x0534, 0x0549, 0x055F, 0x0574, 0x058A, 0x05A0, 0x05B6, 0x05CC, 0x05E2, 0x05F9, 0x0610, 0x0627, 0x063E, 0x0655, 0x066C, 0x0684,
|
||||
0x069B, 0x06B3, 0x06CB, 0x06E3, 0x06FC, 0x0714, 0x072D, 0x0745, 0x075E, 0x0777, 0x0791, 0x07AA, 0x07C3, 0x07DD, 0x07F7, 0x0811, 0x082B, 0x0845, 0x0860, 0x087A,
|
||||
0x0895, 0x08B0, 0x08CB, 0x08E6, 0x0902, 0x091D, 0x0939, 0x0955, 0x0971, 0x098D, 0x09A9, 0x09C6, 0x09E2, 0x09FF, 0x0A1C, 0x0A39, 0x0A56, 0x0A73, 0x0A91, 0x0AAE,
|
||||
0x0ACC, 0x0AEA, 0x0B08, 0x0B26, 0x0B44, 0x0B63, 0x0B81, 0x0BA0, 0x0BBF, 0x0BDE, 0x0BFD, 0x0C1D, 0x0C3C, 0x0C5C, 0x0C7B, 0x0C9B, 0x0CBB, 0x0CDC, 0x0CFC, 0x0D1C,
|
||||
0x0D3D, 0x0D5E, 0x0D7F, 0x0DA0, 0x0DC1, 0x0DE2, 0x0E04, 0x0E25, 0x0E47, 0x0E69, 0x0E8B, 0x0EAD, 0x0ECF, 0x0EF1, 0x0F14, 0x0F37, 0x0F59, 0x0F7C, 0x0F9F, 0x0FC2,
|
||||
0x0FE6, 0x1009, 0x102D, 0x1051, 0x1074, 0x1098, 0x10BC, 0x10E1, 0x1105, 0x112A, 0x114E, 0x1173, 0x1198, 0x11BD, 0x11E2, 0x1207, 0x122D, 0x1252, 0x1278, 0x129D,
|
||||
0x12C3, 0x12E9, 0x130F, 0x1336, 0x135C, 0x1383, 0x13A9, 0x13D0, 0x13F7, 0x141E, 0x1445, 0x146C, 0x1494, 0x14BB, 0x14E3, 0x150A, 0x1532, 0x155A, 0x1582, 0x15AA,
|
||||
0x15D3, 0x15FB, 0x1623, 0x164C, 0x1675, 0x169E, 0x16C7, 0x16F0, 0x1719, 0x1742, 0x176C, 0x1795, 0x17BF, 0x17E9, 0x1813, 0x183D, 0x1867, 0x1891, 0x18BB, 0x18E6,
|
||||
0x1910, 0x193B, 0x1965, 0x1990, 0x19BB, 0x19E6, 0x1A11, 0x1A3D, 0x1A68, 0x1A93, 0x1ABF, 0x1AEB, 0x1B17, 0x1B42, 0x1B6E, 0x1B9A, 0x1BC7, 0x1BF3, 0x1C1F, 0x1C4C,
|
||||
0x1C78, 0x1CA5, 0x1CD2, 0x1CFF, 0x1D2C, 0x1D59, 0x1D86, 0x1DB3, 0x1DE0, 0x1E0E, 0x1E3B, 0x1E69, 0x1E97, 0x1EC4, 0x1EF2, 0x1F20, 0x1F4E, 0x1F7C, 0x1FAB, 0x1FD9,
|
||||
0x2007, 0x2036, 0x2065, 0x2093, 0x20C2, 0x20F1, 0x2120, 0x214F, 0x217E, 0x21AD, 0x21DD, 0x220C, 0x223B, 0x226B, 0x229A, 0x22CA, 0x22FA, 0x232A, 0x235A, 0x238A,
|
||||
0x23BA, 0x23EA, 0x241A, 0x244B, 0x247B, 0x24AB, 0x24DC, 0x250D, 0x253D, 0x256E, 0x259F, 0x25D0, 0x2601, 0x2632, 0x2663, 0x2694, 0x26C5, 0x26F7, 0x2728, 0x275A,
|
||||
0x278B, 0x27BD, 0x27EE, 0x2820, 0x2852, 0x2884, 0x28B6, 0x28E8, 0x291A, 0x294C, 0x297E, 0x29B0, 0x29E3, 0x2A15, 0x2A47, 0x2A7A, 0x2AAC, 0x2ADF, 0x2B12, 0x2B44,
|
||||
0x2B77, 0x2BAA, 0x2BDD, 0x2C10, 0x2C43, 0x2C76, 0x2CA9, 0x2CDC, 0x2D0F, 0x2D43, 0x2D76, 0x2DA9, 0x2DDD, 0x2E10, 0x2E44, 0x2E77, 0x2EAB, 0x2EDF, 0x2F12, 0x2F46,
|
||||
0x2F7A, 0x2FAE, 0x2FE2, 0x3016, 0x304A, 0x307E, 0x30B2, 0x30E6, 0x311A, 0x314E, 0x3182, 0x31B7, 0x31EB, 0x321F, 0x3254, 0x3288, 0x32BD, 0x32F1, 0x3326, 0x335A,
|
||||
0x338F, 0x33C3, 0x33F8, 0x342D, 0x3461, 0x3496, 0x34CB, 0x3500, 0x3535, 0x356A, 0x359F, 0x35D4, 0x3608, 0x363D, 0x3673, 0x36A8, 0x36DD, 0x3712, 0x3747, 0x377C,
|
||||
0x37B1, 0x37E6, 0x381C, 0x3851, 0x3886, 0x38BB, 0x38F1, 0x3926, 0x395B, 0x3991, 0x39C6, 0x39FC, 0x3A31, 0x3A66, 0x3A9C, 0x3AD1, 0x3B07, 0x3B3C, 0x3B72, 0x3BA7,
|
||||
0x3BDD, 0x3C12, 0x3C48, 0x3C7D, 0x3CB3, 0x3CE9, 0x3D1E, 0x3D54, 0x3D89, 0x3DBF, 0x3DF5, 0x3E2A, 0x3E60, 0x3E95, 0x3ECB, 0x3F01, 0x3F36, 0x3F6C, 0x3FA2, 0x3FD7,
|
||||
0x400D, 0x4043, 0x4078, 0x40AE, 0x40E3, 0x4119, 0x414F, 0x4184, 0x41BA, 0x41F0, 0x4225, 0x425B, 0x4290, 0x42C6, 0x42FC, 0x4331, 0x4367, 0x439C, 0x43D2, 0x4407,
|
||||
0x443D, 0x4472, 0x44A8, 0x44DD, 0x4513, 0x4548, 0x457E, 0x45B3, 0x45E9, 0x461E, 0x4654, 0x4689, 0x46BE, 0x46F4, 0x4729, 0x475E, 0x4793, 0x47C9, 0x47FE, 0x4833,
|
||||
0x4868, 0x489E, 0x48D3, 0x4908, 0x493D, 0x4972, 0x49A7, 0x49DC, 0x4A11, 0x4A46, 0x4A7B, 0x4AB0, 0x4AE5, 0x4B1A, 0x4B4E, 0x4B83, 0x4BB8, 0x4BED, 0x4C21, 0x4C56,
|
||||
0x4C8B, 0x4CBF, 0x4CF4, 0x4D28, 0x4D5D, 0x4D91, 0x4DC6, 0x4DFA, 0x4E2E, 0x4E63, 0x4E97, 0x4ECB, 0x4EFF, 0x4F33, 0x4F67, 0x4F9B, 0x4FCF, 0x5003, 0x5037, 0x506B,
|
||||
0x509F, 0x50D3, 0x5106, 0x513A, 0x516E, 0x51A1, 0x51D5, 0x5208, 0x523C, 0x526F, 0x52A3, 0x52D6, 0x5309, 0x533C, 0x536F, 0x53A3, 0x53D6, 0x5409, 0x543B, 0x546E,
|
||||
0x54A1, 0x54D4, 0x5507, 0x5539, 0x556C, 0x559E, 0x55D1, 0x5603, 0x5636, 0x5668, 0x569A, 0x56CC, 0x56FE, 0x5730, 0x5762, 0x5794, 0x57C6, 0x57F8, 0x5829, 0x585B,
|
||||
0x588D, 0x58BE, 0x58F0, 0x5921, 0x5952, 0x5984, 0x59B5, 0x59E6, 0x5A17, 0x5A48, 0x5A79, 0x5AA9, 0x5ADA, 0x5B0B, 0x5B3B, 0x5B6C, 0x5B9C, 0x5BCD, 0x5BFD, 0x5C2D,
|
||||
0x5C5D, 0x5C8D, 0x5CBD, 0x5CED, 0x5D1D, 0x5D4D, 0x5D7C, 0x5DAC, 0x5DDB, 0x5E0B, 0x5E3A, 0x5E69, 0x5E99, 0x5EC8, 0x5EF7, 0x5F26, 0x5F54, 0x5F83, 0x5FB2, 0x5FE0,
|
||||
0x600F, 0x603D, 0x606B, 0x609A, 0x60C8, 0x60F6, 0x6124, 0x6152, 0x617F, 0x61AD, 0x61DB, 0x6208, 0x6235, 0x6263, 0x6290, 0x62BD, 0x62EA, 0x6317, 0x6344, 0x6370,
|
||||
0x639D, 0x63CA, 0x63F6, 0x6422, 0x644E, 0x647B, 0x64A7, 0x64D3, 0x64FE, 0x652A, 0x6556, 0x6581, 0x65AD, 0x65D8, 0x6603, 0x662E, 0x6659, 0x6684, 0x66AF, 0x66DA,
|
||||
0x6704, 0x672F, 0x6759, 0x6783, 0x67AD, 0x67D7, 0x6801, 0x682B, 0x6855, 0x687E, 0x68A8, 0x68D1, 0x68FB, 0x6924, 0x694D, 0x6976, 0x699F, 0x69C7, 0x69F0, 0x6A18,
|
||||
0x6A41, 0x6A69, 0x6A91, 0x6AB9, 0x6AE1, 0x6B09, 0x6B30, 0x6B58, 0x6B7F, 0x6BA6, 0x6BCE, 0x6BF5, 0x6C1C, 0x6C42, 0x6C69, 0x6C90, 0x6CB6, 0x6CDC, 0x6D03, 0x6D29,
|
||||
0x6D4F, 0x6D74, 0x6D9A, 0x6DC0, 0x6DE5, 0x6E0A, 0x6E30, 0x6E55, 0x6E7A, 0x6E9E, 0x6EC3, 0x6EE8, 0x6F0C, 0x6F30, 0x6F55, 0x6F79, 0x6F9D, 0x6FC0, 0x6FE4, 0x7008,
|
||||
0x702B, 0x704E, 0x7071, 0x7094, 0x70B7, 0x70DA, 0x70FC, 0x711F, 0x7141, 0x7163, 0x7185, 0x71A7, 0x71C9, 0x71EB, 0x720C, 0x722E, 0x724F, 0x7270, 0x7291, 0x72B2,
|
||||
0x72D2, 0x72F3, 0x7313, 0x7333, 0x7354, 0x7374, 0x7393, 0x73B3, 0x73D3, 0x73F2, 0x7411, 0x7430, 0x744F, 0x746E, 0x748D, 0x74AB, 0x74CA, 0x74E8, 0x7506, 0x7524,
|
||||
0x7542, 0x7560, 0x757D, 0x759B, 0x75B8, 0x75D5, 0x75F2, 0x760F, 0x762B, 0x7648, 0x7664, 0x7680, 0x769C, 0x76B8, 0x76D4, 0x76F0, 0x770B, 0x7726, 0x7741, 0x775C,
|
||||
0x7777, 0x7792, 0x77AC, 0x77C7, 0x77E1, 0x77FB, 0x7815, 0x782F, 0x7848, 0x7862, 0x787B, 0x7894, 0x78AD, 0x78C6, 0x78DF, 0x78F7, 0x7910, 0x7928, 0x7940, 0x7958,
|
||||
0x7970, 0x7987, 0x799F, 0x79B6, 0x79CD, 0x79E4, 0x79FB, 0x7A11, 0x7A28, 0x7A3E, 0x7A54, 0x7A6A, 0x7A80, 0x7A96, 0x7AAB, 0x7AC1, 0x7AD6, 0x7AEB, 0x7B00, 0x7B14,
|
||||
0x7B29, 0x7B3D, 0x7B51, 0x7B65, 0x7B79, 0x7B8D, 0x7BA1, 0x7BB4, 0x7BC7, 0x7BDA, 0x7BED, 0x7C00, 0x7C13, 0x7C25, 0x7C37, 0x7C49, 0x7C5B, 0x7C6D, 0x7C7F, 0x7C90,
|
||||
0x7CA1, 0x7CB2, 0x7CC3, 0x7CD4, 0x7CE5, 0x7CF5, 0x7D05, 0x7D15, 0x7D25, 0x7D35, 0x7D45, 0x7D54, 0x7D63, 0x7D72, 0x7D81, 0x7D90, 0x7D9F, 0x7DAD, 0x7DBB, 0x7DC9,
|
||||
0x7DD7, 0x7DE5, 0x7DF2, 0x7E00, 0x7E0D, 0x7E1A, 0x7E27, 0x7E34, 0x7E40, 0x7E4C, 0x7E59, 0x7E65, 0x7E71, 0x7E7C, 0x7E88, 0x7E93, 0x7E9E, 0x7EA9, 0x7EB4, 0x7EBF,
|
||||
0x7EC9, 0x7ED3, 0x7EDE, 0x7EE7, 0x7EF1, 0x7EFB, 0x7F04, 0x7F0E, 0x7F17, 0x7F20, 0x7F28, 0x7F31, 0x7F39, 0x7F41, 0x7F4A, 0x7F51, 0x7F59, 0x7F61, 0x7F68, 0x7F6F,
|
||||
0x7F76, 0x7F7D, 0x7F84, 0x7F8A, 0x7F90, 0x7F97, 0x7F9D, 0x7FA2, 0x7FA8, 0x7FAD, 0x7FB3, 0x7FB8, 0x7FBD, 0x7FC1, 0x7FC6, 0x7FCA, 0x7FCF, 0x7FD3, 0x7FD6, 0x7FDA,
|
||||
0x7FDE, 0x7FE1, 0x7FE4, 0x7FE7, 0x7FEA, 0x7FED, 0x7FEF, 0x7FF1, 0x7FF3, 0x7FF5, 0x7FF7, 0x7FF9, 0x7FFA, 0x7FFB, 0x7FFC, 0x7FFD, 0x7FFE, 0x7FFE, 0x7FFF, 0x7FFF
|
||||
};
|
||||
|
||||
#define MIN(a, b) (((a)<(b)) ? (a) : (b))
|
||||
|
||||
size_t Resampler::Convert48To44(int16_t *from, int16_t *to, size_t fromLen, size_t toLen){
|
||||
size_t outLen=fromLen*147/160;
|
||||
if(toLen<outLen)
|
||||
outLen=toLen;
|
||||
unsigned int offset;
|
||||
for(offset=0;offset<outLen;offset++){
|
||||
float offsetf=offset*160.0f/147.0f;
|
||||
float factor=offsetf-floorf(offsetf);
|
||||
to[offset]=(int16_t)((from[(int)floorf(offsetf)]*(1-factor))+(from[(int)ceilf(offsetf)]*(factor)));
|
||||
}
|
||||
return outLen;
|
||||
}
|
||||
|
||||
size_t Resampler::Convert44To48(int16_t *from, int16_t *to, size_t fromLen, size_t toLen){
|
||||
size_t outLen=fromLen*160/147;
|
||||
if(toLen<outLen)
|
||||
outLen=toLen;
|
||||
unsigned int offset;
|
||||
for(offset=0;offset<outLen;offset++){
|
||||
float offsetf=offset*147.0f/160.0f;
|
||||
float factor=offsetf-floorf(offsetf);
|
||||
to[offset]=(int16_t)((from[(int)floorf(offsetf)]*(1-factor))+(from[(int)ceilf(offsetf)]*(factor)));
|
||||
}
|
||||
return outLen;
|
||||
}
|
||||
|
||||
|
||||
size_t Resampler::Convert(int16_t *from, int16_t *to, size_t fromLen, size_t toLen, int num, int denom){
|
||||
size_t outLen=fromLen*num/denom;
|
||||
if(toLen<outLen)
|
||||
outLen=toLen;
|
||||
unsigned int offset;
|
||||
for(offset=0;offset<outLen;offset++){
|
||||
float offsetf=offset*(float)denom/(float)num;
|
||||
float factor=offsetf-floorf(offsetf);
|
||||
to[offset]=(int16_t)((from[(int)floorf(offsetf)]*(1-factor))+(from[(int)ceilf(offsetf)]*(factor)));
|
||||
}
|
||||
return outLen;
|
||||
}
|
||||
|
||||
void Resampler::Rescale60To80(int16_t *in, int16_t *out){
|
||||
memcpy(out, in, 960*2);
|
||||
memcpy(out+960*3, in+960*2, 960*2);
|
||||
for(int i=0;i<960;i++){
|
||||
out[960+i]=(int16_t)(((int32_t)in[960+i]*hann[959-i]) >> 15) + (int16_t)(((int32_t)in[480+i]*hann[i]) >> 15);
|
||||
out[1920+i]=(int16_t)(((int32_t)in[960+480+i]*hann[959-i]) >> 15) + (int16_t)(((int32_t)in[960+i]*hann[i]) >> 15);
|
||||
}
|
||||
}
|
||||
|
||||
void Resampler::Rescale60To40(int16_t *in, int16_t *out){
|
||||
for(int i=0;i<960;i++){
|
||||
out[i]=(int16_t)(((int32_t)in[i]*hann[959-i]) >> 15) + (int16_t)(((int32_t)in[480+i]*hann[i]) >> 15);
|
||||
out[960+i]=(int16_t)(((int32_t)in[1920+i]*hann[i]) >> 15) + (int16_t)(((int32_t)in[1440+i]*hann[959-i]) >> 15);
|
||||
}
|
||||
}
|
||||
22
TMessagesProj/jni/voip/libtgvoip/audio/Resampler.h
Normal file
22
TMessagesProj/jni/voip/libtgvoip/audio/Resampler.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// Created by Grishka on 01.04.17.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_RESAMPLER_H
|
||||
#define LIBTGVOIP_RESAMPLER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class Resampler{
|
||||
public:
|
||||
static size_t Convert48To44(int16_t* from, int16_t* to, size_t fromLen, size_t toLen);
|
||||
static size_t Convert44To48(int16_t* from, int16_t* to, size_t fromLen, size_t toLen);
|
||||
static size_t Convert(int16_t* from, int16_t* to, size_t fromLen, size_t toLen, int num, int denom);
|
||||
static void Rescale60To80(int16_t* in, int16_t* out);
|
||||
static void Rescale60To40(int16_t* in, int16_t* out);
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_RESAMPLER_H
|
||||
795
TMessagesProj/jni/voip/libtgvoip/json11.cpp
Executable file
795
TMessagesProj/jni/voip/libtgvoip/json11.cpp
Executable file
|
|
@ -0,0 +1,795 @@
|
|||
/* Copyright (c) 2013 Dropbox, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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 OR COPYRIGHT HOLDERS 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.
|
||||
*/
|
||||
|
||||
#include "json11.hpp"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <locale>
|
||||
|
||||
namespace json11 {
|
||||
|
||||
static const int max_depth = 200;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::make_shared;
|
||||
using std::initializer_list;
|
||||
using std::move;
|
||||
|
||||
/* Helper for representing null - just a do-nothing struct, plus comparison
|
||||
* operators so the helpers in JsonValue work. We can't use nullptr_t because
|
||||
* it may not be orderable.
|
||||
*/
|
||||
struct NullStruct {
|
||||
bool operator==(NullStruct) const { return true; }
|
||||
bool operator<(NullStruct) const { return false; }
|
||||
};
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Serialization
|
||||
*/
|
||||
|
||||
static void dump(NullStruct, string &out) {
|
||||
out += "null";
|
||||
}
|
||||
|
||||
static void dump(double value, string &out) {
|
||||
if (std::isfinite(value)) {
|
||||
std::ostringstream stm;
|
||||
stm.imbue(std::locale("C"));
|
||||
stm << value;
|
||||
out += stm.str();
|
||||
} else {
|
||||
out += "null";
|
||||
}
|
||||
}
|
||||
|
||||
static void dump(int value, string &out) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%d", value);
|
||||
out += buf;
|
||||
}
|
||||
|
||||
static void dump(bool value, string &out) {
|
||||
out += value ? "true" : "false";
|
||||
}
|
||||
|
||||
static void dump(const string &value, string &out) {
|
||||
out += '"';
|
||||
for (size_t i = 0; i < value.length(); i++) {
|
||||
const char ch = value[i];
|
||||
if (ch == '\\') {
|
||||
out += "\\\\";
|
||||
} else if (ch == '"') {
|
||||
out += "\\\"";
|
||||
} else if (ch == '\b') {
|
||||
out += "\\b";
|
||||
} else if (ch == '\f') {
|
||||
out += "\\f";
|
||||
} else if (ch == '\n') {
|
||||
out += "\\n";
|
||||
} else if (ch == '\r') {
|
||||
out += "\\r";
|
||||
} else if (ch == '\t') {
|
||||
out += "\\t";
|
||||
} else if (static_cast<uint8_t>(ch) <= 0x1f) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof buf, "\\u%04x", ch);
|
||||
out += buf;
|
||||
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
|
||||
&& static_cast<uint8_t>(value[i+2]) == 0xa8) {
|
||||
out += "\\u2028";
|
||||
i += 2;
|
||||
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
|
||||
&& static_cast<uint8_t>(value[i+2]) == 0xa9) {
|
||||
out += "\\u2029";
|
||||
i += 2;
|
||||
} else {
|
||||
out += ch;
|
||||
}
|
||||
}
|
||||
out += '"';
|
||||
}
|
||||
|
||||
static void dump(const Json::array &values, string &out) {
|
||||
bool first = true;
|
||||
out += "[";
|
||||
for (const auto &value : values) {
|
||||
if (!first)
|
||||
out += ", ";
|
||||
value.dump(out);
|
||||
first = false;
|
||||
}
|
||||
out += "]";
|
||||
}
|
||||
|
||||
static void dump(const Json::object &values, string &out) {
|
||||
bool first = true;
|
||||
out += "{";
|
||||
for (const auto &kv : values) {
|
||||
if (!first)
|
||||
out += ", ";
|
||||
dump(kv.first, out);
|
||||
out += ": ";
|
||||
kv.second.dump(out);
|
||||
first = false;
|
||||
}
|
||||
out += "}";
|
||||
}
|
||||
|
||||
void Json::dump(string &out) const {
|
||||
m_ptr->dump(out);
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Value wrappers
|
||||
*/
|
||||
|
||||
template <Json::Type tag, typename T>
|
||||
class Value : public JsonValue {
|
||||
protected:
|
||||
|
||||
// Constructors
|
||||
explicit Value(const T &value) : m_value(value) {}
|
||||
explicit Value(T &&value) : m_value(move(value)) {}
|
||||
|
||||
// Get type tag
|
||||
Json::Type type() const override {
|
||||
return tag;
|
||||
}
|
||||
|
||||
// Comparisons
|
||||
bool equals(const JsonValue * other) const override {
|
||||
return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
|
||||
}
|
||||
bool less(const JsonValue * other) const override {
|
||||
return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
|
||||
}
|
||||
|
||||
const T m_value;
|
||||
void dump(string &out) const override { json11::dump(m_value, out); }
|
||||
};
|
||||
|
||||
class JsonDouble final : public Value<Json::NUMBER, double> {
|
||||
double number_value() const override { return m_value; }
|
||||
int int_value() const override { return static_cast<int>(m_value); }
|
||||
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
|
||||
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
|
||||
public:
|
||||
explicit JsonDouble(double value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonInt final : public Value<Json::NUMBER, int> {
|
||||
double number_value() const override { return m_value; }
|
||||
int int_value() const override { return m_value; }
|
||||
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
|
||||
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
|
||||
public:
|
||||
explicit JsonInt(int value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonBoolean final : public Value<Json::BOOL, bool> {
|
||||
bool bool_value() const override { return m_value; }
|
||||
public:
|
||||
explicit JsonBoolean(bool value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonString final : public Value<Json::STRING, string> {
|
||||
const string &string_value() const override { return m_value; }
|
||||
public:
|
||||
explicit JsonString(const string &value) : Value(value) {}
|
||||
explicit JsonString(string &&value) : Value(move(value)) {}
|
||||
};
|
||||
|
||||
class JsonArray final : public Value<Json::ARRAY, Json::array> {
|
||||
const Json::array &array_items() const override { return m_value; }
|
||||
const Json & operator[](size_t i) const override;
|
||||
public:
|
||||
explicit JsonArray(const Json::array &value) : Value(value) {}
|
||||
explicit JsonArray(Json::array &&value) : Value(move(value)) {}
|
||||
};
|
||||
|
||||
class JsonObject final : public Value<Json::OBJECT, Json::object> {
|
||||
const Json::object &object_items() const override { return m_value; }
|
||||
const Json & operator[](const string &key) const override;
|
||||
public:
|
||||
explicit JsonObject(const Json::object &value) : Value(value) {}
|
||||
explicit JsonObject(Json::object &&value) : Value(move(value)) {}
|
||||
};
|
||||
|
||||
class JsonNull final : public Value<Json::NUL, NullStruct> {
|
||||
public:
|
||||
JsonNull() : Value({}) {}
|
||||
};
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Static globals - static-init-safe
|
||||
*/
|
||||
struct Statics {
|
||||
const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
|
||||
const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
|
||||
const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
|
||||
const string empty_string;
|
||||
const vector<Json> empty_vector;
|
||||
const map<string, Json> empty_map;
|
||||
Statics() {}
|
||||
};
|
||||
|
||||
static const Statics & statics() {
|
||||
static const Statics s {};
|
||||
return s;
|
||||
}
|
||||
|
||||
static const Json & static_null() {
|
||||
// This has to be separate, not in Statics, because Json() accesses statics().null.
|
||||
static const Json json_null;
|
||||
return json_null;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
Json::Json() noexcept : m_ptr(statics().null) {}
|
||||
Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
|
||||
Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
|
||||
Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
|
||||
Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
|
||||
Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
|
||||
Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {}
|
||||
Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {}
|
||||
Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
|
||||
Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {}
|
||||
Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
|
||||
Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Accessors
|
||||
*/
|
||||
|
||||
Json::Type Json::type() const { return m_ptr->type(); }
|
||||
double Json::number_value() const { return m_ptr->number_value(); }
|
||||
int Json::int_value() const { return m_ptr->int_value(); }
|
||||
bool Json::bool_value() const { return m_ptr->bool_value(); }
|
||||
const string & Json::string_value() const { return m_ptr->string_value(); }
|
||||
const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
|
||||
const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
|
||||
const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
|
||||
const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
|
||||
|
||||
double JsonValue::number_value() const { return 0; }
|
||||
int JsonValue::int_value() const { return 0; }
|
||||
bool JsonValue::bool_value() const { return false; }
|
||||
const string & JsonValue::string_value() const { return statics().empty_string; }
|
||||
const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
|
||||
const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
|
||||
const Json & JsonValue::operator[] (size_t) const { return static_null(); }
|
||||
const Json & JsonValue::operator[] (const string &) const { return static_null(); }
|
||||
|
||||
const Json & JsonObject::operator[] (const string &key) const {
|
||||
auto iter = m_value.find(key);
|
||||
return (iter == m_value.end()) ? static_null() : iter->second;
|
||||
}
|
||||
const Json & JsonArray::operator[] (size_t i) const {
|
||||
if (i >= m_value.size()) return static_null();
|
||||
else return m_value[i];
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Comparison
|
||||
*/
|
||||
|
||||
bool Json::operator== (const Json &other) const {
|
||||
if (m_ptr == other.m_ptr)
|
||||
return true;
|
||||
if (m_ptr->type() != other.m_ptr->type())
|
||||
return false;
|
||||
|
||||
return m_ptr->equals(other.m_ptr.get());
|
||||
}
|
||||
|
||||
bool Json::operator< (const Json &other) const {
|
||||
if (m_ptr == other.m_ptr)
|
||||
return false;
|
||||
if (m_ptr->type() != other.m_ptr->type())
|
||||
return m_ptr->type() < other.m_ptr->type();
|
||||
|
||||
return m_ptr->less(other.m_ptr.get());
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Parsing
|
||||
*/
|
||||
|
||||
/* esc(c)
|
||||
*
|
||||
* Format char c suitable for printing in an error message.
|
||||
*/
|
||||
static inline string esc(char c) {
|
||||
char buf[12];
|
||||
if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
|
||||
snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
|
||||
} else {
|
||||
snprintf(buf, sizeof buf, "(%d)", c);
|
||||
}
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
static inline bool in_range(long x, long lower, long upper) {
|
||||
return (x >= lower && x <= upper);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/* JsonParser
|
||||
*
|
||||
* Object that tracks all state of an in-progress parse.
|
||||
*/
|
||||
struct JsonParser final {
|
||||
|
||||
/* State
|
||||
*/
|
||||
const string &str;
|
||||
size_t i;
|
||||
string &err;
|
||||
bool failed;
|
||||
const JsonParse strategy;
|
||||
|
||||
/* fail(msg, err_ret = Json())
|
||||
*
|
||||
* Mark this parse as failed.
|
||||
*/
|
||||
Json fail(string &&msg) {
|
||||
return fail(move(msg), Json());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T fail(string &&msg, const T err_ret) {
|
||||
if (!failed)
|
||||
err = std::move(msg);
|
||||
failed = true;
|
||||
return err_ret;
|
||||
}
|
||||
|
||||
/* consume_whitespace()
|
||||
*
|
||||
* Advance until the current character is non-whitespace.
|
||||
*/
|
||||
void consume_whitespace() {
|
||||
while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
|
||||
i++;
|
||||
}
|
||||
|
||||
/* consume_comment()
|
||||
*
|
||||
* Advance comments (c-style inline and multiline).
|
||||
*/
|
||||
bool consume_comment() {
|
||||
bool comment_found = false;
|
||||
if (str[i] == '/') {
|
||||
i++;
|
||||
if (i == str.size())
|
||||
return fail("unexpected end of input after start of comment", false);
|
||||
if (str[i] == '/') { // inline comment
|
||||
i++;
|
||||
// advance until next line, or end of input
|
||||
while (i < str.size() && str[i] != '\n') {
|
||||
i++;
|
||||
}
|
||||
comment_found = true;
|
||||
}
|
||||
else if (str[i] == '*') { // multiline comment
|
||||
i++;
|
||||
if (i > str.size()-2)
|
||||
return fail("unexpected end of input inside multi-line comment", false);
|
||||
// advance until closing tokens
|
||||
while (!(str[i] == '*' && str[i+1] == '/')) {
|
||||
i++;
|
||||
if (i > str.size()-2)
|
||||
return fail(
|
||||
"unexpected end of input inside multi-line comment", false);
|
||||
}
|
||||
i += 2;
|
||||
comment_found = true;
|
||||
}
|
||||
else
|
||||
return fail("malformed comment", false);
|
||||
}
|
||||
return comment_found;
|
||||
}
|
||||
|
||||
/* consume_garbage()
|
||||
*
|
||||
* Advance until the current character is non-whitespace and non-comment.
|
||||
*/
|
||||
void consume_garbage() {
|
||||
consume_whitespace();
|
||||
if(strategy == JsonParse::COMMENTS) {
|
||||
bool comment_found = false;
|
||||
do {
|
||||
comment_found = consume_comment();
|
||||
if (failed) return;
|
||||
consume_whitespace();
|
||||
}
|
||||
while(comment_found);
|
||||
}
|
||||
}
|
||||
|
||||
/* get_next_token()
|
||||
*
|
||||
* Return the next non-whitespace character. If the end of the input is reached,
|
||||
* flag an error and return 0.
|
||||
*/
|
||||
char get_next_token() {
|
||||
consume_garbage();
|
||||
if (failed) return (char)0;
|
||||
if (i == str.size())
|
||||
return fail("unexpected end of input", (char)0);
|
||||
|
||||
return str[i++];
|
||||
}
|
||||
|
||||
/* encode_utf8(pt, out)
|
||||
*
|
||||
* Encode pt as UTF-8 and add it to out.
|
||||
*/
|
||||
void encode_utf8(long pt, string & out) {
|
||||
if (pt < 0)
|
||||
return;
|
||||
|
||||
if (pt < 0x80) {
|
||||
out += static_cast<char>(pt);
|
||||
} else if (pt < 0x800) {
|
||||
out += static_cast<char>((pt >> 6) | 0xC0);
|
||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
} else if (pt < 0x10000) {
|
||||
out += static_cast<char>((pt >> 12) | 0xE0);
|
||||
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
} else {
|
||||
out += static_cast<char>((pt >> 18) | 0xF0);
|
||||
out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
|
||||
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_string()
|
||||
*
|
||||
* Parse a string, starting at the current position.
|
||||
*/
|
||||
string parse_string() {
|
||||
string out;
|
||||
long last_escaped_codepoint = -1;
|
||||
while (true) {
|
||||
if (i == str.size())
|
||||
return fail("unexpected end of input in string", "");
|
||||
|
||||
char ch = str[i++];
|
||||
|
||||
if (ch == '"') {
|
||||
encode_utf8(last_escaped_codepoint, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
if (in_range(ch, 0, 0x1f))
|
||||
return fail("unescaped " + esc(ch) + " in string", "");
|
||||
|
||||
// The usual case: non-escaped characters
|
||||
if (ch != '\\') {
|
||||
encode_utf8(last_escaped_codepoint, out);
|
||||
last_escaped_codepoint = -1;
|
||||
out += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle escapes
|
||||
if (i == str.size())
|
||||
return fail("unexpected end of input in string", "");
|
||||
|
||||
ch = str[i++];
|
||||
|
||||
if (ch == 'u') {
|
||||
// Extract 4-byte escape sequence
|
||||
string esc = str.substr(i, 4);
|
||||
// Explicitly check length of the substring. The following loop
|
||||
// relies on std::string returning the terminating NUL when
|
||||
// accessing str[length]. Checking here reduces brittleness.
|
||||
if (esc.length() < 4) {
|
||||
return fail("bad \\u escape: " + esc, "");
|
||||
}
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
|
||||
&& !in_range(esc[j], '0', '9'))
|
||||
return fail("bad \\u escape: " + esc, "");
|
||||
}
|
||||
|
||||
long codepoint = strtol(esc.data(), nullptr, 16);
|
||||
|
||||
// JSON specifies that characters outside the BMP shall be encoded as a pair
|
||||
// of 4-hex-digit \u escapes encoding their surrogate pair components. Check
|
||||
// whether we're in the middle of such a beast: the previous codepoint was an
|
||||
// escaped lead (high) surrogate, and this is a trail (low) surrogate.
|
||||
if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
|
||||
&& in_range(codepoint, 0xDC00, 0xDFFF)) {
|
||||
// Reassemble the two surrogate pairs into one astral-plane character, per
|
||||
// the UTF-16 algorithm.
|
||||
encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
|
||||
| (codepoint - 0xDC00)) + 0x10000, out);
|
||||
last_escaped_codepoint = -1;
|
||||
} else {
|
||||
encode_utf8(last_escaped_codepoint, out);
|
||||
last_escaped_codepoint = codepoint;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
encode_utf8(last_escaped_codepoint, out);
|
||||
last_escaped_codepoint = -1;
|
||||
|
||||
if (ch == 'b') {
|
||||
out += '\b';
|
||||
} else if (ch == 'f') {
|
||||
out += '\f';
|
||||
} else if (ch == 'n') {
|
||||
out += '\n';
|
||||
} else if (ch == 'r') {
|
||||
out += '\r';
|
||||
} else if (ch == 't') {
|
||||
out += '\t';
|
||||
} else if (ch == '"' || ch == '\\' || ch == '/') {
|
||||
out += ch;
|
||||
} else {
|
||||
return fail("invalid escape character " + esc(ch), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_number()
|
||||
*
|
||||
* Parse a double.
|
||||
*/
|
||||
Json parse_number() {
|
||||
size_t start_pos = i;
|
||||
|
||||
if (str[i] == '-')
|
||||
i++;
|
||||
|
||||
// Integer part
|
||||
if (str[i] == '0') {
|
||||
i++;
|
||||
if (in_range(str[i], '0', '9'))
|
||||
return fail("leading 0s not permitted in numbers");
|
||||
} else if (in_range(str[i], '1', '9')) {
|
||||
i++;
|
||||
while (in_range(str[i], '0', '9'))
|
||||
i++;
|
||||
} else {
|
||||
return fail("invalid " + esc(str[i]) + " in number");
|
||||
}
|
||||
|
||||
if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
|
||||
&& (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
|
||||
return std::atoi(str.c_str() + start_pos);
|
||||
}
|
||||
|
||||
// Decimal part
|
||||
if (str[i] == '.') {
|
||||
i++;
|
||||
if (!in_range(str[i], '0', '9'))
|
||||
return fail("at least one digit required in fractional part");
|
||||
|
||||
while (in_range(str[i], '0', '9'))
|
||||
i++;
|
||||
}
|
||||
|
||||
// Exponent part
|
||||
if (str[i] == 'e' || str[i] == 'E') {
|
||||
i++;
|
||||
|
||||
if (str[i] == '+' || str[i] == '-')
|
||||
i++;
|
||||
|
||||
if (!in_range(str[i], '0', '9'))
|
||||
return fail("at least one digit required in exponent");
|
||||
|
||||
while (in_range(str[i], '0', '9'))
|
||||
i++;
|
||||
}
|
||||
|
||||
std::istringstream stm(std::string(str.begin()+start_pos, str.end()));
|
||||
stm.imbue(std::locale("C"));
|
||||
double result;
|
||||
stm >> result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* expect(str, res)
|
||||
*
|
||||
* Expect that 'str' starts at the character that was just read. If it does, advance
|
||||
* the input and return res. If not, flag an error.
|
||||
*/
|
||||
Json expect(const string &expected, Json res) {
|
||||
assert(i != 0);
|
||||
i--;
|
||||
if (str.compare(i, expected.length(), expected) == 0) {
|
||||
i += expected.length();
|
||||
return res;
|
||||
} else {
|
||||
return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_json()
|
||||
*
|
||||
* Parse a JSON object.
|
||||
*/
|
||||
Json parse_json(int depth) {
|
||||
if (depth > max_depth) {
|
||||
return fail("exceeded maximum nesting depth");
|
||||
}
|
||||
|
||||
char ch = get_next_token();
|
||||
if (failed)
|
||||
return Json();
|
||||
|
||||
if (ch == '-' || (ch >= '0' && ch <= '9')) {
|
||||
i--;
|
||||
return parse_number();
|
||||
}
|
||||
|
||||
if (ch == 't')
|
||||
return expect("true", true);
|
||||
|
||||
if (ch == 'f')
|
||||
return expect("false", false);
|
||||
|
||||
if (ch == 'n')
|
||||
return expect("null", Json());
|
||||
|
||||
if (ch == '"')
|
||||
return parse_string();
|
||||
|
||||
if (ch == '{') {
|
||||
map<string, Json> data;
|
||||
ch = get_next_token();
|
||||
if (ch == '}')
|
||||
return data;
|
||||
|
||||
while (1) {
|
||||
if (ch != '"')
|
||||
return fail("expected '\"' in object, got " + esc(ch));
|
||||
|
||||
string key = parse_string();
|
||||
if (failed)
|
||||
return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch != ':')
|
||||
return fail("expected ':' in object, got " + esc(ch));
|
||||
|
||||
data[std::move(key)] = parse_json(depth + 1);
|
||||
if (failed)
|
||||
return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch == '}')
|
||||
break;
|
||||
if (ch != ',')
|
||||
return fail("expected ',' in object, got " + esc(ch));
|
||||
|
||||
ch = get_next_token();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
if (ch == '[') {
|
||||
vector<Json> data;
|
||||
ch = get_next_token();
|
||||
if (ch == ']')
|
||||
return data;
|
||||
|
||||
while (1) {
|
||||
i--;
|
||||
data.push_back(parse_json(depth + 1));
|
||||
if (failed)
|
||||
return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch == ']')
|
||||
break;
|
||||
if (ch != ',')
|
||||
return fail("expected ',' in list, got " + esc(ch));
|
||||
|
||||
ch = get_next_token();
|
||||
(void)ch;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
return fail("expected value, got " + esc(ch));
|
||||
}
|
||||
};
|
||||
}//namespace {
|
||||
|
||||
Json Json::parse(const string &in, string &err, JsonParse strategy) {
|
||||
JsonParser parser { in, 0, err, false, strategy };
|
||||
Json result = parser.parse_json(0);
|
||||
|
||||
// Check for any trailing garbage
|
||||
parser.consume_garbage();
|
||||
if (parser.failed)
|
||||
return Json();
|
||||
if (parser.i != in.size())
|
||||
return parser.fail("unexpected trailing " + esc(in[parser.i]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Documented in json11.hpp
|
||||
vector<Json> Json::parse_multi(const string &in,
|
||||
std::string::size_type &parser_stop_pos,
|
||||
string &err,
|
||||
JsonParse strategy) {
|
||||
JsonParser parser { in, 0, err, false, strategy };
|
||||
parser_stop_pos = 0;
|
||||
vector<Json> json_vec;
|
||||
while (parser.i != in.size() && !parser.failed) {
|
||||
json_vec.push_back(parser.parse_json(0));
|
||||
if (parser.failed)
|
||||
break;
|
||||
|
||||
// Check for another object
|
||||
parser.consume_garbage();
|
||||
if (parser.failed)
|
||||
break;
|
||||
parser_stop_pos = parser.i;
|
||||
}
|
||||
return json_vec;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Shape-checking
|
||||
*/
|
||||
|
||||
bool Json::has_shape(const shape & types, string & err) const {
|
||||
if (!is_object()) {
|
||||
err = "expected JSON object, got " + dump();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto & item : types) {
|
||||
if ((*this)[item.first].type() != item.second) {
|
||||
err = "bad type for " + item.first + " in " + dump();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace json11
|
||||
232
TMessagesProj/jni/voip/libtgvoip/json11.hpp
Executable file
232
TMessagesProj/jni/voip/libtgvoip/json11.hpp
Executable file
|
|
@ -0,0 +1,232 @@
|
|||
/* json11
|
||||
*
|
||||
* json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
|
||||
*
|
||||
* The core object provided by the library is json11::Json. A Json object represents any JSON
|
||||
* value: null, bool, number (int or double), string (std::string), array (std::vector), or
|
||||
* object (std::map).
|
||||
*
|
||||
* Json objects act like values: they can be assigned, copied, moved, compared for equality or
|
||||
* order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
|
||||
* Json::parse (static) to parse a std::string as a Json object.
|
||||
*
|
||||
* Internally, the various types of Json object are represented by the JsonValue class
|
||||
* hierarchy.
|
||||
*
|
||||
* A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
|
||||
* so some JSON implementations distinguish between integers and floating-point numbers, while
|
||||
* some don't. In json11, we choose the latter. Because some JSON implementations (namely
|
||||
* Javascript itself) treat all numbers as the same type, distinguishing the two leads
|
||||
* to JSON that will be *silently* changed by a round-trip through those implementations.
|
||||
* Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
|
||||
* provides integer helpers.
|
||||
*
|
||||
* Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
|
||||
* range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
|
||||
* or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
|
||||
* will be exact for +/- 275 years.)
|
||||
*/
|
||||
|
||||
/* Copyright (c) 2013 Dropbox, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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 OR COPYRIGHT HOLDERS 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <initializer_list>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#if _MSC_VER <= 1800 // VS 2013
|
||||
#ifndef noexcept
|
||||
#define noexcept throw()
|
||||
#endif
|
||||
|
||||
#ifndef snprintf
|
||||
#define snprintf _snprintf_s
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace json11 {
|
||||
|
||||
enum JsonParse {
|
||||
STANDARD, COMMENTS
|
||||
};
|
||||
|
||||
class JsonValue;
|
||||
|
||||
class Json final {
|
||||
public:
|
||||
// Types
|
||||
enum Type {
|
||||
NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
|
||||
};
|
||||
|
||||
// Array and object typedefs
|
||||
typedef std::vector<Json> array;
|
||||
typedef std::map<std::string, Json> object;
|
||||
|
||||
// Constructors for the various types of JSON value.
|
||||
Json() noexcept; // NUL
|
||||
Json(std::nullptr_t) noexcept; // NUL
|
||||
Json(double value); // NUMBER
|
||||
Json(int value); // NUMBER
|
||||
Json(bool value); // BOOL
|
||||
Json(const std::string &value); // STRING
|
||||
Json(std::string &&value); // STRING
|
||||
Json(const char * value); // STRING
|
||||
Json(const array &values); // ARRAY
|
||||
Json(array &&values); // ARRAY
|
||||
Json(const object &values); // OBJECT
|
||||
Json(object &&values); // OBJECT
|
||||
|
||||
// Implicit constructor: anything with a to_json() function.
|
||||
template <class T, class = decltype(&T::to_json)>
|
||||
Json(const T & t) : Json(t.to_json()) {}
|
||||
|
||||
// Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
|
||||
template <class M, typename std::enable_if<
|
||||
std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value
|
||||
&& std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value,
|
||||
int>::type = 0>
|
||||
Json(const M & m) : Json(object(m.begin(), m.end())) {}
|
||||
|
||||
// Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
|
||||
template <class V, typename std::enable_if<
|
||||
std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value,
|
||||
int>::type = 0>
|
||||
Json(const V & v) : Json(array(v.begin(), v.end())) {}
|
||||
|
||||
// This prevents Json(some_pointer) from accidentally producing a bool. Use
|
||||
// Json(bool(some_pointer)) if that behavior is desired.
|
||||
Json(void *) = delete;
|
||||
|
||||
// Accessors
|
||||
Type type() const;
|
||||
|
||||
bool is_null() const { return type() == NUL; }
|
||||
bool is_number() const { return type() == NUMBER; }
|
||||
bool is_bool() const { return type() == BOOL; }
|
||||
bool is_string() const { return type() == STRING; }
|
||||
bool is_array() const { return type() == ARRAY; }
|
||||
bool is_object() const { return type() == OBJECT; }
|
||||
|
||||
// Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
|
||||
// distinguish between integer and non-integer numbers - number_value() and int_value()
|
||||
// can both be applied to a NUMBER-typed object.
|
||||
double number_value() const;
|
||||
int int_value() const;
|
||||
|
||||
// Return the enclosed value if this is a boolean, false otherwise.
|
||||
bool bool_value() const;
|
||||
// Return the enclosed string if this is a string, "" otherwise.
|
||||
const std::string &string_value() const;
|
||||
// Return the enclosed std::vector if this is an array, or an empty vector otherwise.
|
||||
const array &array_items() const;
|
||||
// Return the enclosed std::map if this is an object, or an empty map otherwise.
|
||||
const object &object_items() const;
|
||||
|
||||
// Return a reference to arr[i] if this is an array, Json() otherwise.
|
||||
const Json & operator[](size_t i) const;
|
||||
// Return a reference to obj[key] if this is an object, Json() otherwise.
|
||||
const Json & operator[](const std::string &key) const;
|
||||
|
||||
// Serialize.
|
||||
void dump(std::string &out) const;
|
||||
std::string dump() const {
|
||||
std::string out;
|
||||
dump(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Parse. If parse fails, return Json() and assign an error message to err.
|
||||
static Json parse(const std::string & in,
|
||||
std::string & err,
|
||||
JsonParse strategy = JsonParse::STANDARD);
|
||||
static Json parse(const char * in,
|
||||
std::string & err,
|
||||
JsonParse strategy = JsonParse::STANDARD) {
|
||||
if (in) {
|
||||
return parse(std::string(in), err, strategy);
|
||||
} else {
|
||||
err = "null input";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// Parse multiple objects, concatenated or separated by whitespace
|
||||
static std::vector<Json> parse_multi(
|
||||
const std::string & in,
|
||||
std::string::size_type & parser_stop_pos,
|
||||
std::string & err,
|
||||
JsonParse strategy = JsonParse::STANDARD);
|
||||
|
||||
static inline std::vector<Json> parse_multi(
|
||||
const std::string & in,
|
||||
std::string & err,
|
||||
JsonParse strategy = JsonParse::STANDARD) {
|
||||
std::string::size_type parser_stop_pos;
|
||||
return parse_multi(in, parser_stop_pos, err, strategy);
|
||||
}
|
||||
|
||||
bool operator== (const Json &rhs) const;
|
||||
bool operator< (const Json &rhs) const;
|
||||
bool operator!= (const Json &rhs) const { return !(*this == rhs); }
|
||||
bool operator<= (const Json &rhs) const { return !(rhs < *this); }
|
||||
bool operator> (const Json &rhs) const { return (rhs < *this); }
|
||||
bool operator>= (const Json &rhs) const { return !(*this < rhs); }
|
||||
|
||||
/* has_shape(types, err)
|
||||
*
|
||||
* Return true if this is a JSON object and, for each item in types, has a field of
|
||||
* the given type. If not, return false and set err to a descriptive message.
|
||||
*/
|
||||
typedef std::initializer_list<std::pair<std::string, Type>> shape;
|
||||
bool has_shape(const shape & types, std::string & err) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<JsonValue> m_ptr;
|
||||
};
|
||||
|
||||
// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
|
||||
class JsonValue {
|
||||
protected:
|
||||
friend class Json;
|
||||
friend class JsonInt;
|
||||
friend class JsonDouble;
|
||||
virtual Json::Type type() const = 0;
|
||||
virtual bool equals(const JsonValue * other) const = 0;
|
||||
virtual bool less(const JsonValue * other) const = 0;
|
||||
virtual void dump(std::string &out) const = 0;
|
||||
virtual double number_value() const;
|
||||
virtual int int_value() const;
|
||||
virtual bool bool_value() const;
|
||||
virtual const std::string &string_value() const;
|
||||
virtual const Json::array &array_items() const;
|
||||
virtual const Json &operator[](size_t i) const;
|
||||
virtual const Json::object &object_items() const;
|
||||
virtual const Json &operator[](const std::string &key) const;
|
||||
virtual ~JsonValue() {}
|
||||
};
|
||||
|
||||
} // namespace json11
|
||||
104
TMessagesProj/jni/voip/libtgvoip/logging.cpp
Normal file
104
TMessagesProj/jni/voip/libtgvoip/logging.cpp
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// 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 <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "VoIPController.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/system_properties.h>
|
||||
#elif defined(__linux__)
|
||||
#include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#include "os/darwin/DarwinSpecific.h"
|
||||
#endif
|
||||
|
||||
FILE* tgvoipLogFile=NULL;
|
||||
|
||||
void tgvoip_log_file_printf(char level, const char* msg, ...){
|
||||
if(tgvoipLogFile){
|
||||
va_list argptr;
|
||||
va_start(argptr, msg);
|
||||
time_t t = time(0);
|
||||
struct tm *now = localtime(&t);
|
||||
fprintf(tgvoipLogFile, "%02d-%02d %02d:%02d:%02d %c: ", now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec, level);
|
||||
vfprintf(tgvoipLogFile, msg, argptr);
|
||||
fprintf(tgvoipLogFile, "\n");
|
||||
fflush(tgvoipLogFile);
|
||||
}
|
||||
}
|
||||
|
||||
void tgvoip_log_file_write_header(FILE* file){
|
||||
if(file){
|
||||
time_t t = time(0);
|
||||
struct tm *now = localtime(&t);
|
||||
#if defined(_WIN32)
|
||||
#if WINAPI_PARTITION_DESKTOP
|
||||
char systemVersion[64];
|
||||
OSVERSIONINFOA vInfo;
|
||||
vInfo.dwOSVersionInfoSize=sizeof(vInfo);
|
||||
GetVersionExA(&vInfo);
|
||||
snprintf(systemVersion, sizeof(systemVersion), "Windows %d.%d.%d %s", vInfo.dwMajorVersion, vInfo.dwMinorVersion, vInfo.dwBuildNumber, vInfo.szCSDVersion);
|
||||
#else
|
||||
char* systemVersion="Windows RT";
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#ifdef __ANDROID__
|
||||
char systemVersion[128];
|
||||
char sysRel[PROP_VALUE_MAX];
|
||||
char deviceVendor[PROP_VALUE_MAX];
|
||||
char deviceModel[PROP_VALUE_MAX];
|
||||
__system_property_get("ro.build.version.release", sysRel);
|
||||
__system_property_get("ro.product.manufacturer", deviceVendor);
|
||||
__system_property_get("ro.product.model", deviceModel);
|
||||
snprintf(systemVersion, sizeof(systemVersion), "Android %s (%s %s)", sysRel, deviceVendor, deviceModel);
|
||||
#else
|
||||
struct utsname sysname;
|
||||
uname(&sysname);
|
||||
std::string sysver(sysname.sysname);
|
||||
sysver+=" ";
|
||||
sysver+=sysname.release;
|
||||
sysver+=" (";
|
||||
sysver+=sysname.version;
|
||||
sysver+=")";
|
||||
const char* systemVersion=sysver.c_str();
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
char osxVer[128];
|
||||
tgvoip::DarwinSpecific::GetSystemName(osxVer, sizeof(osxVer));
|
||||
char systemVersion[128];
|
||||
#if TARGET_OS_OSX
|
||||
snprintf(systemVersion, sizeof(systemVersion), "OS X %s", osxVer);
|
||||
#elif TARGET_OS_IPHONE
|
||||
snprintf(systemVersion, sizeof(systemVersion), "iOS %s", osxVer);
|
||||
#else
|
||||
snprintf(systemVersion, sizeof(systemVersion), "Unknown Darwin %s", osxVer);
|
||||
#endif
|
||||
#else
|
||||
const char* systemVersion="Unknown OS";
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__)
|
||||
const char* cpuArch="ARM64";
|
||||
#elif defined(__arm__) || defined(_M_ARM)
|
||||
const char* cpuArch="ARM";
|
||||
#elif defined(_M_X64) || defined(__x86_64__)
|
||||
const char* cpuArch="x86_64";
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
const char* cpuArch="x86";
|
||||
#else
|
||||
const char* cpuArch="Unknown CPU";
|
||||
#endif
|
||||
|
||||
fprintf(file, "---------------\nlibtgvoip v" LIBTGVOIP_VERSION " on %s %s\nLog started on %d/%02d/%d at %d:%02d:%02d\n---------------\n", systemVersion, cpuArch, now->tm_mday, now->tm_mon+1, now->tm_year+1900, now->tm_hour, now->tm_min, now->tm_sec);
|
||||
}
|
||||
}
|
||||
106
TMessagesProj/jni/voip/libtgvoip/logging.h
Normal file
106
TMessagesProj/jni/voip/libtgvoip/logging.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void tgvoip_log_file_printf(char level, const char* msg, ...);
|
||||
void tgvoip_log_file_write_header(FILE* file);
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
//#define _LOG_WRAP(...) __BASE_FILE__":"LSTR_INT(__LINE__)": "__VA_ARGS__
|
||||
#define _LOG_WRAP(...) __VA_ARGS__
|
||||
#define TAG "tgvoip"
|
||||
#define LOGV(...) {__android_log_print(ANDROID_LOG_VERBOSE, TAG, _LOG_WRAP(__VA_ARGS__)); tgvoip_log_file_printf('V', __VA_ARGS__);}
|
||||
#define LOGD(...) {__android_log_print(ANDROID_LOG_DEBUG, TAG, _LOG_WRAP(__VA_ARGS__)); tgvoip_log_file_printf('D', __VA_ARGS__);}
|
||||
#define LOGI(...) {__android_log_print(ANDROID_LOG_INFO, TAG, _LOG_WRAP(__VA_ARGS__)); tgvoip_log_file_printf('I', __VA_ARGS__);}
|
||||
#define LOGW(...) {__android_log_print(ANDROID_LOG_WARN, TAG, _LOG_WRAP(__VA_ARGS__)); tgvoip_log_file_printf('W', __VA_ARGS__);}
|
||||
#define LOGE(...) {__android_log_print(ANDROID_LOG_ERROR, TAG, _LOG_WRAP(__VA_ARGS__)); tgvoip_log_file_printf('E', __VA_ARGS__);}
|
||||
|
||||
#elif defined(__APPLE__) && TARGET_OS_IPHONE && defined(TGVOIP_HAVE_TGLOG)
|
||||
|
||||
#include "os/darwin/TGLogWrapper.h"
|
||||
|
||||
#define LOGV(msg, ...) {__tgvoip_call_tglog("V/tgvoip: " msg, ##__VA_ARGS__); tgvoip_log_file_printf('V', msg, ##__VA_ARGS__);}
|
||||
#define LOGD(msg, ...) {__tgvoip_call_tglog("D/tgvoip: " msg, ##__VA_ARGS__); tgvoip_log_file_printf('D', msg, ##__VA_ARGS__);}
|
||||
#define LOGI(msg, ...) {__tgvoip_call_tglog("I/tgvoip: " msg, ##__VA_ARGS__); tgvoip_log_file_printf('I', msg, ##__VA_ARGS__);}
|
||||
#define LOGW(msg, ...) {__tgvoip_call_tglog("W/tgvoip: " msg, ##__VA_ARGS__); tgvoip_log_file_printf('W', msg, ##__VA_ARGS__);}
|
||||
#define LOGE(msg, ...) {__tgvoip_call_tglog("E/tgvoip: " msg, ##__VA_ARGS__); tgvoip_log_file_printf('E', msg, ##__VA_ARGS__);}
|
||||
|
||||
#elif defined(_WIN32) && defined(_DEBUG)
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define _TGVOIP_W32_LOG_PRINT(verb, msg, ...){ char __log_buf[1024]; snprintf(__log_buf, 1024, "%c/tgvoip: " msg "\n", verb, ##__VA_ARGS__); OutputDebugStringA(__log_buf); tgvoip_log_file_printf((char)verb, msg, __VA_ARGS__);}
|
||||
|
||||
#define LOGV(msg, ...) _TGVOIP_W32_LOG_PRINT('V', msg, ##__VA_ARGS__)
|
||||
#define LOGD(msg, ...) _TGVOIP_W32_LOG_PRINT('D', msg, ##__VA_ARGS__)
|
||||
#define LOGI(msg, ...) _TGVOIP_W32_LOG_PRINT('I', msg, ##__VA_ARGS__)
|
||||
#define LOGW(msg, ...) _TGVOIP_W32_LOG_PRINT('W', msg, ##__VA_ARGS__)
|
||||
#define LOGE(msg, ...) _TGVOIP_W32_LOG_PRINT('E', msg, ##__VA_ARGS__)
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef TGVOIP_NO_STDOUT_LOGS
|
||||
#ifndef TGVOIP_NO_STDOUT_COLOR
|
||||
#define _TGVOIP_LOG_PRINT(verb, color, msg, ...) {printf("\033[%dm%c/tgvoip: " msg "\033[0m\n", color, verb, ##__VA_ARGS__); tgvoip_log_file_printf(verb, msg, ##__VA_ARGS__);}
|
||||
#else
|
||||
#define _TGVOIP_LOG_PRINT(verb, color, msg, ...) {printf("%c/tgvoip: " msg "\n", verb, ##__VA_ARGS__); tgvoip_log_file_printf(verb, msg, ##__VA_ARGS__);}
|
||||
#endif
|
||||
#else
|
||||
#define _TGVOIP_LOG_PRINT(verb, color, msg, ...) {tgvoip_log_file_printf(verb, msg, ##__VA_ARGS__);}
|
||||
#endif
|
||||
|
||||
#define LOGV(msg, ...) _TGVOIP_LOG_PRINT('V', 90, msg, ##__VA_ARGS__)
|
||||
#define LOGD(msg, ...) _TGVOIP_LOG_PRINT('D', 37, msg, ##__VA_ARGS__)
|
||||
#define LOGI(msg, ...) _TGVOIP_LOG_PRINT('I', 94, msg, ##__VA_ARGS__)
|
||||
#define LOGW(msg, ...) _TGVOIP_LOG_PRINT('W', 93, msg, ##__VA_ARGS__)
|
||||
#define LOGE(msg, ...) _TGVOIP_LOG_PRINT('E', 91, msg, ##__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(snprintf) && defined(_WIN32) && defined(__cplusplus_winrt)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#ifdef TGVOIP_LOG_VERBOSITY
|
||||
#if TGVOIP_LOG_VERBOSITY<5
|
||||
#undef LOGV
|
||||
#define LOGV(msg, ...)
|
||||
#endif
|
||||
#if TGVOIP_LOG_VERBOSITY<4
|
||||
#undef LOGD
|
||||
#define LOGD(msg, ...)
|
||||
#endif
|
||||
#if TGVOIP_LOG_VERBOSITY<3
|
||||
#undef LOGI
|
||||
#define LOGI(msg, ...)
|
||||
#endif
|
||||
#if TGVOIP_LOG_VERBOSITY<2
|
||||
#undef LOGW
|
||||
#define LOGW(msg, ...)
|
||||
#endif
|
||||
#if TGVOIP_LOG_VERBOSITY<1
|
||||
#undef LOGE
|
||||
#define LOGE(msg, ...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif //__LOGGING_H
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// 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 <stdio.h>
|
||||
#include "../../logging.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
jmethodID AudioInputAndroid::initMethod=NULL;
|
||||
jmethodID AudioInputAndroid::releaseMethod=NULL;
|
||||
jmethodID AudioInputAndroid::startMethod=NULL;
|
||||
jmethodID AudioInputAndroid::stopMethod=NULL;
|
||||
jmethodID AudioInputAndroid::getEnabledEffectsMaskMethod=NULL;
|
||||
jclass AudioInputAndroid::jniClass=NULL;
|
||||
|
||||
AudioInputAndroid::AudioInputAndroid(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
|
||||
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
|
||||
DEBUG_REF("AudioInputAndroid");
|
||||
javaObject=env->NewGlobalRef(obj);
|
||||
|
||||
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
|
||||
enabledEffects=(unsigned int)env->CallIntMethod(javaObject, getEnabledEffectsMaskMethod);
|
||||
});
|
||||
running=false;
|
||||
}
|
||||
|
||||
AudioInputAndroid::~AudioInputAndroid(){
|
||||
{
|
||||
MutexGuard guard(mutex);
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, releaseMethod);
|
||||
DEBUG_DELREF("AudioInputAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
javaObject=NULL;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputAndroid::Start(){
|
||||
MutexGuard guard(mutex);
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
failed=!env->CallBooleanMethod(javaObject, startMethod);
|
||||
});
|
||||
running=true;
|
||||
}
|
||||
|
||||
void AudioInputAndroid::Stop(){
|
||||
MutexGuard guard(mutex);
|
||||
running=false;
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, stopMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioInputAndroid::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);
|
||||
}
|
||||
|
||||
unsigned int AudioInputAndroid::GetEnabledEffects(){
|
||||
return enabledEffects;
|
||||
}
|
||||
|
|
@ -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_AUDIOINPUTANDROID_H
|
||||
#define LIBTGVOIP_AUDIOINPUTANDROID_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAndroid : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputAndroid();
|
||||
virtual ~AudioInputAndroid();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleCallback(JNIEnv* env, jobject buffer);
|
||||
unsigned int GetEnabledEffects();
|
||||
static jmethodID initMethod;
|
||||
static jmethodID releaseMethod;
|
||||
static jmethodID startMethod;
|
||||
static jmethodID stopMethod;
|
||||
static jmethodID getEnabledEffectsMaskMethod;
|
||||
static jclass jniClass;
|
||||
|
||||
static constexpr unsigned int EFFECT_AEC=1;
|
||||
static constexpr unsigned int EFFECT_NS=2;
|
||||
|
||||
private:
|
||||
jobject javaObject;
|
||||
bool running;
|
||||
Mutex mutex;
|
||||
unsigned int enabledEffects=0;
|
||||
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTANDROID_H
|
||||
|
|
@ -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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
unsigned int AudioInputOpenSLES::nativeBufferSize;
|
||||
|
||||
AudioInputOpenSLES::AudioInputOpenSLES(){
|
||||
slEngine=OpenSLEngineWrapper::CreateEngine();
|
||||
|
||||
LOGI("Native buffer size is %u samples", nativeBufferSize);
|
||||
if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
|
||||
LOGE("20ms is not divisible by native buffer size!!");
|
||||
}else if(nativeBufferSize>BUFFER_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));
|
||||
slRecorderObj=NULL;
|
||||
}
|
||||
|
||||
AudioInputOpenSLES::~AudioInputOpenSLES(){
|
||||
//Stop();
|
||||
(*slBufferQueue)->Clear(slBufferQueue);
|
||||
(*slRecorderObj)->Destroy(slRecorderObj);
|
||||
slRecorderObj=NULL;
|
||||
slRecorder=NULL;
|
||||
slBufferQueue=NULL;
|
||||
slEngine=NULL;
|
||||
OpenSLEngineWrapper::DestroyEngine();
|
||||
free(buffer);
|
||||
buffer=NULL;
|
||||
free(nativeBuffer);
|
||||
nativeBuffer=NULL;
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
|
||||
((AudioInputOpenSLES*)context)->HandleSLCallback();
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::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, AudioInputOpenSLES::BufferCallback, this);
|
||||
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::Start(){
|
||||
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_RECORDING);
|
||||
CHECK_SL_ERROR(result, "Error starting record");
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::Stop(){
|
||||
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_STOPPED);
|
||||
CHECK_SL_ERROR(result, "Error stopping record");
|
||||
}
|
||||
|
||||
|
||||
void AudioInputOpenSLES::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){
|
||||
//LOGV("nativeBufferSize<BUFFER_SIZE");
|
||||
if(positionInBuffer>=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;offset<nativeBufferSize;offset+=BUFFER_SIZE){
|
||||
InvokeCallback(((unsigned char *) nativeBuffer)+offset*2, BUFFER_SIZE*sizeof(int16_t));
|
||||
}
|
||||
}
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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 <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputOpenSLES : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputOpenSLES();
|
||||
virtual ~AudioInputOpenSLES();
|
||||
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
|
||||
static unsigned 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
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// 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 <stdio.h>
|
||||
#include "../../logging.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
jmethodID AudioOutputAndroid::initMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::releaseMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::startMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::stopMethod=NULL;
|
||||
jclass AudioOutputAndroid::jniClass=NULL;
|
||||
|
||||
AudioOutputAndroid::AudioOutputAndroid(){
|
||||
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, "<init>", "(J)V");
|
||||
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
|
||||
DEBUG_REF("AudioOutputAndroid");
|
||||
javaObject=env->NewGlobalRef(obj);
|
||||
|
||||
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
running=false;
|
||||
}
|
||||
|
||||
AudioOutputAndroid::~AudioOutputAndroid(){
|
||||
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);
|
||||
DEBUG_DELREF("AudioOutputAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
javaObject=NULL;
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAndroid::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 AudioOutputAndroid::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 AudioOutputAndroid::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 AudioOutputAndroid::IsPlaying(){
|
||||
return running;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// 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 <jni.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputAndroid : public AudioOutput{
|
||||
|
||||
public:
|
||||
|
||||
AudioOutputAndroid();
|
||||
virtual ~AudioOutputAndroid();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying() 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
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// 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 <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#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); failed=true; return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
unsigned int AudioOutputOpenSLES::nativeBufferSize;
|
||||
|
||||
AudioOutputOpenSLES::AudioOutputOpenSLES(){
|
||||
SLresult result;
|
||||
slEngine=OpenSLEngineWrapper::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(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
|
||||
LOGE("20ms is not divisible by native buffer size!!");
|
||||
nativeBufferSize=BUFFER_SIZE;
|
||||
}else if(nativeBufferSize>BUFFER_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;
|
||||
}
|
||||
|
||||
AudioOutputOpenSLES::~AudioOutputOpenSLES(){
|
||||
if(!stopped)
|
||||
Stop();
|
||||
(*slBufferQueue)->Clear(slBufferQueue);
|
||||
LOGV("destroy slPlayerObj");
|
||||
(*slPlayerObj)->Destroy(slPlayerObj);
|
||||
LOGV("destroy slOutputMixObj");
|
||||
(*slOutputMixObj)->Destroy(slOutputMixObj);
|
||||
OpenSLEngineWrapper::DestroyEngine();
|
||||
free(buffer);
|
||||
free(nativeBuffer);
|
||||
}
|
||||
|
||||
|
||||
void AudioOutputOpenSLES::SetNativeBufferSize(unsigned int size){
|
||||
AudioOutputOpenSLES::nativeBufferSize=size;
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
|
||||
((AudioOutputOpenSLES*)context)->HandleSLCallback();
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::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, AudioOutputOpenSLES::BufferCallback, this);
|
||||
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSLES::IsPhone(){
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::EnableLoudspeaker(bool enabled){
|
||||
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::Start(){
|
||||
stopped=false;
|
||||
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PLAYING);
|
||||
CHECK_SL_ERROR(result, "Error starting player");
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::Stop(){
|
||||
stopped=true;
|
||||
LOGV("Stopping OpenSL output");
|
||||
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PAUSED);
|
||||
CHECK_SL_ERROR(result, "Error starting player");
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::HandleSLCallback(){
|
||||
/*if(stopped){
|
||||
//LOGV("left HandleSLCallback early");
|
||||
return;
|
||||
}*/
|
||||
//LOGV("before InvokeCallback");
|
||||
if(!stopped){
|
||||
while(remainingDataSize<nativeBufferSize*2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(nativeBuffer, remainingData, nativeBufferSize*2);
|
||||
remainingDataSize-=nativeBufferSize*2;
|
||||
if(remainingDataSize>0)
|
||||
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 AudioOutputOpenSLES::IsPlaying(){
|
||||
if(slPlayer){
|
||||
uint32_t state;
|
||||
(*slPlayer)->GetPlayState(slPlayer, &state);
|
||||
return state==SL_PLAYSTATE_PLAYING;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
float AudioOutputOpenSLES::GetLevel(){
|
||||
return 0; // we don't use this anyway
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// 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 <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputOpenSLES : public AudioOutput{
|
||||
public:
|
||||
AudioOutputOpenSLES();
|
||||
virtual ~AudioOutputOpenSLES();
|
||||
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(unsigned int size);
|
||||
static unsigned 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
|
||||
70
TMessagesProj/jni/voip/libtgvoip/os/android/JNIUtilities.h
Normal file
70
TMessagesProj/jni/voip/libtgvoip/os/android/JNIUtilities.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Created by Grishka on 15.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_JNIUTILITIES_H
|
||||
#define LIBTGVOIP_JNIUTILITIES_H
|
||||
|
||||
#include <functional>
|
||||
#include <jni.h>
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include "../../Buffers.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
namespace tgvoip{
|
||||
namespace jni{
|
||||
|
||||
inline JNIEnv *GetEnv() {
|
||||
JNIEnv *env = nullptr;
|
||||
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
|
||||
return env;
|
||||
}
|
||||
|
||||
inline void DoWithJNI(std::function<void(JNIEnv*)> f){
|
||||
JNIEnv *env=GetEnv();
|
||||
bool didAttach=false;
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
f(env);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
inline void AttachAndCallVoidMethod(jmethodID method, jobject obj, ...){
|
||||
if(!method || !obj)
|
||||
return;
|
||||
va_list va;
|
||||
va_start(va, obj);
|
||||
DoWithJNI([&va, method, obj](JNIEnv* env){
|
||||
env->CallVoidMethodV(obj, method, va);
|
||||
});
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
inline std::string JavaStringToStdString(JNIEnv* env, jstring jstr){
|
||||
if(!jstr)
|
||||
return "";
|
||||
const char* jchars=env->GetStringUTFChars(jstr, NULL);
|
||||
std::string str(jchars);
|
||||
env->ReleaseStringUTFChars(jstr, jchars);
|
||||
return str;
|
||||
}
|
||||
|
||||
inline jbyteArray BufferToByteArray(JNIEnv* env, Buffer& buf){
|
||||
jbyteArray arr=env->NewByteArray((jsize)buf.Length());
|
||||
jbyte* elements=env->GetByteArrayElements(arr, NULL);
|
||||
memcpy(elements, *buf, buf.Length());
|
||||
env->ReleaseByteArrayElements(arr, elements, 0);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_JNIUTILITIES_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.
|
||||
//
|
||||
|
||||
#include <cstddef>
|
||||
#include "OpenSLEngineWrapper.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return NULL; }
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
|
||||
SLObjectItf OpenSLEngineWrapper::sharedEngineObj=NULL;
|
||||
SLEngineItf OpenSLEngineWrapper::sharedEngine=NULL;
|
||||
int OpenSLEngineWrapper::count=0;
|
||||
|
||||
void OpenSLEngineWrapper::DestroyEngine(){
|
||||
count--;
|
||||
LOGI("release: engine instance count %d", count);
|
||||
if(count==0){
|
||||
(*sharedEngineObj)->Destroy(sharedEngineObj);
|
||||
sharedEngineObj=NULL;
|
||||
sharedEngine=NULL;
|
||||
}
|
||||
LOGI("after release");
|
||||
}
|
||||
|
||||
SLEngineItf OpenSLEngineWrapper::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;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// 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 <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class OpenSLEngineWrapper{
|
||||
public:
|
||||
static SLEngineItf CreateEngine();
|
||||
static void DestroyEngine();
|
||||
|
||||
private:
|
||||
static SLObjectItf sharedEngineObj;
|
||||
static SLEngineItf sharedEngine;
|
||||
static int count;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_OPENSLENGINEWRAPPER_H
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#include "VideoRendererAndroid.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
jmethodID VideoRendererAndroid::resetMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::decodeAndDisplayMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::setStreamEnabledMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::setRotationMethod=NULL;
|
||||
std::vector<uint32_t> VideoRendererAndroid::availableDecoders;
|
||||
int VideoRendererAndroid::maxResolution;
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
VideoRendererAndroid::VideoRendererAndroid(jobject jobj) : queue(50){
|
||||
this->jobj=jobj;
|
||||
}
|
||||
|
||||
VideoRendererAndroid::~VideoRendererAndroid(){
|
||||
running=false;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::Shutdown
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
thread->Join();
|
||||
delete thread;
|
||||
/*decoderThread.Post([this]{
|
||||
decoderEnv->DeleteGlobalRef(jobj);
|
||||
});*/
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer> &_csd){
|
||||
csd.clear();
|
||||
for(Buffer& b:_csd){
|
||||
csd.push_back(Buffer::CopyOf(b));
|
||||
}
|
||||
this->codec=codec;
|
||||
this->width=width;
|
||||
this->height=height;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::ResetDecoder
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
Request req2{
|
||||
Buffer(0),
|
||||
Request::Type::UpdateStreamState
|
||||
};
|
||||
queue.Put(std::move(req2));
|
||||
|
||||
if(!thread){
|
||||
thread=new Thread(std::bind(&VideoRendererAndroid::RunThread, this));
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::DecodeAndDisplay(Buffer frame, uint32_t pts){
|
||||
/*decoderThread.Post(std::bind([this](Buffer frame){
|
||||
}, std::move(frame)));*/
|
||||
//LOGV("2 before decode %u", (unsigned int)frame.Length());
|
||||
Request req{
|
||||
std::move(frame),
|
||||
Request::Type::DecodeFrame
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::RunThread(){
|
||||
JNIEnv* env;
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
|
||||
constexpr size_t bufferSize=200*1024;
|
||||
unsigned char* buf=reinterpret_cast<unsigned char*>(malloc(bufferSize));
|
||||
jobject jbuf=env->NewDirectByteBuffer(buf, bufferSize);
|
||||
uint16_t lastSetRotation=0;
|
||||
|
||||
while(running){
|
||||
//LOGV("before get from queue");
|
||||
Request request=std::move(queue.GetBlocking());
|
||||
//LOGV("1 before decode %u", (unsigned int)request.Length());
|
||||
if(request.type==Request::Type::Shutdown){
|
||||
LOGI("Shutting down video decoder thread");
|
||||
break;
|
||||
}else if(request.type==Request::Type::DecodeFrame){
|
||||
if(request.buffer.Length()>bufferSize){
|
||||
LOGE("Frame data is too long (%u, max %u)", (int) request.buffer.Length(), (int) bufferSize);
|
||||
}else{
|
||||
if(lastSetRotation!=rotation){
|
||||
lastSetRotation=rotation;
|
||||
env->CallVoidMethod(jobj, setRotationMethod, (jint)rotation);
|
||||
}
|
||||
memcpy(buf, *request.buffer, request.buffer.Length());
|
||||
env->CallVoidMethod(jobj, decodeAndDisplayMethod, jbuf, (jint) request.buffer.Length(), 0);
|
||||
}
|
||||
}else if(request.type==Request::Type::ResetDecoder){
|
||||
jobjectArray jcsd=NULL;
|
||||
if(!csd.empty()){
|
||||
jcsd=env->NewObjectArray((jsize)csd.size(), env->FindClass("[B"), NULL);
|
||||
jsize i=0;
|
||||
for(Buffer& b:csd){
|
||||
env->SetObjectArrayElement(jcsd, i, jni::BufferToByteArray(env, b));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
std::string codecStr="";
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecStr="video/avc";
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecStr="video/hevc";
|
||||
break;
|
||||
case CODEC_VP8:
|
||||
codecStr="video/x-vnd.on2.vp8";
|
||||
break;
|
||||
case CODEC_VP9:
|
||||
codecStr="video/x-vnd.on2.vp9";
|
||||
break;
|
||||
}
|
||||
env->CallVoidMethod(jobj, resetMethod, env->NewStringUTF(codecStr.c_str()), (jint)width, (jint)height, jcsd);
|
||||
}else if(request.type==Request::Type::UpdateStreamState){
|
||||
env->CallVoidMethod(jobj, setStreamEnabledMethod, streamEnabled);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
sharedJVM->DetachCurrentThread();
|
||||
LOGI("==== decoder thread exiting ====");
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::SetStreamEnabled(bool enabled){
|
||||
LOGI("Video stream state: %d", enabled);
|
||||
streamEnabled=enabled;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::UpdateStreamState
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::SetRotation(uint16_t rotation){
|
||||
this->rotation=rotation;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
#define LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
|
||||
#include "../../video/VideoRenderer.h"
|
||||
#include "../../MessageThread.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include "../../BlockingQueue.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoRendererAndroid : public VideoRenderer{
|
||||
public:
|
||||
VideoRendererAndroid(jobject jobj);
|
||||
virtual ~VideoRendererAndroid();
|
||||
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
|
||||
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
|
||||
virtual void SetStreamEnabled(bool enabled) override;
|
||||
virtual void SetRotation(uint16_t rotation) override;
|
||||
|
||||
static jmethodID resetMethod;
|
||||
static jmethodID decodeAndDisplayMethod;
|
||||
static jmethodID setStreamEnabledMethod;
|
||||
static jmethodID setRotationMethod;
|
||||
static std::vector<uint32_t> availableDecoders;
|
||||
static int maxResolution;
|
||||
private:
|
||||
struct Request{
|
||||
enum Type{
|
||||
DecodeFrame,
|
||||
ResetDecoder,
|
||||
UpdateStreamState,
|
||||
Shutdown
|
||||
};
|
||||
|
||||
Buffer buffer;
|
||||
Type type;
|
||||
};
|
||||
void RunThread();
|
||||
Thread* thread=NULL;
|
||||
bool running=true;
|
||||
BlockingQueue<Request> queue;
|
||||
std::vector<Buffer> csd;
|
||||
int width;
|
||||
int height;
|
||||
bool streamEnabled=true;
|
||||
uint32_t codec;
|
||||
uint16_t rotation=0;
|
||||
jobject jobj;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#include "VideoSourceAndroid.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
std::vector<uint32_t> VideoSourceAndroid::availableEncoders;
|
||||
|
||||
VideoSourceAndroid::VideoSourceAndroid(jobject jobj) : javaObject(jobj){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
jclass cls=env->GetObjectClass(javaObject);
|
||||
startMethod=env->GetMethodID(cls, "start", "()V");
|
||||
stopMethod=env->GetMethodID(cls, "stop", "()V");
|
||||
prepareEncoderMethod=env->GetMethodID(cls, "prepareEncoder", "(Ljava/lang/String;I)V");
|
||||
requestKeyFrameMethod=env->GetMethodID(cls, "requestKeyFrame", "()V");
|
||||
setBitrateMethod=env->GetMethodID(cls, "setBitrate", "(I)V");
|
||||
});
|
||||
}
|
||||
|
||||
VideoSourceAndroid::~VideoSourceAndroid(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
DEBUG_DELREF("VideoSourceAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Start(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, startMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Stop(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, stopMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SendFrame(Buffer frame, uint32_t flags){
|
||||
callback(frame, flags, rotation);
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height){
|
||||
LOGD("Video stream parameters: %d x %d", width, height);
|
||||
this->width=width;
|
||||
this->height=height;
|
||||
this->csd=std::move(csd);
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Reset(uint32_t codec, int maxResolution){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
std::string codecStr="";
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecStr="video/avc";
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecStr="video/hevc";
|
||||
break;
|
||||
case CODEC_VP8:
|
||||
codecStr="video/x-vnd.on2.vp8";
|
||||
break;
|
||||
case CODEC_VP9:
|
||||
codecStr="video/x-vnd.on2.vp9";
|
||||
break;
|
||||
}
|
||||
env->CallVoidMethod(javaObject, prepareEncoderMethod, env->NewStringUTF(codecStr.c_str()), maxResolution);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::RequestKeyFrame(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, requestKeyFrameMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SetBitrate(uint32_t bitrate){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, setBitrateMethod, (jint)bitrate);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEOSOURCEANDROID_H
|
||||
#define LIBTGVOIP_VIDEOSOURCEANDROID_H
|
||||
|
||||
#include "../../video/VideoSource.h"
|
||||
#include "../../Buffers.h"
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoSourceAndroid : public VideoSource{
|
||||
public:
|
||||
VideoSourceAndroid(jobject jobj);
|
||||
virtual ~VideoSourceAndroid();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Reset(uint32_t codec, int maxResolution) override;
|
||||
void SendFrame(Buffer frame, uint32_t flags);
|
||||
void SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height);
|
||||
virtual void RequestKeyFrame() override;
|
||||
virtual void SetBitrate(uint32_t bitrate) override;
|
||||
|
||||
static std::vector<uint32_t> availableEncoders;
|
||||
private:
|
||||
jobject javaObject;
|
||||
jmethodID prepareEncoderMethod;
|
||||
jmethodID startMethod;
|
||||
jmethodID stopMethod;
|
||||
jmethodID requestKeyFrameMethod;
|
||||
jmethodID setBitrateMethod;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif //LIBTGVOIP_VIDEOSOURCEANDROID_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.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnit::AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioInputAudioUnit::~AudioInputAudioUnit(){
|
||||
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Start(){
|
||||
isRecording=true;
|
||||
io->EnableInput(true);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Stop(){
|
||||
isRecording=false;
|
||||
io->EnableInput(false);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
#if TARGET_OS_OSX
|
||||
assert(remainingDataSize+buf.mDataByteSize/2<10240);
|
||||
float* src=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* dst=reinterpret_cast<int16_t*>(remainingData+remainingDataSize);
|
||||
for(int j=0;j<buf.mDataByteSize/4;j++){
|
||||
dst[j]=(int16_t)(src[j]*INT16_MAX);
|
||||
}
|
||||
remainingDataSize+=buf.mDataByteSize/2;
|
||||
#else
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
remainingDataSize+=buf.mDataByteSize;
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioInputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -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_AUDIOINPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioInputAudioUnit : public AudioInput{
|
||||
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioInputAudioUnit);
|
||||
AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioInputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "AudioInputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../audio/Resampler.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("input: " msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnitLegacy::AudioInputAudioUnitLegacy(std::string deviceID) : AudioInput(deviceID){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
|
||||
inBufferList.mBuffers[0].mData=malloc(10240);
|
||||
inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioInputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
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 unit");
|
||||
}
|
||||
|
||||
AudioInputAudioUnitLegacy::~AudioInputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Start(){
|
||||
isRecording=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Stop(){
|
||||
isRecording=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioInputAudioUnitLegacy* input=(AudioInputAudioUnitLegacy*) inRefCon;
|
||||
input->inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
AudioUnitRender(input->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &input->inBufferList);
|
||||
input->HandleBufferCallback(&input->inBufferList);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
size_t len=buf.mDataByteSize;
|
||||
if(hardwareSampleRate!=48000){
|
||||
len=tgvoip::audio::Resampler::Convert((int16_t*)buf.mData, (int16_t*)(remainingData+remainingDataSize), buf.mDataByteSize/2, (10240-(buf.mDataByteSize+remainingDataSize))/2, 48000, hardwareSampleRate)*2;
|
||||
}else{
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
}
|
||||
remainingDataSize+=len;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInputAudioUnitLegacy::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioInputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID inputDevice=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &inputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
inputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!inputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kInputBus,
|
||||
&inputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kInputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=hardwareFormat.mSampleRate, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched capture device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(inputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
LOGV("System default input device changed");
|
||||
AudioInputAudioUnitLegacy* self=(AudioInputAudioUnitLegacy*)inClientData;
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -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_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnitLegacy : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioInputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnit unit;
|
||||
AudioBufferList inBufferList;
|
||||
int hardwareSampleRate;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
|
@ -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 <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "AudioUnitIO.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnit::AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
isPlaying=false;
|
||||
remainingDataSize=0;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioOutputAudioUnit::~AudioOutputAudioUnit(){
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Start(){
|
||||
isPlaying=true;
|
||||
io->EnableOutput(true);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Stop(){
|
||||
isPlaying=false;
|
||||
io->EnableOutput(false);
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnit::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
#if TARGET_OS_OSX
|
||||
unsigned int k;
|
||||
while(remainingDataSize<buf.mDataByteSize/2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
float* dst=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* src=reinterpret_cast<int16_t*>(remainingData);
|
||||
for(k=0;k<buf.mDataByteSize/4;k++){
|
||||
dst[k]=src[k]/(float)INT16_MAX;
|
||||
}
|
||||
remainingDataSize-=buf.mDataByteSize/2;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize/2, remainingDataSize);
|
||||
#else
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioOutputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -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_AUDIOOUTPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioOutputAudioUnit : public AudioOutput{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioOutputAudioUnit);
|
||||
AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioOutputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool isPlaying;
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include "AudioOutputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("output: " msg": OSStatus=%d", (int)res); return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnitLegacy::AudioOutputAudioUnitLegacy(std::string deviceID){
|
||||
remainingDataSize=0;
|
||||
isPlaying=false;
|
||||
sysDevID=0;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
char model[128];
|
||||
memset(model, 0, sizeof(model));
|
||||
size_t msize=sizeof(model);
|
||||
int mres=sysctlbyname("hw.model", model, &msize, NULL, 0);
|
||||
if(mres==0){
|
||||
LOGV("Mac model: %s", model);
|
||||
isMacBookPro=(strncmp("MacBookPro", model, 10)==0);
|
||||
}
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=/*hardwareFormat.mSampleRate*/48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioOutputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
status=AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing unit");
|
||||
}
|
||||
|
||||
AudioOutputAudioUnitLegacy::~AudioOutputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Start(){
|
||||
isPlaying=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Stop(){
|
||||
isPlaying=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioOutputAudioUnitLegacy* input=(AudioOutputAudioUnitLegacy*) inRefCon;
|
||||
input->HandleBufferCallback(ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnitLegacy::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioOutputAudioUnitLegacy::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioOutputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID outputDevice=0;
|
||||
OSStatus status;
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &outputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
outputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!outputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kOutputBus,
|
||||
&outputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting output device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched playback device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
sysDevID=outputDevice;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(outputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
|
||||
if(isMacBookPro){
|
||||
if(AudioObjectHasProperty(outputDevice, &dataSourceProp)){
|
||||
UInt32 dataSource;
|
||||
size=4;
|
||||
AudioObjectGetPropertyData(outputDevice, &dataSourceProp, 0, NULL, &size, &dataSource);
|
||||
SetPanRight(dataSource=='ispk');
|
||||
AudioObjectAddPropertyListener(outputDevice, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}else{
|
||||
SetPanRight(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetPanRight(bool panRight){
|
||||
LOGI("%sabling pan right on macbook pro", panRight ? "En" : "Dis");
|
||||
int32_t channelMap[]={panRight ? -1 : 0, 0};
|
||||
OSStatus status=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, kOutputBus, channelMap, sizeof(channelMap));
|
||||
CHECK_AU_ERROR(status, "Error setting channel map");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioOutputAudioUnitLegacy* self=(AudioOutputAudioUnitLegacy*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioDevicePropertyDataSource){
|
||||
UInt32 dataSource;
|
||||
UInt32 size=4;
|
||||
AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &size, &dataSource);
|
||||
self->SetPanRight(dataSource=='ispk');
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -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 LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputAudioUnitLegacy : public AudioOutput{
|
||||
|
||||
public:
|
||||
AudioOutputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioOutputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
void SetPanRight(bool panRight);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isPlaying;
|
||||
AudioUnit unit;
|
||||
int hardwareSampleRate;
|
||||
bool isMacBookPro;
|
||||
AudioDeviceID sysDevID;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// 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 <stdio.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../VoIPServerConfig.h"
|
||||
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#define INPUT_BUFFER_SIZE 20480
|
||||
#else
|
||||
#define INPUT_BUFFER_SIZE 10240
|
||||
#endif
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
extern "C" {
|
||||
OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
|
||||
Float32 inDuckedLevel,
|
||||
const AudioTimeStamp* __nullable inStartTime,
|
||||
Float32 inRampDuration) __attribute__((weak_import));
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID){
|
||||
input=NULL;
|
||||
output=NULL;
|
||||
inputEnabled=false;
|
||||
outputEnabled=false;
|
||||
failed=false;
|
||||
started=false;
|
||||
inBufferList.mBuffers[0].mData=malloc(INPUT_BUFFER_SIZE);
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
DarwinSpecific::ConfigureAudioSession();
|
||||
#endif
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent inputComponent;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
inputComponent = AudioComponentFindNext(NULL, &desc);
|
||||
status = AudioComponentInstanceNew(inputComponent, &unit);
|
||||
|
||||
UInt32 flag=1;
|
||||
#if TARGET_OS_IPHONE
|
||||
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");
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0;
|
||||
#else
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0;
|
||||
#endif
|
||||
status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error disabling AGC");
|
||||
|
||||
AudioStreamBasicDescription audioFormat;
|
||||
audioFormat.mSampleRate = 48000;
|
||||
audioFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
#if TARGET_OS_IPHONE
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 16;
|
||||
audioFormat.mBytesPerPacket = 2;
|
||||
audioFormat.mBytesPerFrame = 2;
|
||||
#else // OS X
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 32;
|
||||
audioFormat.mBytesPerPacket = 4;
|
||||
audioFormat.mBytesPerFrame = 4;
|
||||
#endif
|
||||
audioFormat.mFramesPerPacket = 1;
|
||||
audioFormat.mChannelsPerFrame = 1;
|
||||
|
||||
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 = AudioUnitIO::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");
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
input=new AudioInputAudioUnit(inputDeviceID, this);
|
||||
output=new AudioOutputAudioUnit(outputDeviceID, this);
|
||||
}
|
||||
|
||||
AudioUnitIO::~AudioUnitIO(){
|
||||
#if TARGET_OS_OSX
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
#endif
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
delete input;
|
||||
delete output;
|
||||
}
|
||||
|
||||
OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::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){
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
|
||||
if(input && inputEnabled){
|
||||
input->HandleBufferCallback(&inBufferList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableInput(bool enabled){
|
||||
inputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableOutput(bool enabled){
|
||||
outputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
if(actualDuckingEnabled!=duckingEnabled){
|
||||
actualDuckingEnabled=duckingEnabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioUnitIO::StartIfNeeded(){
|
||||
if(started)
|
||||
return;
|
||||
started=true;
|
||||
OSStatus status = AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing AudioUnit");
|
||||
status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
AudioInput* AudioUnitIO::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
AudioOutput* AudioUnitIO::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioUnitIO* self=(AudioUnitIO*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default output device changed");
|
||||
if(self->currentOutputDevice=="default"){
|
||||
self->SetCurrentDevice(false, self->currentOutputDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultInputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentInputDevice=="default"){
|
||||
self->SetCurrentDevice(true, self->currentInputDevice);
|
||||
}
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID){
|
||||
LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str());
|
||||
if(started){
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
}
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID device=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &device);
|
||||
CHECK_AU_ERROR(status, "Error getting default device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
device=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!device){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice(input, "default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status=AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
input ? kInputBus : kOutputBus,
|
||||
&device,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
if(input)
|
||||
currentInputDevice=deviceID;
|
||||
else
|
||||
currentOutputDevice=deviceID;
|
||||
|
||||
/*AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(device, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}*/
|
||||
if(started){
|
||||
started=false;
|
||||
StartIfNeeded();
|
||||
}
|
||||
if(!input){
|
||||
currentOutputDeviceID=device;
|
||||
}
|
||||
LOGV("Set current %sput device done", input ? "in" : "out");
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetDuckingEnabled(bool enabled){
|
||||
duckingEnabled=enabled;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
if(outputEnabled && duckingEnabled!=actualDuckingEnabled){
|
||||
actualDuckingEnabled=enabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// 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 <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include "../../threading.h"
|
||||
#include <string>
|
||||
#include "../../audio/AudioIO.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnit;
|
||||
class AudioOutputAudioUnit;
|
||||
|
||||
class AudioUnitIO : public AudioIO{
|
||||
public:
|
||||
AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID);
|
||||
~AudioUnitIO();
|
||||
void EnableInput(bool enabled);
|
||||
void EnableOutput(bool enabled);
|
||||
virtual AudioInput* GetInput();
|
||||
virtual AudioOutput* GetOutput();
|
||||
#if TARGET_OS_OSX
|
||||
void SetCurrentDevice(bool input, std::string deviceID);
|
||||
void SetDuckingEnabled(bool enabled);
|
||||
#endif
|
||||
|
||||
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);
|
||||
void StartIfNeeded();
|
||||
#if TARGET_OS_OSX
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
std::string currentInputDevice;
|
||||
std::string currentOutputDevice;
|
||||
bool duckingEnabled=true;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
bool actualDuckingEnabled=true;
|
||||
#endif // TGVOIP_NO_OSX_PRIVATE_API
|
||||
AudioDeviceID currentOutputDeviceID;
|
||||
#endif
|
||||
AudioComponentInstance unit;
|
||||
AudioInputAudioUnit* input;
|
||||
AudioOutputAudioUnit* output;
|
||||
AudioBufferList inBufferList;
|
||||
bool inputEnabled;
|
||||
bool outputEnabled;
|
||||
bool started;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif /* LIBTGVOIP_AUDIOUNITIO_H */
|
||||
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// 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_DARWINSPECIFIC_H
|
||||
#define TGVOIP_DARWINSPECIFIC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
struct CellularCarrierInfo;
|
||||
|
||||
class DarwinSpecific{
|
||||
public:
|
||||
enum{
|
||||
THREAD_PRIO_USER_INTERACTIVE,
|
||||
THREAD_PRIO_USER_INITIATED,
|
||||
THREAD_PRIO_UTILITY,
|
||||
THREAD_PRIO_BACKGROUND,
|
||||
THREAD_PRIO_DEFAULT
|
||||
};
|
||||
static void GetSystemName(char* buf, size_t len);
|
||||
static void SetCurrentThreadPriority(int priority);
|
||||
static CellularCarrierInfo GetCarrierInfo();
|
||||
static void ConfigureAudioSession();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TGVOIP_DARWINSPECIFIC_H
|
||||
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// 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 "DarwinSpecific.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IOS
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
#import <CoreTelephony/CTCarrier.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
void DarwinSpecific::GetSystemName(char* buf, size_t len){
|
||||
NSString* v=[[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
strcpy(buf, [v UTF8String]);
|
||||
//[v getCString:buf maxLength:sizeof(buf) encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
void DarwinSpecific::SetCurrentThreadPriority(int priority){
|
||||
NSThread* thread=[NSThread currentThread];
|
||||
if([thread respondsToSelector:@selector(setQualityOfService:)]){
|
||||
NSQualityOfService qos;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
qos=NSQualityOfServiceUserInteractive;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
qos=NSQualityOfServiceUserInitiated;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
qos=NSQualityOfServiceUtility;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
qos=NSQualityOfServiceBackground;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
qos=NSQualityOfServiceDefault;
|
||||
break;
|
||||
}
|
||||
[thread setQualityOfService:qos];
|
||||
}else{
|
||||
double p;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
p=1.0;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
p=0.8;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
p=0.4;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
p=0.2;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
p=0.5;
|
||||
break;
|
||||
}
|
||||
[NSThread setThreadPriority:p];
|
||||
}
|
||||
}
|
||||
|
||||
CellularCarrierInfo DarwinSpecific::GetCarrierInfo(){
|
||||
CellularCarrierInfo info;
|
||||
#if TARGET_OS_IOS
|
||||
CTTelephonyNetworkInfo* netinfo=[CTTelephonyNetworkInfo new];
|
||||
CTCarrier* carrier=[netinfo subscriberCellularProvider];
|
||||
if(carrier){
|
||||
NSString* name=[carrier carrierName];
|
||||
NSString* mcc=[carrier mobileCountryCode];
|
||||
NSString* mnc=[carrier mobileNetworkCode];
|
||||
NSString* countryCode=[carrier isoCountryCode];
|
||||
if(name && mcc && mnc && countryCode){
|
||||
info.name=[name cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mcc=[mcc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mnc=[mnc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.countryCode=[[countryCode uppercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return info;
|
||||
}
|
||||
|
||||
void DarwinSpecific::ConfigureAudioSession(){
|
||||
#if TARGET_OS_IOS
|
||||
AVAudioSession* session=[AVAudioSession sharedInstance];
|
||||
NSError* error=nil;
|
||||
[session setPreferredSampleRate:48000.0 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred sample rate on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
[session setPreferredIOBufferDuration:0.020 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred IO buffer duration on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
LOGI("Configured AVAudioSession: sampleRate=%f, IOBufferDuration=%f", session.sampleRate, session.IOBufferDuration);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
#define TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
|
||||
#include "../../video/VideoRenderer.h"
|
||||
#include <vector>
|
||||
#include <objc/objc.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class TGVVideoRenderer;
|
||||
#else
|
||||
typedef struct objc_object TGVVideoRenderer;
|
||||
#endif
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class SampleBufferDisplayLayerRenderer : public VideoRenderer{
|
||||
public:
|
||||
SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer);
|
||||
virtual ~SampleBufferDisplayLayerRenderer();
|
||||
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
|
||||
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
|
||||
virtual void SetStreamEnabled(bool enabled) override;
|
||||
static int GetMaximumResolution();
|
||||
static std::vector<uint32_t> GetAvailableDecoders();
|
||||
private:
|
||||
TGVVideoRenderer* renderer;
|
||||
CMFormatDescriptionRef formatDesc=NULL;
|
||||
bool needReset=false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER */
|
||||
|
|
@ -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 <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#include <UIKit/UIKit.h>
|
||||
#endif
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
#include "TGVVideoRenderer.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
SampleBufferDisplayLayerRenderer::SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer) : renderer(renderer){
|
||||
|
||||
}
|
||||
|
||||
SampleBufferDisplayLayerRenderer::~SampleBufferDisplayLayerRenderer(){
|
||||
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd){
|
||||
LOGI("video renderer reset: %d x %d", width, height);
|
||||
if(formatDesc){
|
||||
CFRelease(formatDesc);
|
||||
}
|
||||
if(codec==CODEC_AVC){
|
||||
if(csd.size()!=2){
|
||||
LOGE("H264 requires exactly 2 CSD buffers");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*csd[0]+4, *csd[1]+4};
|
||||
size_t paramSizes[]={csd[0].Length()-4, csd[1].Length()-4};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, params, paramSizes, 4, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromH264ParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else if(codec==CODEC_HEVC){
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(csd.size()!=1){
|
||||
LOGE("HEVC requires exactly 1 CSD buffer");
|
||||
return;
|
||||
}
|
||||
int offsets[]={0, 0, 0};
|
||||
Buffer& buf=csd[0];
|
||||
int currentNAL=0;
|
||||
for(int i=0;i<buf.Length()-4;i++){
|
||||
if(buf[i]==0 && buf[i+1]==0 && buf[i+2]==0 && buf[i+3]==1){
|
||||
offsets[currentNAL]=i+4;
|
||||
currentNAL++;
|
||||
}
|
||||
}
|
||||
LOGV("CSD NAL offsets: %d %d %d", offsets[0], offsets[1], offsets[2]);
|
||||
if(offsets[0]==0 || offsets[1]==0 || offsets[2]==0){
|
||||
LOGE("Error splitting CSD buffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*buf+offsets[0], *buf+offsets[1], *buf+offsets[2]};
|
||||
size_t paramSizes[]={(size_t)((offsets[1]-4)-offsets[0]), (size_t)((offsets[2]-4)-offsets[1]), (size_t)(buf.Length()-offsets[2])};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromHEVCParameterSets(NULL, 3, params, paramSizes, 4, NULL, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromHEVCParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else{
|
||||
LOGE("HEVC not available on this OS");
|
||||
}
|
||||
}
|
||||
needReset=true;
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::DecodeAndDisplay(Buffer frame, uint32_t pts){
|
||||
CMBlockBufferRef blockBuffer;
|
||||
|
||||
OSStatus status=CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, *frame, frame.Length(), kCFAllocatorNull, NULL, 0, frame.Length(), 0, &blockBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferCreateWithMemoryBlock failed: %d", status);
|
||||
return;
|
||||
}
|
||||
uint32_t _len=(uint32_t)(frame.Length()-4);
|
||||
uint8_t lenBytes[]={(uint8_t)(_len >> 24), (uint8_t)(_len >> 16), (uint8_t)(_len >> 8), (uint8_t)_len};
|
||||
status=CMBlockBufferReplaceDataBytes(lenBytes, blockBuffer, 0, 4);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferReplaceDataBytes failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
status=CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, formatDesc, 1, 0, NULL, 0, NULL, &sampleBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMSampleBufferCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
|
||||
CFRelease(blockBuffer);
|
||||
CFArrayRef attachments=CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
|
||||
CFMutableDictionaryRef dict=(CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||
|
||||
[renderer _enqueueBuffer:sampleBuffer reset:needReset];
|
||||
needReset=false;
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::SetStreamEnabled(bool enabled){
|
||||
|
||||
}
|
||||
|
||||
int SampleBufferDisplayLayerRenderer::GetMaximumResolution(){
|
||||
#if TARGET_OS_IPHONE
|
||||
CGRect screenSize=[UIScreen mainScreen].nativeBounds;
|
||||
CGFloat minSize=std::min(screenSize.size.width, screenSize.size.height);
|
||||
if(minSize>720.f){
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}else if(minSize>480.f){
|
||||
return INIT_VIDEO_RES_720;
|
||||
}else{
|
||||
return INIT_VIDEO_RES_480;
|
||||
}
|
||||
#else // OS X
|
||||
// TODO support OS X
|
||||
#endif
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> SampleBufferDisplayLayerRenderer::GetAvailableDecoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)){
|
||||
res.push_back(CODEC_HEVC);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern void (*TGVoipLoggingFunction)(NSString *);
|
||||
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
|
|
@ -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(const char* format, ...);
|
||||
|
||||
#if defined __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif //TGVOIP_TGLOGWRAPPER_H
|
||||
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
void (*TGVoipLoggingFunction)(NSString *) = NULL;
|
||||
|
||||
void __tgvoip_call_tglog(const char* format, ...){
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *string = [[NSString alloc] initWithFormat:[[NSString alloc]initWithUTF8String:format] arguments:args];
|
||||
va_end(args);
|
||||
if (TGVoipLoggingFunction) {
|
||||
TGVoipLoggingFunction(string);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamPauseReason){
|
||||
TGVStreamPauseReasonBackground,
|
||||
TGVStreamPauseReasonPoorConnection
|
||||
};
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamStopReason){
|
||||
TGVStreamStopReasonUser,
|
||||
TGVStreamStopReasonPoorConnection
|
||||
};
|
||||
|
||||
@protocol TGVVideoRendererDelegate <NSObject>
|
||||
|
||||
- (void)incomingVideoRotationDidChange: (int)rotation;
|
||||
- (void)incomingVideoStreamWillStartWithFrameSize: (CGSize)size;
|
||||
- (void)incomingVideoStreamDidStopWithReason: (TGVStreamStopReason)reason;
|
||||
- (void)incomingVideoStreamDidPauseWithReason: (TGVStreamPauseReason)reason;
|
||||
- (void)incomingVideoStreamWillResume;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoRenderer : NSObject
|
||||
|
||||
- (instancetype)initWithDisplayLayer: (AVSampleBufferDisplayLayer *)layer delegate: (id<TGVVideoRendererDelegate>)delegate;
|
||||
- (tgvoip::video::VideoRenderer*)nativeVideoRenderer;
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import "TGVVideoRenderer.h"
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
@implementation TGVVideoRenderer{
|
||||
AVSampleBufferDisplayLayer* layer;
|
||||
id<TGVVideoRendererDelegate> delegate;
|
||||
tgvoip::video::SampleBufferDisplayLayerRenderer* nativeRenderer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDisplayLayer:(AVSampleBufferDisplayLayer *)layer delegate:(nonnull id<TGVVideoRendererDelegate>)delegate{
|
||||
self=[super init];
|
||||
self->layer=layer;
|
||||
self->delegate=delegate;
|
||||
nativeRenderer=new tgvoip::video::SampleBufferDisplayLayerRenderer(self);
|
||||
layer.videoGravity=AVLayerVideoGravityResizeAspect;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeRenderer;
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoRenderer *)nativeVideoRenderer{
|
||||
return nativeRenderer;
|
||||
}
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset{
|
||||
if(reset){
|
||||
LOGV("Resetting layer");
|
||||
[layer flush];
|
||||
}
|
||||
LOGV("Enqueue buffer");
|
||||
[layer enqueueSampleBuffer:buffer];
|
||||
NSError* error=[layer error];
|
||||
if(error){
|
||||
LOGE("enqueueSampleBuffer failed: %s", [error.description cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
|
|
@ -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.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoSource;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVVideoResolution){
|
||||
TGVVideoResolution1080,
|
||||
TGVVideoResolution720,
|
||||
TGVVideoResolution480,
|
||||
TGVVideoResolution360
|
||||
};
|
||||
|
||||
@protocol TGVVideoSourceDelegate <NSObject>
|
||||
|
||||
- (void)setFrameRate: (unsigned int)frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoSource : NSObject
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate;
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer;
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution;
|
||||
- (void)setVideoRotation: (int)rotation;
|
||||
- (void)pauseStream;
|
||||
- (void)resumeStream;
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource;
|
||||
|
||||
@end
|
||||
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import "TGVVideoSource.h"
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
|
||||
@implementation TGVVideoSource{
|
||||
tgvoip::video::VideoToolboxEncoderSource* nativeSource;
|
||||
id<TGVVideoSourceDelegate> delegate;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate{
|
||||
self=[super init];
|
||||
nativeSource=new tgvoip::video::VideoToolboxEncoderSource();
|
||||
self->delegate=delegate;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeSource;
|
||||
}
|
||||
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer{
|
||||
nativeSource->EncodeFrame(buffer);
|
||||
}
|
||||
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution{
|
||||
return TGVVideoResolution1080;
|
||||
}
|
||||
|
||||
- (void)setVideoRotation: (int)rotation{
|
||||
|
||||
}
|
||||
|
||||
- (void)pauseStream{
|
||||
|
||||
}
|
||||
|
||||
- (void)resumeStream{
|
||||
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource{
|
||||
return nativeSource;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// 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_VIDEOTOOLBOXENCODERSOURCE
|
||||
#define LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
|
||||
|
||||
#include "../../video/VideoSource.h"
|
||||
#include <CoreMedia/CoreMedia.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoToolboxEncoderSource : public VideoSource{
|
||||
public:
|
||||
VideoToolboxEncoderSource();
|
||||
virtual ~VideoToolboxEncoderSource();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Reset(uint32_t codec, int maxResolution) override;
|
||||
virtual void RequestKeyFrame() override;
|
||||
virtual void SetBitrate(uint32_t bitrate) override;
|
||||
void EncodeFrame(CMSampleBufferRef frame);
|
||||
static std::vector<uint32_t> GetAvailableEncoders();
|
||||
private:
|
||||
void EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags);
|
||||
void SetEncoderBitrateAndLimit(uint32_t bitrate);
|
||||
bool needUpdateStreamParams=true;
|
||||
uint32_t codec=0;
|
||||
VTCompressionSessionRef session=NULL;
|
||||
bool keyframeRequested=false;
|
||||
uint32_t bitrateChangeRequested=0;
|
||||
uint32_t lastBitrate=512*1024;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE */
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
#define CHECK_ERR(err, msg) if(err!=noErr){LOGE("VideoToolboxEncoder: " msg " failed: %d", err); return;}
|
||||
|
||||
VideoToolboxEncoderSource::VideoToolboxEncoderSource(){
|
||||
|
||||
}
|
||||
|
||||
VideoToolboxEncoderSource::~VideoToolboxEncoderSource(){
|
||||
if(session){
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Start(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Stop(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Reset(uint32_t codec, int maxResolution){
|
||||
if(session){
|
||||
LOGV("Releasing old compression session");
|
||||
//VTCompressionSessionCompleteFrames(session, kCMTimeInvalid);
|
||||
VTCompressionSessionInvalidate(session);
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
LOGV("Released compression session");
|
||||
}
|
||||
CMVideoCodecType codecType;
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecType=kCMVideoCodecType_H264;
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecType=kCMVideoCodecType_HEVC;
|
||||
break;
|
||||
default:
|
||||
LOGE("VideoToolboxEncoder: Unsupported codec");
|
||||
return;
|
||||
}
|
||||
needUpdateStreamParams=true;
|
||||
this->codec=codec;
|
||||
// typedef void (*VTCompressionOutputCallback)(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer);
|
||||
uint32_t width, height;
|
||||
switch(maxResolution){
|
||||
case INIT_VIDEO_RES_1080:
|
||||
width=1920;
|
||||
height=1080;
|
||||
break;
|
||||
case INIT_VIDEO_RES_720:
|
||||
width=1280;
|
||||
height=720;
|
||||
break;
|
||||
case INIT_VIDEO_RES_480:
|
||||
width=854;
|
||||
height=480;
|
||||
break;
|
||||
case INIT_VIDEO_RES_360:
|
||||
default:
|
||||
width=640;
|
||||
height=360;
|
||||
break;
|
||||
}
|
||||
OSStatus status=VTCompressionSessionCreate(NULL, width, height, codecType, NULL, NULL, NULL, [](void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer){
|
||||
reinterpret_cast<VideoToolboxEncoderSource*>(outputCallbackRefCon)->EncoderCallback(status, sampleBuffer, infoFlags);
|
||||
}, this, &session);
|
||||
if(status!=noErr){
|
||||
LOGE("VTCompressionSessionCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
LOGD("Created VTCompressionSession");
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AllowFrameReordering)");
|
||||
int64_t interval=15;
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(interval));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(MaxKeyFrameIntervalDuration)");
|
||||
SetEncoderBitrateAndLimit(lastBitrate);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(RealTime)");
|
||||
LOGD("VTCompressionSession initialized");
|
||||
|
||||
// TODO change camera frame rate dynamically based on resolution + codec
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::RequestKeyFrame(){
|
||||
keyframeRequested=true;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncodeFrame(CMSampleBufferRef frame){
|
||||
if(!session)
|
||||
return;
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(frame);
|
||||
CMMediaType type=CMFormatDescriptionGetMediaType(format);
|
||||
if(type!=kCMMediaType_Video){
|
||||
//LOGW("Received non-video CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
if(bitrateChangeRequested){
|
||||
LOGI("VideoToolboxEocnder: setting bitrate to %u", bitrateChangeRequested);
|
||||
SetEncoderBitrateAndLimit(bitrateChangeRequested);
|
||||
lastBitrate=bitrateChangeRequested;
|
||||
bitrateChangeRequested=0;
|
||||
}
|
||||
CFDictionaryRef frameProps=NULL;
|
||||
if(keyframeRequested){
|
||||
LOGI("VideoToolboxEncoder: requesting keyframe");
|
||||
const void* keys[]={kVTEncodeFrameOptionKey_ForceKeyFrame};
|
||||
const void* values[]={kCFBooleanTrue};
|
||||
frameProps=CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
|
||||
keyframeRequested=false;
|
||||
}
|
||||
|
||||
//CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
//LOGD("EncodeFrame %d x %d", size.width, size.height);
|
||||
CVImageBufferRef imgBuffer=CMSampleBufferGetImageBuffer(frame);
|
||||
//OSType pixFmt=CVPixelBufferGetPixelFormatType(imgBuffer);
|
||||
//LOGV("pixel format: %c%c%c%c", PRINT_FOURCC(pixFmt));
|
||||
OSStatus status=VTCompressionSessionEncodeFrame(session, imgBuffer, CMSampleBufferGetPresentationTimeStamp(frame), CMSampleBufferGetDuration(frame), frameProps, NULL, NULL);
|
||||
CHECK_ERR(status, "VTCompressionSessionEncodeFrame");
|
||||
if(frameProps)
|
||||
CFRelease(frameProps);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetBitrate(uint32_t bitrate){
|
||||
bitrateChangeRequested=bitrate;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags){
|
||||
if(status!=noErr){
|
||||
LOGE("EncoderCallback error: %d", status);
|
||||
return;
|
||||
}
|
||||
if(flags & kVTEncodeInfo_FrameDropped){
|
||||
LOGW("VideoToolboxEncoder: Frame dropped");
|
||||
}
|
||||
if(!CMSampleBufferGetNumSamples(buffer)){
|
||||
LOGW("Empty CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t startCode[]={0, 0, 0, 1};
|
||||
if(needUpdateStreamParams){
|
||||
LOGI("VideoToolboxEncoder: Updating stream params");
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(buffer);
|
||||
CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
width=size.width;
|
||||
height=size.height;
|
||||
csd.clear();
|
||||
if(codec==CODEC_AVC){
|
||||
for(size_t i=0;i<2;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetH264ParameterSetAtIndex");
|
||||
Buffer b(pl+4);
|
||||
b.CopyFrom(ps, 4, pl);
|
||||
b.CopyFrom(startCode, 0, 4);
|
||||
csd.push_back(std::move(b));
|
||||
}
|
||||
}else if(codec==CODEC_HEVC){
|
||||
LOGD("here1");
|
||||
BufferOutputStream csdBuf(1024);
|
||||
for(size_t i=0;i<3;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetHEVCParameterSetAtIndex");
|
||||
csdBuf.WriteBytes(startCode, 4);
|
||||
csdBuf.WriteBytes(ps, pl);
|
||||
}
|
||||
csd.push_back(std::move(csdBuf));
|
||||
}
|
||||
needUpdateStreamParams=false;
|
||||
}
|
||||
CMBlockBufferRef blockBuffer=CMSampleBufferGetDataBuffer(buffer);
|
||||
size_t len=CMBlockBufferGetDataLength(blockBuffer);
|
||||
|
||||
int frameFlags=0;
|
||||
CFArrayRef attachmentsArray=CMSampleBufferGetSampleAttachmentsArray(buffer, 0);
|
||||
if(attachmentsArray && CFArrayGetCount(attachmentsArray)){
|
||||
CFBooleanRef notSync;
|
||||
CFDictionaryRef dict=(CFDictionaryRef)CFArrayGetValueAtIndex(attachmentsArray, 0);
|
||||
BOOL keyExists=CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, (const void **)¬Sync);
|
||||
if(!keyExists || !CFBooleanGetValue(notSync)){
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
}else{
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
|
||||
Buffer frame(len);
|
||||
CMBlockBufferCopyDataBytes(blockBuffer, 0, len, *frame);
|
||||
uint32_t offset=0;
|
||||
while(offset<len){
|
||||
uint32_t nalLen=CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(*frame+offset));
|
||||
//LOGV("NAL length %u", nalLen);
|
||||
frame.CopyFrom(startCode, offset, 4);
|
||||
offset+=nalLen+4;
|
||||
}
|
||||
callback(std::move(frame), frameFlags);
|
||||
|
||||
//LOGV("EncoderCallback: %u bytes total", (unsigned int)len);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetEncoderBitrateAndLimit(uint32_t bitrate){
|
||||
OSStatus status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bitrate));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AverageBitRate)");
|
||||
|
||||
int64_t dataLimitValue=(int64_t)(bitrate/8);
|
||||
CFNumberRef bytesPerSecond=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitValue);
|
||||
int64_t oneValue=1;
|
||||
CFNumberRef one=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneValue);
|
||||
const void* numbers[]={bytesPerSecond, one};
|
||||
CFArrayRef limits=CFArrayCreate(NULL, numbers, 2, &kCFTypeArrayCallBacks);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, limits);
|
||||
CFRelease(bytesPerSecond);
|
||||
CFRelease(one);
|
||||
CFRelease(limits);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(DataRateLimits");
|
||||
}
|
||||
|
||||
std::vector<uint32_t> VideoToolboxEncoderSource::GetAvailableEncoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
CFArrayRef encoders;
|
||||
OSStatus status=VTCopyVideoEncoderList(NULL, &encoders);
|
||||
for(CFIndex i=0;i<CFArrayGetCount(encoders);i++){
|
||||
CFDictionaryRef v=(CFDictionaryRef)CFArrayGetValueAtIndex(encoders, i);
|
||||
NSDictionary* encoder=(__bridge NSDictionary*)v;
|
||||
//NSString* name=(NSString*)CFDictionaryGetValue(v, kVTVideoEncoderList_EncoderName);
|
||||
uint32_t codecType=[(NSNumber*)encoder[(NSString*)kVTVideoEncoderList_CodecType] intValue];
|
||||
//LOGV("Encoders[%u]: %s, %c%c%c%c", i, [(NSString*)encoder[(NSString*)kVTVideoEncoderList_EncoderName] cStringUsingEncoding:NSUTF8StringEncoding], PRINT_FOURCC(codecType));
|
||||
if(codecType==kCMVideoCodecType_HEVC){
|
||||
res.push_back(CODEC_HEVC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(encoders);
|
||||
return res;
|
||||
}
|
||||
175
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.cpp
Normal file
175
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include "AudioInputALSA.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
|
||||
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
|
||||
|
||||
AudioInputALSA::AudioInputALSA(std::string devID){
|
||||
isRecording=false;
|
||||
handle=NULL;
|
||||
|
||||
lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libasound: %s", dlerror());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_readi", _snd_pcm_readi);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
|
||||
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioInputALSA::~AudioInputALSA(){
|
||||
if(handle)
|
||||
_snd_pcm_close(handle);
|
||||
if(lib)
|
||||
dlclose(lib);
|
||||
}
|
||||
|
||||
void AudioInputALSA::Start(){
|
||||
if(failed || isRecording)
|
||||
return;
|
||||
|
||||
isRecording=true;
|
||||
thread=new Thread(std::bind(&AudioInputALSA::RunThread, this));
|
||||
thread->SetName("AudioInputALSA");
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void AudioInputALSA::Stop(){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
isRecording=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
thread=NULL;
|
||||
}
|
||||
|
||||
void AudioInputALSA::RunThread(){
|
||||
unsigned char buffer[BUFFER_SIZE*2];
|
||||
snd_pcm_sframes_t frames;
|
||||
while(isRecording){
|
||||
frames=_snd_pcm_readi(handle, buffer, BUFFER_SIZE);
|
||||
if (frames < 0){
|
||||
frames = _snd_pcm_recover(handle, frames, 0);
|
||||
}
|
||||
if (frames < 0) {
|
||||
LOGE("snd_pcm_readi failed: %s\n", _snd_strerror(frames));
|
||||
break;
|
||||
}
|
||||
InvokeCallback(buffer, sizeof(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputALSA::SetCurrentDevice(std::string devID){
|
||||
bool wasRecording=isRecording;
|
||||
isRecording=false;
|
||||
if(handle){
|
||||
thread->Join();
|
||||
_snd_pcm_close(handle);
|
||||
}
|
||||
currentDevice=devID;
|
||||
|
||||
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_CAPTURE, 0);
|
||||
if(res<0)
|
||||
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
|
||||
CHECK_ERROR(res, "snd_pcm_open failed");
|
||||
|
||||
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
|
||||
CHECK_ERROR(res, "snd_pcm_set_params failed");
|
||||
|
||||
if(wasRecording){
|
||||
isRecording=true;
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputALSA::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
|
||||
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
|
||||
int (*_snd_device_name_free_hint)(void** hinst);
|
||||
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib)
|
||||
return;
|
||||
|
||||
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
|
||||
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
|
||||
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
|
||||
|
||||
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** hints;
|
||||
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
if(err!=0){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** n=hints;
|
||||
while(*n){
|
||||
char* name=_snd_device_name_get_hint(*n, "NAME");
|
||||
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
|
||||
free(name);
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
char* desc=_snd_device_name_get_hint(*n, "DESC");
|
||||
char* ioid=_snd_device_name_get_hint(*n, "IOID");
|
||||
if(!ioid || strcmp(ioid, "Input")==0){
|
||||
char* l1=strtok(desc, "\n");
|
||||
char* l2=strtok(NULL, "\n");
|
||||
char* tmp=strtok(l1, ",");
|
||||
char* actualName=tmp;
|
||||
while((tmp=strtok(NULL, ","))){
|
||||
actualName=tmp;
|
||||
}
|
||||
if(actualName[0]==' ')
|
||||
actualName++;
|
||||
AudioInputDevice dev;
|
||||
dev.id=std::string(name);
|
||||
if(l2){
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
|
||||
dev.displayName=std::string(buf);
|
||||
}else{
|
||||
dev.displayName=std::string(actualName);
|
||||
}
|
||||
devs.push_back(dev);
|
||||
}
|
||||
free(name);
|
||||
free(desc);
|
||||
free(ioid);
|
||||
n++;
|
||||
}
|
||||
|
||||
dlclose(lib);
|
||||
}
|
||||
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.h
Normal file
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.h
Normal file
|
|
@ -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_AUDIOINPUTALSA_H
|
||||
#define LIBTGVOIP_AUDIOINPUTALSA_H
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputALSA : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputALSA(std::string devID);
|
||||
virtual ~AudioInputALSA();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
|
||||
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
|
||||
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
|
||||
int (*_snd_pcm_close)(snd_pcm_t* pcm);
|
||||
snd_pcm_sframes_t (*_snd_pcm_readi)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
|
||||
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
|
||||
const char* (*_snd_strerror)(int errnum);
|
||||
void* lib;
|
||||
|
||||
snd_pcm_t* handle;
|
||||
Thread* thread;
|
||||
bool isRecording;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTALSA_H
|
||||
204
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.cpp
Normal file
204
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include "AudioInputPulse.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "AudioPulse.h"
|
||||
#include "PulseFunctions.h"
|
||||
#if !defined(__GLIBC__)
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputPulse::AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
|
||||
isRecording=false;
|
||||
isConnected=false;
|
||||
didStart=false;
|
||||
|
||||
this->mainloop=mainloop;
|
||||
this->context=context;
|
||||
stream=NULL;
|
||||
remainingDataSize=0;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
stream=CreateAndInitStream();
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
isLocked=false;
|
||||
if(!stream){
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioInputPulse::~AudioInputPulse(){
|
||||
if(stream){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream* AudioInputPulse::CreateAndInitStream(){
|
||||
pa_sample_spec sampleSpec{
|
||||
.format=PA_SAMPLE_S16LE,
|
||||
.rate=48000,
|
||||
.channels=1
|
||||
};
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
|
||||
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip capture", &sampleSpec, NULL, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!stream){
|
||||
LOGE("Error initializing PulseAudio (pa_stream_new)");
|
||||
failed=true;
|
||||
return NULL;
|
||||
}
|
||||
pa_stream_set_state_callback(stream, AudioInputPulse::StreamStateCallback, this);
|
||||
pa_stream_set_read_callback(stream, AudioInputPulse::StreamReadCallback, this);
|
||||
return stream;
|
||||
}
|
||||
|
||||
void AudioInputPulse::Start(){
|
||||
if(failed || isRecording)
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
isRecording=true;
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
void AudioInputPulse::Stop(){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
isRecording=false;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioInputPulse::IsRecording(){
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
void AudioInputPulse::SetCurrentDevice(std::string devID){
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
currentDevice=devID;
|
||||
if(isRecording && isConnected){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
isConnected=false;
|
||||
stream=CreateAndInitStream();
|
||||
}
|
||||
|
||||
pa_buffer_attr bufferAttr={
|
||||
.maxlength=(uint32_t)-1,
|
||||
.tlength=(uint32_t)-1,
|
||||
.prebuf=(uint32_t)-1,
|
||||
.minreq=(uint32_t)-1,
|
||||
.fragsize=960*2
|
||||
};
|
||||
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
int err=pa_stream_connect_record(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags);
|
||||
if(err!=0){
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
/*if(devID!="default"){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
CHECK_ERROR(err, "pa_stream_connect_record");
|
||||
|
||||
while(true){
|
||||
pa_stream_state_t streamState=pa_stream_get_state(stream);
|
||||
if(!PA_STREAM_IS_GOOD(streamState)){
|
||||
LOGE("Error connecting to audio device '%s'", devID.c_str());
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(streamState==PA_STREAM_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
isConnected=true;
|
||||
|
||||
if(isRecording){
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioInputPulse::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
return AudioPulse::DoOneOperation([&](pa_context* ctx){
|
||||
return pa_context_get_source_info_list(ctx, [](pa_context* ctx, const pa_source_info* info, int eol, void* userdata){
|
||||
if(eol>0)
|
||||
return;
|
||||
std::vector<AudioInputDevice>* devs=(std::vector<AudioInputDevice>*)userdata;
|
||||
AudioInputDevice dev;
|
||||
dev.id=std::string(info->name);
|
||||
dev.displayName=std::string(info->description);
|
||||
devs->push_back(dev);
|
||||
}, &devs);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamStateCallback(pa_stream *s, void* arg) {
|
||||
AudioInputPulse* self=(AudioInputPulse*) arg;
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
|
||||
((AudioInputPulse*)userdata)->StreamReadCallback(stream, requestedBytes);
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes) {
|
||||
size_t bytesRemaining = requestedBytes;
|
||||
uint8_t *buffer = NULL;
|
||||
pa_usec_t latency;
|
||||
if(pa_stream_get_latency(stream, &latency, NULL)==0){
|
||||
estimatedDelay=(int32_t)(latency/100);
|
||||
}
|
||||
while (bytesRemaining > 0) {
|
||||
size_t bytesToFill = 102400;
|
||||
|
||||
if (bytesToFill > bytesRemaining) bytesToFill = bytesRemaining;
|
||||
|
||||
int err=pa_stream_peek(stream, (const void**) &buffer, &bytesToFill);
|
||||
CHECK_ERROR(err, "pa_stream_peek");
|
||||
|
||||
if(isRecording){
|
||||
if(remainingDataSize+bytesToFill>sizeof(remainingData)){
|
||||
LOGE("Capture buffer is too big (%d)", (int)bytesToFill);
|
||||
}
|
||||
memcpy(remainingData+remainingDataSize, buffer, bytesToFill);
|
||||
remainingDataSize+=bytesToFill;
|
||||
while(remainingDataSize>=960*2){
|
||||
InvokeCallback(remainingData, 960*2);
|
||||
memmove(remainingData, remainingData+960*2, remainingDataSize-960*2);
|
||||
remainingDataSize-=960*2;
|
||||
}
|
||||
}
|
||||
|
||||
err=pa_stream_drop(stream);
|
||||
CHECK_ERROR(err, "pa_stream_drop");
|
||||
|
||||
bytesRemaining -= bytesToFill;
|
||||
}
|
||||
}
|
||||
52
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.h
Normal file
52
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.h
Normal file
|
|
@ -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_AUDIOINPUTPULSE_H
|
||||
#define LIBTGVOIP_AUDIOINPUTPULSE_H
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) typeof(name)* _import_##name
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputPulse : public AudioInput{
|
||||
public:
|
||||
AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
|
||||
virtual ~AudioInputPulse();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsRecording();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static bool EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void StreamStateCallback(pa_stream* s, void* arg);
|
||||
static void StreamReadCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
|
||||
void StreamReadCallback(pa_stream* stream, size_t requestedBytes);
|
||||
pa_stream* CreateAndInitStream();
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_context* context;
|
||||
pa_stream* stream;
|
||||
|
||||
bool isRecording;
|
||||
bool isConnected;
|
||||
bool didStart;
|
||||
bool isLocked;
|
||||
unsigned char remainingData[960*8*2];
|
||||
size_t remainingDataSize;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECLARE_DL_FUNCTION
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTPULSE_H
|
||||
177
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.cpp
Normal file
177
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include "AudioOutputALSA.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
|
||||
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputALSA::AudioOutputALSA(std::string devID){
|
||||
isPlaying=false;
|
||||
handle=NULL;
|
||||
|
||||
lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libasound: %s", dlerror());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_writei", _snd_pcm_writei);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
|
||||
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioOutputALSA::~AudioOutputALSA(){
|
||||
if(handle)
|
||||
_snd_pcm_close(handle);
|
||||
if(lib)
|
||||
dlclose(lib);
|
||||
}
|
||||
|
||||
void AudioOutputALSA::Start(){
|
||||
if(failed || isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=true;
|
||||
thread=new Thread(std::bind(&AudioOutputALSA::RunThread, this));
|
||||
thread->SetName("AudioOutputALSA");
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void AudioOutputALSA::Stop(){
|
||||
if(!isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
thread=NULL;
|
||||
}
|
||||
|
||||
bool AudioOutputALSA::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
void AudioOutputALSA::RunThread(){
|
||||
unsigned char buffer[BUFFER_SIZE*2];
|
||||
snd_pcm_sframes_t frames;
|
||||
while(isPlaying){
|
||||
InvokeCallback(buffer, sizeof(buffer));
|
||||
frames=_snd_pcm_writei(handle, buffer, BUFFER_SIZE);
|
||||
if (frames < 0){
|
||||
frames = _snd_pcm_recover(handle, frames, 0);
|
||||
}
|
||||
if (frames < 0) {
|
||||
LOGE("snd_pcm_writei failed: %s\n", _snd_strerror(frames));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputALSA::SetCurrentDevice(std::string devID){
|
||||
bool wasPlaying=isPlaying;
|
||||
isPlaying=false;
|
||||
if(handle){
|
||||
thread->Join();
|
||||
_snd_pcm_close(handle);
|
||||
}
|
||||
currentDevice=devID;
|
||||
|
||||
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if(res<0)
|
||||
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
|
||||
CHECK_ERROR(res, "snd_pcm_open failed");
|
||||
|
||||
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
|
||||
CHECK_ERROR(res, "snd_pcm_set_params failed");
|
||||
|
||||
if(wasPlaying){
|
||||
isPlaying=true;
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputALSA::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
|
||||
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
|
||||
int (*_snd_device_name_free_hint)(void** hinst);
|
||||
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib)
|
||||
return;
|
||||
|
||||
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
|
||||
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
|
||||
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
|
||||
|
||||
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** hints;
|
||||
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
if(err!=0){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** n=hints;
|
||||
while(*n){
|
||||
char* name=_snd_device_name_get_hint(*n, "NAME");
|
||||
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
|
||||
free(name);
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
char* desc=_snd_device_name_get_hint(*n, "DESC");
|
||||
char* ioid=_snd_device_name_get_hint(*n, "IOID");
|
||||
if(!ioid || strcmp(ioid, "Output")==0){
|
||||
char* l1=strtok(desc, "\n");
|
||||
char* l2=strtok(NULL, "\n");
|
||||
char* tmp=strtok(l1, ",");
|
||||
char* actualName=tmp;
|
||||
while((tmp=strtok(NULL, ","))){
|
||||
actualName=tmp;
|
||||
}
|
||||
if(actualName[0]==' ')
|
||||
actualName++;
|
||||
AudioOutputDevice dev;
|
||||
dev.id=std::string(name);
|
||||
if(l2){
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
|
||||
dev.displayName=std::string(buf);
|
||||
}else{
|
||||
dev.displayName=std::string(actualName);
|
||||
}
|
||||
devs.push_back(dev);
|
||||
}
|
||||
free(name);
|
||||
free(desc);
|
||||
free(ioid);
|
||||
n++;
|
||||
}
|
||||
|
||||
dlclose(lib);
|
||||
}
|
||||
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.h
Normal file
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.h
Normal file
|
|
@ -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_AUDIOOUTPUTALSA_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTALSA_H
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../threading.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioOutputALSA : public AudioOutput{
|
||||
public:
|
||||
AudioOutputALSA(std::string devID);
|
||||
virtual ~AudioOutputALSA();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
|
||||
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
|
||||
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
|
||||
int (*_snd_pcm_close)(snd_pcm_t* pcm);
|
||||
snd_pcm_sframes_t (*_snd_pcm_writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
|
||||
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
|
||||
const char* (*_snd_strerror)(int errnum);
|
||||
void* lib;
|
||||
|
||||
snd_pcm_t* handle;
|
||||
Thread* thread;
|
||||
bool isPlaying;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTALSA_H
|
||||
187
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.cpp
Normal file
187
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include "AudioOutputPulse.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "AudioPulse.h"
|
||||
#include "PulseFunctions.h"
|
||||
#if !defined(__GLIBC__)
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputPulse::AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
|
||||
isPlaying=false;
|
||||
isConnected=false;
|
||||
didStart=false;
|
||||
isLocked=false;
|
||||
|
||||
this->mainloop=mainloop;
|
||||
this->context=context;
|
||||
stream=NULL;
|
||||
remainingDataSize=0;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
stream=CreateAndInitStream();
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioOutputPulse::~AudioOutputPulse(){
|
||||
if(stream){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream* AudioOutputPulse::CreateAndInitStream(){
|
||||
pa_sample_spec sampleSpec{
|
||||
.format=PA_SAMPLE_S16LE,
|
||||
.rate=48000,
|
||||
.channels=1
|
||||
};
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
|
||||
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip playback", &sampleSpec, NULL, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!stream){
|
||||
LOGE("Error initializing PulseAudio (pa_stream_new)");
|
||||
failed=true;
|
||||
return NULL;
|
||||
}
|
||||
pa_stream_set_state_callback(stream, AudioOutputPulse::StreamStateCallback, this);
|
||||
pa_stream_set_write_callback(stream, AudioOutputPulse::StreamWriteCallback, this);
|
||||
return stream;
|
||||
}
|
||||
|
||||
void AudioOutputPulse::Start(){
|
||||
if(failed || isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=true;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::Stop(){
|
||||
if(!isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=false;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputPulse::SetCurrentDevice(std::string devID){
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
currentDevice=devID;
|
||||
if(isPlaying && isConnected){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
isConnected=false;
|
||||
stream=CreateAndInitStream();
|
||||
}
|
||||
|
||||
pa_buffer_attr bufferAttr={
|
||||
.maxlength=(uint32_t)-1,
|
||||
.tlength=960*2,
|
||||
.prebuf=(uint32_t)-1,
|
||||
.minreq=(uint32_t)-1,
|
||||
.fragsize=(uint32_t)-1
|
||||
};
|
||||
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
int err=pa_stream_connect_playback(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags, NULL, NULL);
|
||||
if(err!=0 && devID!="default"){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
CHECK_ERROR(err, "pa_stream_connect_playback");
|
||||
|
||||
while(true){
|
||||
pa_stream_state_t streamState=pa_stream_get_state(stream);
|
||||
if(!PA_STREAM_IS_GOOD(streamState)){
|
||||
LOGE("Error connecting to audio device '%s'", devID.c_str());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(streamState==PA_STREAM_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
isConnected=true;
|
||||
|
||||
if(isPlaying){
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
return AudioPulse::DoOneOperation([&](pa_context* ctx){
|
||||
return pa_context_get_sink_info_list(ctx, [](pa_context* ctx, const pa_sink_info* info, int eol, void* userdata){
|
||||
if(eol>0)
|
||||
return;
|
||||
std::vector<AudioOutputDevice>* devs=(std::vector<AudioOutputDevice>*)userdata;
|
||||
AudioOutputDevice dev;
|
||||
dev.id=std::string(info->name);
|
||||
dev.displayName=std::string(info->description);
|
||||
devs->push_back(dev);
|
||||
}, &devs);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamStateCallback(pa_stream *s, void* arg) {
|
||||
AudioOutputPulse* self=(AudioOutputPulse*) arg;
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
|
||||
((AudioOutputPulse*)userdata)->StreamWriteCallback(stream, requestedBytes);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes) {
|
||||
//assert(requestedBytes<=sizeof(remainingData));
|
||||
if(requestedBytes>sizeof(remainingData)){
|
||||
requestedBytes=960*2; // force buffer size to 20ms. This probably wrecks the jitter buffer, but still better than crashing
|
||||
}
|
||||
pa_usec_t latency;
|
||||
if(pa_stream_get_latency(stream, &latency, NULL)==0){
|
||||
estimatedDelay=(int32_t)(latency/100);
|
||||
}
|
||||
while(requestedBytes>remainingDataSize){
|
||||
if(isPlaying){
|
||||
InvokeCallback(remainingData+remainingDataSize, 960*2);
|
||||
remainingDataSize+=960*2;
|
||||
}else{
|
||||
memset(remainingData+remainingDataSize, 0, requestedBytes-remainingDataSize);
|
||||
remainingDataSize=requestedBytes;
|
||||
}
|
||||
}
|
||||
int err=pa_stream_write(stream, remainingData, requestedBytes, NULL, 0, PA_SEEK_RELATIVE);
|
||||
CHECK_ERROR(err, "pa_stream_write");
|
||||
remainingDataSize-=requestedBytes;
|
||||
if(remainingDataSize>0)
|
||||
memmove(remainingData, remainingData+requestedBytes, remainingDataSize);
|
||||
}
|
||||
48
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.h
Normal file
48
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.h
Normal file
|
|
@ -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 LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../threading.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioOutputPulse : public AudioOutput{
|
||||
public:
|
||||
AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
|
||||
virtual ~AudioOutputPulse();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static bool EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void StreamStateCallback(pa_stream* s, void* arg);
|
||||
static void StreamWriteCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
|
||||
void StreamWriteCallback(pa_stream* stream, size_t requestedBytes);
|
||||
pa_stream* CreateAndInitStream();
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_context* context;
|
||||
pa_stream* stream;
|
||||
|
||||
bool isPlaying;
|
||||
bool isConnected;
|
||||
bool didStart;
|
||||
bool isLocked;
|
||||
unsigned char remainingData[960*8*2];
|
||||
size_t remainingDataSize;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
288
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.cpp
Normal file
288
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
//
|
||||
// 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 "AudioPulse.h"
|
||||
#include <dlfcn.h>
|
||||
#include "../../logging.h"
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) typeof(name)* AudioPulse::_import_##name=NULL
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); return false;}
|
||||
#define LOAD_DL_FUNCTION(name) {_import_##name=(typeof(_import_##name))dlsym(lib, #name); CHECK_DL_ERROR(_import_##name, "Error getting entry point for " #name);}
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
bool AudioPulse::loaded=false;
|
||||
void* AudioPulse::lib=NULL;
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_context_new);
|
||||
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
DECLARE_DL_FUNCTION(pa_context_connect);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
|
||||
DECLARE_DL_FUNCTION(pa_operation_unref);
|
||||
DECLARE_DL_FUNCTION(pa_stream_cork);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
DECLARE_DL_FUNCTION(pa_stream_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_stream_unref);
|
||||
DECLARE_DL_FUNCTION(pa_context_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_context_unref);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
DECLARE_DL_FUNCTION(pa_stream_begin_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_strerror);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_record);
|
||||
DECLARE_DL_FUNCTION(pa_stream_peek);
|
||||
DECLARE_DL_FUNCTION(pa_stream_drop);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_operation_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_new);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_sets);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_free);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
#include "PulseFunctions.h"
|
||||
|
||||
bool AudioPulse::Load(){
|
||||
if(loaded)
|
||||
return true;
|
||||
|
||||
lib=dlopen("libpulse.so.0", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libpulse.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libpulse: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
LOAD_DL_FUNCTION(pa_context_new);
|
||||
LOAD_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
LOAD_DL_FUNCTION(pa_context_set_state_callback);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
LOAD_DL_FUNCTION(pa_context_connect);
|
||||
LOAD_DL_FUNCTION(pa_context_get_state);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
LOAD_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_connect_playback);
|
||||
LOAD_DL_FUNCTION(pa_operation_unref);
|
||||
LOAD_DL_FUNCTION(pa_stream_cork);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
LOAD_DL_FUNCTION(pa_stream_disconnect);
|
||||
LOAD_DL_FUNCTION(pa_stream_unref);
|
||||
LOAD_DL_FUNCTION(pa_context_disconnect);
|
||||
LOAD_DL_FUNCTION(pa_context_unref);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
LOAD_DL_FUNCTION(pa_stream_begin_write);
|
||||
LOAD_DL_FUNCTION(pa_stream_write);
|
||||
LOAD_DL_FUNCTION(pa_stream_get_state);
|
||||
LOAD_DL_FUNCTION(pa_strerror);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_connect_record);
|
||||
LOAD_DL_FUNCTION(pa_stream_peek);
|
||||
LOAD_DL_FUNCTION(pa_stream_drop);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_new);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_get_api);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_iterate);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_free);
|
||||
LOAD_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
LOAD_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
LOAD_DL_FUNCTION(pa_operation_get_state);
|
||||
LOAD_DL_FUNCTION(pa_proplist_new);
|
||||
LOAD_DL_FUNCTION(pa_proplist_sets);
|
||||
LOAD_DL_FUNCTION(pa_proplist_free);
|
||||
LOAD_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
loaded=true;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioPulse::AudioPulse(std::string inputDevice, std::string outputDevice){
|
||||
if(!Load()){
|
||||
failed=true;
|
||||
LOGE("Failed to load libpulse");
|
||||
return;
|
||||
}
|
||||
|
||||
mainloop=pa_threaded_mainloop_new();
|
||||
if(!mainloop){
|
||||
LOGE("Error initializing PulseAudio (pa_threaded_mainloop_new)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
mainloopApi=pa_threaded_mainloop_get_api(mainloop);
|
||||
#ifndef MAXPATHLEN
|
||||
char exeName[20];
|
||||
#else
|
||||
char exePath[MAXPATHLEN];
|
||||
char exeName[MAXPATHLEN];
|
||||
ssize_t lres=readlink("/proc/self/exe", exePath, sizeof(exePath));
|
||||
if(lres==-1)
|
||||
lres=readlink("/proc/curproc/file", exePath, sizeof(exePath));
|
||||
if(lres==-1)
|
||||
lres=readlink("/proc/curproc/exe", exePath, sizeof(exePath));
|
||||
if(lres>0){
|
||||
strcpy(exeName, basename(exePath));
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
snprintf(exeName, sizeof(exeName), "Process %d", getpid());
|
||||
}
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "phone");
|
||||
context=pa_context_new_with_proplist(mainloopApi, exeName, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!context){
|
||||
LOGE("Error initializing PulseAudio (pa_context_new)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
pa_context_set_state_callback(context, [](pa_context* context, void* arg){
|
||||
AudioPulse* self=reinterpret_cast<AudioPulse*>(arg);
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}, this);
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
isLocked=true;
|
||||
int err=pa_threaded_mainloop_start(mainloop);
|
||||
CHECK_ERROR(err, "pa_threaded_mainloop_start");
|
||||
didStart=true;
|
||||
|
||||
err=pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
|
||||
CHECK_ERROR(err, "pa_context_connect");
|
||||
|
||||
while(true){
|
||||
pa_context_state_t contextState=pa_context_get_state(context);
|
||||
if(!PA_CONTEXT_IS_GOOD(contextState)){
|
||||
LOGE("Error initializing PulseAudio (PA_CONTEXT_IS_GOOD)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(contextState==PA_CONTEXT_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
isLocked=false;
|
||||
|
||||
output=new AudioOutputPulse(context, mainloop, outputDevice);
|
||||
input=new AudioInputPulse(context, mainloop, inputDevice);
|
||||
}
|
||||
|
||||
AudioPulse::~AudioPulse(){
|
||||
if(mainloop && didStart){
|
||||
if(isLocked)
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
}
|
||||
|
||||
if(input)
|
||||
delete input;
|
||||
if(output)
|
||||
delete output;
|
||||
|
||||
if(context){
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
}
|
||||
if(mainloop)
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
}
|
||||
|
||||
AudioOutput* AudioPulse::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
AudioInput* AudioPulse::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
bool AudioPulse::DoOneOperation(std::function<pa_operation*(pa_context*)> f){
|
||||
if(!Load())
|
||||
return false;
|
||||
|
||||
pa_mainloop* ml;
|
||||
pa_mainloop_api* mlAPI;
|
||||
pa_context* ctx;
|
||||
pa_operation* op=NULL;
|
||||
int paReady=0;
|
||||
|
||||
ml=pa_mainloop_new();
|
||||
mlAPI=pa_mainloop_get_api(ml);
|
||||
ctx=pa_context_new(mlAPI, "libtgvoip");
|
||||
|
||||
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
||||
pa_context_set_state_callback(ctx, [](pa_context* context, void* arg){
|
||||
pa_context_state_t state;
|
||||
int* pa_ready=(int*)arg;
|
||||
|
||||
state=pa_context_get_state(context);
|
||||
switch(state){
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
default:
|
||||
break;
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
*pa_ready=2;
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
*pa_ready=1;
|
||||
break;
|
||||
}
|
||||
}, &paReady);
|
||||
|
||||
while(true){
|
||||
if(paReady==0){
|
||||
pa_mainloop_iterate(ml, 1, NULL);
|
||||
continue;
|
||||
}
|
||||
if(paReady==2){
|
||||
pa_context_disconnect(ctx);
|
||||
pa_context_unref(ctx);
|
||||
pa_mainloop_free(ml);
|
||||
return false;
|
||||
}
|
||||
if(!op){
|
||||
op=f(ctx);
|
||||
continue;
|
||||
}
|
||||
if(pa_operation_get_state(op)==PA_OPERATION_DONE){
|
||||
pa_operation_unref(op);
|
||||
pa_context_disconnect(ctx);
|
||||
pa_context_unref(ctx);
|
||||
pa_mainloop_free(ml);
|
||||
return true;
|
||||
}
|
||||
pa_mainloop_iterate(ml, 1, NULL);
|
||||
}
|
||||
}
|
||||
95
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.h
Normal file
95
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// 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_PULSEAUDIOLOADER_H
|
||||
#define LIBTGVOIP_PULSEAUDIOLOADER_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include "../../audio/AudioIO.h"
|
||||
#include "AudioInputPulse.h"
|
||||
#include "AudioOutputPulse.h"
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) static typeof(name)* _import_##name
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
class AudioPulse : public AudioIO{
|
||||
public:
|
||||
AudioPulse(std::string inputDevice, std::string outputDevice);
|
||||
virtual ~AudioPulse();
|
||||
virtual AudioInput* GetInput();
|
||||
virtual AudioOutput* GetOutput();
|
||||
|
||||
static bool Load();
|
||||
static bool DoOneOperation(std::function<pa_operation*(pa_context*)> f);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_context_new);
|
||||
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
DECLARE_DL_FUNCTION(pa_context_connect);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
|
||||
DECLARE_DL_FUNCTION(pa_operation_unref);
|
||||
DECLARE_DL_FUNCTION(pa_stream_cork);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
DECLARE_DL_FUNCTION(pa_stream_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_stream_unref);
|
||||
DECLARE_DL_FUNCTION(pa_context_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_context_unref);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
DECLARE_DL_FUNCTION(pa_stream_begin_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_strerror);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_record);
|
||||
DECLARE_DL_FUNCTION(pa_stream_peek);
|
||||
DECLARE_DL_FUNCTION(pa_stream_drop);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_operation_get_state);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_proplist_new);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_sets);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_free);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
private:
|
||||
static void* lib;
|
||||
static bool loaded;
|
||||
AudioInputPulse* input=NULL;
|
||||
AudioOutputPulse* output=NULL;
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_mainloop_api* mainloopApi;
|
||||
pa_context* context;
|
||||
bool isLocked=false;
|
||||
bool didStart=false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECLARE_DL_FUNCTION
|
||||
|
||||
#endif // LIBTGVOIP_PULSEAUDIOLOADER_H
|
||||
48
TMessagesProj/jni/voip/libtgvoip/os/linux/PulseFunctions.h
Normal file
48
TMessagesProj/jni/voip/libtgvoip/os/linux/PulseFunctions.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
#define LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
|
||||
#define pa_threaded_mainloop_new AudioPulse::_import_pa_threaded_mainloop_new
|
||||
#define pa_threaded_mainloop_get_api AudioPulse::_import_pa_threaded_mainloop_get_api
|
||||
#define pa_context_new AudioPulse::_import_pa_context_new
|
||||
#define pa_context_new_with_proplist AudioPulse::_import_pa_context_new_with_proplist
|
||||
#define pa_context_set_state_callback AudioPulse::_import_pa_context_set_state_callback
|
||||
#define pa_threaded_mainloop_lock AudioPulse::_import_pa_threaded_mainloop_lock
|
||||
#define pa_threaded_mainloop_unlock AudioPulse::_import_pa_threaded_mainloop_unlock
|
||||
#define pa_threaded_mainloop_start AudioPulse::_import_pa_threaded_mainloop_start
|
||||
#define pa_context_connect AudioPulse::_import_pa_context_connect
|
||||
#define pa_context_get_state AudioPulse::_import_pa_context_get_state
|
||||
#define pa_threaded_mainloop_wait AudioPulse::_import_pa_threaded_mainloop_wait
|
||||
#define pa_stream_new_with_proplist AudioPulse::_import_pa_stream_new_with_proplist
|
||||
#define pa_stream_set_state_callback AudioPulse::_import_pa_stream_set_state_callback
|
||||
#define pa_stream_set_write_callback AudioPulse::_import_pa_stream_set_write_callback
|
||||
#define pa_stream_connect_playback AudioPulse::_import_pa_stream_connect_playback
|
||||
#define pa_operation_unref AudioPulse::_import_pa_operation_unref
|
||||
#define pa_stream_cork AudioPulse::_import_pa_stream_cork
|
||||
#define pa_threaded_mainloop_stop AudioPulse::_import_pa_threaded_mainloop_stop
|
||||
#define pa_stream_disconnect AudioPulse::_import_pa_stream_disconnect
|
||||
#define pa_stream_unref AudioPulse::_import_pa_stream_unref
|
||||
#define pa_context_disconnect AudioPulse::_import_pa_context_disconnect
|
||||
#define pa_context_unref AudioPulse::_import_pa_context_unref
|
||||
#define pa_threaded_mainloop_free AudioPulse::_import_pa_threaded_mainloop_free
|
||||
#define pa_threaded_mainloop_signal AudioPulse::_import_pa_threaded_mainloop_signal
|
||||
#define pa_stream_begin_write AudioPulse::_import_pa_stream_begin_write
|
||||
#define pa_stream_write AudioPulse::_import_pa_stream_write
|
||||
#define pa_strerror AudioPulse::_import_pa_strerror
|
||||
#define pa_stream_get_state AudioPulse::_import_pa_stream_get_state
|
||||
#define pa_stream_set_read_callback AudioPulse::_import_pa_stream_set_read_callback
|
||||
#define pa_stream_connect_record AudioPulse::_import_pa_stream_connect_record
|
||||
#define pa_stream_peek AudioPulse::_import_pa_stream_peek
|
||||
#define pa_stream_drop AudioPulse::_import_pa_stream_drop
|
||||
#define pa_mainloop_new AudioPulse::_import_pa_mainloop_new
|
||||
#define pa_mainloop_get_api AudioPulse::_import_pa_mainloop_get_api
|
||||
#define pa_mainloop_iterate AudioPulse::_import_pa_mainloop_iterate
|
||||
#define pa_mainloop_free AudioPulse::_import_pa_mainloop_free
|
||||
#define pa_context_get_sink_info_list AudioPulse::_import_pa_context_get_sink_info_list
|
||||
#define pa_context_get_source_info_list AudioPulse::_import_pa_context_get_source_info_list
|
||||
#define pa_operation_get_state AudioPulse::_import_pa_operation_get_state
|
||||
#define pa_proplist_new AudioPulse::_import_pa_proplist_new
|
||||
#define pa_proplist_sets AudioPulse::_import_pa_proplist_sets
|
||||
#define pa_proplist_free AudioPulse::_import_pa_proplist_free
|
||||
#define pa_stream_get_latency AudioPulse::_import_pa_stream_get_latency
|
||||
|
||||
#endif //LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
644
TMessagesProj/jni/voip/libtgvoip/os/posix/NetworkSocketPosix.cpp
Normal file
644
TMessagesProj/jni/voip/libtgvoip/os/posix/NetworkSocketPosix.cpp
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
//
|
||||
// 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 "NetworkSocketPosix.h"
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <netdb.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../Buffers.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#include <sys/system_properties.h>
|
||||
extern JavaVM* sharedJVM;
|
||||
extern jclass jniUtilitiesClass;
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
|
||||
NetworkSocketPosix::NetworkSocketPosix(NetworkProtocol protocol) : NetworkSocket(protocol), lastRecvdV4(0), lastRecvdV6("::0"){
|
||||
needUpdateNat64Prefix=true;
|
||||
nat64Present=false;
|
||||
switchToV6at=0;
|
||||
isV4Available=false;
|
||||
fd=-1;
|
||||
useTCP=false;
|
||||
closing=false;
|
||||
|
||||
tcpConnectedAddress=NULL;
|
||||
tcpConnectedPort=0;
|
||||
|
||||
if(protocol==PROTO_TCP)
|
||||
timeout=10.0;
|
||||
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
|
||||
}
|
||||
|
||||
NetworkSocketPosix::~NetworkSocketPosix(){
|
||||
if(fd>=0){
|
||||
Close();
|
||||
}
|
||||
if(tcpConnectedAddress)
|
||||
delete tcpConnectedAddress;
|
||||
if(pendingOutgoingPacket)
|
||||
delete pendingOutgoingPacket;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::SetMaxPriority(){
|
||||
#ifdef __APPLE__
|
||||
int prio=NET_SERVICE_TYPE_VO;
|
||||
int res=setsockopt(fd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting darwin-specific net priority: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
int prio=6;
|
||||
int res=setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting priority: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
prio=46 << 2;
|
||||
res=setsockopt(fd, SOL_IP, IP_TOS, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting ip tos: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
#else
|
||||
LOGI("cannot set socket priority");
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Send(NetworkPacket *packet){
|
||||
if(!packet || (protocol==PROTO_UDP && !packet->address)){
|
||||
LOGW("tried to send null packet");
|
||||
return;
|
||||
}
|
||||
int res;
|
||||
if(protocol==PROTO_UDP){
|
||||
sockaddr_in6 addr;
|
||||
IPv4Address *v4addr=dynamic_cast<IPv4Address *>(packet->address);
|
||||
if(v4addr){
|
||||
if(needUpdateNat64Prefix && !isV4Available && VoIPController::GetCurrentTime()>switchToV6at && switchToV6at!=0){
|
||||
LOGV("Updating NAT64 prefix");
|
||||
nat64Present=false;
|
||||
addrinfo *addr0;
|
||||
int res=getaddrinfo("ipv4only.arpa", NULL, NULL, &addr0);
|
||||
if(res!=0){
|
||||
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
|
||||
}else{
|
||||
addrinfo *addrPtr;
|
||||
unsigned char *addr170=NULL;
|
||||
unsigned char *addr171=NULL;
|
||||
for(addrPtr=addr0; addrPtr; addrPtr=addrPtr->ai_next){
|
||||
if(addrPtr->ai_family==AF_INET6){
|
||||
sockaddr_in6 *translatedAddr=(sockaddr_in6 *) addrPtr->ai_addr;
|
||||
uint32_t v4part=*((uint32_t *) &translatedAddr->sin6_addr.s6_addr[12]);
|
||||
if(v4part==0xAA0000C0 && !addr170){
|
||||
addr170=translatedAddr->sin6_addr.s6_addr;
|
||||
}
|
||||
if(v4part==0xAB0000C0 && !addr171){
|
||||
addr171=translatedAddr->sin6_addr.s6_addr;
|
||||
}
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
LOGV("Got translated address: %s", inet_ntop(AF_INET6, &translatedAddr->sin6_addr, buf, sizeof(buf)));
|
||||
}
|
||||
}
|
||||
if(addr170 && addr171 && memcmp(addr170, addr171, 12)==0){
|
||||
nat64Present=true;
|
||||
memcpy(nat64Prefix, addr170, 12);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
LOGV("Found nat64 prefix from %s", inet_ntop(AF_INET6, addr170, buf, sizeof(buf)));
|
||||
}else{
|
||||
LOGV("Didn't find nat64");
|
||||
}
|
||||
freeaddrinfo(addr0);
|
||||
}
|
||||
needUpdateNat64Prefix=false;
|
||||
}
|
||||
memset(&addr, 0, sizeof(sockaddr_in6));
|
||||
addr.sin6_family=AF_INET6;
|
||||
*((uint32_t *) &addr.sin6_addr.s6_addr[12])=v4addr->GetAddress();
|
||||
if(nat64Present)
|
||||
memcpy(addr.sin6_addr.s6_addr, nat64Prefix, 12);
|
||||
else
|
||||
addr.sin6_addr.s6_addr[11]=addr.sin6_addr.s6_addr[10]=0xFF;
|
||||
|
||||
}else{
|
||||
IPv6Address *v6addr=dynamic_cast<IPv6Address *>(packet->address);
|
||||
assert(v6addr!=NULL);
|
||||
memcpy(addr.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
|
||||
addr.sin6_family=AF_INET6;
|
||||
}
|
||||
addr.sin6_port=htons(packet->port);
|
||||
res=(int)sendto(fd, packet->data, packet->length, 0, (const sockaddr *) &addr, sizeof(addr));
|
||||
}else{
|
||||
res=(int)send(fd, packet->data, packet->length, 0);
|
||||
}
|
||||
if(res<=0){
|
||||
if(errno==EAGAIN || errno==EWOULDBLOCK){
|
||||
if(pendingOutgoingPacket){
|
||||
LOGE("Got EAGAIN but there's already a pending packet");
|
||||
failed=true;
|
||||
}else{
|
||||
LOGV("Socket %d not ready to send", fd);
|
||||
pendingOutgoingPacket=new Buffer(packet->length);
|
||||
pendingOutgoingPacket->CopyFrom(packet->data, 0, packet->length);
|
||||
readyToSend=false;
|
||||
}
|
||||
}else{
|
||||
LOGE("error sending: %d / %s", errno, strerror(errno));
|
||||
if(errno==ENETUNREACH && !isV4Available && VoIPController::GetCurrentTime()<switchToV6at){
|
||||
switchToV6at=VoIPController::GetCurrentTime();
|
||||
LOGI("Network unreachable, trying NAT64");
|
||||
}
|
||||
}
|
||||
}else if((size_t)res!=packet->length && packet->protocol==PROTO_TCP){
|
||||
if(pendingOutgoingPacket){
|
||||
LOGE("send returned less than packet length but there's already a pending packet");
|
||||
failed=true;
|
||||
}else{
|
||||
LOGV("Socket %d not ready to send", fd);
|
||||
pendingOutgoingPacket=new Buffer(packet->length-res);
|
||||
pendingOutgoingPacket->CopyFrom(packet->data+res, 0, packet->length-res);
|
||||
readyToSend=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSocketPosix::OnReadyToSend(){
|
||||
if(pendingOutgoingPacket){
|
||||
NetworkPacket pkt={0};
|
||||
pkt.data=**pendingOutgoingPacket;
|
||||
pkt.length=pendingOutgoingPacket->Length();
|
||||
Send(&pkt);
|
||||
delete pendingOutgoingPacket;
|
||||
pendingOutgoingPacket=NULL;
|
||||
return false;
|
||||
}
|
||||
readyToSend=true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Receive(NetworkPacket *packet){
|
||||
if(failed){
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
if(protocol==PROTO_UDP){
|
||||
int addrLen=sizeof(sockaddr_in6);
|
||||
sockaddr_in6 srcAddr;
|
||||
ssize_t len=recvfrom(fd, packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
|
||||
if(len>0)
|
||||
packet->length=(size_t) len;
|
||||
else{
|
||||
LOGE("error receiving %d / %s", errno, strerror(errno));
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
|
||||
if(!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)){
|
||||
isV4Available=true;
|
||||
LOGI("Detected IPv4 connectivity, will not try IPv6");
|
||||
}
|
||||
if(IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12)==0)){
|
||||
in_addr v4addr=*((in_addr *) &srcAddr.sin6_addr.s6_addr[12]);
|
||||
lastRecvdV4=IPv4Address(v4addr.s_addr);
|
||||
packet->address=&lastRecvdV4;
|
||||
}else{
|
||||
lastRecvdV6=IPv6Address(srcAddr.sin6_addr.s6_addr);
|
||||
packet->address=&lastRecvdV6;
|
||||
}
|
||||
packet->protocol=PROTO_UDP;
|
||||
packet->port=ntohs(srcAddr.sin6_port);
|
||||
}else if(protocol==PROTO_TCP){
|
||||
int res=(int)recv(fd, packet->data, packet->length, 0);
|
||||
if(res<=0){
|
||||
LOGE("Error receiving from TCP socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
packet->length=0;
|
||||
}else{
|
||||
packet->length=(size_t)res;
|
||||
packet->address=tcpConnectedAddress;
|
||||
packet->port=tcpConnectedPort;
|
||||
packet->protocol=PROTO_TCP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Open(){
|
||||
if(protocol!=PROTO_UDP)
|
||||
return;
|
||||
fd=socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(fd<0){
|
||||
LOGE("error creating socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
int flag=0;
|
||||
int res=setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag));
|
||||
if(res<0){
|
||||
LOGE("error enabling dual stack socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
SetMaxPriority();
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
int tries=0;
|
||||
sockaddr_in6 addr;
|
||||
//addr.sin6_addr.s_addr=0;
|
||||
memset(&addr, 0, sizeof(sockaddr_in6));
|
||||
//addr.sin6_len=sizeof(sa_family_t);
|
||||
addr.sin6_family=AF_INET6;
|
||||
for(tries=0;tries<10;tries++){
|
||||
addr.sin6_port=htons(GenerateLocalPort());
|
||||
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
|
||||
LOGV("trying bind to port %u", ntohs(addr.sin6_port));
|
||||
if(res<0){
|
||||
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(tries==10){
|
||||
addr.sin6_port=0;
|
||||
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
|
||||
if(res<0){
|
||||
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
|
||||
//SetState(STATE_FAILED);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
size_t addrLen=sizeof(sockaddr_in6);
|
||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
|
||||
LOGD("Bound to local UDP port %u", ntohs(addr.sin6_port));
|
||||
|
||||
needUpdateNat64Prefix=true;
|
||||
isV4Available=false;
|
||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Close(){
|
||||
closing=true;
|
||||
failed=true;
|
||||
|
||||
if (fd>=0) {
|
||||
shutdown(fd, SHUT_RDWR);
|
||||
close(fd);
|
||||
fd=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Connect(const NetworkAddress *address, uint16_t port){
|
||||
const IPv4Address* v4addr=dynamic_cast<const IPv4Address*>(address);
|
||||
const IPv6Address* v6addr=dynamic_cast<const IPv6Address*>(address);
|
||||
struct sockaddr_in v4={0};
|
||||
struct sockaddr_in6 v6={0};
|
||||
struct sockaddr* addr=NULL;
|
||||
size_t addrLen=0;
|
||||
if(v4addr){
|
||||
v4.sin_family=AF_INET;
|
||||
v4.sin_addr.s_addr=v4addr->GetAddress();
|
||||
v4.sin_port=htons(port);
|
||||
addr=reinterpret_cast<sockaddr*>(&v4);
|
||||
addrLen=sizeof(v4);
|
||||
}else if(v6addr){
|
||||
v6.sin6_family=AF_INET6;
|
||||
memcpy(v6.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
|
||||
v6.sin6_flowinfo=0;
|
||||
v6.sin6_scope_id=0;
|
||||
v6.sin6_port=htons(port);
|
||||
addr=reinterpret_cast<sockaddr*>(&v6);
|
||||
addrLen=sizeof(v6);
|
||||
}else{
|
||||
LOGE("Unknown address type in TCP connect");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(fd<0){
|
||||
LOGE("Error creating TCP socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
int opt=1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
|
||||
timeval timeout;
|
||||
timeout.tv_sec=5;
|
||||
timeout.tv_usec=0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
timeout.tv_sec=60;
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
int res=(int)connect(fd, (const sockaddr*) addr, (socklen_t)addrLen);
|
||||
if(res!=0 && errno!=EINVAL && errno!=EINPROGRESS){
|
||||
LOGW("error connecting TCP socket to %s:%u: %d / %s; %d / %s", address->ToString().c_str(), port, res, strerror(res), errno, strerror(errno));
|
||||
close(fd);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
tcpConnectedAddress=v4addr ? (NetworkAddress*)new IPv4Address(*v4addr) : (NetworkAddress*)new IPv6Address(*v6addr);
|
||||
tcpConnectedPort=port;
|
||||
LOGI("successfully connected to %s:%d", tcpConnectedAddress->ToString().c_str(), tcpConnectedPort);
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::OnActiveInterfaceChanged(){
|
||||
needUpdateNat64Prefix=true;
|
||||
isV4Available=false;
|
||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::GetLocalInterfaceInfo(IPv4Address *v4addr, IPv6Address *v6addr){
|
||||
std::string name="";
|
||||
// Android doesn't support ifaddrs
|
||||
#ifdef __ANDROID__
|
||||
JNIEnv *env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
jmethodID getLocalNetworkAddressesAndInterfaceNameMethod=env->GetStaticMethodID(jniUtilitiesClass, "getLocalNetworkAddressesAndInterfaceName", "()[Ljava/lang/String;");
|
||||
jobjectArray jinfo=(jobjectArray) env->CallStaticObjectMethod(jniUtilitiesClass, getLocalNetworkAddressesAndInterfaceNameMethod);
|
||||
if(jinfo){
|
||||
jstring jitfName=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 0));
|
||||
jstring jipv4=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 1));
|
||||
jstring jipv6=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 2));
|
||||
if(jitfName){
|
||||
const char *itfchars=env->GetStringUTFChars(jitfName, NULL);
|
||||
name=std::string(itfchars);
|
||||
env->ReleaseStringUTFChars(jitfName, itfchars);
|
||||
}
|
||||
|
||||
if(v4addr && jipv4){
|
||||
const char* ipchars=env->GetStringUTFChars(jipv4, NULL);
|
||||
*v4addr=IPv4Address(ipchars);
|
||||
env->ReleaseStringUTFChars(jipv4, ipchars);
|
||||
}
|
||||
if(v6addr && jipv6){
|
||||
const char* ipchars=env->GetStringUTFChars(jipv6, NULL);
|
||||
*v6addr=IPv6Address(ipchars);
|
||||
env->ReleaseStringUTFChars(jipv6, ipchars);
|
||||
}
|
||||
}else{
|
||||
LOGW("Failed to get android network interface info");
|
||||
}
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
#else
|
||||
struct ifaddrs* interfaces;
|
||||
if(!getifaddrs(&interfaces)){
|
||||
struct ifaddrs* interface;
|
||||
for(interface=interfaces;interface;interface=interface->ifa_next){
|
||||
if(!(interface->ifa_flags & IFF_UP) || !(interface->ifa_flags & IFF_RUNNING) || (interface->ifa_flags & IFF_LOOPBACK))
|
||||
continue;
|
||||
const struct sockaddr_in* addr=(const struct sockaddr_in*)interface->ifa_addr;
|
||||
if(addr){
|
||||
if(addr->sin_family==AF_INET){
|
||||
if((ntohl(addr->sin_addr.s_addr) & 0xFFFF0000)==0xA9FE0000)
|
||||
continue;
|
||||
if(v4addr)
|
||||
*v4addr=IPv4Address(addr->sin_addr.s_addr);
|
||||
name=interface->ifa_name;
|
||||
}else if(addr->sin_family==AF_INET6){
|
||||
const struct sockaddr_in6* addr6=(const struct sockaddr_in6*)addr;
|
||||
if((addr6->sin6_addr.s6_addr[0] & 0xF0)==0xF0)
|
||||
continue;
|
||||
if(v6addr)
|
||||
*v6addr=IPv6Address(addr6->sin6_addr.s6_addr);
|
||||
name=interface->ifa_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(interfaces);
|
||||
}
|
||||
#endif
|
||||
return name;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocketPosix::GetLocalPort(){
|
||||
sockaddr_in6 addr;
|
||||
size_t addrLen=sizeof(sockaddr_in6);
|
||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
|
||||
return ntohs(addr.sin6_port);
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::V4AddressToString(uint32_t address){
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
in_addr addr;
|
||||
addr.s_addr=address;
|
||||
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::V6AddressToString(const unsigned char *address){
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
in6_addr addr;
|
||||
memcpy(addr.s6_addr, address, 16);
|
||||
inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
uint32_t NetworkSocketPosix::StringToV4Address(std::string address){
|
||||
in_addr addr;
|
||||
inet_pton(AF_INET, address.c_str(), &addr);
|
||||
return addr.s_addr;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::StringToV6Address(std::string address, unsigned char *out){
|
||||
in6_addr addr;
|
||||
inet_pton(AF_INET6, address.c_str(), &addr);
|
||||
memcpy(out, addr.s6_addr, 16);
|
||||
}
|
||||
|
||||
IPv4Address *NetworkSocketPosix::ResolveDomainName(std::string name){
|
||||
addrinfo* addr0;
|
||||
IPv4Address* ret=NULL;
|
||||
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
|
||||
if(res!=0){
|
||||
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
|
||||
}else{
|
||||
addrinfo* addrPtr;
|
||||
for(addrPtr=addr0;addrPtr;addrPtr=addrPtr->ai_next){
|
||||
if(addrPtr->ai_family==AF_INET){
|
||||
sockaddr_in* addr=(sockaddr_in*)addrPtr->ai_addr;
|
||||
ret=new IPv4Address(addr->sin_addr.s_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(addr0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NetworkAddress *NetworkSocketPosix::GetConnectedAddress(){
|
||||
return tcpConnectedAddress;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocketPosix::GetConnectedPort(){
|
||||
return tcpConnectedPort;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::SetTimeouts(int sendTimeout, int recvTimeout){
|
||||
timeval timeout;
|
||||
timeout.tv_sec=sendTimeout;
|
||||
timeout.tv_usec=0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
timeout.tv_sec=recvTimeout;
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
}
|
||||
|
||||
bool NetworkSocketPosix::Select(std::vector<NetworkSocket *> &readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket *> &errorFds, SocketSelectCanceller* _canceller) {
|
||||
fd_set readSet;
|
||||
fd_set writeSet;
|
||||
fd_set errorSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
FD_ZERO(&errorSet);
|
||||
SocketSelectCancellerPosix *canceller = dynamic_cast<SocketSelectCancellerPosix *>(_canceller);
|
||||
if (canceller)
|
||||
FD_SET(canceller->pipeRead, &readSet);
|
||||
|
||||
int maxfd = canceller ? canceller->pipeRead : 0;
|
||||
|
||||
for (NetworkSocket *&s:readFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
FD_SET(sfd, &readSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
for (NetworkSocket *&s:writeFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
FD_SET(sfd, &writeSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
bool anyFailed = false;
|
||||
|
||||
for (NetworkSocket *&s:errorFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
if (s->timeout > 0 && VoIPController::GetCurrentTime() - s->lastSuccessfulOperationTime > s->timeout) {
|
||||
LOGW("Socket %d timed out", sfd);
|
||||
s->failed = true;
|
||||
}
|
||||
anyFailed |= s->IsFailed();
|
||||
FD_SET(sfd, &errorSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
select(maxfd + 1, &readSet, &writeSet, &errorSet, NULL);
|
||||
|
||||
if (canceller && FD_ISSET(canceller->pipeRead, &readSet) && !anyFailed) {
|
||||
char c;
|
||||
(void) read(canceller->pipeRead, &c, 1);
|
||||
return false;
|
||||
} else if (anyFailed) {
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
}
|
||||
|
||||
std::vector<NetworkSocket *>::iterator itr = readFds.begin();
|
||||
while (itr != readFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if (sfd > 0 && FD_ISSET(sfd, &readSet)) {
|
||||
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
|
||||
}
|
||||
if (sfd <= 0 || !FD_ISSET(sfd, &readSet) || !(*itr)->OnReadyToReceive()) {
|
||||
itr = readFds.erase(itr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
itr = writeFds.begin();
|
||||
while (itr != writeFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if (sfd <= 0 || !FD_ISSET(sfd, &writeSet)) {
|
||||
itr = writeFds.erase(itr);
|
||||
} else {
|
||||
LOGV("Socket %d is ready to send", sfd);
|
||||
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
|
||||
if ((*itr)->OnReadyToSend())
|
||||
++itr;
|
||||
else
|
||||
itr = writeFds.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
itr = errorFds.begin();
|
||||
while (itr != errorFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if ((sfd <= 0 || !FD_ISSET(sfd, &errorSet)) && !(*itr)->IsFailed()) {
|
||||
itr = errorFds.erase(itr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
//LOGV("select fds left: read=%d, write=%d, error=%d", (int)readFds.size(), (int)writeFds.size(), (int)errorFds.size());
|
||||
|
||||
return readFds.size() > 0 || errorFds.size() > 0 || writeFds.size() > 0;
|
||||
}
|
||||
|
||||
SocketSelectCancellerPosix::SocketSelectCancellerPosix(){
|
||||
int p[2];
|
||||
int pipeRes=pipe(p);
|
||||
if(pipeRes!=0){
|
||||
LOGE("pipe() failed");
|
||||
abort();
|
||||
}
|
||||
pipeRead=p[0];
|
||||
pipeWrite=p[1];
|
||||
}
|
||||
|
||||
SocketSelectCancellerPosix::~SocketSelectCancellerPosix(){
|
||||
close(pipeRead);
|
||||
close(pipeWrite);
|
||||
}
|
||||
|
||||
void SocketSelectCancellerPosix::CancelSelect(){
|
||||
char c=1;
|
||||
(void) write(pipeWrite, &c, 1);
|
||||
}
|
||||
|
||||
int NetworkSocketPosix::GetDescriptorFromSocket(NetworkSocket *socket){
|
||||
NetworkSocketPosix* sp=dynamic_cast<NetworkSocketPosix*>(socket);
|
||||
if(sp)
|
||||
return sp->fd;
|
||||
NetworkSocketWrapper* sw=dynamic_cast<NetworkSocketWrapper*>(socket);
|
||||
if(sw)
|
||||
return GetDescriptorFromSocket(sw->GetWrapped());
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// 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_NETWORKSOCKETPOSIX_H
|
||||
#define LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
||||
|
||||
#include "../../NetworkSocket.h"
|
||||
#include "../../Buffers.h"
|
||||
#include <vector>
|
||||
#include <sys/select.h>
|
||||
#include <pthread.h>
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
class SocketSelectCancellerPosix : public SocketSelectCanceller{
|
||||
friend class NetworkSocketPosix;
|
||||
public:
|
||||
SocketSelectCancellerPosix();
|
||||
virtual ~SocketSelectCancellerPosix();
|
||||
virtual void CancelSelect();
|
||||
private:
|
||||
int pipeRead;
|
||||
int pipeWrite;
|
||||
};
|
||||
|
||||
class NetworkSocketPosix : public NetworkSocket{
|
||||
public:
|
||||
NetworkSocketPosix(NetworkProtocol protocol);
|
||||
virtual ~NetworkSocketPosix();
|
||||
virtual void Send(NetworkPacket* packet) override;
|
||||
virtual void Receive(NetworkPacket* packet) override;
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
virtual void Connect(const NetworkAddress* address, uint16_t port) override;
|
||||
virtual std::string GetLocalInterfaceInfo(IPv4Address* v4addr, IPv6Address* v6addr) override;
|
||||
virtual void OnActiveInterfaceChanged() override;
|
||||
virtual uint16_t GetLocalPort() override;
|
||||
|
||||
static std::string V4AddressToString(uint32_t address);
|
||||
static std::string V6AddressToString(const unsigned char address[16]);
|
||||
static uint32_t StringToV4Address(std::string address);
|
||||
static void StringToV6Address(std::string address, unsigned char* out);
|
||||
static IPv4Address* ResolveDomainName(std::string name);
|
||||
static bool Select(std::vector<NetworkSocket*>& readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*>& errorFds, SocketSelectCanceller* canceller);
|
||||
|
||||
virtual NetworkAddress *GetConnectedAddress() override;
|
||||
|
||||
virtual uint16_t GetConnectedPort() override;
|
||||
|
||||
virtual void SetTimeouts(int sendTimeout, int recvTimeout) override;
|
||||
virtual bool OnReadyToSend() override;
|
||||
|
||||
protected:
|
||||
virtual void SetMaxPriority() override;
|
||||
|
||||
private:
|
||||
static int GetDescriptorFromSocket(NetworkSocket* socket);
|
||||
int fd;
|
||||
bool needUpdateNat64Prefix;
|
||||
bool nat64Present;
|
||||
double switchToV6at;
|
||||
bool isV4Available;
|
||||
bool useTCP;
|
||||
bool closing;
|
||||
IPv4Address lastRecvdV4;
|
||||
IPv6Address lastRecvdV6;
|
||||
NetworkAddress* tcpConnectedAddress;
|
||||
uint16_t tcpConnectedPort;
|
||||
Buffer* pendingOutgoingPacket=NULL;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
||||
459
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.cpp
Executable file
459
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.cpp
Executable file
|
|
@ -0,0 +1,459 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include "AudioInputWASAPI.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
|
||||
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if(*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputWASAPI::AudioInputWASAPI(std::string deviceID){
|
||||
isRecording=false;
|
||||
remainingDataLen=0;
|
||||
refCount=1;
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
CHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
|
||||
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
|
||||
#undef CreateEventEx
|
||||
#define CreateEventEx __CreateEventExA
|
||||
#endif
|
||||
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nChannels=1;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.nBlockAlign=2;
|
||||
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
|
||||
format.wBitsPerSample=16;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
|
||||
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
res=enumerator->RegisterEndpointNotificationCallback(this);
|
||||
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
|
||||
audioSessionControl=NULL;
|
||||
device=NULL;
|
||||
#endif
|
||||
|
||||
audioClient=NULL;
|
||||
captureClient=NULL;
|
||||
thread=NULL;
|
||||
started=false;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioInputWASAPI::~AudioInputWASAPI(){
|
||||
if(audioClient && started){
|
||||
audioClient->Stop();
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
SetEvent(shutdownEvent);
|
||||
if(thread){
|
||||
WaitForSingleObjectEx(thread, INFINITE, false);
|
||||
CloseHandle(thread);
|
||||
}
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&captureClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
#endif
|
||||
CloseHandle(shutdownEvent);
|
||||
CloseHandle(audioSamplesReadyEvent);
|
||||
CloseHandle(streamSwitchEvent);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(enumerator)
|
||||
enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
SafeRelease(&enumerator);
|
||||
#endif
|
||||
}
|
||||
void AudioInputWASAPI::Start(){
|
||||
isRecording=true;
|
||||
if(!thread){
|
||||
thread=CreateThread(NULL, 0, AudioInputWASAPI::StartThread, this, 0, NULL);
|
||||
}
|
||||
|
||||
if(audioClient && !started){
|
||||
LOGI("audioClient->Start");
|
||||
audioClient->Start();
|
||||
started=true;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::Stop(){
|
||||
isRecording=false;
|
||||
}
|
||||
|
||||
bool AudioInputWASAPI::IsRecording(){
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
SCHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator *deviceEnumerator = NULL;
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
|
||||
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
|
||||
res=deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
SCHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
SCHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
SCHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* devID;
|
||||
res=device->GetId(&devID);
|
||||
SCHECK_RES(res, "get device id");
|
||||
|
||||
IPropertyStore* propStore;
|
||||
res=device->OpenPropertyStore(STGM_READ, &propStore);
|
||||
SafeRelease(&device);
|
||||
SCHECK_RES(res, "OpenPropertyStore");
|
||||
|
||||
PROPVARIANT friendlyName;
|
||||
PropVariantInit(&friendlyName);
|
||||
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
|
||||
SafeRelease(&propStore);
|
||||
|
||||
AudioInputDevice dev;
|
||||
|
||||
wchar_t actualFriendlyName[128];
|
||||
if(friendlyName.vt==VT_LPWSTR){
|
||||
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
|
||||
}else{
|
||||
wcscpy(actualFriendlyName, L"Unknown");
|
||||
}
|
||||
PropVariantClear(&friendlyName);
|
||||
|
||||
char buf[256];
|
||||
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.id=buf;
|
||||
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.displayName=buf;
|
||||
devs.push_back(dev);
|
||||
|
||||
CoTaskMemFree(devID);
|
||||
}
|
||||
|
||||
SafeRelease(&deviceCollection);
|
||||
SafeRelease(&deviceEnumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::SetCurrentDevice(std::string deviceID){
|
||||
if(thread){
|
||||
streamChangeToDevice=deviceID;
|
||||
SetEvent(streamSwitchEvent);
|
||||
}else{
|
||||
ActuallySetCurrentDevice(deviceID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
HRESULT res;
|
||||
|
||||
if(audioClient){
|
||||
res=audioClient->Stop();
|
||||
CHECK_RES(res, "audioClient->Stop");
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
res=audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
|
||||
}
|
||||
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&captureClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
if(deviceID=="default"){
|
||||
isDefaultDevice=true;
|
||||
res=enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device);
|
||||
CHECK_RES(res, "GetDefaultAudioEndpoint");
|
||||
}else{
|
||||
isDefaultDevice=false;
|
||||
res=enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
CHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
CHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
CHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* _devID;
|
||||
res=device->GetId(&_devID);
|
||||
CHECK_RES(res, "get device id");
|
||||
|
||||
char devID[128];
|
||||
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
|
||||
|
||||
CoTaskMemFree(_devID);
|
||||
if(deviceID==devID){
|
||||
this->device=device;
|
||||
//device->AddRef();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deviceCollection)
|
||||
SafeRelease(&deviceCollection);
|
||||
|
||||
if(!device){
|
||||
LOGE("Didn't find capture device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
|
||||
CHECK_RES(res, "device->Activate");
|
||||
#else
|
||||
std::wstring devID;
|
||||
|
||||
if (deviceID=="default"){
|
||||
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId(Windows::Media::Devices::AudioDeviceRole::Communications);
|
||||
if(defaultDevID==nullptr){
|
||||
LOGE("Didn't find capture device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}else{
|
||||
isDefaultDevice=true;
|
||||
devID=defaultDevID->Data();
|
||||
}
|
||||
}else{
|
||||
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
|
||||
wchar_t* wstr=new wchar_t[wchars_num];
|
||||
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
|
||||
devID=wstr;
|
||||
}
|
||||
|
||||
HRESULT res1, res2;
|
||||
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
|
||||
CHECK_RES(res1, "activate1");
|
||||
CHECK_RES(res2, "activate2");
|
||||
|
||||
AudioClientProperties properties={};
|
||||
properties.cbSize=sizeof AudioClientProperties;
|
||||
properties.eCategory=AudioCategory_Communications;
|
||||
res = audioClient2->SetClientProperties(&properties);
|
||||
CHECK_RES(res, "audioClient2->SetClientProperties");
|
||||
|
||||
audioClient=audioClient2;
|
||||
#endif
|
||||
|
||||
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
|
||||
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
|
||||
// Use 1000ms buffer to avoid resampling glitches on Windows 8.1 and older. This should not increase latency.
|
||||
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 1000*10000, 0, &format, &guid);
|
||||
CHECK_RES(res, "audioClient->Initialize");
|
||||
|
||||
uint32_t bufSize;
|
||||
res = audioClient->GetBufferSize(&bufSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
|
||||
LOGV("buffer size: %u", bufSize);
|
||||
estimatedDelay=0;
|
||||
REFERENCE_TIME latency, devicePeriod;
|
||||
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
|
||||
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
|
||||
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
|
||||
}
|
||||
}
|
||||
|
||||
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
|
||||
CHECK_RES(res, "audioClient->SetEventHandle");
|
||||
|
||||
res = audioClient->GetService(IID_PPV_ARGS(&captureClient));
|
||||
CHECK_RES(res, "audioClient->GetService");
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
|
||||
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
|
||||
|
||||
res=audioSessionControl->RegisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
|
||||
#endif
|
||||
|
||||
if(isRecording)
|
||||
audioClient->Start();
|
||||
|
||||
LOGV("set current input device done");
|
||||
}
|
||||
|
||||
DWORD WINAPI AudioInputWASAPI::StartThread(void* arg) {
|
||||
LOGV("WASAPI capture thread starting");
|
||||
((AudioInputWASAPI*)arg)->RunThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::RunThread() {
|
||||
if(failed)
|
||||
return;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
|
||||
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
CHECK_RES(res, "CoInitializeEx in capture thread");
|
||||
|
||||
uint32_t bufferSize=0;
|
||||
uint64_t framesWritten=0;
|
||||
|
||||
bool running=true;
|
||||
//double prevCallback=VoIPController::GetCurrentTime();
|
||||
|
||||
while(running){
|
||||
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
|
||||
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
|
||||
LOGV("capture thread shutting down");
|
||||
running=false;
|
||||
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
|
||||
LOGV("stream switch");
|
||||
ActuallySetCurrentDevice(streamChangeToDevice);
|
||||
ResetEvent(streamSwitchEvent);
|
||||
bufferSize=0;
|
||||
LOGV("stream switch done");
|
||||
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
|
||||
if(!audioClient)
|
||||
continue;
|
||||
res=captureClient->GetNextPacketSize(&bufferSize);
|
||||
CHECK_RES(res, "captureClient->GetNextPacketSize");
|
||||
BYTE* data;
|
||||
uint32_t framesAvailable=0;
|
||||
DWORD flags;
|
||||
|
||||
res=captureClient->GetBuffer(&data, &framesAvailable, &flags, NULL, NULL);
|
||||
CHECK_RES(res, "captureClient->GetBuffer");
|
||||
size_t dataLen=framesAvailable*2;
|
||||
assert(remainingDataLen+dataLen<sizeof(remainingData));
|
||||
|
||||
if(flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY){
|
||||
LOGW("Audio capture data discontinuity");
|
||||
}
|
||||
|
||||
//double t=VoIPController::GetCurrentTime();
|
||||
//LOGV("audio capture: %u, time %f, flags %u", framesAvailable, t-prevCallback, flags);
|
||||
//prevCallback=t;
|
||||
|
||||
memcpy(remainingData+remainingDataLen, data, dataLen);
|
||||
remainingDataLen+=dataLen;
|
||||
while(remainingDataLen>960*2){
|
||||
if(isRecording)
|
||||
InvokeCallback(remainingData, 960*2);
|
||||
|
||||
//LOGV("remaining data len %u", remainingDataLen);
|
||||
memmove(remainingData, remainingData+(960*2), remainingDataLen-960*2);
|
||||
remainingDataLen-=960*2;
|
||||
}
|
||||
|
||||
res=captureClient->ReleaseBuffer(framesAvailable);
|
||||
CHECK_RES(res, "captureClient->ReleaseBuffer");
|
||||
//estimatedDelay=(int32_t)((devicePosition-framesWritten)/48);
|
||||
|
||||
framesWritten+=framesAvailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT AudioInputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
|
||||
if(!isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioInputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
|
||||
if(flow==eCapture && role==eCommunications && isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG AudioInputWASAPI::AddRef(){
|
||||
return InterlockedIncrement(&refCount);
|
||||
}
|
||||
|
||||
ULONG AudioInputWASAPI::Release(){
|
||||
return InterlockedDecrement(&refCount);
|
||||
}
|
||||
|
||||
HRESULT AudioInputWASAPI::QueryInterface(REFIID iid, void** obj){
|
||||
if(!obj){
|
||||
return E_POINTER;
|
||||
}
|
||||
*obj=NULL;
|
||||
|
||||
if(iid==IID_IUnknown){
|
||||
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IMMNotificationClient)){
|
||||
*obj=static_cast<IMMNotificationClient*>(this);
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IAudioSessionEvents)){
|
||||
*obj=static_cast<IAudioSessionEvents*>(this);
|
||||
AddRef();
|
||||
}else{
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
105
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.h
Executable file
105
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.h
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// 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_AUDIOINPUTWASAPI_H
|
||||
#define LIBTGVOIP_AUDIOINPUTWASAPI_H
|
||||
|
||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
||||
#define TGVOIP_WINDOWS_PHONE
|
||||
#endif
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
|
||||
#define TGVOIP_WINDOWS_DESKTOP
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4201)
|
||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
||||
#include <mmdeviceapi.h>
|
||||
#endif
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
#include <audiopolicy.h>
|
||||
#include <functiondiscoverykeys.h>
|
||||
#else
|
||||
#include <audioclient.h>
|
||||
#include "WindowsSandboxUtils.h"
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
class AudioInputWASAPI : public AudioInput, IMMNotificationClient, IAudioSessionEvents{
|
||||
#else
|
||||
class AudioInputWASAPI : public AudioInput{
|
||||
#endif
|
||||
|
||||
public:
|
||||
AudioInputWASAPI(std::string deviceID);
|
||||
virtual ~AudioInputWASAPI();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsRecording();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD_(ULONG, AddRef)();
|
||||
STDMETHOD_(ULONG, Release)();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void ActuallySetCurrentDevice(std::string deviceID);
|
||||
static DWORD WINAPI StartThread(void* arg);
|
||||
void RunThread();
|
||||
WAVEFORMATEX format;
|
||||
bool isRecording;
|
||||
HANDLE shutdownEvent;
|
||||
HANDLE audioSamplesReadyEvent;
|
||||
HANDLE streamSwitchEvent;
|
||||
HANDLE thread;
|
||||
IAudioClient* audioClient=NULL;
|
||||
IAudioCaptureClient* captureClient=NULL;
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
IMMDeviceEnumerator* enumerator;
|
||||
IAudioSessionControl* audioSessionControl;
|
||||
IMMDevice* device;
|
||||
#endif
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataLen;
|
||||
bool isDefaultDevice;
|
||||
ULONG refCount;
|
||||
std::string streamChangeToDevice;
|
||||
bool started;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
|
||||
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
|
||||
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
|
||||
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
|
||||
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
|
||||
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
|
||||
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
|
||||
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
|
||||
|
||||
//
|
||||
// IUnknown
|
||||
//
|
||||
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTWASAPI_H
|
||||
170
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.cpp
Executable file
170
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.cpp
Executable file
|
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "AudioInputWave.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=MMSYSERR_NOERROR){wchar_t _buf[1024]; waveInGetErrorTextW(res, _buf, 1024); LOGE(msg ": %ws (MMRESULT=0x%08X)", _buf, res); failed=true;}
|
||||
|
||||
AudioInputWave::AudioInputWave(std::string deviceID){
|
||||
isRecording=false;
|
||||
|
||||
for(int i=0;i<4;i++){
|
||||
ZeroMemory(&buffers[i], sizeof(WAVEHDR));
|
||||
buffers[i].dwBufferLength=960*2;
|
||||
buffers[i].lpData=(char*)malloc(960*2);
|
||||
}
|
||||
|
||||
hWaveIn=NULL;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioInputWave::~AudioInputWave(){
|
||||
for(int i=0;i<4;i++){
|
||||
free(buffers[i].lpData);
|
||||
}
|
||||
waveInClose(hWaveIn);
|
||||
}
|
||||
|
||||
void AudioInputWave::Start(){
|
||||
if(!isRecording){
|
||||
isRecording=true;
|
||||
|
||||
MMRESULT res;
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInPrepareHeader failed");
|
||||
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
res=waveInStart(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStart failed");
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::Stop(){
|
||||
if(isRecording){
|
||||
isRecording=false;
|
||||
|
||||
MMRESULT res=waveInStop(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStop failed");
|
||||
res=waveInReset(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInReset failed");
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInUnprepareHeader failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CALLBACK AudioInputWave::WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2){
|
||||
if(uMsg==WIM_DATA){
|
||||
((AudioInputWave*)dwInstance)->OnData((WAVEHDR*)dwParam1);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::OnData(WAVEHDR* hdr){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
InvokeCallback((unsigned char*)hdr->lpData, hdr->dwBufferLength);
|
||||
hdr->dwFlags&= ~WHDR_DONE;
|
||||
MMRESULT res=waveInAddBuffer(hWaveIn, hdr, sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
|
||||
void AudioInputWave::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
|
||||
UINT num=waveInGetNumDevs();
|
||||
WAVEINCAPSW caps;
|
||||
char nameBuf[512];
|
||||
for(UINT i=0;i<num;i++){
|
||||
waveInGetDevCapsW(i, &caps, sizeof(caps));
|
||||
AudioInputDevice dev;
|
||||
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
|
||||
dev.displayName=std::string(nameBuf);
|
||||
dev.id=std::string(nameBuf);
|
||||
devs.push_back(dev);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::SetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
|
||||
bool wasRecording=isRecording;
|
||||
isRecording=false;
|
||||
if(hWaveIn){
|
||||
MMRESULT res;
|
||||
if(isRecording){
|
||||
res=waveInStop(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStop failed");
|
||||
res=waveInReset(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInReset failed");
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInUnprepareHeader failed");
|
||||
}
|
||||
}
|
||||
res=waveInClose(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInClose failed");
|
||||
}
|
||||
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.cbSize=0;
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.wBitsPerSample=16;
|
||||
format.nChannels=1;
|
||||
format.nBlockAlign=2;
|
||||
|
||||
LOGV("before open device %s", deviceID.c_str());
|
||||
|
||||
if(deviceID=="default"){
|
||||
MMRESULT res=waveInOpen(&hWaveIn, WAVE_MAPPER, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
||||
CHECK_ERROR(res, "waveInOpen failed");
|
||||
}else{
|
||||
UINT num=waveInGetNumDevs();
|
||||
WAVEINCAPSW caps;
|
||||
char nameBuf[512];
|
||||
hWaveIn=NULL;
|
||||
for(UINT i=0;i<num;i++){
|
||||
waveInGetDevCapsW(i, &caps, sizeof(caps));
|
||||
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
|
||||
std::string name=std::string(nameBuf);
|
||||
if(name==deviceID){
|
||||
MMRESULT res=waveInOpen(&hWaveIn, i, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION | WAVE_MAPPED);
|
||||
CHECK_ERROR(res, "waveInOpen failed");
|
||||
LOGD("Opened device %s", nameBuf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!hWaveIn){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isRecording=wasRecording;
|
||||
|
||||
if(isRecording){
|
||||
MMRESULT res;
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInPrepareHeader failed");
|
||||
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
res=waveInStart(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStart failed");
|
||||
}
|
||||
}
|
||||
40
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.h
Normal file
40
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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_AUDIOINPUTWAVE_H
|
||||
#define LIBTGVOIP_AUDIOINPUTWAVE_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputWave : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputWave(std::string deviceID);
|
||||
virtual ~AudioInputWave();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void CALLBACK WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
||||
void OnData(WAVEHDR* hdr);
|
||||
HWAVEIN hWaveIn;
|
||||
WAVEFORMATEX format;
|
||||
WAVEHDR buffers[4];
|
||||
bool isRecording;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTWAVE_H
|
||||
455
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.cpp
Executable file
455
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.cpp
Executable file
|
|
@ -0,0 +1,455 @@
|
|||
//
|
||||
// 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 <assert.h>
|
||||
#include "AudioOutputWASAPI.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
|
||||
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if(*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
|
||||
#endif
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputWASAPI::AudioOutputWASAPI(std::string deviceID){
|
||||
isPlaying=false;
|
||||
remainingDataLen=0;
|
||||
refCount=1;
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
CHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
|
||||
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
|
||||
#undef CreateEventEx
|
||||
#define CreateEventEx __CreateEventExA
|
||||
#endif
|
||||
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nChannels=1;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.nBlockAlign=2;
|
||||
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
|
||||
format.wBitsPerSample=16;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
|
||||
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
res=enumerator->RegisterEndpointNotificationCallback(this);
|
||||
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
|
||||
audioSessionControl=NULL;
|
||||
device=NULL;
|
||||
#endif
|
||||
|
||||
audioClient=NULL;
|
||||
renderClient=NULL;
|
||||
thread=NULL;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioOutputWASAPI::~AudioOutputWASAPI(){
|
||||
if(audioClient){
|
||||
audioClient->Stop();
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
SetEvent(shutdownEvent);
|
||||
if(thread){
|
||||
WaitForSingleObjectEx(thread, INFINITE, false);
|
||||
CloseHandle(thread);
|
||||
}
|
||||
SafeRelease(&renderClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
CloseHandle(shutdownEvent);
|
||||
CloseHandle(audioSamplesReadyEvent);
|
||||
CloseHandle(streamSwitchEvent);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(enumerator)
|
||||
enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
SafeRelease(&enumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::Start(){
|
||||
isPlaying=true;
|
||||
if(!thread){
|
||||
thread=CreateThread(NULL, 0, AudioOutputWASAPI::StartThread, this, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::Stop(){
|
||||
isPlaying=false;
|
||||
}
|
||||
|
||||
bool AudioOutputWASAPI::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
SCHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator *deviceEnumerator = NULL;
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
|
||||
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
|
||||
res=deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
SCHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
SCHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
SCHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* devID;
|
||||
res=device->GetId(&devID);
|
||||
SCHECK_RES(res, "get device id");
|
||||
|
||||
IPropertyStore* propStore;
|
||||
res=device->OpenPropertyStore(STGM_READ, &propStore);
|
||||
SafeRelease(&device);
|
||||
SCHECK_RES(res, "OpenPropertyStore");
|
||||
|
||||
PROPVARIANT friendlyName;
|
||||
PropVariantInit(&friendlyName);
|
||||
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
|
||||
SafeRelease(&propStore);
|
||||
|
||||
AudioOutputDevice dev;
|
||||
|
||||
wchar_t actualFriendlyName[128];
|
||||
if(friendlyName.vt==VT_LPWSTR){
|
||||
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
|
||||
}else{
|
||||
wcscpy(actualFriendlyName, L"Unknown");
|
||||
}
|
||||
PropVariantClear(&friendlyName);
|
||||
|
||||
char buf[256];
|
||||
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.id=buf;
|
||||
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.displayName=buf;
|
||||
devs.push_back(dev);
|
||||
|
||||
CoTaskMemFree(devID);
|
||||
}
|
||||
|
||||
SafeRelease(&deviceCollection);
|
||||
SafeRelease(&deviceEnumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::SetCurrentDevice(std::string deviceID){
|
||||
if(thread){
|
||||
streamChangeToDevice=deviceID;
|
||||
SetEvent(streamSwitchEvent);
|
||||
}else{
|
||||
ActuallySetCurrentDevice(deviceID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
HRESULT res;
|
||||
|
||||
if(audioClient){
|
||||
res=audioClient->Stop();
|
||||
CHECK_RES(res, "audioClient->Stop");
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
res=audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
|
||||
}
|
||||
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&renderClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
|
||||
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
if(deviceID=="default"){
|
||||
isDefaultDevice=true;
|
||||
res=enumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &device);
|
||||
CHECK_RES(res, "GetDefaultAudioEndpoint");
|
||||
}else{
|
||||
isDefaultDevice=false;
|
||||
res=enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
CHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
CHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
CHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* _devID;
|
||||
res=device->GetId(&_devID);
|
||||
CHECK_RES(res, "get device id");
|
||||
|
||||
char devID[128];
|
||||
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
|
||||
|
||||
CoTaskMemFree(_devID);
|
||||
if(deviceID==devID){
|
||||
this->device=device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deviceCollection)
|
||||
SafeRelease(&deviceCollection);
|
||||
|
||||
if(!device){
|
||||
LOGE("Didn't find playback device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
|
||||
CHECK_RES(res, "device->Activate");
|
||||
#else
|
||||
std::wstring devID;
|
||||
|
||||
if (deviceID=="default"){
|
||||
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId(Windows::Media::Devices::AudioDeviceRole::Communications);
|
||||
if(defaultDevID==nullptr){
|
||||
LOGE("Didn't find playback device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}else{
|
||||
isDefaultDevice=true;
|
||||
devID=defaultDevID->Data();
|
||||
}
|
||||
}else{
|
||||
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
|
||||
wchar_t* wstr=new wchar_t[wchars_num];
|
||||
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
|
||||
devID=wstr;
|
||||
}
|
||||
|
||||
HRESULT res1, res2;
|
||||
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
|
||||
CHECK_RES(res1, "activate1");
|
||||
CHECK_RES(res2, "activate2");
|
||||
|
||||
AudioClientProperties properties={};
|
||||
properties.cbSize=sizeof AudioClientProperties;
|
||||
properties.eCategory=AudioCategory_Communications;
|
||||
res = audioClient2->SetClientProperties(&properties);
|
||||
CHECK_RES(res, "audioClient2->SetClientProperties");
|
||||
|
||||
audioClient = audioClient2;
|
||||
#endif
|
||||
|
||||
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
|
||||
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
|
||||
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 60 * 10000, 0, &format, &guid);
|
||||
CHECK_RES(res, "audioClient->Initialize");
|
||||
|
||||
uint32_t bufSize;
|
||||
res = audioClient->GetBufferSize(&bufSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
|
||||
LOGV("buffer size: %u", bufSize);
|
||||
estimatedDelay=0;
|
||||
REFERENCE_TIME latency, devicePeriod;
|
||||
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
|
||||
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
|
||||
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
|
||||
}
|
||||
}
|
||||
|
||||
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
|
||||
CHECK_RES(res, "audioClient->SetEventHandle");
|
||||
|
||||
res = audioClient->GetService(IID_PPV_ARGS(&renderClient));
|
||||
CHECK_RES(res, "audioClient->GetService");
|
||||
|
||||
BYTE* data;
|
||||
res = renderClient->GetBuffer(bufSize, &data);
|
||||
CHECK_RES(res, "renderClient->GetBuffer");
|
||||
|
||||
res = renderClient->ReleaseBuffer(bufSize, AUDCLNT_BUFFERFLAGS_SILENT);
|
||||
CHECK_RES(res, "renderClient->ReleaseBuffer");
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
|
||||
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
|
||||
|
||||
res=audioSessionControl->RegisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
|
||||
#endif
|
||||
|
||||
audioClient->Start();
|
||||
|
||||
LOGV("set current output device done");
|
||||
}
|
||||
|
||||
DWORD WINAPI AudioOutputWASAPI::StartThread(void* arg) {
|
||||
((AudioOutputWASAPI*)arg)->RunThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::RunThread() {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
|
||||
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
CHECK_RES(res, "CoInitializeEx in render thread");
|
||||
|
||||
uint32_t bufferSize;
|
||||
res=audioClient->GetBufferSize(&bufferSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
uint64_t framesWritten=0;
|
||||
|
||||
bool running=true;
|
||||
//double prevCallback=VoIPController::GetCurrentTime();
|
||||
|
||||
while(running){
|
||||
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
|
||||
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
|
||||
LOGV("render thread shutting down");
|
||||
running=false;
|
||||
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
|
||||
LOGV("stream switch");
|
||||
ActuallySetCurrentDevice(streamChangeToDevice);
|
||||
ResetEvent(streamSwitchEvent);
|
||||
LOGV("stream switch done");
|
||||
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
|
||||
if(!audioClient)
|
||||
continue;
|
||||
|
||||
BYTE* data;
|
||||
uint32_t padding;
|
||||
uint32_t framesAvailable;
|
||||
res=audioClient->GetCurrentPadding(&padding);
|
||||
CHECK_RES(res, "audioClient->GetCurrentPadding");
|
||||
framesAvailable=bufferSize-padding;
|
||||
res=renderClient->GetBuffer(framesAvailable, &data);
|
||||
CHECK_RES(res, "renderClient->GetBuffer");
|
||||
|
||||
//double t=VoIPController::GetCurrentTime();
|
||||
//LOGV("framesAvail: %u, time: %f, isPlaying: %d", framesAvailable, t-prevCallback, isPlaying);
|
||||
//prevCallback=t;
|
||||
|
||||
size_t bytesAvailable=framesAvailable*2;
|
||||
while(bytesAvailable>remainingDataLen){
|
||||
if(isPlaying){
|
||||
InvokeCallback(remainingData+remainingDataLen, 960*2);
|
||||
}else{
|
||||
memset(remainingData+remainingDataLen, 0, 960*2);
|
||||
}
|
||||
remainingDataLen+=960*2;
|
||||
}
|
||||
memcpy(data, remainingData, bytesAvailable);
|
||||
if(remainingDataLen>bytesAvailable){
|
||||
memmove(remainingData, remainingData+bytesAvailable, remainingDataLen-bytesAvailable);
|
||||
}
|
||||
remainingDataLen-=bytesAvailable;
|
||||
|
||||
res=renderClient->ReleaseBuffer(framesAvailable, 0);
|
||||
CHECK_RES(res, "renderClient->ReleaseBuffer");
|
||||
framesWritten+=framesAvailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT AudioOutputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
|
||||
if(!isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioOutputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
|
||||
if(flow==eRender && role==eCommunications && isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG AudioOutputWASAPI::AddRef(){
|
||||
return InterlockedIncrement(&refCount);
|
||||
}
|
||||
|
||||
ULONG AudioOutputWASAPI::Release(){
|
||||
return InterlockedDecrement(&refCount);
|
||||
}
|
||||
|
||||
HRESULT AudioOutputWASAPI::QueryInterface(REFIID iid, void** obj){
|
||||
if(!obj){
|
||||
return E_POINTER;
|
||||
}
|
||||
*obj=NULL;
|
||||
|
||||
if(iid==IID_IUnknown){
|
||||
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IMMNotificationClient)){
|
||||
*obj=static_cast<IMMNotificationClient*>(this);
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IAudioSessionEvents)){
|
||||
*obj=static_cast<IAudioSessionEvents*>(this);
|
||||
AddRef();
|
||||
}else{
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
103
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.h
Executable file
103
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.h
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// 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_AUDIOOUTPUTWASAPI_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTWASAPI_H
|
||||
|
||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
||||
#define TGVOIP_WINDOWS_PHONE
|
||||
#endif
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
|
||||
#define TGVOIP_WINDOWS_DESKTOP
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4201)
|
||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
||||
#include <mmdeviceapi.h>
|
||||
#endif
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
#include <audiopolicy.h>
|
||||
#include <functiondiscoverykeys.h>
|
||||
#else
|
||||
#include <audioclient.h>
|
||||
#include "WindowsSandboxUtils.h"
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
class AudioOutputWASAPI : public AudioOutput, IMMNotificationClient, IAudioSessionEvents{
|
||||
#else
|
||||
class AudioOutputWASAPI : public AudioOutput{
|
||||
#endif
|
||||
public:
|
||||
AudioOutputWASAPI(std::string deviceID);
|
||||
virtual ~AudioOutputWASAPI();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD_(ULONG, AddRef)();
|
||||
STDMETHOD_(ULONG, Release)();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void ActuallySetCurrentDevice(std::string deviceID);
|
||||
static DWORD WINAPI StartThread(void* arg);
|
||||
void RunThread();
|
||||
WAVEFORMATEX format;
|
||||
bool isPlaying;
|
||||
HANDLE shutdownEvent;
|
||||
HANDLE audioSamplesReadyEvent;
|
||||
HANDLE streamSwitchEvent;
|
||||
HANDLE thread;
|
||||
IAudioClient* audioClient=NULL;
|
||||
IAudioRenderClient* renderClient=NULL;
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
IMMDeviceEnumerator* enumerator;
|
||||
IAudioSessionControl* audioSessionControl;
|
||||
IMMDevice* device;
|
||||
#endif
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataLen;
|
||||
bool isDefaultDevice;
|
||||
ULONG refCount;
|
||||
std::string streamChangeToDevice;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
|
||||
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
|
||||
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
|
||||
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
|
||||
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
|
||||
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
|
||||
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
|
||||
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
|
||||
|
||||
//
|
||||
// IUnknown
|
||||
//
|
||||
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTWASAPI_H
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue