mirror of
https://github.com/DrKLO/Telegram.git
synced 2024-12-22 06:25:14 +01:00
Update to 6.1.0 (1938)
This commit is contained in:
parent
0b83be7356
commit
ae0d24ebf2
1327 changed files with 86726 additions and 70038 deletions
|
@ -23,3 +23,5 @@ RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSIO
|
||||||
"ndk;$ANDROID_NDK_VERSION"
|
"ndk;$ANDROID_NDK_VERSION"
|
||||||
ENV PATH ${ANDROID_NDK_HOME}:$PATH
|
ENV PATH ${ANDROID_NDK_HOME}:$PATH
|
||||||
ENV PATH ${ANDROID_NDK_HOME}/prebuilt/linux-x86_64/bin/:$PATH
|
ENV PATH ${ANDROID_NDK_HOME}/prebuilt/linux-x86_64/bin/:$PATH
|
||||||
|
|
||||||
|
CMD mkdir -p /home/source/TMessagesProj/build/outputs/apk && cp -R /home/source/. /home/gradle && cd /home/gradle && gradle assembleRelease && cp -R /home/gradle/TMessagesProj/build/outputs/apk/. /home/source/TMessagesProj/build/outputs/apk
|
||||||
|
|
|
@ -20,10 +20,12 @@ dependencies {
|
||||||
implementation 'androidx.core:core:1.1.0-beta01'
|
implementation 'androidx.core:core:1.1.0-beta01'
|
||||||
implementation 'androidx.palette:palette:1.0.0'
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||||
|
implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0'
|
||||||
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
compileOnly 'org.checkerframework:checker-qual:2.5.2'
|
compileOnly 'org.checkerframework:checker-qual:2.5.2'
|
||||||
compileOnly 'org.checkerframework:checker-compat-qual:2.5.0'
|
compileOnly 'org.checkerframework:checker-compat-qual:2.5.0'
|
||||||
implementation 'com.google.firebase:firebase-messaging:20.1.3'
|
implementation 'com.google.firebase:firebase-messaging:20.1.4'
|
||||||
implementation 'com.google.firebase:firebase-config:19.1.3'
|
implementation 'com.google.firebase:firebase-config:19.1.3'
|
||||||
implementation 'com.google.android.gms:play-services-maps:17.0.0'
|
implementation 'com.google.android.gms:play-services-maps:17.0.0'
|
||||||
implementation 'com.google.android.gms:play-services-auth:17.0.0'
|
implementation 'com.google.android.gms:play-services-auth:17.0.0'
|
||||||
|
@ -90,6 +92,7 @@ android {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
multiDexEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +128,6 @@ android {
|
||||||
initWith debug
|
initWith debug
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
dependencies {
|
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
|
||||||
}
|
|
||||||
manifestPlaceholders = [applicationClassName: "MultiDexApplicationLoader"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HA {
|
HA {
|
||||||
|
@ -137,6 +136,7 @@ android {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
multiDexEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ android {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
|
multiDexEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,7 +286,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig.versionCode = 1911
|
defaultConfig.versionCode = 1938
|
||||||
|
|
||||||
def tgVoipDexFileName = "libtgvoip.dex"
|
def tgVoipDexFileName = "libtgvoip.dex"
|
||||||
def tgVoipDexClasses = ["AudioRecordJNI", "AudioTrackJNI", "NativeTgVoipDelegate", "NativeTgVoipInstance", "TgVoipNativeLoader", "Resampler", "VLog"]
|
def tgVoipDexClasses = ["AudioRecordJNI", "AudioTrackJNI", "NativeTgVoipDelegate", "NativeTgVoipInstance", "TgVoipNativeLoader", "Resampler", "VLog"]
|
||||||
|
@ -380,7 +381,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionName "6.0.1"
|
versionName "6.1.0"
|
||||||
|
|
||||||
vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi']
|
vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi']
|
||||||
|
|
||||||
|
@ -390,8 +391,6 @@ android {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestPlaceholders = [applicationClassName: "ApplicationLoader"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
{
|
|
||||||
"client_id": "760348033671-jiv412evc1r36rl4k7vhl1ba83atdmot.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "760348033671-7cj8o8gntcp2o052du67k1t0pi9frbso.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"client_id": "760348033671-2hh8ebmuflsnjoc0kldkfells9rhtfni.apps.googleusercontent.com",
|
"client_id": "760348033671-2hh8ebmuflsnjoc0kldkfells9rhtfni.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
|
@ -29,19 +21,17 @@
|
||||||
],
|
],
|
||||||
"api_key": [
|
"api_key": [
|
||||||
{
|
{
|
||||||
"current_key": "AIzaSyAijSIM_t8bfdRrn2-mCumXRu37pPslqkU"
|
"current_key": "AIzaSyA-t0jLPjUt2FxrA8VPK2EiYHcYcboIR6k"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": {
|
"services": {
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
"appinvite_service": {
|
||||||
"status": 1,
|
"other_platform_oauth_client": [
|
||||||
"other_platform_oauth_client": []
|
{
|
||||||
},
|
"client_id": "760348033671-jiv412evc1r36rl4k7vhl1ba83atdmot.apps.googleusercontent.com",
|
||||||
"ads_service": {
|
"client_type": 3
|
||||||
"status": 2
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,14 +43,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
{
|
|
||||||
"client_id": "760348033671-jiv412evc1r36rl4k7vhl1ba83atdmot.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "760348033671-7cj8o8gntcp2o052du67k1t0pi9frbso.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"client_id": "760348033671-2hh8ebmuflsnjoc0kldkfells9rhtfni.apps.googleusercontent.com",
|
"client_id": "760348033671-2hh8ebmuflsnjoc0kldkfells9rhtfni.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
|
@ -68,22 +50,20 @@
|
||||||
],
|
],
|
||||||
"api_key": [
|
"api_key": [
|
||||||
{
|
{
|
||||||
"current_key": "AIzaSyAijSIM_t8bfdRrn2-mCumXRu37pPslqkU"
|
"current_key": "AIzaSyA-t0jLPjUt2FxrA8VPK2EiYHcYcboIR6k"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": {
|
"services": {
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
"appinvite_service": {
|
||||||
"status": 1,
|
"other_platform_oauth_client": [
|
||||||
"other_platform_oauth_client": []
|
{
|
||||||
},
|
"client_id": "760348033671-jiv412evc1r36rl4k7vhl1ba83atdmot.apps.googleusercontent.com",
|
||||||
"ads_service": {
|
"client_type": 3
|
||||||
"status": 2
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration_version": "1"
|
"configuration_version": "1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,9 +103,9 @@ include $(MY_LOCAL_PATH)/libtgvoip/Android.mk
|
||||||
LOCAL_PATH := $(MY_LOCAL_PATH) # restore local path after include
|
LOCAL_PATH := $(MY_LOCAL_PATH) # restore local path after include
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
TGVOIP_NATIVE_VERSION := 2.1
|
TGVOIP_NATIVE_VERSION := 3.1
|
||||||
TGVOIP_ADDITIONAL_CFLAGS := -DTGVOIP_NO_VIDEO
|
TGVOIP_ADDITIONAL_CFLAGS := -DTGVOIP_NO_VIDEO
|
||||||
include $(MY_LOCAL_PATH)/libtgvoip2/Android.mk
|
include $(MY_LOCAL_PATH)/libtgvoip3/Android.mk
|
||||||
LOCAL_PATH := $(MY_LOCAL_PATH) # restore local path after include
|
LOCAL_PATH := $(MY_LOCAL_PATH) # restore local path after include
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ static JNINativeMethod NativeByteBufferMethods[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
jlong getCurrentTimeMillis(JNIEnv *env, jclass c, jint instanceNum) {
|
jlong getCurrentTimeMillis(JNIEnv *env, jclass c, jint instanceNum) {
|
||||||
return ConnectionsManager::getInstance(instanceNum).getCurrentTimeMillis();
|
return ConnectionsManager::getInstance(instanceNum).getCurrentTimeMillis() + ((jlong) ConnectionsManager::getInstance(instanceNum).getTimeDifference()) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
jint getCurrentTime(JNIEnv *env, jclass c, jint instanceNum) {
|
jint getCurrentTime(JNIEnv *env, jclass c, jint instanceNum) {
|
||||||
|
@ -367,7 +367,7 @@ void setSystemLangCode(JNIEnv *env, jclass c, jint instanceNum, jstring langCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring systemLangCode, jstring configPath, jstring logPath, jstring regId, jstring cFingerprint, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) {
|
void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring systemLangCode, jstring configPath, jstring logPath, jstring regId, jstring cFingerprint, jint timezoneOffset, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) {
|
||||||
const char *deviceModelStr = env->GetStringUTFChars(deviceModel, 0);
|
const char *deviceModelStr = env->GetStringUTFChars(deviceModel, 0);
|
||||||
const char *systemVersionStr = env->GetStringUTFChars(systemVersion, 0);
|
const char *systemVersionStr = env->GetStringUTFChars(systemVersion, 0);
|
||||||
const char *appVersionStr = env->GetStringUTFChars(appVersion, 0);
|
const char *appVersionStr = env->GetStringUTFChars(appVersion, 0);
|
||||||
|
@ -378,7 +378,7 @@ void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jin
|
||||||
const char *regIdStr = env->GetStringUTFChars(regId, 0);
|
const char *regIdStr = env->GetStringUTFChars(regId, 0);
|
||||||
const char *cFingerprintStr = env->GetStringUTFChars(cFingerprint, 0);
|
const char *cFingerprintStr = env->GetStringUTFChars(cFingerprint, 0);
|
||||||
|
|
||||||
ConnectionsManager::getInstance(instanceNum).init((uint32_t) version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(systemLangCodeStr), std::string(configPathStr), std::string(logPathStr), std::string(regIdStr), std::string(cFingerprintStr), userId, true, enablePushConnection, hasNetwork, networkType);
|
ConnectionsManager::getInstance(instanceNum).init((uint32_t) version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(systemLangCodeStr), std::string(configPathStr), std::string(logPathStr), std::string(regIdStr), std::string(cFingerprintStr), timezoneOffset, userId, true, enablePushConnection, hasNetwork, networkType);
|
||||||
|
|
||||||
if (deviceModelStr != 0) {
|
if (deviceModelStr != 0) {
|
||||||
env->ReleaseStringUTFChars(deviceModel, deviceModelStr);
|
env->ReleaseStringUTFChars(deviceModel, deviceModelStr);
|
||||||
|
@ -431,7 +431,7 @@ static JNINativeMethod ConnectionsManagerMethods[] = {
|
||||||
{"native_setProxySettings", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *) setProxySettings},
|
{"native_setProxySettings", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *) setProxySettings},
|
||||||
{"native_getConnectionState", "(I)I", (void *) getConnectionState},
|
{"native_getConnectionState", "(I)I", (void *) getConnectionState},
|
||||||
{"native_setUserId", "(II)V", (void *) setUserId},
|
{"native_setUserId", "(II)V", (void *) setUserId},
|
||||||
{"native_init", "(IIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init},
|
{"native_init", "(IIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIZZI)V", (void *) init},
|
||||||
{"native_setLangCode", "(ILjava/lang/String;)V", (void *) setLangCode},
|
{"native_setLangCode", "(ILjava/lang/String;)V", (void *) setLangCode},
|
||||||
{"native_setRegId", "(ILjava/lang/String;)V", (void *) setRegId},
|
{"native_setRegId", "(ILjava/lang/String;)V", (void *) setRegId},
|
||||||
{"native_setSystemLangCode", "(ILjava/lang/String;)V", (void *) setSystemLangCode},
|
{"native_setSystemLangCode", "(ILjava/lang/String;)V", (void *) setSystemLangCode},
|
||||||
|
|
26
TMessagesProj/jni/libtgvoip2/.gitignore
vendored
26
TMessagesProj/jni/libtgvoip2/.gitignore
vendored
|
@ -1,26 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
bin
|
|
||||||
.idea
|
|
||||||
build
|
|
||||||
*/Debug/*
|
|
||||||
*/Release/*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
[Pp]review/
|
|
||||||
[Pp]roduction/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
DerivedData/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
|
|
||||||
xcuserdata/
|
|
||||||
autom4te.cache/
|
|
|
@ -1,92 +0,0 @@
|
||||||
//
|
|
||||||
// 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{
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t 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
|
|
|
@ -1,237 +0,0 @@
|
||||||
//
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
unsigned char* new_buffer;
|
|
||||||
need = std::max(need, size_t{1024});
|
|
||||||
new_buffer = reinterpret_cast<unsigned char*>(std::realloc(buffer, size + need));
|
|
||||||
if (new_buffer == NULL) {
|
|
||||||
std::free(buffer);
|
|
||||||
buffer = NULL;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
buffer = new_buffer;
|
|
||||||
size += need;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void BufferOutputStream::Reset(){
|
|
||||||
offset=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BufferOutputStream::Rewind(size_t numBytes){
|
|
||||||
if(numBytes>offset)
|
|
||||||
throw std::out_of_range("buffer underflow");
|
|
||||||
offset-=numBytes;
|
|
||||||
}
|
|
|
@ -1,357 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <bitset>
|
|
||||||
#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 Buffer{
|
|
||||||
public:
|
|
||||||
Buffer(size_t capacity){
|
|
||||||
if(capacity>0){
|
|
||||||
data=(unsigned char *) malloc(capacity);
|
|
||||||
if(!data)
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}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;
|
|
||||||
freeFn=other.freeFn;
|
|
||||||
reallocFn=other.reallocFn;
|
|
||||||
other.data=NULL;
|
|
||||||
};
|
|
||||||
Buffer(BufferOutputStream&& stream){
|
|
||||||
data=stream.buffer;
|
|
||||||
length=stream.offset;
|
|
||||||
stream.buffer=NULL;
|
|
||||||
}
|
|
||||||
Buffer(){
|
|
||||||
data=NULL;
|
|
||||||
length=0;
|
|
||||||
}
|
|
||||||
~Buffer(){
|
|
||||||
if(data){
|
|
||||||
if(freeFn)
|
|
||||||
freeFn(data);
|
|
||||||
else
|
|
||||||
free(data);
|
|
||||||
}
|
|
||||||
data=NULL;
|
|
||||||
length=0;
|
|
||||||
};
|
|
||||||
Buffer& operator=(Buffer&& other){
|
|
||||||
if(this!=&other){
|
|
||||||
if(data){
|
|
||||||
if(freeFn)
|
|
||||||
freeFn(data);
|
|
||||||
else
|
|
||||||
free(data);
|
|
||||||
}
|
|
||||||
data=other.data;
|
|
||||||
length=other.length;
|
|
||||||
freeFn=other.freeFn;
|
|
||||||
reallocFn=other.reallocFn;
|
|
||||||
other.data=NULL;
|
|
||||||
other.length=0;
|
|
||||||
}
|
|
||||||
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){
|
|
||||||
if(reallocFn)
|
|
||||||
data=(unsigned char *) reallocFn(data, newSize);
|
|
||||||
else
|
|
||||||
data=(unsigned char *) realloc(data, newSize);
|
|
||||||
if(!data)
|
|
||||||
throw std::bad_alloc();
|
|
||||||
length=newSize;
|
|
||||||
}
|
|
||||||
size_t Length() const{
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
bool IsEmpty() const{
|
|
||||||
return length==0 || !data;
|
|
||||||
}
|
|
||||||
static Buffer CopyOf(const Buffer& other){
|
|
||||||
if(other.IsEmpty())
|
|
||||||
return Buffer();
|
|
||||||
Buffer buf(other.length);
|
|
||||||
buf.CopyFrom(other, other.length);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
static Buffer CopyOf(const Buffer& other, size_t offset, size_t length){
|
|
||||||
if(offset+length>other.Length())
|
|
||||||
throw std::out_of_range("offset+length out of bounds");
|
|
||||||
Buffer buf(length);
|
|
||||||
buf.CopyFrom(other, length, offset);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
static Buffer Wrap(unsigned char* data, size_t size, std::function<void(void*)> freeFn, std::function<void*(void*, size_t)> reallocFn){
|
|
||||||
Buffer b=Buffer();
|
|
||||||
b.data=data;
|
|
||||||
b.length=size;
|
|
||||||
b.freeFn=freeFn;
|
|
||||||
b.reallocFn=reallocFn;
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
unsigned char* data;
|
|
||||||
size_t length;
|
|
||||||
std::function<void(void*)> freeFn;
|
|
||||||
std::function<void*(void*, size_t)> reallocFn;
|
|
||||||
};
|
|
||||||
|
|
||||||
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() const {
|
|
||||||
AVG_T avg=(AVG_T)0;
|
|
||||||
for(T i:data){
|
|
||||||
avg+=i;
|
|
||||||
}
|
|
||||||
return avg/(AVG_T)size;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVG_T Average(size_t firstN) const {
|
|
||||||
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() const {
|
|
||||||
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() const {
|
|
||||||
T min=std::numeric_limits<T>::max();
|
|
||||||
for(T i:data){
|
|
||||||
if(i<min)
|
|
||||||
min=i;
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
T Max() const {
|
|
||||||
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) const {
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
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() const {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
std::array<T, size> data;
|
|
||||||
ptrdiff_t offset=0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t bufSize, size_t bufCount> class BufferPool{
|
|
||||||
public:
|
|
||||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(BufferPool);
|
|
||||||
BufferPool(){
|
|
||||||
bufferStart=(unsigned char*)malloc(bufSize*bufCount);
|
|
||||||
if(!bufferStart)
|
|
||||||
throw std::bad_alloc();
|
|
||||||
};
|
|
||||||
~BufferPool(){
|
|
||||||
assert(usedBuffers.none());
|
|
||||||
free(bufferStart);
|
|
||||||
};
|
|
||||||
Buffer Get(){
|
|
||||||
auto freeFn=[this](void* _buf){
|
|
||||||
assert(_buf!=NULL);
|
|
||||||
unsigned char* buf=(unsigned char*)_buf;
|
|
||||||
size_t offset=buf-bufferStart;
|
|
||||||
assert(offset%bufSize==0);
|
|
||||||
size_t index=offset/bufSize;
|
|
||||||
assert(index<bufCount);
|
|
||||||
|
|
||||||
MutexGuard m(mutex);
|
|
||||||
assert(usedBuffers.test(index));
|
|
||||||
usedBuffers[index]=0;
|
|
||||||
};
|
|
||||||
auto resizeFn=[](void* buf, size_t newSize)->void*{
|
|
||||||
if(newSize>bufSize)
|
|
||||||
throw std::invalid_argument("newSize>bufferSize");
|
|
||||||
return buf;
|
|
||||||
};
|
|
||||||
MutexGuard m(mutex);
|
|
||||||
for(size_t i=0;i<bufCount;i++){
|
|
||||||
if(!usedBuffers[i]){
|
|
||||||
usedBuffers[i]=1;
|
|
||||||
return Buffer::Wrap(bufferStart+(bufSize*i), bufSize, freeFn, resizeFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::bitset<bufCount> usedBuffers;
|
|
||||||
unsigned char* bufferStart;
|
|
||||||
Mutex mutex;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_BUFFERINPUTSTREAM_H
|
|
|
@ -1,148 +0,0 @@
|
||||||
//
|
|
||||||
// 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){
|
|
||||||
for(int 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;
|
|
||||||
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::PacketLost(uint32_t seq){
|
|
||||||
for(int i=0;i<100;i++){
|
|
||||||
if(inflightPackets[i].seq==seq && inflightPackets[i].sendTime>0){
|
|
||||||
inflightPackets[i].sendTime=0;
|
|
||||||
inflightDataSize-=inflightPackets[i].size;
|
|
||||||
lossCount++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CongestionControl::Tick(){
|
|
||||||
tickCount++;
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
//
|
|
||||||
// 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 PacketLost(uint32_t seq);
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_CONGESTIONCONTROL_H
|
|
|
@ -1,248 +0,0 @@
|
||||||
//
|
|
||||||
// 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 "webrtc_dsp/modules/audio_processing/include/audio_processing.h"
|
|
||||||
#include "webrtc_dsp/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;
|
|
||||||
|
|
||||||
webrtc::Config extraConfig;
|
|
||||||
#ifdef TGVOIP_USE_DESKTOP_DSP
|
|
||||||
extraConfig.Set(new webrtc::DelayAgnostic(true));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
apm=webrtc::AudioProcessingBuilder().Create(extraConfig);
|
|
||||||
|
|
||||||
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;
|
|
||||||
apm->ApplyConfig(config);
|
|
||||||
|
|
||||||
webrtc::NoiseSuppression::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=webrtc::NoiseSuppression::Level::kLow;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
nsLevel=webrtc::NoiseSuppression::Level::kModerate;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
nsLevel=webrtc::NoiseSuppression::Level::kVeryHigh;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
default:
|
|
||||||
nsLevel=webrtc::NoiseSuppression::Level::kHigh;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
apm->noise_suppression()->set_level(nsLevel);
|
|
||||||
apm->noise_suppression()->Enable(enableNS);
|
|
||||||
if(enableAGC){
|
|
||||||
apm->gain_control()->set_mode(webrtc::GainControl::Mode::kAdaptiveDigital);
|
|
||||||
apm->gain_control()->set_target_level_dbfs(ServerConfig::GetSharedInstance()->GetInt("webrtc_agc_target_level", 9));
|
|
||||||
apm->gain_control()->enable_limiter(ServerConfig::GetSharedInstance()->GetBoolean("webrtc_agc_enable_limiter", true));
|
|
||||||
apm->gain_control()->set_compression_gain_db(ServerConfig::GetSharedInstance()->GetInt("webrtc_agc_compression_gain", 20));
|
|
||||||
}
|
|
||||||
apm->voice_detection()->set_likelihood(webrtc::VoiceDetection::Likelihood::kVeryLowLikelihood);
|
|
||||||
|
|
||||||
audioFrame=new webrtc::AudioFrame();
|
|
||||||
audioFrame->samples_per_channel_=480;
|
|
||||||
audioFrame->sample_rate_hz_=48000;
|
|
||||||
audioFrame->num_channels_=1;
|
|
||||||
|
|
||||||
farendQueue=new BlockingQueue<Buffer>(11);
|
|
||||||
running=true;
|
|
||||||
bufferFarendThread=new Thread(std::bind(&EchoCanceller::RunBufferFarendThread, this));
|
|
||||||
bufferFarendThread->SetName("VoipECBufferFarEnd");
|
|
||||||
bufferFarendThread->Start();
|
|
||||||
|
|
||||||
#else
|
|
||||||
this->enableAEC=this->enableAGC=enableAGC=this->enableNS=enableNS=false;
|
|
||||||
isOn=true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
EchoCanceller::~EchoCanceller(){
|
|
||||||
#ifndef TGVOIP_NO_DSP
|
|
||||||
farendQueue->Put(Buffer());
|
|
||||||
bufferFarendThread->Join();
|
|
||||||
delete bufferFarendThread;
|
|
||||||
delete farendQueue;
|
|
||||||
delete audioFrame;
|
|
||||||
delete apm;
|
|
||||||
#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
|
|
||||||
try{
|
|
||||||
Buffer buf=farendBufferPool.Get();
|
|
||||||
buf.CopyFrom(data, 0, 960*2);
|
|
||||||
farendQueue->Put(std::move(buf));
|
|
||||||
}catch(std::bad_alloc& x){
|
|
||||||
LOGW("Echo canceller can't keep up with real time");
|
|
||||||
}
|
|
||||||
#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;
|
|
||||||
while(running){
|
|
||||||
Buffer buf=farendQueue->GetBlocking();
|
|
||||||
if(buf.IsEmpty()){
|
|
||||||
LOGI("Echo canceller buffer farend thread exiting");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int16_t* samplesIn=(int16_t*)*buf;
|
|
||||||
memcpy(frame.mutable_data(), samplesIn, 480*2);
|
|
||||||
apm->ProcessReverseStream(&frame);
|
|
||||||
memcpy(frame.mutable_data(), samplesIn+480, 480*2);
|
|
||||||
apm->ProcessReverseStream(&frame);
|
|
||||||
didBufferFarend=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#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);
|
|
||||||
|
|
||||||
memcpy(audioFrame->mutable_data(), inOut, 480*2);
|
|
||||||
if(enableAEC)
|
|
||||||
apm->set_stream_delay_ms(delay);
|
|
||||||
apm->ProcessStream(audioFrame);
|
|
||||||
if(enableVAD)
|
|
||||||
hasVoice=apm->voice_detection()->stream_has_voice();
|
|
||||||
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);
|
|
||||||
if(enableVAD){
|
|
||||||
hasVoice=hasVoice || apm->voice_detection()->stream_has_voice();
|
|
||||||
}
|
|
||||||
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
|
|
||||||
apm->voice_detection()->Enable(enabled);
|
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
//
|
|
||||||
// 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<Buffer>* farendQueue;
|
|
||||||
BufferPool<960*2, 10> 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
|
|
|
@ -1,455 +0,0 @@
|
||||||
//
|
|
||||||
// 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){
|
|
||||||
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=Buffer::Wrap(data, len, [](void*){}, [](void* a, size_t)->void*{return a;});
|
|
||||||
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.IsEmpty()){
|
|
||||||
slots[i].buffer=Buffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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::Wrap(buffer, len, [](void*){}, [](void* a, size_t)->void*{return a;});
|
|
||||||
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.IsEmpty()){
|
|
||||||
slots[i].buffer=Buffer();
|
|
||||||
}
|
|
||||||
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.IsEmpty() && 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;
|
|
||||||
pkt->buffer.CopyFrom(slots[i].buffer, slots[i].size);
|
|
||||||
pkt->isEC=slots[i].isEC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slots[i].buffer=Buffer();
|
|
||||||
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.IsEmpty() && slots[i].timestamp==pkt->timestamp){
|
|
||||||
//LOGV("Found existing packet for timestamp %u, overwrite %d", pkt->timestamp, overwriteExisting);
|
|
||||||
if(overwriteExisting){
|
|
||||||
slots[i].buffer.CopyFrom(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, double(minDelay));
|
|
||||||
}
|
|
||||||
|
|
||||||
for(i=0;i<JITTER_SLOT_COUNT;i++){
|
|
||||||
if(!slots[i].buffer.IsEmpty()){
|
|
||||||
if(slots[i].timestamp<nextTimestamp-1){
|
|
||||||
slots[i].buffer=Buffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*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.IsEmpty())
|
|
||||||
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.IsEmpty() && slots[i].timestamp<bestTimestamp){
|
|
||||||
toRemove=i;
|
|
||||||
bestTimestamp=slots[i].timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Advance();
|
|
||||||
slots[toRemove].buffer=Buffer();
|
|
||||||
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;
|
|
||||||
slots[i].buffer.CopyFrom(pkt->buffer, pkt->size);
|
|
||||||
#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.IsEmpty())
|
|
||||||
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.store(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;
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <atomic>
|
|
||||||
#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{
|
|
||||||
Buffer buffer=Buffer();
|
|
||||||
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<JITTER_SLOT_SIZE, JITTER_SLOT_COUNT> bufferPool;
|
|
||||||
Mutex mutex;
|
|
||||||
jitter_packet_t slots[JITTER_SLOT_COUNT];
|
|
||||||
int64_t nextTimestamp=0;
|
|
||||||
uint32_t step;
|
|
||||||
std::atomic<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
|
|
|
@ -1,211 +0,0 @@
|
||||||
//
|
|
||||||
// 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){
|
|
||||||
std::lock_guard<std::mutex> lock(m_callback);
|
|
||||||
callback=f;
|
|
||||||
callbackParam=param;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t MediaStreamItf::InvokeCallback(unsigned char *data, size_t length){
|
|
||||||
std::lock_guard<std::mutex> lock(m_callback);
|
|
||||||
if(callback)
|
|
||||||
return (*callback)(data, length, callbackParam);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioMixer::AudioMixer() : 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();
|
|
||||||
Buffer buf=processedQueue.GetBlocking();
|
|
||||||
memcpy(data, *buf, 960*2);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
try{
|
|
||||||
Buffer data=bufferPool.Get();
|
|
||||||
//LOGV("Audio mixer processing a frame");
|
|
||||||
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(std::move(data));
|
|
||||||
}catch(std::bad_alloc& x){
|
|
||||||
LOGE("AudioMixer: no buffers left");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <mutex>
|
|
||||||
#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);
|
|
||||||
|
|
||||||
virtual ~MediaStreamItf() = default;
|
|
||||||
private:
|
|
||||||
size_t (*callback)(unsigned char*, size_t, void*) = NULL;
|
|
||||||
std::mutex m_callback;
|
|
||||||
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<960*2, 16> bufferPool;
|
|
||||||
BlockingQueue<Buffer> 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
|
|
|
@ -1,182 +0,0 @@
|
||||||
//
|
|
||||||
// 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)){
|
|
||||||
running=true;
|
|
||||||
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;
|
|
||||||
{
|
|
||||||
MutexGuard _m(queueAccessMutex);
|
|
||||||
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;
|
|
||||||
{
|
|
||||||
MutexGuard _m(queueAccessMutex);
|
|
||||||
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);
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return m.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageThread::InsertMessageInternal(MessageThread::Message &m){
|
|
||||||
MutexGuard _m(queueAccessMutex);
|
|
||||||
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){
|
|
||||||
MutexGuard _m(queueAccessMutex);
|
|
||||||
|
|
||||||
for(std::vector<Message>::iterator m=queue.begin();m!=queue.end();){
|
|
||||||
if(m->id==id){
|
|
||||||
m=queue.erase(m);
|
|
||||||
}else{
|
|
||||||
++m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageThread::CancelSelf(){
|
|
||||||
assert(IsCurrent());
|
|
||||||
cancelCurrent=true;
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
std::atomic<bool> running;
|
|
||||||
std::vector<Message> queue;
|
|
||||||
Mutex queueMutex;
|
|
||||||
Mutex queueAccessMutex;
|
|
||||||
uint32_t lastMessageID=1;
|
|
||||||
bool cancelCurrent=false;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE event;
|
|
||||||
#else
|
|
||||||
pthread_cond_t cond;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_MESSAGETHREAD_H
|
|
|
@ -1,650 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 29.03.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include "os/windows/NetworkSocketWinsock.h"
|
|
||||||
#else
|
|
||||||
#include "os/posix/NetworkSocketPosix.h"
|
|
||||||
#endif
|
|
||||||
#include "logging.h"
|
|
||||||
#include "VoIPServerConfig.h"
|
|
||||||
#include "VoIPController.h"
|
|
||||||
#include "Buffers.h"
|
|
||||||
#include "NetworkSocket.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(NetworkAddress *inet4addr, NetworkAddress *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
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress 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=Receive(len);
|
|
||||||
if(pkt.IsEmpty())
|
|
||||||
return 0;
|
|
||||||
size_t actualLen=std::min(len, pkt.data.Length());
|
|
||||||
memcpy(buffer, *pkt.data, actualLen);
|
|
||||||
return actualLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkAddress::operator==(const NetworkAddress &other) const{
|
|
||||||
if(isIPv6!=other.isIPv6)
|
|
||||||
return false;
|
|
||||||
if(!isIPv6){
|
|
||||||
return addr.ipv4==other.addr.ipv4;
|
|
||||||
}
|
|
||||||
return memcmp(addr.ipv6, other.addr.ipv6, 16)==0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkAddress::operator!=(const NetworkAddress &other) const{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NetworkAddress::ToString() const{
|
|
||||||
if(isIPv6){
|
|
||||||
#ifndef _WIN32
|
|
||||||
return NetworkSocketPosix::V6AddressToString(addr.ipv6);
|
|
||||||
#else
|
|
||||||
return NetworkSocketWinsock::V6AddressToString(addr.ipv6);
|
|
||||||
#endif
|
|
||||||
}else{
|
|
||||||
#ifndef _WIN32
|
|
||||||
return NetworkSocketPosix::V4AddressToString(addr.ipv4);
|
|
||||||
#else
|
|
||||||
return NetworkSocketWinsock::V4AddressToString(addr.ipv4);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkAddress::IsEmpty() const{
|
|
||||||
if(isIPv6){
|
|
||||||
const uint64_t* a=reinterpret_cast<const uint64_t*>(addr.ipv6);
|
|
||||||
return a[0]==0LL && a[1]==0LL;
|
|
||||||
}
|
|
||||||
return addr.ipv4==0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkAddress::PrefixMatches(const unsigned int prefix, const NetworkAddress &other) const{
|
|
||||||
if(isIPv6!=other.isIPv6)
|
|
||||||
return false;
|
|
||||||
if(!isIPv6){
|
|
||||||
uint32_t mask=0xFFFFFFFF << (32-prefix);
|
|
||||||
return (addr.ipv4 & mask) == (other.addr.ipv4 & mask);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkAddress::Empty(){
|
|
||||||
NetworkAddress addr;
|
|
||||||
addr.isIPv6=false;
|
|
||||||
addr.addr.ipv4=0;
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkAddress::IPv4(std::string str){
|
|
||||||
NetworkAddress addr;
|
|
||||||
addr.isIPv6=false;
|
|
||||||
#ifndef _WIN32
|
|
||||||
addr.addr.ipv4=NetworkSocketPosix::StringToV4Address(str);
|
|
||||||
#else
|
|
||||||
addr.addr.ipv4=NetworkSocketWinsock::StringToV4Address(str);
|
|
||||||
#endif
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkAddress::IPv4(uint32_t addr){
|
|
||||||
NetworkAddress a;
|
|
||||||
a.isIPv6=false;
|
|
||||||
a.addr.ipv4=addr;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkAddress::IPv6(std::string str){
|
|
||||||
NetworkAddress addr;
|
|
||||||
addr.isIPv6=false;
|
|
||||||
#ifndef _WIN32
|
|
||||||
NetworkSocketPosix::StringToV6Address(str, addr.addr.ipv6);
|
|
||||||
#else
|
|
||||||
NetworkSocketWinsock::StringToV6Address(str, addr.addr.ipv6);
|
|
||||||
#endif
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkAddress::IPv6(const uint8_t addr[16]){
|
|
||||||
NetworkAddress a;
|
|
||||||
a.isIPv6=true;
|
|
||||||
memcpy(a.addr.ipv6, addr, 16);
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(NetworkProtocol::TCP){
|
|
||||||
this->wrapped=wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocketTCPObfuscated::~NetworkSocketTCPObfuscated(){
|
|
||||||
if(wrapped)
|
|
||||||
delete wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocket *NetworkSocketTCPObfuscated::GetWrapped(){
|
|
||||||
return wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketTCPObfuscated::InitConnection(){
|
|
||||||
Buffer buf(64);
|
|
||||||
GenerateTCPO2States(*buf, &recvState, &sendState);
|
|
||||||
wrapped->Send(NetworkPacket{
|
|
||||||
std::move(buf),
|
|
||||||
NetworkAddress::Empty(),
|
|
||||||
0,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketTCPObfuscated::Send(NetworkPacket packet){
|
|
||||||
BufferOutputStream os(packet.data.Length()+4);
|
|
||||||
size_t len=packet.data.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);
|
|
||||||
EncryptForTCPO2(os.GetBuffer(), os.GetLength(), &sendState);
|
|
||||||
wrapped->Send(NetworkPacket{
|
|
||||||
Buffer(std::move(os)),
|
|
||||||
NetworkAddress::Empty(),
|
|
||||||
0,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
});
|
|
||||||
//LOGD("Sent %u bytes", os.GetLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkSocketTCPObfuscated::OnReadyToSend(){
|
|
||||||
LOGV("TCPO socket ready to send");
|
|
||||||
if(!initialized){
|
|
||||||
LOGV("Initializing TCPO2 connection");
|
|
||||||
initialized=true;
|
|
||||||
InitConnection();
|
|
||||||
readyToSend=true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return wrapped->OnReadyToSend();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket NetworkSocketTCPObfuscated::Receive(size_t maxLen){
|
|
||||||
unsigned char len1;
|
|
||||||
size_t packetLen=0;
|
|
||||||
size_t offset=0;
|
|
||||||
size_t len;
|
|
||||||
len=wrapped->Receive(&len1, 1);
|
|
||||||
if(len<=0){
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
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){
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
EncryptForTCPO2(len2, 3, &recvState);
|
|
||||||
packetLen=((size_t)len2[0] | ((size_t)len2[1] << 8) | ((size_t)len2[2] << 16))*4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(packetLen>1500){
|
|
||||||
LOGW("packet too big to fit into buffer (%u vs %u)", (unsigned int)packetLen, (unsigned int)1500);
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
Buffer buf(packetLen);
|
|
||||||
|
|
||||||
while(offset<packetLen){
|
|
||||||
len=wrapped->Receive(*buf, packetLen-offset);
|
|
||||||
if(len<=0){
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
offset+=len;
|
|
||||||
}
|
|
||||||
EncryptForTCPO2(*buf, packetLen, &recvState);
|
|
||||||
return NetworkPacket{
|
|
||||||
std::move(buf),
|
|
||||||
wrapped->GetConnectedAddress(),
|
|
||||||
wrapped->GetConnectedPort(),
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ? NetworkProtocol::UDP : NetworkProtocol::TCP){
|
|
||||||
this->tcp=tcp;
|
|
||||||
this->udp=udp;
|
|
||||||
this->username=username;
|
|
||||||
this->password=password;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocketSOCKS5Proxy::~NetworkSocketSOCKS5Proxy(){
|
|
||||||
delete tcp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketSOCKS5Proxy::Send(NetworkPacket packet){
|
|
||||||
if(protocol==NetworkProtocol::TCP){
|
|
||||||
tcp->Send(std::move(packet));
|
|
||||||
}else if(protocol==NetworkProtocol::UDP){
|
|
||||||
BufferOutputStream out(1500);
|
|
||||||
out.WriteInt16(0); // RSV
|
|
||||||
out.WriteByte(0); // FRAG
|
|
||||||
if(!packet.address.isIPv6){
|
|
||||||
out.WriteByte(1); // ATYP (IPv4)
|
|
||||||
out.WriteInt32(packet.address.addr.ipv4);
|
|
||||||
}else{
|
|
||||||
out.WriteByte(4); // ATYP (IPv6)
|
|
||||||
out.WriteBytes(packet.address.addr.ipv6, 16);
|
|
||||||
}
|
|
||||||
out.WriteInt16(htons(packet.port));
|
|
||||||
out.WriteBytes(packet.data);
|
|
||||||
udp->Send(NetworkPacket{
|
|
||||||
Buffer(std::move(out)),
|
|
||||||
connectedAddress,
|
|
||||||
connectedPort,
|
|
||||||
NetworkProtocol::UDP
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket NetworkSocketSOCKS5Proxy::Receive(size_t maxLen){
|
|
||||||
if(protocol==NetworkProtocol::TCP){
|
|
||||||
NetworkPacket packet=tcp->Receive();
|
|
||||||
packet.address=connectedAddress;
|
|
||||||
packet.port=connectedPort;
|
|
||||||
return packet;
|
|
||||||
}else{
|
|
||||||
NetworkPacket p=udp->Receive();
|
|
||||||
if(!p.IsEmpty() && p.address==connectedAddress && p.port==connectedPort){
|
|
||||||
BufferInputStream in(p.data);
|
|
||||||
in.ReadInt16(); // RSV
|
|
||||||
in.ReadByte(); // FRAG
|
|
||||||
unsigned char atyp=in.ReadByte();
|
|
||||||
NetworkAddress address=NetworkAddress::Empty();
|
|
||||||
if(atyp==1){ // IPv4
|
|
||||||
address=NetworkAddress::IPv4((uint32_t) in.ReadInt32());
|
|
||||||
}else if(atyp==4){ // IPv6
|
|
||||||
unsigned char addr[16];
|
|
||||||
in.ReadBytes(addr, 16);
|
|
||||||
address=NetworkAddress::IPv6(addr);
|
|
||||||
}
|
|
||||||
return NetworkPacket{
|
|
||||||
Buffer::CopyOf(p.data, in.GetOffset(), in.Remaining()),
|
|
||||||
address,
|
|
||||||
htons(in.ReadInt16()),
|
|
||||||
protocol
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketSOCKS5Proxy::Open(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketSOCKS5Proxy::Close(){
|
|
||||||
tcp->Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketSOCKS5Proxy::Connect(const NetworkAddress address, uint16_t port){
|
|
||||||
connectedAddress=address;
|
|
||||||
connectedPort=port;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocket *NetworkSocketSOCKS5Proxy::GetWrapped(){
|
|
||||||
return protocol==NetworkProtocol::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);
|
|
||||||
if(state==ConnectionState::Initial){
|
|
||||||
BufferOutputStream p(16);
|
|
||||||
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(NetworkPacket{
|
|
||||||
Buffer(std::move(p)),
|
|
||||||
NetworkAddress::Empty(),
|
|
||||||
0,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
});
|
|
||||||
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(512);
|
|
||||||
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(NetworkPacket{
|
|
||||||
Buffer(std::move(p)),
|
|
||||||
NetworkAddress::Empty(),
|
|
||||||
0,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
});
|
|
||||||
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==NetworkProtocol::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==NetworkProtocol::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=NetworkAddress::IPv4(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.IsEmpty()){
|
|
||||||
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=NetworkAddress::IPv6(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(){
|
|
||||||
BufferOutputStream out(1024);
|
|
||||||
if(protocol==NetworkProtocol::TCP){
|
|
||||||
out.WriteByte(5); // VER
|
|
||||||
out.WriteByte(1); // CMD (CONNECT)
|
|
||||||
out.WriteByte(0); // RSV
|
|
||||||
if(!connectedAddress.isIPv6){
|
|
||||||
out.WriteByte(1); // ATYP (IPv4)
|
|
||||||
out.WriteInt32(connectedAddress.addr.ipv4);
|
|
||||||
}else{
|
|
||||||
out.WriteByte(4); // ATYP (IPv6)
|
|
||||||
out.WriteBytes((unsigned char*)connectedAddress.addr.ipv6, 16);
|
|
||||||
}
|
|
||||||
out.WriteInt16(htons(connectedPort)); // DST.PORT
|
|
||||||
}else if(protocol==NetworkProtocol::UDP){
|
|
||||||
LOGV("Sending udp associate");
|
|
||||||
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(NetworkPacket{
|
|
||||||
Buffer(std::move(out)),
|
|
||||||
NetworkAddress::Empty(),
|
|
||||||
0,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
});
|
|
||||||
state=ConnectionState::WaitingForCommandResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkSocketSOCKS5Proxy::NeedSelectForSending(){
|
|
||||||
return state==ConnectionState::Initial || state==ConnectionState::Connected;
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 29.03.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef LIBTGVOIP_NETWORKSOCKET_H
|
|
||||||
#define LIBTGVOIP_NETWORKSOCKET_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <atomic>
|
|
||||||
#include "utils.h"
|
|
||||||
#include "Buffers.h"
|
|
||||||
|
|
||||||
namespace tgvoip {
|
|
||||||
|
|
||||||
enum class NetworkProtocol{
|
|
||||||
UDP=0,
|
|
||||||
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;
|
|
||||||
bool operator==(const NetworkAddress& other) const;
|
|
||||||
bool operator!=(const NetworkAddress& other) const;
|
|
||||||
virtual ~NetworkAddress()=default;
|
|
||||||
virtual bool IsEmpty() const;
|
|
||||||
virtual bool PrefixMatches(const unsigned int prefix, const NetworkAddress& other) const;
|
|
||||||
|
|
||||||
static NetworkAddress Empty();
|
|
||||||
static NetworkAddress IPv4(std::string str);
|
|
||||||
static NetworkAddress IPv4(uint32_t addr);
|
|
||||||
static NetworkAddress IPv6(std::string str);
|
|
||||||
static NetworkAddress IPv6(const uint8_t addr[16]);
|
|
||||||
|
|
||||||
bool isIPv6=false;
|
|
||||||
union{
|
|
||||||
uint32_t ipv4;
|
|
||||||
uint8_t ipv6[16];
|
|
||||||
} addr;
|
|
||||||
|
|
||||||
private:
|
|
||||||
NetworkAddress(){};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NetworkPacket{
|
|
||||||
TGVOIP_MOVE_ONLY(NetworkPacket);
|
|
||||||
Buffer data;
|
|
||||||
NetworkAddress address;
|
|
||||||
uint16_t port;
|
|
||||||
NetworkProtocol protocol;
|
|
||||||
|
|
||||||
static NetworkPacket Empty(){
|
|
||||||
return NetworkPacket{Buffer(), NetworkAddress::Empty(), 0, NetworkProtocol::UDP};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsEmpty(){
|
|
||||||
return data.IsEmpty() || (protocol==NetworkProtocol::UDP && (port==0 || address.IsEmpty()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 NetworkPacket Receive(size_t maxLen=0)=0;
|
|
||||||
size_t Receive(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(NetworkAddress* inet4addr, NetworkAddress* inet6addr);
|
|
||||||
virtual void OnActiveInterfaceChanged(){};
|
|
||||||
virtual NetworkAddress GetConnectedAddress(){ return NetworkAddress::Empty(); };
|
|
||||||
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 NetworkAddress 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];
|
|
||||||
std::atomic<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) override;
|
|
||||||
virtual NetworkPacket Receive(size_t maxLen) override;
|
|
||||||
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) override;
|
|
||||||
virtual NetworkPacket Receive(size_t maxLen) override;
|
|
||||||
virtual void Open() override;
|
|
||||||
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=NetworkAddress::Empty();
|
|
||||||
uint16_t connectedPort;
|
|
||||||
ConnectionState state=ConnectionState::Initial;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_NETWORKSOCKET_H
|
|
|
@ -1,287 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
#if defined HAVE_CONFIG_H || defined TGVOIP_USE_INSTALLED_OPUS
|
|
||||||
#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<Buffer>(33);
|
|
||||||
semaphore = new Semaphore(32, 0);
|
|
||||||
} else {
|
|
||||||
decodedQueue = NULL;
|
|
||||||
semaphore = NULL;
|
|
||||||
}
|
|
||||||
dec=opus_decoder_create(48000, 1, NULL);
|
|
||||||
if (needEC)
|
|
||||||
ecDec = opus_decoder_create(48000, 1, NULL);
|
|
||||||
else
|
|
||||||
ecDec = NULL;
|
|
||||||
// todo buffer = reinterpret_cast<unsigned char*>(aligned_alloc(2, 8192));
|
|
||||||
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 (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 (reinterpret_cast<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) {
|
|
||||||
Buffer lastDecoded=decodedQueue->GetBlocking();
|
|
||||||
if (lastDecoded.IsEmpty())
|
|
||||||
return 0;
|
|
||||||
memcpy(data, *lastDecoded, PACKET_SIZE);
|
|
||||||
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 = static_cast<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() {
|
|
||||||
LOGI("decoder: packets per frame %d", packetsPerFrame);
|
|
||||||
while(running) {
|
|
||||||
int playbackDuration = DecodeNextFrame();
|
|
||||||
for (int i = 0; i < playbackDuration / 20; i++) {
|
|
||||||
semaphore->Acquire();
|
|
||||||
if (!running) {
|
|
||||||
LOGI("==== decoder exiting ====");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Buffer buf=bufferPool.Get();
|
|
||||||
if (remainingDataLen > 0) {
|
|
||||||
for (effects::AudioEffect*& effect:postProcEffects) {
|
|
||||||
effect->Process(reinterpret_cast<int16_t*>(processedBuffer+(PACKET_SIZE * i)), 960);
|
|
||||||
}
|
|
||||||
buf.CopyFrom(processedBuffer + (PACKET_SIZE * i), 0, PACKET_SIZE);
|
|
||||||
} else {
|
|
||||||
//LOGE("Error decoding, result=%d", size);
|
|
||||||
memset(*buf, 0, PACKET_SIZE);
|
|
||||||
}
|
|
||||||
decodedQueue->Put(std::move(buf));
|
|
||||||
} catch (const std::bad_alloc&) {
|
|
||||||
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, reinterpret_cast<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, reinterpret_cast<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.999802f, 0.995062f, 0.984031f, 0.966778f, 0.943413f, 0.914084f, 0.878975f, 0.838309f, 0.792344f, 0.741368f,
|
|
||||||
0.685706f, 0.625708f, 0.561754f, 0.494249f, 0.423619f, 0.350311f, 0.274788f, 0.197527f, 0.119018f, 0.039757f};
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
samples[i] = static_cast<int16_t>(round(plcSamples[i] * coeffs[i] + samples[i] * (1.f - 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, reinterpret_cast<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(reinterpret_cast<int16_t*>(decodeBuffer),
|
|
||||||
reinterpret_cast<int16_t*>(processedBuffer));
|
|
||||||
} else if (playbackDuration == 40) {
|
|
||||||
processedBuffer = buffer;
|
|
||||||
audio::Resampler::Rescale60To40(reinterpret_cast<int16_t*>(decodeBuffer),
|
|
||||||
reinterpret_cast<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 it = std::find(postProcEffects.begin(), postProcEffects.end(), effect);
|
|
||||||
if (it != postProcEffects.end())
|
|
||||||
postProcEffects.erase(it);
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
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<Buffer>* decodedQueue;
|
|
||||||
BufferPool<960*2, 32> bufferPool;
|
|
||||||
unsigned char* buffer;
|
|
||||||
unsigned char* lastDecoded;
|
|
||||||
unsigned char* processedBuffer;
|
|
||||||
size_t outputBufferSize;
|
|
||||||
std::atomic<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;
|
|
||||||
std::atomic<bool> async;
|
|
||||||
alignas(2) unsigned char nextBuffer[8192];
|
|
||||||
alignas(2) unsigned char decodeBuffer[8192];
|
|
||||||
size_t nextLen;
|
|
||||||
unsigned int packetsPerFrame;
|
|
||||||
ptrdiff_t remainingDataLen;
|
|
||||||
bool prevWasEC;
|
|
||||||
int16_t prevLastSample;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_OPUSDECODER_H
|
|
|
@ -1,265 +0,0 @@
|
||||||
//
|
|
||||||
// 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"
|
|
||||||
#if defined HAVE_CONFIG_H || defined TGVOIP_USE_INSTALLED_OPUS
|
|
||||||
#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(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_AUTO));
|
|
||||||
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_BITRATE(8000));
|
|
||||||
}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->SetMaxPriority();
|
|
||||||
thread->Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip::OpusEncoder::Stop(){
|
|
||||||
if(!running)
|
|
||||||
return;
|
|
||||||
running=false;
|
|
||||||
queue.Put(Buffer());
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
int32_t r=opus_encode(enc, data, static_cast<int>(len), buffer, 4096);
|
|
||||||
// int bw;
|
|
||||||
// opus_encoder_ctl(enc, OPUS_GET_BANDWIDTH(&bw));
|
|
||||||
// LOGV("Opus bandwidth: %d", bw);
|
|
||||||
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){
|
|
||||||
assert(len==960*2);
|
|
||||||
OpusEncoder* e=(OpusEncoder*)param;
|
|
||||||
try{
|
|
||||||
Buffer buf=e->bufferPool.Get();
|
|
||||||
buf.CopyFrom(data, 0, 960*2);
|
|
||||||
e->queue.Put(std::move(buf));
|
|
||||||
}catch(std::bad_alloc& x){
|
|
||||||
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){
|
|
||||||
Buffer _packet=queue.GetBlocking();
|
|
||||||
if(!_packet.IsEmpty()){
|
|
||||||
int16_t* packet=(int16_t*)*_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));
|
|
||||||
if(secondaryEncoder){
|
|
||||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(currentBitrate));
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(vadNoVoiceBitrate));
|
|
||||||
if(secondaryEncoder){
|
|
||||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(vadNoVoiceBitrate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wasVadMode=true;
|
|
||||||
}else if(wasVadMode){
|
|
||||||
wasVadMode=false;
|
|
||||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(currentBitrate));
|
|
||||||
if(secondaryEncoder){
|
|
||||||
opus_encoder_ctl(secondaryEncoder, OPUS_SET_BITRATE(currentBitrate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Encode(frame, 960*packetsPerFrame);
|
|
||||||
bufferedCount=0;
|
|
||||||
frameHasVoice=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(std::function <void(unsigned char*, size_t, unsigned char*, size_t)> f){
|
|
||||||
callback=f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip::OpusEncoder::InvokeCallback(unsigned char *data, size_t length, unsigned char *secondaryData, size_t secondaryLength){
|
|
||||||
callback(data, length, secondaryData, secondaryLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
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(std::function <void(unsigned char*, size_t, unsigned char*, size_t)> callback);
|
|
||||||
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];
|
|
||||||
std::atomic<uint32_t> requestedBitrate;
|
|
||||||
uint32_t currentBitrate;
|
|
||||||
Thread* thread;
|
|
||||||
BlockingQueue<Buffer> queue;
|
|
||||||
BufferPool<960*2, 10> bufferPool;
|
|
||||||
EchoCanceller* echoCanceller;
|
|
||||||
std::atomic<int> complexity;
|
|
||||||
std::atomic<bool> running;
|
|
||||||
uint32_t frameDuration;
|
|
||||||
int packetLossPercent;
|
|
||||||
AudioLevelMeter* levelMeter;
|
|
||||||
std::atomic<bool> secondaryEncoderEnabled;
|
|
||||||
bool vadMode=false;
|
|
||||||
uint32_t vadNoVoiceBitrate;
|
|
||||||
std::vector<effects::AudioEffect*> postProcEffects;
|
|
||||||
int secondaryEnabledBandwidth;
|
|
||||||
int vadModeVoiceBandwidth;
|
|
||||||
int vadModeNoVoiceBandwidth;
|
|
||||||
|
|
||||||
bool wasSecondaryEncoderEnabled=false;
|
|
||||||
|
|
||||||
std::function <void(unsigned char*, size_t, unsigned char*, size_t)> callback;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_OPUSENCODER_H
|
|
|
@ -1,226 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 19.03.2018.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "PacketReassembler.h"
|
|
||||||
#include "logging.h"
|
|
||||||
#include "PrivateDefines.h"
|
|
||||||
#include "video/VideoFEC.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#define NUM_OLD_PACKETS 3
|
|
||||||
#define NUM_FEC_PACKETS 10
|
|
||||||
|
|
||||||
using namespace tgvoip;
|
|
||||||
using namespace tgvoip::video;
|
|
||||||
|
|
||||||
PacketReassembler::PacketReassembler(){
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketReassembler::~PacketReassembler(){
|
|
||||||
}
|
|
||||||
|
|
||||||
void PacketReassembler::Reset(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PacketReassembler::AddFragment(Buffer pkt, unsigned int fragmentIndex, unsigned int fragmentCount, uint32_t pts, uint8_t _fseq, bool keyframe, uint16_t rotation){
|
|
||||||
for(std::unique_ptr<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;
|
|
||||||
}
|
|
||||||
if(fragmentIndex>=packet->partCount){
|
|
||||||
LOGE("Received fragment index %u is greater than total %u", fragmentIndex, fragmentCount);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
packet->AddFragment(std::move(pkt), fragmentIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint32_t fseq=(lastFrameSeq & 0xFFFFFF00) | (uint32_t)_fseq;
|
|
||||||
if((uint8_t)lastFrameSeq>_fseq)
|
|
||||||
fseq+=256;
|
|
||||||
//LOGV("fseq: %u", (unsigned int)fseq);
|
|
||||||
|
|
||||||
/*if(pts<maxTimestamp){
|
|
||||||
LOGW("Received fragment doesn't belong here (ts=%u < maxTs=%u)", pts, maxTimestamp);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
if(lastFrameSeq>3 && fseq<lastFrameSeq-3){
|
|
||||||
LOGW("Packet too late (fseq=%u, lastFseq=%u)", fseq, lastFrameSeq);
|
|
||||||
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);
|
|
||||||
|
|
||||||
packets.push_back(std::unique_ptr<Packet>(new Packet(fseq, pts, fragmentCount, 0, keyframe, rotation)));
|
|
||||||
packets[packets.size()-1]->AddFragment(std::move(pkt), fragmentIndex);
|
|
||||||
while(packets.size()>3){
|
|
||||||
std::unique_ptr<Packet>& _old=packets[0];
|
|
||||||
if(_old->receivedPartCount==_old->partCount){
|
|
||||||
std::unique_ptr<Packet> old=std::move(packets[0]);
|
|
||||||
packets.erase(packets.begin());
|
|
||||||
|
|
||||||
Buffer buffer=old->Reassemble();
|
|
||||||
callback(std::move(buffer), old->seq, old->isKeyframe, old->rotation);
|
|
||||||
oldPackets.push_back(std::move(old));
|
|
||||||
while(oldPackets.size()>NUM_OLD_PACKETS)
|
|
||||||
oldPackets.erase(oldPackets.begin());
|
|
||||||
}else{
|
|
||||||
LOGW("Packet %u not reassembled (%u of %u)", packets[0]->seq, packets[0]->receivedPartCount, packets[0]->partCount);
|
|
||||||
if(packets[0]->partCount-packets[0]->receivedPartCount==1 && !waitingForFEC){
|
|
||||||
bool found=false;
|
|
||||||
for(FecPacket& fec:fecPackets){
|
|
||||||
if(packets[0]->seq<=fec.seq && packets[0]->seq>fec.seq-fec.prevFrameCount){
|
|
||||||
LOGI("Found FEC packet: %u %u", fec.seq, fec.prevFrameCount);
|
|
||||||
found=true;
|
|
||||||
TryDecodeFEC(fec);
|
|
||||||
packets.erase(packets.begin());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found){
|
|
||||||
waitingForFEC=true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
waitingForFEC=false;
|
|
||||||
LOGE("unrecoverable packet loss");
|
|
||||||
std::unique_ptr<Packet> old=std::move(packets[0]);
|
|
||||||
packets.erase(packets.begin());
|
|
||||||
oldPackets.push_back(std::move(old));
|
|
||||||
while(oldPackets.size()>NUM_OLD_PACKETS)
|
|
||||||
oldPackets.erase(oldPackets.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrameSeq=fseq;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PacketReassembler::AddFEC(Buffer data, uint8_t _fseq, unsigned int frameCount, unsigned int fecScheme){
|
|
||||||
uint32_t fseq=(lastFrameSeq & 0xFFFFFF00) | (uint32_t)_fseq;
|
|
||||||
std::ostringstream _s;
|
|
||||||
for(unsigned int i=0;i<frameCount;i++){
|
|
||||||
_s << (fseq-i);
|
|
||||||
_s << " ";
|
|
||||||
}
|
|
||||||
//LOGV("Received FEC packet: len %u, scheme %u, frames %s", (unsigned int)data.Length(), fecScheme, _s.str().c_str());
|
|
||||||
FecPacket fec{
|
|
||||||
fseq,
|
|
||||||
frameCount,
|
|
||||||
fecScheme,
|
|
||||||
std::move(data)
|
|
||||||
};
|
|
||||||
|
|
||||||
if(waitingForFEC){
|
|
||||||
if(packets[0]->seq<=fec.seq && packets[0]->seq>fec.seq-fec.prevFrameCount){
|
|
||||||
LOGI("Found FEC packet: %u %u", fec.seq, fec.prevFrameCount);
|
|
||||||
TryDecodeFEC(fec);
|
|
||||||
packets.erase(packets.begin());
|
|
||||||
waitingForFEC=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fecPackets.push_back(std::move(fec));
|
|
||||||
while(fecPackets.size()>NUM_FEC_PACKETS)
|
|
||||||
fecPackets.erase(fecPackets.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PacketReassembler::SetCallback(std::function<void(Buffer packet, uint32_t pts, bool keyframe, uint16_t rotation)> callback){
|
|
||||||
this->callback=callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PacketReassembler::TryDecodeFEC(PacketReassembler::FecPacket &fec){
|
|
||||||
/*LOGI("Decoding FEC");
|
|
||||||
|
|
||||||
std::vector<Buffer> packetsForRecovery;
|
|
||||||
for(std::unique_ptr<Packet>& p:oldPackets){
|
|
||||||
if(p->seq<=fec.seq && p->seq>fec.seq-fec.prevFrameCount){
|
|
||||||
LOGD("Adding frame %u from old", p->seq);
|
|
||||||
for(uint32_t i=0;i<p->partCount;i++){
|
|
||||||
packetsForRecovery.push_back(i<p->parts.size() ? Buffer::CopyOf(p->parts[i]) : Buffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::unique_ptr<Packet>& p:packets){
|
|
||||||
if(p->seq<=fec.seq && p->seq>fec.seq-fec.prevFrameCount){
|
|
||||||
LOGD("Adding frame %u from pending", p->seq);
|
|
||||||
for(uint32_t i=0;i<p->partCount;i++){
|
|
||||||
//LOGV("[%u] size %u", i, p.parts[i].Length());
|
|
||||||
packetsForRecovery.push_back(i<p->parts.size() ? Buffer::CopyOf(p->parts[i]) : Buffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fec.fecScheme==FEC_SCHEME_XOR){
|
|
||||||
Buffer recovered=ParityFEC::Decode(packetsForRecovery, fec.data);
|
|
||||||
LOGI("Recovered packet size %u", (unsigned int)recovered.Length());
|
|
||||||
if(!recovered.IsEmpty()){
|
|
||||||
std::unique_ptr<Packet>& pkt=packets[0];
|
|
||||||
if(pkt->parts.size()<pkt->partCount){
|
|
||||||
pkt->parts.push_back(std::move(recovered));
|
|
||||||
}else{
|
|
||||||
for(Buffer &b:pkt->parts){
|
|
||||||
if(b.IsEmpty()){
|
|
||||||
b=std::move(recovered);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkt->receivedPartCount++;
|
|
||||||
callback(pkt->Reassemble(), pkt->seq, pkt->isKeyframe, pkt->rotation);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Packet
|
|
||||||
|
|
||||||
void PacketReassembler::Packet::AddFragment(Buffer pkt, uint32_t fragmentIndex){
|
|
||||||
//LOGV("Add fragment %u/%u to packet %u", fragmentIndex, partCount, timestamp);
|
|
||||||
if(parts.size()==fragmentIndex){
|
|
||||||
parts.push_back(std::move(pkt));
|
|
||||||
//LOGV("add1");
|
|
||||||
}else if(parts.size()>fragmentIndex){
|
|
||||||
assert(parts[fragmentIndex].IsEmpty());
|
|
||||||
parts[fragmentIndex]=std::move(pkt);
|
|
||||||
//LOGV("add2");
|
|
||||||
}else{
|
|
||||||
while(parts.size()<fragmentIndex)
|
|
||||||
parts.push_back(Buffer());
|
|
||||||
parts.push_back(std::move(pkt));
|
|
||||||
//LOGV("add3");
|
|
||||||
}
|
|
||||||
receivedPartCount++;
|
|
||||||
//assert(parts.size()>=receivedPartCount);
|
|
||||||
if(parts.size()<receivedPartCount)
|
|
||||||
LOGW("Received %u parts but parts.size is %u", (unsigned int)receivedPartCount, (unsigned int)parts.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer PacketReassembler::Packet::Reassemble(){
|
|
||||||
assert(partCount==receivedPartCount);
|
|
||||||
assert(parts.size()==partCount);
|
|
||||||
if(partCount==1){
|
|
||||||
return Buffer::CopyOf(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));
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 19.03.2018.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef TGVOIP_PACKETREASSEMBLER_H
|
|
||||||
#define TGVOIP_PACKETREASSEMBLER_H
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <functional>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "Buffers.h"
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
namespace tgvoip {
|
|
||||||
class PacketReassembler{
|
|
||||||
public:
|
|
||||||
PacketReassembler();
|
|
||||||
virtual ~PacketReassembler();
|
|
||||||
|
|
||||||
void Reset();
|
|
||||||
void AddFragment(Buffer pkt, unsigned int fragmentIndex, unsigned int fragmentCount, uint32_t pts, uint8_t fseq, bool keyframe, uint16_t rotation);
|
|
||||||
void AddFEC(Buffer data, uint8_t fseq, unsigned int frameCount, unsigned int fecScheme);
|
|
||||||
void SetCallback(std::function<void(Buffer packet, uint32_t pts, bool keyframe, uint16_t rotation)> callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Packet{
|
|
||||||
uint32_t seq;
|
|
||||||
uint32_t timestamp;
|
|
||||||
uint32_t partCount;
|
|
||||||
uint32_t receivedPartCount;
|
|
||||||
bool isKeyframe;
|
|
||||||
uint16_t rotation;
|
|
||||||
std::vector<Buffer> parts;
|
|
||||||
|
|
||||||
Packet(uint32_t seq, uint32_t timestamp, uint32_t partCount, uint32_t receivedPartCount, bool keyframe, uint16_t rotation)
|
|
||||||
:seq(seq), timestamp(timestamp), partCount(partCount), receivedPartCount(receivedPartCount), isKeyframe(keyframe), rotation(rotation){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddFragment(Buffer pkt, uint32_t fragmentIndex);
|
|
||||||
Buffer Reassemble();
|
|
||||||
};
|
|
||||||
struct FecPacket{
|
|
||||||
uint32_t seq;
|
|
||||||
uint32_t prevFrameCount;
|
|
||||||
uint32_t fecScheme;
|
|
||||||
Buffer data;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool TryDecodeFEC(FecPacket& fec);
|
|
||||||
|
|
||||||
std::function<void(Buffer, uint32_t, bool, uint16_t)> callback;
|
|
||||||
std::vector<std::unique_ptr<Packet>> packets;
|
|
||||||
std::vector<std::unique_ptr<Packet>> oldPackets; // for FEC
|
|
||||||
std::vector<FecPacket> fecPackets;
|
|
||||||
uint32_t maxTimestamp=0;
|
|
||||||
uint32_t lastFrameSeq=0;
|
|
||||||
bool waitingForFEC=false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //TGVOIP_PACKETREASSEMBLER_H
|
|
|
@ -1,64 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 19/03/2019.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef LIBTGVOIP_PACKETSENDER_H
|
|
||||||
#define LIBTGVOIP_PACKETSENDER_H
|
|
||||||
|
|
||||||
#include "VoIPController.h"
|
|
||||||
#include <functional>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace tgvoip{
|
|
||||||
class PacketSender{
|
|
||||||
public:
|
|
||||||
PacketSender(VoIPController* controller) : controller(controller) {};
|
|
||||||
virtual ~PacketSender(){};
|
|
||||||
virtual void PacketAcknowledged(uint32_t seq, double sendTime, double ackTime, uint8_t type, uint32_t size)=0;
|
|
||||||
virtual void PacketLost(uint32_t seq, uint8_t type, uint32_t size)=0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void SendExtra(Buffer& data, unsigned char type){
|
|
||||||
controller->SendExtra(data, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IncrementUnsentStreamPackets(){
|
|
||||||
controller->unsentStreamPackets++;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t SendPacket(VoIPController::PendingOutgoingPacket pkt){
|
|
||||||
uint32_t seq=controller->GenerateOutSeq();
|
|
||||||
pkt.seq=seq;
|
|
||||||
controller->SendOrEnqueuePacket(std::move(pkt), true, this);
|
|
||||||
return seq;
|
|
||||||
}
|
|
||||||
|
|
||||||
double GetConnectionInitTime(){
|
|
||||||
return controller->connectionInitTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HistoricBuffer<double, 32>& RTTHistory(){
|
|
||||||
return controller->rttHistory;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageThread& GetMessageThread(){
|
|
||||||
return controller->messageThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VoIPController::ProtocolInfo& GetProtocolInfo(){
|
|
||||||
return controller->protocolInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendStreamFlags(VoIPController::Stream& stm){
|
|
||||||
controller->SendStreamFlags(stm);
|
|
||||||
}
|
|
||||||
|
|
||||||
const VoIPController::Config& GetConfig(){
|
|
||||||
return controller->config;
|
|
||||||
}
|
|
||||||
|
|
||||||
VoIPController* controller;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_PACKETSENDER_H
|
|
|
@ -1,142 +0,0 @@
|
||||||
//
|
|
||||||
// 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_FLAG_PAUSED 8
|
|
||||||
|
|
||||||
#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 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
|
|
||||||
|
|
||||||
#define FEC_SCHEME_XOR 1
|
|
||||||
#define FEC_SCHEME_CM256 2
|
|
||||||
|
|
||||||
#endif //TGVOIP_PRIVATEDEFINES_H
|
|
|
@ -1,426 +0,0 @@
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "TgVoip.h"
|
|
||||||
|
|
||||||
#include "VoIPController.h"
|
|
||||||
#include "VoIPServerConfig.h"
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
#ifndef TGVOIP_USE_CUSTOM_CRYPTO
|
|
||||||
extern "C" {
|
|
||||||
#include <openssl/sha.h>
|
|
||||||
#include <openssl/aes.h>
|
|
||||||
//#include <openssl/modes.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_aes_ige_encrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){
|
|
||||||
AES_KEY akey;
|
|
||||||
AES_set_encrypt_key(key, 32*8, &akey);
|
|
||||||
AES_ige_encrypt(in, out, length, &akey, iv, AES_ENCRYPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_aes_ige_decrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){
|
|
||||||
AES_KEY akey;
|
|
||||||
AES_set_decrypt_key(key, 32*8, &akey);
|
|
||||||
AES_ige_encrypt(in, out, length, &akey, iv, AES_DECRYPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_rand_bytes(uint8_t* buffer, size_t len){
|
|
||||||
RAND_bytes(buffer, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_sha1(uint8_t* msg, size_t len, uint8_t* output){
|
|
||||||
SHA1(msg, len, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_sha256(uint8_t* msg, size_t len, uint8_t* output){
|
|
||||||
SHA256(msg, len, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_aes_ctr_encrypt(uint8_t* inout, size_t length, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num){
|
|
||||||
AES_KEY akey;
|
|
||||||
AES_set_encrypt_key(key, 32*8, &akey);
|
|
||||||
AES_ctr128_encrypt(inout, inout, length, &akey, iv, ecount, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_aes_cbc_encrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){
|
|
||||||
AES_KEY akey;
|
|
||||||
AES_set_encrypt_key(key, 256, &akey);
|
|
||||||
AES_cbc_encrypt(in, out, length, &akey, iv, AES_ENCRYPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tgvoip_openssl_aes_cbc_decrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){
|
|
||||||
AES_KEY akey;
|
|
||||||
AES_set_decrypt_key(key, 256, &akey);
|
|
||||||
AES_cbc_encrypt(in, out, length, &akey, iv, AES_DECRYPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
tgvoip::CryptoFunctions tgvoip::VoIPController::crypto={
|
|
||||||
tgvoip_openssl_rand_bytes,
|
|
||||||
tgvoip_openssl_sha1,
|
|
||||||
tgvoip_openssl_sha256,
|
|
||||||
tgvoip_openssl_aes_ige_encrypt,
|
|
||||||
tgvoip_openssl_aes_ige_decrypt,
|
|
||||||
tgvoip_openssl_aes_ctr_encrypt,
|
|
||||||
tgvoip_openssl_aes_cbc_encrypt,
|
|
||||||
tgvoip_openssl_aes_cbc_decrypt
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
class TgVoipImpl : public TgVoip {
|
|
||||||
public:
|
|
||||||
TgVoipImpl(
|
|
||||||
std::vector<TgVoipEndpoint> const &endpoints,
|
|
||||||
TgVoipPersistentState const &persistentState,
|
|
||||||
std::unique_ptr<TgVoipProxy> const &proxy,
|
|
||||||
TgVoipConfig const &config,
|
|
||||||
TgVoipEncryptionKey const &encryptionKey,
|
|
||||||
TgVoipNetworkType initialNetworkType
|
|
||||||
#ifdef TGVOIP_USE_CUSTOM_CRYPTO
|
|
||||||
,
|
|
||||||
TgVoipCrypto const &crypto
|
|
||||||
#endif
|
|
||||||
#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO
|
|
||||||
,
|
|
||||||
TgVoipAudioDataCallbacks const &audioDataCallbacks
|
|
||||||
#endif
|
|
||||||
) {
|
|
||||||
#ifdef TGVOIP_USE_CUSTOM_CRYPTO
|
|
||||||
tgvoip::VoIPController::crypto.sha1 = crypto.sha1;
|
|
||||||
tgvoip::VoIPController::crypto.sha256 = crypto.sha256;
|
|
||||||
tgvoip::VoIPController::crypto.rand_bytes = crypto.rand_bytes;
|
|
||||||
tgvoip::VoIPController::crypto.aes_ige_encrypt = crypto.aes_ige_encrypt;
|
|
||||||
tgvoip::VoIPController::crypto.aes_ige_decrypt = crypto.aes_ige_decrypt;
|
|
||||||
tgvoip::VoIPController::crypto.aes_ctr_encrypt = crypto.aes_ctr_encrypt;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
controller_ = new tgvoip::VoIPController();
|
|
||||||
controller_->implData = this;
|
|
||||||
|
|
||||||
#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO
|
|
||||||
controller_->SetAudioDataCallbacks(audioDataCallbacks.input, audioDataCallbacks.output, audioDataCallbacks.preprocessed);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
controller_->SetPersistentState(persistentState.value);
|
|
||||||
|
|
||||||
if (proxy != nullptr) {
|
|
||||||
controller_->SetProxy(tgvoip::PROXY_SOCKS5, proxy->host, proxy->port, proxy->login, proxy->password);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto callbacks = tgvoip::VoIPController::Callbacks();
|
|
||||||
callbacks.connectionStateChanged = &TgVoipImpl::controllerStateCallback;
|
|
||||||
callbacks.groupCallKeyReceived = nullptr;
|
|
||||||
callbacks.groupCallKeySent = nullptr;
|
|
||||||
callbacks.signalBarCountChanged = &TgVoipImpl::signalBarsCallback;
|
|
||||||
callbacks.upgradeToGroupCallRequested = nullptr;
|
|
||||||
controller_->SetCallbacks(callbacks);
|
|
||||||
|
|
||||||
std::vector<tgvoip::Endpoint> mappedEndpoints;
|
|
||||||
for (auto endpoint : endpoints) {
|
|
||||||
tgvoip::Endpoint::Type mappedType;
|
|
||||||
switch (endpoint.type) {
|
|
||||||
case TgVoipEndpointType::UdpRelay:
|
|
||||||
mappedType = tgvoip::Endpoint::Type::UDP_RELAY;
|
|
||||||
break;
|
|
||||||
case TgVoipEndpointType::Lan:
|
|
||||||
mappedType = tgvoip::Endpoint::Type::UDP_P2P_LAN;
|
|
||||||
break;
|
|
||||||
case TgVoipEndpointType::Inet:
|
|
||||||
mappedType = tgvoip::Endpoint::Type::UDP_P2P_INET;
|
|
||||||
break;
|
|
||||||
case TgVoipEndpointType::TcpRelay:
|
|
||||||
mappedType = tgvoip::Endpoint::Type::TCP_RELAY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mappedType = tgvoip::Endpoint::Type::UDP_RELAY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tgvoip::IPv4Address address(endpoint.host.ipv4);
|
|
||||||
tgvoip::IPv6Address addressv6(endpoint.host.ipv6);
|
|
||||||
|
|
||||||
mappedEndpoints.emplace_back(endpoint.endpointId, endpoint.port, address, addressv6, mappedType, endpoint.peerTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
int mappedDataSaving;
|
|
||||||
switch (config.dataSaving) {
|
|
||||||
case TgVoipDataSaving::Mobile:
|
|
||||||
mappedDataSaving = tgvoip::DATA_SAVING_MOBILE;
|
|
||||||
break;
|
|
||||||
case TgVoipDataSaving::Always:
|
|
||||||
mappedDataSaving = tgvoip::DATA_SAVING_ALWAYS;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mappedDataSaving = tgvoip::DATA_SAVING_NEVER;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tgvoip::VoIPController::Config mappedConfig(
|
|
||||||
config.initializationTimeout,
|
|
||||||
config.receiveTimeout,
|
|
||||||
mappedDataSaving,
|
|
||||||
config.enableAEC,
|
|
||||||
config.enableNS,
|
|
||||||
config.enableAGC,
|
|
||||||
config.enableCallUpgrade
|
|
||||||
);
|
|
||||||
mappedConfig.logFilePath = config.logPath;
|
|
||||||
mappedConfig.statsDumpFilePath = {};
|
|
||||||
|
|
||||||
controller_->SetConfig(mappedConfig);
|
|
||||||
|
|
||||||
setNetworkType(initialNetworkType);
|
|
||||||
|
|
||||||
std::vector<uint8_t> encryptionKeyValue = encryptionKey.value;
|
|
||||||
controller_->SetEncryptionKey(reinterpret_cast<char*>(encryptionKeyValue.data()), encryptionKey.isOutgoing);
|
|
||||||
controller_->SetRemoteEndpoints(mappedEndpoints, config.enableP2P, config.maxApiLayer);
|
|
||||||
|
|
||||||
controller_->Start();
|
|
||||||
|
|
||||||
controller_->Connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
~TgVoipImpl() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOnStateUpdated(std::function<void(TgVoipState)> onStateUpdated) {
|
|
||||||
std::lock_guard<std::mutex> lock(m_onStateUpdated);
|
|
||||||
onStateUpdated_ = onStateUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOnSignalBarsUpdated(std::function<void(int)> onSignalBarsUpdated) {
|
|
||||||
std::lock_guard<std::mutex> lock(m_onSignalBarsUpdated);
|
|
||||||
onSignalBarsUpdated_ = onSignalBarsUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNetworkType(TgVoipNetworkType networkType) override {
|
|
||||||
int mappedType;
|
|
||||||
|
|
||||||
switch (networkType) {
|
|
||||||
case TgVoipNetworkType::Unknown:
|
|
||||||
mappedType = tgvoip::NET_TYPE_UNKNOWN;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Gprs:
|
|
||||||
mappedType = tgvoip::NET_TYPE_GPRS;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Edge:
|
|
||||||
mappedType = tgvoip::NET_TYPE_EDGE;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::ThirdGeneration:
|
|
||||||
mappedType = tgvoip::NET_TYPE_3G;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Hspa:
|
|
||||||
mappedType = tgvoip::NET_TYPE_HSPA;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Lte:
|
|
||||||
mappedType = tgvoip::NET_TYPE_LTE;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::WiFi:
|
|
||||||
mappedType = tgvoip::NET_TYPE_WIFI;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Ethernet:
|
|
||||||
mappedType = tgvoip::NET_TYPE_ETHERNET;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::OtherHighSpeed:
|
|
||||||
mappedType = tgvoip::NET_TYPE_OTHER_HIGH_SPEED;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::OtherLowSpeed:
|
|
||||||
mappedType = tgvoip::NET_TYPE_OTHER_LOW_SPEED;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::OtherMobile:
|
|
||||||
mappedType = tgvoip::NET_TYPE_OTHER_MOBILE;
|
|
||||||
break;
|
|
||||||
case TgVoipNetworkType::Dialup:
|
|
||||||
mappedType = tgvoip::NET_TYPE_DIALUP;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mappedType = tgvoip::NET_TYPE_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
controller_->SetNetworkType(mappedType);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMuteMicrophone(bool muteMicrophone) override {
|
|
||||||
controller_->SetMicMute(muteMicrophone);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAudioOutputGainControlEnabled(bool enabled) override {
|
|
||||||
controller_->SetAudioOutputGainControlEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEchoCancellationStrength(int strength) override {
|
|
||||||
controller_->SetEchoCancellationStrength(strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getLastError() override {
|
|
||||||
switch (controller_->GetLastError()) {
|
|
||||||
case tgvoip::ERROR_INCOMPATIBLE: return "ERROR_INCOMPATIBLE";
|
|
||||||
case tgvoip::ERROR_TIMEOUT: return "ERROR_TIMEOUT";
|
|
||||||
case tgvoip::ERROR_AUDIO_IO: return "ERROR_AUDIO_IO";
|
|
||||||
case tgvoip::ERROR_PROXY: return "ERROR_PROXY";
|
|
||||||
default: return "ERROR_UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getDebugInfo() override {
|
|
||||||
return controller_->GetDebugString();
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t getPreferredRelayId() override {
|
|
||||||
return controller_->GetPreferredRelayID();
|
|
||||||
}
|
|
||||||
|
|
||||||
TgVoipTrafficStats getTrafficStats() override {
|
|
||||||
tgvoip::VoIPController::TrafficStats stats;
|
|
||||||
controller_->GetStats(&stats);
|
|
||||||
return {
|
|
||||||
.bytesSentWifi = stats.bytesSentWifi,
|
|
||||||
.bytesReceivedWifi = stats.bytesRecvdWifi,
|
|
||||||
.bytesSentMobile = stats.bytesSentMobile,
|
|
||||||
.bytesReceivedMobile = stats.bytesRecvdMobile
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
TgVoipPersistentState getPersistentState() override {
|
|
||||||
return {controller_->GetPersistentState()};
|
|
||||||
}
|
|
||||||
|
|
||||||
TgVoipFinalState stop() override {
|
|
||||||
controller_->Stop();
|
|
||||||
|
|
||||||
TgVoipFinalState finalState = {
|
|
||||||
.persistentState = getPersistentState(),
|
|
||||||
.debugLog = controller_->GetDebugLog(),
|
|
||||||
.trafficStats = getTrafficStats(),
|
|
||||||
.isRatingSuggested = controller_->NeedRate()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete controller_;
|
|
||||||
controller_ = nullptr;
|
|
||||||
|
|
||||||
return finalState;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void controllerStateCallback(tgvoip::VoIPController* controller, int state) {
|
|
||||||
TgVoipImpl* self = reinterpret_cast<TgVoipImpl*>(controller->implData);
|
|
||||||
std::lock_guard<std::mutex> lock(self->m_onStateUpdated);
|
|
||||||
|
|
||||||
if (self->onStateUpdated_) {
|
|
||||||
TgVoipState mappedState;
|
|
||||||
switch (state) {
|
|
||||||
case tgvoip::STATE_WAIT_INIT:
|
|
||||||
mappedState = TgVoipState::WaitInit;
|
|
||||||
break;
|
|
||||||
case tgvoip::STATE_WAIT_INIT_ACK:
|
|
||||||
mappedState = TgVoipState::WaitInitAck;
|
|
||||||
break;
|
|
||||||
case tgvoip::STATE_ESTABLISHED:
|
|
||||||
mappedState = TgVoipState::Estabilished;
|
|
||||||
break;
|
|
||||||
case tgvoip::STATE_FAILED:
|
|
||||||
mappedState = TgVoipState::Failed;
|
|
||||||
break;
|
|
||||||
case tgvoip::STATE_RECONNECTING:
|
|
||||||
mappedState = TgVoipState::Reconnecting;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mappedState = TgVoipState::Estabilished;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->onStateUpdated_(mappedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signalBarsCallback(tgvoip::VoIPController* controller, int signalBars) {
|
|
||||||
TgVoipImpl* self = reinterpret_cast<TgVoipImpl*>(controller->implData);
|
|
||||||
std::lock_guard<std::mutex> lock(self->m_onSignalBarsUpdated);
|
|
||||||
|
|
||||||
if (self->onSignalBarsUpdated_) {
|
|
||||||
self->onSignalBarsUpdated_(signalBars);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
tgvoip::VoIPController *controller_;
|
|
||||||
std::function<void(TgVoipState)> onStateUpdated_;
|
|
||||||
std::function<void(int)> onSignalBarsUpdated_;
|
|
||||||
std::mutex m_onStateUpdated, m_onSignalBarsUpdated;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::function<void(std::string const &)> globalLoggingFunction;
|
|
||||||
|
|
||||||
void __tgvoip_call_tglog(const char *format, ...){
|
|
||||||
va_list vaArgs;
|
|
||||||
va_start(vaArgs, format);
|
|
||||||
|
|
||||||
va_list vaCopy;
|
|
||||||
va_copy(vaCopy, vaArgs);
|
|
||||||
const int length = std::vsnprintf(nullptr, 0, format, vaCopy);
|
|
||||||
va_end(vaCopy);
|
|
||||||
|
|
||||||
std::vector<char> zc(length + 1);
|
|
||||||
std::vsnprintf(zc.data(), zc.size(), format, vaArgs);
|
|
||||||
va_end(vaArgs);
|
|
||||||
|
|
||||||
if (globalLoggingFunction != nullptr) {
|
|
||||||
globalLoggingFunction(std::string(zc.data(), zc.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TgVoip::setLoggingFunction(std::function<void(std::string const &)> loggingFunction) {
|
|
||||||
globalLoggingFunction = loggingFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TgVoip::setGlobalServerConfig(const std::string &serverConfig) {
|
|
||||||
tgvoip::ServerConfig::GetSharedInstance()->Update(serverConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TgVoip::getConnectionMaxLayer() {
|
|
||||||
return tgvoip::VoIPController::GetConnectionMaxLayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string TgVoip::getVersion() {
|
|
||||||
return tgvoip::VoIPController::GetVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
TgVoip *TgVoip::makeInstance(
|
|
||||||
TgVoipConfig const &config,
|
|
||||||
TgVoipPersistentState const &persistentState,
|
|
||||||
std::vector<TgVoipEndpoint> const &endpoints,
|
|
||||||
std::unique_ptr<TgVoipProxy> const &proxy,
|
|
||||||
TgVoipNetworkType initialNetworkType,
|
|
||||||
TgVoipEncryptionKey const &encryptionKey
|
|
||||||
#ifdef TGVOIP_USE_CUSTOM_CRYPTO
|
|
||||||
,
|
|
||||||
TgVoipCrypto const &crypto
|
|
||||||
#endif
|
|
||||||
#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO
|
|
||||||
,
|
|
||||||
TgVoipAudioDataCallbacks const &audioDataCallbacks
|
|
||||||
#endif
|
|
||||||
) {
|
|
||||||
return new TgVoipImpl(
|
|
||||||
endpoints,
|
|
||||||
persistentState,
|
|
||||||
proxy,
|
|
||||||
config,
|
|
||||||
encryptionKey,
|
|
||||||
initialNetworkType
|
|
||||||
#ifdef TGVOIP_USE_CUSTOM_CRYPTO
|
|
||||||
,
|
|
||||||
crypto
|
|
||||||
#endif
|
|
||||||
#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO
|
|
||||||
,
|
|
||||||
audioDataCallbacks
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TgVoip::~TgVoip() = default;
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,887 +0,0 @@
|
||||||
//
|
|
||||||
// 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.6"
|
|
||||||
|
|
||||||
#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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// API compatibility
|
|
||||||
struct IPv4Address{
|
|
||||||
IPv4Address(std::string addr) : addr(addr){};
|
|
||||||
std::string addr;
|
|
||||||
};
|
|
||||||
struct IPv6Address{
|
|
||||||
IPv6Address(std::string addr) : addr(addr){};
|
|
||||||
std::string addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
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(int64_t id, uint16_t port, const NetworkAddress address, const NetworkAddress v6address, Type type, unsigned char* peerTag);
|
|
||||||
Endpoint();
|
|
||||||
~Endpoint();
|
|
||||||
const NetworkAddress& GetAddress() const;
|
|
||||||
NetworkAddress& GetAddress();
|
|
||||||
bool IsIPv6Only() const;
|
|
||||||
int64_t CleanID() const;
|
|
||||||
int64_t id;
|
|
||||||
uint16_t port;
|
|
||||||
NetworkAddress address;
|
|
||||||
NetworkAddress v6address;
|
|
||||||
Type type;
|
|
||||||
unsigned char peerTag[16];
|
|
||||||
|
|
||||||
private:
|
|
||||||
double lastPingTime;
|
|
||||||
uint32_t lastPingSeq;
|
|
||||||
HistoricBuffer<double, 6> rtts;
|
|
||||||
HistoricBuffer<double, 4> selfRtts;
|
|
||||||
std::map<int64_t, double> udpPingTimes;
|
|
||||||
double averageRTT;
|
|
||||||
std::shared_ptr<NetworkSocket> socket;
|
|
||||||
int udpPongCount;
|
|
||||||
int totalUdpPings=0;
|
|
||||||
int totalUdpPingReplies=0;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 PacketSender;
|
|
||||||
namespace video{
|
|
||||||
class VideoPacketSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
class VoIPController{
|
|
||||||
friend class VoIPGroupController;
|
|
||||||
friend class PacketSender;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
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
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
bool paused=false;
|
|
||||||
int resolution;
|
|
||||||
unsigned int width=0;
|
|
||||||
unsigned int height=0;
|
|
||||||
uint16_t rotation=0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProtocolInfo{
|
|
||||||
uint32_t version;
|
|
||||||
uint32_t maxVideoResolution;
|
|
||||||
std::vector<uint32_t> videoDecoders;
|
|
||||||
bool videoCaptureSupported;
|
|
||||||
bool videoDisplaySupported;
|
|
||||||
bool callUpgradeSupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
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;
|
|
||||||
PacketSender* sender;
|
|
||||||
bool lost;
|
|
||||||
};
|
|
||||||
struct QueuedPacket{
|
|
||||||
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, PacketSender* source);
|
|
||||||
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 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);
|
|
||||||
std::shared_ptr<Stream> GetStreamByID(unsigned char id, bool outgoing);
|
|
||||||
Endpoint* GetEndpointForPacket(const PendingOutgoingPacket& pkt);
|
|
||||||
bool SendOrEnqueuePacket(PendingOutgoingPacket pkt, bool enqueue=true, PacketSender* source=NULL);
|
|
||||||
static std::string NetworkTypeToString(int type);
|
|
||||||
CellularCarrierInfo GetCarrierInfo();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct UnacknowledgedExtraData{
|
|
||||||
unsigned char type;
|
|
||||||
Buffer data;
|
|
||||||
uint32_t firstContainingSeq;
|
|
||||||
};
|
|
||||||
struct RecentIncomingPacket{
|
|
||||||
uint32_t seq;
|
|
||||||
double recvTime;
|
|
||||||
};
|
|
||||||
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 RawPendingOutgoingPacket{
|
|
||||||
TGVOIP_MOVE_ONLY(RawPendingOutgoingPacket);
|
|
||||||
NetworkPacket packet;
|
|
||||||
std::shared_ptr<NetworkSocket> socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
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 TickJitterBufferAndCongestionControl();
|
|
||||||
void ResetUdpAvailability();
|
|
||||||
std::string GetPacketTypeString(unsigned char type);
|
|
||||||
void SetupOutgoingVideoStream();
|
|
||||||
bool WasOutgoingPacketAcknowledged(uint32_t seq);
|
|
||||||
RecentOutgoingPacket* GetRecentOutgoingPacket(uint32_t seq);
|
|
||||||
void NetworkPacketReceived(std::shared_ptr<NetworkPacket> packet);
|
|
||||||
void TrySendQueuedPackets();
|
|
||||||
|
|
||||||
int state;
|
|
||||||
std::map<int64_t, Endpoint> endpoints;
|
|
||||||
int64_t currentEndpoint=0;
|
|
||||||
int64_t preferredRelay=0;
|
|
||||||
int64_t peerPreferredRelay=0;
|
|
||||||
std::atomic<bool> runReceiver;
|
|
||||||
std::atomic<uint32_t> seq;
|
|
||||||
uint32_t lastRemoteSeq;
|
|
||||||
uint32_t lastRemoteAckSeq;
|
|
||||||
uint32_t lastSentSeq;
|
|
||||||
std::vector<RecentOutgoingPacket> recentOutgoingPackets;
|
|
||||||
std::vector<uint32_t> recentIncomingPackets;
|
|
||||||
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;
|
|
||||||
std::atomic<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;
|
|
||||||
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;
|
|
||||||
NetworkAddress resolvedProxyAddress=NetworkAddress::Empty();
|
|
||||||
|
|
||||||
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;
|
|
||||||
NetworkAddress myIPv6=NetworkAddress::Empty();
|
|
||||||
bool shittyInternetMode;
|
|
||||||
int extraEcLevel=0;
|
|
||||||
std::vector<Buffer> ecAudioPackets;
|
|
||||||
bool didAddIPv6Relays;
|
|
||||||
bool didSendIPv6Endpoint;
|
|
||||||
int publicEndpointsReqCount=0;
|
|
||||||
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;
|
|
||||||
BufferPool<1024, 32> outgoingAudioBufferPool;
|
|
||||||
BlockingQueue<RawPendingOutgoingPacket> rawSendQueue;
|
|
||||||
|
|
||||||
uint32_t initTimeoutID=MessageThread::INVALID_ID;
|
|
||||||
uint32_t udpPingTimeoutID=MessageThread::INVALID_ID;
|
|
||||||
|
|
||||||
effects::Volume outputVolume;
|
|
||||||
effects::Volume inputVolume;
|
|
||||||
|
|
||||||
std::vector<uint32_t> peerVideoDecoders;
|
|
||||||
|
|
||||||
MessageThread messageThread;
|
|
||||||
|
|
||||||
// Locked whenever the endpoints vector is modified (but not endpoints themselves) and whenever iterated outside of messageThread.
|
|
||||||
// After the call is started, only messageThread is allowed to modify the endpoints vector.
|
|
||||||
Mutex endpointsMutex;
|
|
||||||
// Locked while audio i/o is being initialized and deinitialized so as to allow it to fully initialize before deinitialization begins.
|
|
||||||
Mutex audioIOMutex;
|
|
||||||
|
|
||||||
#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;
|
|
||||||
uint32_t lastReceivedVideoFrameNumber=UINT32_MAX;
|
|
||||||
|
|
||||||
video::VideoPacketSender* videoPacketSender=NULL;
|
|
||||||
uint32_t sendLosses=0;
|
|
||||||
uint32_t unacknowledgedIncomingPacketCount=0;
|
|
||||||
|
|
||||||
ProtocolInfo protocolInfo={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;
|
|
||||||
uint32_t unackNopThreshold;
|
|
||||||
|
|
||||||
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, NetworkAddress reflectorAddress, NetworkAddress 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, PacketSender* sender=NULL);
|
|
||||||
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
|
|
|
@ -1,816 +0,0 @@
|
||||||
//
|
|
||||||
// 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, NetworkAddress reflectorAddress, NetworkAddress 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, PacketSender* source){
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
//
|
|
||||||
// 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();
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,65 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <atomic>
|
|
||||||
|
|
||||||
#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();
|
|
||||||
std::atomic<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();
|
|
||||||
std::atomic<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 */
|
|
|
@ -1,41 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,123 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 01.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include "Resampler.h"
|
|
||||||
|
|
||||||
using namespace tgvoip::audio;
|
|
||||||
static constexpr 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;
|
|
||||||
for (unsigned int offset = 0; offset < outLen; offset++) {
|
|
||||||
float offsetf = offset * 160.0f / 147.0f;
|
|
||||||
float factor = offsetf - floorf(offsetf);
|
|
||||||
to[offset] = static_cast<int16_t>(from[static_cast<size_t>(floorf(offsetf))] * (1 - factor) +
|
|
||||||
from[static_cast<size_t>(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] = static_cast<int16_t>(from[static_cast<size_t>(floorf(offsetf))] * (1 - factor) +
|
|
||||||
from[static_cast<size_t>(ceilf(offsetf))] * factor);
|
|
||||||
}
|
|
||||||
return outLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t Resampler::Convert(int16_t* from, int16_t* to, size_t fromLen, size_t toLen, size_t num, size_t denom) {
|
|
||||||
size_t outLen = fromLen * num / denom;
|
|
||||||
if (toLen < outLen)
|
|
||||||
outLen = toLen;
|
|
||||||
unsigned int offset;
|
|
||||||
for (offset = 0; offset < outLen; offset++) {
|
|
||||||
float offsetf = offset * static_cast<float>(denom) / static_cast<float>(num);
|
|
||||||
float factor = offsetf - floorf(offsetf);
|
|
||||||
to[offset] = static_cast<int16_t>(from[static_cast<size_t>(floorf(offsetf))] * (1 - factor) +
|
|
||||||
from[static_cast<size_t>(ceilf(offsetf))] * factor);
|
|
||||||
}
|
|
||||||
return outLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Resampler::Rescale60To80(int16_t* in, int16_t* out) {
|
|
||||||
std::memcpy(out, in, 960 * 2);
|
|
||||||
std::memcpy(out + 960 * 3, in + 960 * 2, 960 * 2);
|
|
||||||
for (int i = 0; i < 960; i++) {
|
|
||||||
out[960 + i] = static_cast<int16_t>(((static_cast<int32_t>(in[960 + i]) * hann[959 - i]) >> 15) +
|
|
||||||
((static_cast<int32_t>(in[480 + i]) * hann[i]) >> 15));
|
|
||||||
out[1920 + i] = static_cast<int16_t>(((static_cast<int32_t>(in[960 + 480 + i]) * hann[959 - i]) >> 15) +
|
|
||||||
((static_cast<int32_t>(in[480 + 480 + i]) * hann[i]) >> 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Resampler::Rescale60To40(int16_t* in, int16_t* out) {
|
|
||||||
for (int i = 0; i < 960; i++) {
|
|
||||||
out[i] = static_cast<int16_t>(((static_cast<int32_t>(in[i]) * hann[959 - i]) >> 15) +
|
|
||||||
((static_cast<int32_t>(in[480 + i]) * hann[i]) >> 15));
|
|
||||||
out[960 + i] = static_cast<int16_t>(((static_cast<int32_t>(in[1920 + i]) * hann[i]) >> 15) +
|
|
||||||
((static_cast<int32_t>(in[1440 + i]) * hann[959 - i]) >> 15));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//
|
|
||||||
// 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, size_t num, size_t denom);
|
|
||||||
static void Rescale60To80(int16_t* in, int16_t* out);
|
|
||||||
static void Rescale60To40(int16_t* in, int16_t* out);
|
|
||||||
};
|
|
||||||
} // namespace audio
|
|
||||||
} // namespace tgvoip
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_RESAMPLER_H
|
|
|
@ -1,795 +0,0 @@
|
||||||
/* 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
|
|
|
@ -1,104 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,73 +0,0 @@
|
||||||
//
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,137 +0,0 @@
|
||||||
//
|
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,107 +0,0 @@
|
||||||
//
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,171 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,66 +0,0 @@
|
||||||
//
|
|
||||||
// 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 void DoWithJNI(std::function<void(JNIEnv*)> f){
|
|
||||||
JNIEnv *env=NULL;
|
|
||||||
bool didAttach=false;
|
|
||||||
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
|
|
||||||
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
|
|
|
@ -1,48 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
//
|
|
||||||
// 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, streamPaused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoRendererAndroid::SetStreamPaused(bool paused){
|
|
||||||
streamPaused=paused;
|
|
||||||
Request req{
|
|
||||||
Buffer(0),
|
|
||||||
Request::Type::UpdateStreamState
|
|
||||||
};
|
|
||||||
queue.Put(std::move(req));
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
virtual void SetStreamPaused(bool paused) 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;
|
|
||||||
bool streamPaused=false;
|
|
||||||
uint32_t codec;
|
|
||||||
uint16_t rotation=0;
|
|
||||||
jobject jobj;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_VIDEORENDERERANDROID_H
|
|
|
@ -1,92 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Grishka on 12.08.2018.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "VideoSourceAndroid.h"
|
|
||||||
#include "JNIUtilities.h"
|
|
||||||
#include "../../logging.h"
|
|
||||||
#include "../../PrivateDefines.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){
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoSourceAndroid::SetStreamPaused(bool paused){
|
|
||||||
streamStateCallback(paused);
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
void SetStreamPaused(bool paused);
|
|
||||||
|
|
||||||
static std::vector<uint32_t> availableEncoders;
|
|
||||||
private:
|
|
||||||
jobject javaObject;
|
|
||||||
jmethodID prepareEncoderMethod;
|
|
||||||
jmethodID startMethod;
|
|
||||||
jmethodID stopMethod;
|
|
||||||
jmethodID requestKeyFrameMethod;
|
|
||||||
jmethodID setBitrateMethod;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_VIDEOSOURCEANDROID_H
|
|
|
@ -1,73 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,38 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,308 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,84 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,39 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <atomic>
|
|
||||||
#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:
|
|
||||||
std::atomic<bool> isPlaying;
|
|
||||||
unsigned char remainingData[10240];
|
|
||||||
size_t remainingDataSize;
|
|
||||||
AudioUnitIO* io;
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
|
|
@ -1,365 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,321 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,58 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <atomic>
|
|
||||||
#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;
|
|
||||||
std::atomic<bool> inputEnabled;
|
|
||||||
std::atomic<bool> outputEnabled;
|
|
||||||
bool started;
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
|
|
||||||
#endif /* LIBTGVOIP_AUDIOUNITIO_H */
|
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,43 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
virtual void SetRotation(uint16_t rotation) override;
|
|
||||||
virtual void SetStreamPaused(bool paused) override;
|
|
||||||
static int GetMaximumResolution();
|
|
||||||
static std::vector<uint32_t> GetAvailableDecoders();
|
|
||||||
private:
|
|
||||||
TGVVideoRenderer* renderer;
|
|
||||||
CMFormatDescriptionRef formatDesc=NULL;
|
|
||||||
bool needReset=false;
|
|
||||||
bool streamEnabled=false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER */
|
|
|
@ -1,3 +0,0 @@
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
extern void (*TGVoipLoggingFunction)(NSString *);
|
|
|
@ -1,48 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
- (void)_setSizeWidth: (uint16_t)width height: (uint16_t)height;
|
|
||||||
- (void)_setRotation: (uint16_t)rotation;
|
|
||||||
- (void)_setStopped;
|
|
||||||
- (void)_setPaused;
|
|
||||||
- (void)_setResumed;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,51 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
|
|
||||||
#ifdef __OBJC__
|
|
||||||
@class TGVVideoSource;
|
|
||||||
#else
|
|
||||||
typedef struct objc_object TGVVideoSource;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace tgvoip{
|
|
||||||
namespace video{
|
|
||||||
class VideoToolboxEncoderSource : public VideoSource{
|
|
||||||
public:
|
|
||||||
VideoToolboxEncoderSource(TGVVideoSource* parent);
|
|
||||||
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);
|
|
||||||
void SetStreamPaused(bool paused);
|
|
||||||
static std::vector<uint32_t> GetAvailableEncoders();
|
|
||||||
static bool SupportsFullHD();
|
|
||||||
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;
|
|
||||||
unsigned int lastFrameRate=0;
|
|
||||||
TGVVideoSource* objcObject;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE */
|
|
|
@ -1,175 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,204 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,177 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,187 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,288 +0,0 @@
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,644 +0,0 @@
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
#include <NetworkSocket.h>
|
|
||||||
|
|
||||||
extern JavaVM* sharedJVM;
|
|
||||||
extern jclass jniUtilitiesClass;
|
|
||||||
#else
|
|
||||||
#include <ifaddrs.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace tgvoip;
|
|
||||||
|
|
||||||
|
|
||||||
NetworkSocketPosix::NetworkSocketPosix(NetworkProtocol protocol) : NetworkSocket(protocol){
|
|
||||||
needUpdateNat64Prefix=true;
|
|
||||||
nat64Present=false;
|
|
||||||
switchToV6at=0;
|
|
||||||
isV4Available=false;
|
|
||||||
fd=-1;
|
|
||||||
closing=false;
|
|
||||||
|
|
||||||
tcpConnectedPort=0;
|
|
||||||
|
|
||||||
if(protocol==NetworkProtocol::TCP)
|
|
||||||
timeout=10.0;
|
|
||||||
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocketPosix::~NetworkSocketPosix(){
|
|
||||||
if(fd>=0){
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.data.IsEmpty() || (protocol==NetworkProtocol::UDP && packet.port==0)){
|
|
||||||
LOGW("tried to send null packet");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int res;
|
|
||||||
if(protocol==NetworkProtocol::UDP){
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
if(!packet.address.isIPv6){
|
|
||||||
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])=packet.address.addr.ipv4;
|
|
||||||
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{
|
|
||||||
memcpy(addr.sin6_addr.s6_addr, packet.address.addr.ipv6, 16);
|
|
||||||
addr.sin6_family=AF_INET6;
|
|
||||||
}
|
|
||||||
addr.sin6_port=htons(packet.port);
|
|
||||||
std::lock_guard<std::mutex> lock(m_fd);
|
|
||||||
res = static_cast<int>(sendto(fd, *packet.data, packet.data.Length(), 0,
|
|
||||||
reinterpret_cast<sockaddr*>(&addr), sizeof(addr)));
|
|
||||||
} else {
|
|
||||||
std::lock_guard<std::mutex> lock(m_fd);
|
|
||||||
res = static_cast<int>(send(fd, *packet.data, packet.data.Length(), 0));
|
|
||||||
}
|
|
||||||
if(res<=0){
|
|
||||||
if(errno==EAGAIN || errno==EWOULDBLOCK){
|
|
||||||
if(!pendingOutgoingPacket.IsEmpty()){
|
|
||||||
LOGE("Got EAGAIN but there's already a pending packet");
|
|
||||||
failed=true;
|
|
||||||
}else{
|
|
||||||
LOGV("Socket %d not ready to send", int(fd));
|
|
||||||
pendingOutgoingPacket=std::move(packet);
|
|
||||||
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.data.Length() && packet.protocol==NetworkProtocol::TCP){
|
|
||||||
if(!pendingOutgoingPacket.IsEmpty()){
|
|
||||||
LOGE("send returned less than packet length but there's already a pending packet");
|
|
||||||
failed=true;
|
|
||||||
}else{
|
|
||||||
LOGV("Socket %d not ready to send", int(fd));
|
|
||||||
pendingOutgoingPacket=std::move(packet);
|
|
||||||
readyToSend=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkSocketPosix::OnReadyToSend(){
|
|
||||||
if (!pendingOutgoingPacket.IsEmpty()) {
|
|
||||||
Send(std::move(pendingOutgoingPacket));
|
|
||||||
pendingOutgoingPacket = NetworkPacket::Empty();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readyToSend = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket NetworkSocketPosix::Receive(size_t maxLen) {
|
|
||||||
if (maxLen == 0)
|
|
||||||
maxLen = INT32_MAX;
|
|
||||||
if (failed) {
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
if (protocol == NetworkProtocol::UDP) {
|
|
||||||
int addrLen = sizeof(sockaddr_in6);
|
|
||||||
sockaddr_in6 srcAddr;
|
|
||||||
ssize_t len = recvfrom(fd, *recvBuffer, std::min(recvBuffer.Length(), maxLen), 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
|
|
||||||
if (len > 0) {
|
|
||||||
if (!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)) {
|
|
||||||
isV4Available = true;
|
|
||||||
LOGI("Detected IPv4 connectivity, will not try IPv6");
|
|
||||||
}
|
|
||||||
NetworkAddress addr = NetworkAddress::Empty();
|
|
||||||
if (IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12) == 0)) {
|
|
||||||
in_addr v4addr = *(reinterpret_cast<in_addr*>(&srcAddr.sin6_addr.s6_addr[12]));
|
|
||||||
addr = NetworkAddress::IPv4(v4addr.s_addr);
|
|
||||||
} else {
|
|
||||||
addr = NetworkAddress::IPv6(srcAddr.sin6_addr.s6_addr);
|
|
||||||
}
|
|
||||||
return NetworkPacket {
|
|
||||||
Buffer::CopyOf(recvBuffer, 0, (size_t)len),
|
|
||||||
addr,
|
|
||||||
ntohs(srcAddr.sin6_port),
|
|
||||||
NetworkProtocol::UDP
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
LOGE("error receiving %d / %s", errno, strerror(errno));
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
|
|
||||||
}else if(protocol==NetworkProtocol::TCP){
|
|
||||||
ssize_t res=(int)recv(fd, *recvBuffer, std::min(recvBuffer.Length(), maxLen), 0);
|
|
||||||
if(res<=0){
|
|
||||||
LOGE("Error receiving from TCP socket: %d / %s", errno, strerror(errno));
|
|
||||||
failed=true;
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}else{
|
|
||||||
return NetworkPacket{
|
|
||||||
Buffer::CopyOf(recvBuffer, 0, (size_t)res),
|
|
||||||
tcpConnectedAddress,
|
|
||||||
tcpConnectedPort,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketPosix::Open(){
|
|
||||||
if(protocol!=NetworkProtocol::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();
|
|
||||||
if(fcntl(fd, F_SETFL, O_NONBLOCK)==-1){
|
|
||||||
LOGE("error setting nonblock flag on socket: %d / %s", errno, strerror(errno));
|
|
||||||
failed=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
flag=1;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(flag));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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(){
|
|
||||||
if (closing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
closing = true;
|
|
||||||
failed = true;
|
|
||||||
|
|
||||||
if (fd >= 0) {
|
|
||||||
shutdown(fd, SHUT_RDWR);
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketPosix::Connect(const NetworkAddress address, uint16_t port){
|
|
||||||
struct sockaddr_in v4={0};
|
|
||||||
struct sockaddr_in6 v6={0};
|
|
||||||
struct sockaddr* addr=NULL;
|
|
||||||
size_t addrLen=0;
|
|
||||||
if(!address.isIPv6){
|
|
||||||
v4.sin_family=AF_INET;
|
|
||||||
v4.sin_addr.s_addr=address.addr.ipv4;
|
|
||||||
v4.sin_port=htons(port);
|
|
||||||
addr=reinterpret_cast<sockaddr*>(&v4);
|
|
||||||
addrLen=sizeof(v4);
|
|
||||||
}else{
|
|
||||||
v6.sin6_family=AF_INET6;
|
|
||||||
memcpy(v6.sin6_addr.s6_addr, address.addr.ipv6, 16);
|
|
||||||
v6.sin6_flowinfo=0;
|
|
||||||
v6.sin6_scope_id=0;
|
|
||||||
v6.sin6_port=htons(port);
|
|
||||||
addr=reinterpret_cast<sockaddr*>(&v6);
|
|
||||||
addrLen=sizeof(v6);
|
|
||||||
}
|
|
||||||
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=address;
|
|
||||||
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(NetworkAddress *v4addr, NetworkAddress *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=NetworkAddress::IPv4(ipchars);
|
|
||||||
env->ReleaseStringUTFChars(jipv4, ipchars);
|
|
||||||
}
|
|
||||||
if(v6addr && jipv6){
|
|
||||||
const char* ipchars=env->GetStringUTFChars(jipv6, NULL);
|
|
||||||
*v6addr=NetworkAddress::IPv6(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=NetworkAddress::IPv4(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=NetworkAddress::IPv6(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkSocketPosix::ResolveDomainName(std::string name){
|
|
||||||
addrinfo* addr0;
|
|
||||||
NetworkAddress ret=NetworkAddress::Empty();
|
|
||||||
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=NetworkAddress::IPv4(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(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;
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <mutex>
|
|
||||||
#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() override;
|
|
||||||
virtual void Send(NetworkPacket packet) override;
|
|
||||||
virtual NetworkPacket Receive(size_t maxLen) override;
|
|
||||||
virtual void Open() override;
|
|
||||||
virtual void Close() override;
|
|
||||||
virtual void Connect(const NetworkAddress address, uint16_t port) override;
|
|
||||||
virtual std::string GetLocalInterfaceInfo(NetworkAddress* v4addr, NetworkAddress* 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 NetworkAddress 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);
|
|
||||||
std::atomic<int> fd;
|
|
||||||
std::mutex m_fd;
|
|
||||||
bool needUpdateNat64Prefix;
|
|
||||||
bool nat64Present;
|
|
||||||
double switchToV6at;
|
|
||||||
std::atomic<bool> isV4Available;
|
|
||||||
std::atomic<bool> closing;
|
|
||||||
NetworkAddress tcpConnectedAddress = NetworkAddress::Empty();
|
|
||||||
uint16_t tcpConnectedPort;
|
|
||||||
NetworkPacket pendingOutgoingPacket = NetworkPacket::Empty();
|
|
||||||
Buffer recvBuffer = Buffer(2048);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
|
|
@ -1,459 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,105 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,170 +0,0 @@
|
||||||
//
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,455 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,103 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
|
@ -1,165 +0,0 @@
|
||||||
//
|
|
||||||
// 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 "AudioOutputWave.h"
|
|
||||||
#include "../../logging.h"
|
|
||||||
#include "../../VoIPController.h"
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 960
|
|
||||||
#define CHECK_ERROR(res, msg) if(res!=MMSYSERR_NOERROR){wchar_t _buf[1024]; waveOutGetErrorTextW(res, _buf, 1024); LOGE(msg ": %ws (MMRESULT=0x%08X)", _buf, res); failed=true;}
|
|
||||||
|
|
||||||
using namespace tgvoip::audio;
|
|
||||||
|
|
||||||
AudioOutputWave::AudioOutputWave(std::string deviceID){
|
|
||||||
isPlaying=false;
|
|
||||||
hWaveOut=NULL;
|
|
||||||
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
ZeroMemory(&buffers[i], sizeof(WAVEHDR));
|
|
||||||
buffers[i].dwBufferLength=960*2;
|
|
||||||
buffers[i].lpData=(char*)malloc(960*2);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetCurrentDevice(deviceID);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioOutputWave::~AudioOutputWave(){
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
free(buffers[i].lpData);
|
|
||||||
}
|
|
||||||
waveOutClose(hWaveOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioOutputWave::Start(){
|
|
||||||
if(!isPlaying){
|
|
||||||
isPlaying=true;
|
|
||||||
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
MMRESULT res=waveOutPrepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutPrepareHeader failed");
|
|
||||||
//InvokeCallback((unsigned char*)buffers[i].lpData, buffers[i].dwBufferLength);
|
|
||||||
ZeroMemory(buffers[i].lpData, buffers[i].dwBufferLength);
|
|
||||||
res=waveOutWrite(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutWrite failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioOutputWave::Stop(){
|
|
||||||
if(isPlaying){
|
|
||||||
isPlaying=false;
|
|
||||||
|
|
||||||
MMRESULT res=waveOutReset(hWaveOut);
|
|
||||||
CHECK_ERROR(res, "waveOutReset failed");
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
res=waveOutUnprepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutUnprepareHeader failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioOutputWave::IsPlaying(){
|
|
||||||
return isPlaying;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioOutputWave::WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
|
||||||
if(uMsg==WOM_DONE){
|
|
||||||
((AudioOutputWave*)dwInstance)->OnBufferDone((WAVEHDR*)dwParam1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioOutputWave::OnBufferDone(WAVEHDR* hdr){
|
|
||||||
if(!isPlaying)
|
|
||||||
return;
|
|
||||||
|
|
||||||
InvokeCallback((unsigned char*)hdr->lpData, hdr->dwBufferLength);
|
|
||||||
hdr->dwFlags&= ~WHDR_DONE;
|
|
||||||
MMRESULT res=waveOutWrite(hWaveOut, hdr, sizeof(WAVEHDR));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioOutputWave::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
|
|
||||||
UINT num=waveOutGetNumDevs();
|
|
||||||
WAVEOUTCAPSW caps;
|
|
||||||
char nameBuf[512];
|
|
||||||
for(UINT i=0;i<num;i++){
|
|
||||||
waveOutGetDevCapsW(i, &caps, sizeof(caps));
|
|
||||||
AudioOutputDevice 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 AudioOutputWave::SetCurrentDevice(std::string deviceID){
|
|
||||||
currentDevice=deviceID;
|
|
||||||
|
|
||||||
bool wasPlaying=isPlaying;
|
|
||||||
isPlaying=false;
|
|
||||||
LOGV("closing, hWaveOut=%d", (int)hWaveOut);
|
|
||||||
if(hWaveOut){
|
|
||||||
MMRESULT res;
|
|
||||||
if(isPlaying){
|
|
||||||
res=waveOutReset(hWaveOut);
|
|
||||||
CHECK_ERROR(res, "waveOutReset failed");
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
res=waveOutUnprepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutUnprepareHeader failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res=waveOutClose(hWaveOut);
|
|
||||||
CHECK_ERROR(res, "waveOutClose 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=waveOutOpen(&hWaveOut, WAVE_MAPPER, &format, (DWORD_PTR)AudioOutputWave::WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
||||||
CHECK_ERROR(res, "waveOutOpen failed");
|
|
||||||
}else{
|
|
||||||
UINT num=waveOutGetNumDevs();
|
|
||||||
WAVEOUTCAPSW caps;
|
|
||||||
char nameBuf[512];
|
|
||||||
hWaveOut=NULL;
|
|
||||||
for(UINT i=0;i<num;i++){
|
|
||||||
waveOutGetDevCapsW(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=waveOutOpen(&hWaveOut, i, &format, (DWORD_PTR)AudioOutputWave::WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION | WAVE_MAPPED);
|
|
||||||
CHECK_ERROR(res, "waveOutOpen failed");
|
|
||||||
LOGD("Opened device %s", nameBuf);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!hWaveOut){
|
|
||||||
SetCurrentDevice("default");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isPlaying=wasPlaying;
|
|
||||||
|
|
||||||
if(isPlaying){
|
|
||||||
MMRESULT res;
|
|
||||||
for(int i=0;i<4;i++){
|
|
||||||
res=waveOutPrepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutPrepareHeader failed");
|
|
||||||
res=waveOutWrite(hWaveOut, &buffers[i], sizeof(WAVEHDR));
|
|
||||||
CHECK_ERROR(res, "waveOutWrite failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// 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_AUDIOOUTPUTWAVE_H
|
|
||||||
#define LIBTGVOIP_AUDIOOUTPUTWAVE_H
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "../../audio/AudioOutput.h"
|
|
||||||
|
|
||||||
namespace tgvoip{
|
|
||||||
namespace audio{
|
|
||||||
|
|
||||||
class AudioOutputWave : public AudioOutput{
|
|
||||||
public:
|
|
||||||
AudioOutputWave(std::string deviceID);
|
|
||||||
virtual ~AudioOutputWave();
|
|
||||||
virtual void Start();
|
|
||||||
virtual void Stop();
|
|
||||||
virtual bool IsPlaying();
|
|
||||||
virtual void SetCurrentDevice(std::string deviceID);
|
|
||||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
|
||||||
|
|
||||||
private:
|
|
||||||
HWAVEOUT hWaveOut;
|
|
||||||
WAVEFORMATEX format;
|
|
||||||
WAVEHDR buffers[4];
|
|
||||||
static void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
|
||||||
void OnBufferDone(WAVEHDR* hdr);
|
|
||||||
bool isPlaying;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_AUDIOOUTPUTWAVE_H
|
|
|
@ -1,469 +0,0 @@
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <collection.h>
|
|
||||||
#include "CXWrapper.h"
|
|
||||||
#include <wrl.h>
|
|
||||||
#include <robuffer.h>
|
|
||||||
|
|
||||||
using namespace Windows::Storage::Streams;
|
|
||||||
using namespace Microsoft::WRL;
|
|
||||||
using namespace libtgvoip;
|
|
||||||
using namespace Platform;
|
|
||||||
using namespace tgvoip;
|
|
||||||
using namespace Windows::Security::Cryptography;
|
|
||||||
using namespace Windows::Security::Cryptography::Core;
|
|
||||||
using namespace Windows::Storage::Streams;
|
|
||||||
using namespace Windows::Data::Json;
|
|
||||||
using namespace Windows::Phone::Media::Devices;
|
|
||||||
|
|
||||||
//CryptographicHash^ MicrosoftCryptoImpl::sha1Hash;
|
|
||||||
//CryptographicHash^ MicrosoftCryptoImpl::sha256Hash;
|
|
||||||
HashAlgorithmProvider^ MicrosoftCryptoImpl::sha1Provider;
|
|
||||||
HashAlgorithmProvider^ MicrosoftCryptoImpl::sha256Provider;
|
|
||||||
SymmetricKeyAlgorithmProvider^ MicrosoftCryptoImpl::aesKeyProvider;
|
|
||||||
|
|
||||||
/*struct tgvoip_cx_data{
|
|
||||||
VoIPControllerWrapper^ self;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
VoIPControllerWrapper::VoIPControllerWrapper(){
|
|
||||||
VoIPController::crypto.aes_ige_decrypt=MicrosoftCryptoImpl::AesIgeDecrypt;
|
|
||||||
VoIPController::crypto.aes_ige_encrypt=MicrosoftCryptoImpl::AesIgeEncrypt;
|
|
||||||
VoIPController::crypto.aes_ctr_encrypt = MicrosoftCryptoImpl::AesCtrEncrypt;
|
|
||||||
VoIPController::crypto.sha1=MicrosoftCryptoImpl::SHA1;
|
|
||||||
VoIPController::crypto.sha256=MicrosoftCryptoImpl::SHA256;
|
|
||||||
VoIPController::crypto.rand_bytes=MicrosoftCryptoImpl::RandBytes;
|
|
||||||
MicrosoftCryptoImpl::Init();
|
|
||||||
controller=new VoIPController();
|
|
||||||
controller->implData=(void*)this;
|
|
||||||
VoIPController::Callbacks callbacks={0};
|
|
||||||
callbacks.connectionStateChanged=VoIPControllerWrapper::OnStateChanged;
|
|
||||||
callbacks.signalBarCountChanged=VoIPControllerWrapper::OnSignalBarsChanged;
|
|
||||||
controller->SetCallbacks(callbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
VoIPControllerWrapper::~VoIPControllerWrapper(){
|
|
||||||
controller->Stop();
|
|
||||||
delete controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::Start(){
|
|
||||||
controller->Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::Connect(){
|
|
||||||
controller->Connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetPublicEndpoints(const Platform::Array<libtgvoip::Endpoint^>^ endpoints, bool allowP2P, int32_t connectionMaxLayer){
|
|
||||||
std::vector<tgvoip::Endpoint> eps;
|
|
||||||
for (unsigned int i = 0; i < endpoints->Length; i++)
|
|
||||||
{
|
|
||||||
libtgvoip::Endpoint^ _ep = endpoints[i];
|
|
||||||
tgvoip::Endpoint ep;
|
|
||||||
ep.id = _ep->id;
|
|
||||||
ep.type = tgvoip::Endpoint::Type::UDP_RELAY;
|
|
||||||
char buf[128];
|
|
||||||
if (_ep->ipv4){
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, _ep->ipv4->Data(), -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
ep.address = NetworkAddress::IPv4(buf);
|
|
||||||
}
|
|
||||||
if (_ep->ipv6){
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, _ep->ipv6->Data(), -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
ep.v6address = NetworkAddress::IPv6(buf);
|
|
||||||
}
|
|
||||||
ep.port = _ep->port;
|
|
||||||
if (_ep->peerTag->Length != 16)
|
|
||||||
throw ref new Platform::InvalidArgumentException("Peer tag must be exactly 16 bytes long");
|
|
||||||
memcpy(ep.peerTag, _ep->peerTag->Data, 16);
|
|
||||||
eps.push_back(ep);
|
|
||||||
}
|
|
||||||
controller->SetRemoteEndpoints(eps, allowP2P, connectionMaxLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetNetworkType(NetworkType type){
|
|
||||||
controller->SetNetworkType((int)type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetMicMute(bool mute){
|
|
||||||
controller->SetMicMute(mute);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64 VoIPControllerWrapper::GetPreferredRelayID(){
|
|
||||||
return controller->GetPreferredRelayID();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t VoIPControllerWrapper::GetConnectionMaxLayer(){
|
|
||||||
return tgvoip::VoIPController::GetConnectionMaxLayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetEncryptionKey(const Platform::Array<uint8>^ key, bool isOutgoing){
|
|
||||||
if(key->Length!=256)
|
|
||||||
throw ref new Platform::InvalidArgumentException("Encryption key must be exactly 256 bytes long");
|
|
||||||
controller->SetEncryptionKey((char*)key->Data, isOutgoing);
|
|
||||||
}
|
|
||||||
|
|
||||||
int VoIPControllerWrapper::GetSignalBarsCount(){
|
|
||||||
return controller->GetSignalBarsCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
CallState VoIPControllerWrapper::GetConnectionState(){
|
|
||||||
return (CallState)controller->GetConnectionState();
|
|
||||||
}
|
|
||||||
|
|
||||||
TrafficStats^ VoIPControllerWrapper::GetStats(){
|
|
||||||
tgvoip::VoIPController::TrafficStats _stats;
|
|
||||||
controller->GetStats(&_stats);
|
|
||||||
|
|
||||||
TrafficStats^ stats = ref new TrafficStats();
|
|
||||||
stats->bytesSentWifi = _stats.bytesSentWifi;
|
|
||||||
stats->bytesSentMobile = _stats.bytesSentMobile;
|
|
||||||
stats->bytesRecvdWifi = _stats.bytesRecvdWifi;
|
|
||||||
stats->bytesRecvdMobile = _stats.bytesRecvdMobile;
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform::String^ VoIPControllerWrapper::GetDebugString(){
|
|
||||||
std::string log = controller->GetDebugString();
|
|
||||||
size_t len = sizeof(wchar_t)*(log.length() + 1);
|
|
||||||
wchar_t* wlog = (wchar_t*)malloc(len);
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, log.c_str(), -1, wlog, len / sizeof(wchar_t));
|
|
||||||
Platform::String^ res = ref new Platform::String(wlog);
|
|
||||||
free(wlog);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform::String^ VoIPControllerWrapper::GetDebugLog(){
|
|
||||||
std::string log=controller->GetDebugLog();
|
|
||||||
size_t len=sizeof(wchar_t)*(log.length()+1);
|
|
||||||
wchar_t* wlog=(wchar_t*)malloc(len);
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, log.c_str(), -1, wlog, len/sizeof(wchar_t));
|
|
||||||
Platform::String^ res=ref new Platform::String(wlog);
|
|
||||||
free(wlog);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error VoIPControllerWrapper::GetLastError(){
|
|
||||||
return (Error)controller->GetLastError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform::String^ VoIPControllerWrapper::GetVersion(){
|
|
||||||
const char* v=VoIPController::GetVersion();
|
|
||||||
wchar_t buf[32];
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, v, -1, buf, sizeof(buf));
|
|
||||||
return ref new Platform::String(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::OnStateChanged(VoIPController* c, int state){
|
|
||||||
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnStateChangedInternal(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::OnSignalBarsChanged(VoIPController* c, int count){
|
|
||||||
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnSignalBarsChangedInternal(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::OnStateChangedInternal(int state){
|
|
||||||
CallStateChanged(this, (CallState)state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::OnSignalBarsChangedInternal(int count){
|
|
||||||
SignalBarsChanged(this, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetConfig(VoIPConfig^ wrapper){
|
|
||||||
VoIPController::Config config{0};
|
|
||||||
config.initTimeout=wrapper->initTimeout;
|
|
||||||
config.recvTimeout=wrapper->recvTimeout;
|
|
||||||
config.dataSaving=(int)wrapper->dataSaving;
|
|
||||||
config.logFilePath;
|
|
||||||
config.statsDumpFilePath;
|
|
||||||
|
|
||||||
config.enableAEC=wrapper->enableAEC;
|
|
||||||
config.enableNS=wrapper->enableNS;
|
|
||||||
config.enableAGC=wrapper->enableAGC;
|
|
||||||
|
|
||||||
config.enableCallUpgrade=wrapper->enableCallUpgrade;
|
|
||||||
|
|
||||||
config.logPacketStats=wrapper->logPacketStats;
|
|
||||||
config.enableVolumeControl=wrapper->enableVolumeControl;
|
|
||||||
|
|
||||||
config.enableVideoSend=wrapper->enableVideoSend;
|
|
||||||
config.enableVideoReceive=wrapper->enableVideoReceive;
|
|
||||||
|
|
||||||
if(wrapper->logFilePath!=nullptr&&!wrapper->logFilePath->IsEmpty()){
|
|
||||||
config.logFilePath = wstring(wrapper->logFilePath->Data());
|
|
||||||
}
|
|
||||||
if (wrapper->statsDumpFilePath != nullptr&&!wrapper->statsDumpFilePath->IsEmpty()){
|
|
||||||
config.statsDumpFilePath = wstring(wrapper->statsDumpFilePath->Data());
|
|
||||||
}
|
|
||||||
|
|
||||||
controller->SetConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetProxy(ProxyProtocol protocol, Platform::String^ address, uint16_t port, Platform::String^ username, Platform::String^ password){
|
|
||||||
char _address[2000];
|
|
||||||
char _username[256];
|
|
||||||
char _password[256];
|
|
||||||
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, address->Data(), -1, _address, sizeof(_address), NULL, NULL);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, username->Data(), -1, _username, sizeof(_username), NULL, NULL);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, password->Data(), -1, _password, sizeof(_password), NULL, NULL);
|
|
||||||
|
|
||||||
controller->SetProxy((int)protocol, _address, port, _username, _password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetAudioOutputGainControlEnabled(bool enabled){
|
|
||||||
controller->SetAudioOutputGainControlEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetInputVolume(float level){
|
|
||||||
controller->SetInputVolume(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SetOutputVolume(float level){
|
|
||||||
controller->SetOutputVolume(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::UpdateServerConfig(Platform::String^ json){
|
|
||||||
std::string config=ToUtf8(json->Data(), json->Length());
|
|
||||||
ServerConfig::GetSharedInstance()->Update(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoIPControllerWrapper::SwitchSpeaker(bool external){
|
|
||||||
auto routingManager = AudioRoutingManager::GetDefault();
|
|
||||||
if (external){
|
|
||||||
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Speakerphone);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if ((routingManager->AvailableAudioEndpoints & AvailableAudioRoutingEndpoints::Bluetooth) == AvailableAudioRoutingEndpoints::Bluetooth){
|
|
||||||
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Bluetooth);
|
|
||||||
}
|
|
||||||
else if ((routingManager->AvailableAudioEndpoints & AvailableAudioRoutingEndpoints::Earpiece) == AvailableAudioRoutingEndpoints::Earpiece){
|
|
||||||
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Earpiece);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::AesIgeEncrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv){
|
|
||||||
IBuffer^ keybuf=IBufferFromPtr(key, 32);
|
|
||||||
CryptographicKey^ _key=aesKeyProvider->CreateSymmetricKey(keybuf);
|
|
||||||
uint8_t tmpOut[16];
|
|
||||||
uint8_t* xPrev=iv+16;
|
|
||||||
uint8_t* yPrev=iv;
|
|
||||||
uint8_t x[16];
|
|
||||||
uint8_t y[16];
|
|
||||||
for(size_t offset=0;offset<len;offset+=16){
|
|
||||||
for (size_t i=0;i<16;i++){
|
|
||||||
if (offset+i < len){
|
|
||||||
x[i] = in[offset+i];
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
x[i]=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XorInt128(x, yPrev, y);
|
|
||||||
IBuffer^ inbuf=IBufferFromPtr(y, 16);
|
|
||||||
IBuffer^ outbuf=CryptographicEngine::Encrypt(_key, inbuf, nullptr);
|
|
||||||
IBufferToPtr(outbuf, 16, tmpOut);
|
|
||||||
XorInt128(tmpOut, xPrev, y);
|
|
||||||
memcpy(xPrev, x, 16);
|
|
||||||
memcpy(yPrev, y, 16);
|
|
||||||
memcpy(out+offset, y, 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::AesIgeDecrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv){
|
|
||||||
IBuffer^ keybuf=IBufferFromPtr(key, 32);
|
|
||||||
CryptographicKey^ _key=aesKeyProvider->CreateSymmetricKey(keybuf);
|
|
||||||
uint8_t tmpOut[16];
|
|
||||||
uint8_t* xPrev=iv;
|
|
||||||
uint8_t* yPrev=iv+16;
|
|
||||||
uint8_t x[16];
|
|
||||||
uint8_t y[16];
|
|
||||||
for(size_t offset=0;offset<len;offset+=16){
|
|
||||||
for (size_t i=0;i<16;i++){
|
|
||||||
if (offset+i < len){
|
|
||||||
x[i] = in[offset+i];
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
x[i]=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XorInt128(x, yPrev, y);
|
|
||||||
IBuffer^ inbuf=IBufferFromPtr(y, 16);
|
|
||||||
IBuffer^ outbuf=CryptographicEngine::Decrypt(_key, inbuf, nullptr);
|
|
||||||
IBufferToPtr(outbuf, 16, tmpOut);
|
|
||||||
XorInt128(tmpOut, xPrev, y);
|
|
||||||
memcpy(xPrev, x, 16);
|
|
||||||
memcpy(yPrev, y, 16);
|
|
||||||
memcpy(out+offset, y, 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define GETU32(pt) (((uint32_t)(pt)[0] << 24) ^ ((uint32_t)(pt)[1] << 16) ^ ((uint32_t)(pt)[2] << 8) ^ ((uint32_t)(pt)[3]))
|
|
||||||
#define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
|
|
||||||
|
|
||||||
typedef uint8_t u8;
|
|
||||||
|
|
||||||
#define L_ENDIAN
|
|
||||||
|
|
||||||
/* increment counter (128-bit int) by 2^64 */
|
|
||||||
static void AES_ctr128_inc(unsigned char *counter) {
|
|
||||||
unsigned long c;
|
|
||||||
|
|
||||||
/* Grab 3rd dword of counter and increment */
|
|
||||||
#ifdef L_ENDIAN
|
|
||||||
c = GETU32(counter + 8);
|
|
||||||
c++;
|
|
||||||
PUTU32(counter + 8, c);
|
|
||||||
#else
|
|
||||||
c = GETU32(counter + 4);
|
|
||||||
c++;
|
|
||||||
PUTU32(counter + 4, c);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* if no overflow, we're done */
|
|
||||||
if (c)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Grab top dword of counter and increment */
|
|
||||||
#ifdef L_ENDIAN
|
|
||||||
c = GETU32(counter + 12);
|
|
||||||
c++;
|
|
||||||
PUTU32(counter + 12, c);
|
|
||||||
#else
|
|
||||||
c = GETU32(counter + 0);
|
|
||||||
c++;
|
|
||||||
PUTU32(counter + 0, c);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::AesCtrEncrypt(uint8_t* inout, size_t len, uint8_t* key, uint8_t* counter, uint8_t* ecount_buf, uint32_t* num){
|
|
||||||
unsigned int n;
|
|
||||||
unsigned long l = len;
|
|
||||||
|
|
||||||
//assert(in && out && key && counter && num);
|
|
||||||
//assert(*num < AES_BLOCK_SIZE);
|
|
||||||
|
|
||||||
IBuffer^ keybuf = IBufferFromPtr(key, 32);
|
|
||||||
CryptographicKey^ _key = aesKeyProvider->CreateSymmetricKey(keybuf);
|
|
||||||
|
|
||||||
n = *num;
|
|
||||||
|
|
||||||
while (l--) {
|
|
||||||
if (n == 0) {
|
|
||||||
IBuffer^ inbuf = IBufferFromPtr(counter, 16);
|
|
||||||
IBuffer^ outbuf = CryptographicEngine::Encrypt(_key, inbuf, nullptr);
|
|
||||||
IBufferToPtr(outbuf, 16, ecount_buf);
|
|
||||||
//AES_encrypt(counter, ecount_buf, key);
|
|
||||||
AES_ctr128_inc(counter);
|
|
||||||
}
|
|
||||||
*inout = *(inout++) ^ ecount_buf[n];
|
|
||||||
n = (n + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
*num = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::SHA1(uint8_t* msg, size_t len, uint8_t* out){
|
|
||||||
//EnterCriticalSection(&hashMutex);
|
|
||||||
|
|
||||||
IBuffer^ arr=IBufferFromPtr(msg, len);
|
|
||||||
CryptographicHash^ hash=sha1Provider->CreateHash();
|
|
||||||
hash->Append(arr);
|
|
||||||
IBuffer^ res=hash->GetValueAndReset();
|
|
||||||
IBufferToPtr(res, 20, out);
|
|
||||||
|
|
||||||
//LeaveCriticalSection(&hashMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::SHA256(uint8_t* msg, size_t len, uint8_t* out){
|
|
||||||
//EnterCriticalSection(&hashMutex);
|
|
||||||
|
|
||||||
IBuffer^ arr=IBufferFromPtr(msg, len);
|
|
||||||
CryptographicHash^ hash=sha256Provider->CreateHash();
|
|
||||||
hash->Append(arr);
|
|
||||||
IBuffer^ res=hash->GetValueAndReset();
|
|
||||||
IBufferToPtr(res, 32, out);
|
|
||||||
//LeaveCriticalSection(&hashMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::RandBytes(uint8_t* buffer, size_t len){
|
|
||||||
IBuffer^ res=CryptographicBuffer::GenerateRandom(len);
|
|
||||||
IBufferToPtr(res, len, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::Init(){
|
|
||||||
/*sha1Hash=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1)->CreateHash();
|
|
||||||
sha256Hash=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256)->CreateHash();*/
|
|
||||||
sha1Provider=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1);
|
|
||||||
sha256Provider=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256);
|
|
||||||
aesKeyProvider=SymmetricKeyAlgorithmProvider::OpenAlgorithm(SymmetricAlgorithmNames::AesEcb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::XorInt128(uint8_t* a, uint8_t* b, uint8_t* out){
|
|
||||||
uint64_t* _a=reinterpret_cast<uint64_t*>(a);
|
|
||||||
uint64_t* _b=reinterpret_cast<uint64_t*>(b);
|
|
||||||
uint64_t* _out=reinterpret_cast<uint64_t*>(out);
|
|
||||||
_out[0]=_a[0]^_b[0];
|
|
||||||
_out[1]=_a[1]^_b[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void MicrosoftCryptoImpl::IBufferToPtr(IBuffer^ buffer, size_t len, uint8_t* out)
|
|
||||||
{
|
|
||||||
ComPtr<IBufferByteAccess> bufferByteAccess;
|
|
||||||
reinterpret_cast<IInspectable*>(buffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess));
|
|
||||||
|
|
||||||
byte* hashBuffer;
|
|
||||||
bufferByteAccess->Buffer(&hashBuffer);
|
|
||||||
CopyMemory(out, hashBuffer, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
IBuffer^ MicrosoftCryptoImpl::IBufferFromPtr(uint8_t* msg, size_t len)
|
|
||||||
{
|
|
||||||
ComPtr<NativeBuffer> nativeBuffer=Make<NativeBuffer>((byte *)msg, len);
|
|
||||||
return reinterpret_cast<IBuffer^>(nativeBuffer.Get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Platform::String^ VoIPControllerWrapper::TestAesIge(){
|
|
||||||
MicrosoftCryptoImpl::Init();
|
|
||||||
Platform::String^ res="";
|
|
||||||
Platform::Array<uint8>^ data=ref new Platform::Array<uint8>(32);
|
|
||||||
Platform::Array<uint8>^ out=ref new Platform::Array<uint8>(32);
|
|
||||||
Platform::Array<uint8>^ key=ref new Platform::Array<uint8>(16);
|
|
||||||
Platform::Array<uint8>^ iv=ref new Platform::Array<uint8>(32);
|
|
||||||
|
|
||||||
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("0000000000000000000000000000000000000000000000000000000000000000"), &data);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), &iv);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f"), &key);
|
|
||||||
MicrosoftCryptoImpl::AesIgeEncrypt(data->Data, out->Data, 32, key->Data, iv->Data);
|
|
||||||
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
|
|
||||||
res+="\n";
|
|
||||||
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("1A8519A6557BE652E9DA8E43DA4EF4453CF456B4CA488AA383C79C98B34797CB"), &data);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), &iv);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f"), &key);
|
|
||||||
MicrosoftCryptoImpl::AesIgeDecrypt(data->Data, out->Data, 32, key->Data, iv->Data);
|
|
||||||
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
|
|
||||||
res+="\n";
|
|
||||||
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("99706487A1CDE613BC6DE0B6F24B1C7AA448C8B9C3403E3467A8CAD89340F53B"), &data);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353"), &iv);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("5468697320697320616E20696D706C65"), &key);
|
|
||||||
MicrosoftCryptoImpl::AesIgeEncrypt(data->Data, out->Data, 32, key->Data, iv->Data);
|
|
||||||
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
|
|
||||||
res+="\n";
|
|
||||||
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("4C2E204C6574277320686F70652042656E20676F74206974207269676874210A"), &data);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353"), &iv);
|
|
||||||
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("5468697320697320616E20696D706C65"), &key);
|
|
||||||
MicrosoftCryptoImpl::AesIgeDecrypt(data->Data, out->Data, 32, key->Data, iv->Data);
|
|
||||||
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
|
|
||||||
return res;
|
|
||||||
}*/
|
|
|
@ -1,273 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <wrl.h>
|
|
||||||
#include <wrl/implements.h>
|
|
||||||
#include <windows.storage.streams.h>
|
|
||||||
#include <robuffer.h>
|
|
||||||
#include <vector>
|
|
||||||
#include "../../VoIPController.h"
|
|
||||||
#include "../../VoIPServerConfig.h"
|
|
||||||
|
|
||||||
using namespace Platform;
|
|
||||||
|
|
||||||
#define STACK_ARRAY(TYPE, LEN) \
|
|
||||||
static_cast<TYPE*>(::alloca((LEN) * sizeof(TYPE)))
|
|
||||||
|
|
||||||
inline std::wstring ToUtf16(const char* utf8, size_t len) {
|
|
||||||
int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len),
|
|
||||||
nullptr, 0);
|
|
||||||
wchar_t* ws = STACK_ARRAY(wchar_t, len16);
|
|
||||||
::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len), ws, len16);
|
|
||||||
return std::wstring(ws, len16);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::wstring ToUtf16(const std::string& str) {
|
|
||||||
return ToUtf16(str.data(), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string ToUtf8(const wchar_t* wide, size_t len) {
|
|
||||||
int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len),
|
|
||||||
nullptr, 0, nullptr, nullptr);
|
|
||||||
char* ns = STACK_ARRAY(char, len8);
|
|
||||||
::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len), ns, len8,
|
|
||||||
nullptr, nullptr);
|
|
||||||
return std::string(ns, len8);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string ToUtf8(const wchar_t* wide) {
|
|
||||||
return ToUtf8(wide, wcslen(wide));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string ToUtf8(const std::wstring& wstr) {
|
|
||||||
return ToUtf8(wstr.data(), wstr.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace libtgvoip{
|
|
||||||
public ref class Endpoint sealed{
|
|
||||||
public:
|
|
||||||
property int64 id;
|
|
||||||
property uint16 port;
|
|
||||||
property Platform::String^ ipv4;
|
|
||||||
property Platform::String^ ipv6;
|
|
||||||
property Platform::Array<uint8>^ peerTag;
|
|
||||||
};
|
|
||||||
|
|
||||||
public ref class TrafficStats sealed{
|
|
||||||
public:
|
|
||||||
property uint64_t bytesSentWifi;
|
|
||||||
property uint64_t bytesRecvdWifi;
|
|
||||||
property uint64_t bytesSentMobile;
|
|
||||||
property uint64_t bytesRecvdMobile;
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum class CallState : int{
|
|
||||||
WaitInit=1,
|
|
||||||
WaitInitAck,
|
|
||||||
Established,
|
|
||||||
Failed
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum class Error : int{
|
|
||||||
Unknown=0,
|
|
||||||
Incompatible,
|
|
||||||
Timeout,
|
|
||||||
AudioIO
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum class NetworkType : int{
|
|
||||||
Unknown=0,
|
|
||||||
GPRS,
|
|
||||||
EDGE,
|
|
||||||
UMTS,
|
|
||||||
HSPA,
|
|
||||||
LTE,
|
|
||||||
WiFi,
|
|
||||||
Ethernet,
|
|
||||||
OtherHighSpeed,
|
|
||||||
OtherLowSpeed,
|
|
||||||
Dialup,
|
|
||||||
OtherMobile
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum class DataSavingMode{
|
|
||||||
Never=0,
|
|
||||||
MobileOnly,
|
|
||||||
Always
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum class ProxyProtocol{
|
|
||||||
None=0,
|
|
||||||
SOCKS5
|
|
||||||
};
|
|
||||||
|
|
||||||
public ref class VoIPConfig sealed {
|
|
||||||
public:
|
|
||||||
VoIPConfig() {
|
|
||||||
logPacketStats = false;
|
|
||||||
enableVolumeControl = false;
|
|
||||||
enableVideoSend = false;
|
|
||||||
enableVideoReceive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
property double initTimeout;
|
|
||||||
property double recvTimeout;
|
|
||||||
property DataSavingMode dataSaving;
|
|
||||||
property String^ logFilePath;
|
|
||||||
property String^ statsDumpFilePath;
|
|
||||||
|
|
||||||
property bool enableAEC;
|
|
||||||
property bool enableNS;
|
|
||||||
property bool enableAGC;
|
|
||||||
|
|
||||||
property bool enableCallUpgrade;
|
|
||||||
|
|
||||||
property bool logPacketStats;
|
|
||||||
property bool enableVolumeControl;
|
|
||||||
|
|
||||||
property bool enableVideoSend;
|
|
||||||
property bool enableVideoReceive;
|
|
||||||
};
|
|
||||||
|
|
||||||
ref class VoIPControllerWrapper;
|
|
||||||
public delegate void CallStateChangedEventHandler(VoIPControllerWrapper^ sender, CallState newState);
|
|
||||||
|
|
||||||
ref class VoIPControllerWrapper;
|
|
||||||
public delegate void SignalBarsChangedEventHandler(VoIPControllerWrapper^ sender, int newCount);
|
|
||||||
|
|
||||||
public ref class VoIPControllerWrapper sealed{
|
|
||||||
public:
|
|
||||||
VoIPControllerWrapper();
|
|
||||||
virtual ~VoIPControllerWrapper();
|
|
||||||
void Start();
|
|
||||||
void Connect();
|
|
||||||
void SetPublicEndpoints(const Platform::Array<Endpoint^>^ endpoints, bool allowP2P, int32_t connectionMaxLayer);
|
|
||||||
void SetNetworkType(NetworkType type);
|
|
||||||
void SetMicMute(bool mute);
|
|
||||||
void SetEncryptionKey(const Platform::Array<uint8>^ key, bool isOutgoing);
|
|
||||||
void SetConfig(VoIPConfig^ config);
|
|
||||||
void SetProxy(ProxyProtocol protocol, Platform::String^ address, uint16_t port, Platform::String^ username, Platform::String^ password);
|
|
||||||
int GetSignalBarsCount();
|
|
||||||
CallState GetConnectionState();
|
|
||||||
TrafficStats^ GetStats();
|
|
||||||
Platform::String^ GetDebugString();
|
|
||||||
Platform::String^ GetDebugLog();
|
|
||||||
Error GetLastError();
|
|
||||||
static Platform::String^ GetVersion();
|
|
||||||
int64 GetPreferredRelayID();
|
|
||||||
void SetAudioOutputGainControlEnabled(bool enabled);
|
|
||||||
|
|
||||||
void SetInputVolume(float level);
|
|
||||||
void SetOutputVolume(float level);
|
|
||||||
|
|
||||||
property String^ CurrentAudioInput
|
|
||||||
{
|
|
||||||
String^ get()
|
|
||||||
{
|
|
||||||
return ref new String(ToUtf16(controller->GetCurrentAudioInputID()).data());
|
|
||||||
}
|
|
||||||
void set(String^ value)
|
|
||||||
{
|
|
||||||
controller->SetCurrentAudioInput(ToUtf8(value->Data()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property String^ CurrentAudioOutput
|
|
||||||
{
|
|
||||||
String^ get()
|
|
||||||
{
|
|
||||||
return ref new String(ToUtf16(controller->GetCurrentAudioOutputID()).data());
|
|
||||||
}
|
|
||||||
void set(String^ value)
|
|
||||||
{
|
|
||||||
controller->SetCurrentAudioOutput(ToUtf8(value->Data()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t GetConnectionMaxLayer();
|
|
||||||
static void UpdateServerConfig(Platform::String^ json);
|
|
||||||
static void SwitchSpeaker(bool external);
|
|
||||||
//static Platform::String^ TestAesIge();
|
|
||||||
|
|
||||||
event CallStateChangedEventHandler^ CallStateChanged;
|
|
||||||
event SignalBarsChangedEventHandler^ SignalBarsChanged;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void OnStateChanged(tgvoip::VoIPController* c, int state);
|
|
||||||
static void OnSignalBarsChanged(tgvoip::VoIPController* c, int count);
|
|
||||||
void OnStateChangedInternal(int state);
|
|
||||||
void OnSignalBarsChangedInternal(int count);
|
|
||||||
tgvoip::VoIPController* controller;
|
|
||||||
};
|
|
||||||
|
|
||||||
ref class MicrosoftCryptoImpl{
|
|
||||||
public:
|
|
||||||
static void AesIgeEncrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv);
|
|
||||||
static void AesIgeDecrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv);
|
|
||||||
static void AesCtrEncrypt(uint8_t* inout, size_t len, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num);
|
|
||||||
static void SHA1(uint8_t* msg, size_t len, uint8_t* out);
|
|
||||||
static void SHA256(uint8_t* msg, size_t len, uint8_t* out);
|
|
||||||
static void RandBytes(uint8_t* buffer, size_t len);
|
|
||||||
static void Init();
|
|
||||||
private:
|
|
||||||
static inline void XorInt128(uint8_t* a, uint8_t* b, uint8_t* out);
|
|
||||||
static void IBufferToPtr(Windows::Storage::Streams::IBuffer^ buffer, size_t len, uint8_t* out);
|
|
||||||
static Windows::Storage::Streams::IBuffer^ IBufferFromPtr(uint8_t* msg, size_t len);
|
|
||||||
/*static Windows::Security::Cryptography::Core::CryptographicHash^ sha1Hash;
|
|
||||||
static Windows::Security::Cryptography::Core::CryptographicHash^ sha256Hash;*/
|
|
||||||
static Windows::Security::Cryptography::Core::HashAlgorithmProvider^ sha1Provider;
|
|
||||||
static Windows::Security::Cryptography::Core::HashAlgorithmProvider^ sha256Provider;
|
|
||||||
static Windows::Security::Cryptography::Core::SymmetricKeyAlgorithmProvider^ aesKeyProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NativeBuffer :
|
|
||||||
public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::WinRtClassicComMix>,
|
|
||||||
ABI::Windows::Storage::Streams::IBuffer,
|
|
||||||
Windows::Storage::Streams::IBufferByteAccess>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NativeBuffer(byte *buffer, UINT totalSize)
|
|
||||||
{
|
|
||||||
m_length=totalSize;
|
|
||||||
m_buffer=buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~NativeBuffer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP RuntimeClassInitialize(byte *buffer, UINT totalSize)
|
|
||||||
{
|
|
||||||
m_length=totalSize;
|
|
||||||
m_buffer=buffer;
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP Buffer(byte **value)
|
|
||||||
{
|
|
||||||
*value=m_buffer;
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP get_Capacity(UINT32 *value)
|
|
||||||
{
|
|
||||||
*value=m_length;
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP get_Length(UINT32 *value)
|
|
||||||
{
|
|
||||||
*value=m_length;
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP put_Length(UINT32 value)
|
|
||||||
{
|
|
||||||
m_length=value;
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
UINT32 m_length;
|
|
||||||
byte *m_buffer;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,706 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include "NetworkSocketWinsock.h"
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
|
|
||||||
#else
|
|
||||||
#include <IPHlpApi.h>
|
|
||||||
#endif
|
|
||||||
#include <assert.h>
|
|
||||||
#include "../../logging.h"
|
|
||||||
#include "../../VoIPController.h"
|
|
||||||
#include "WindowsSpecific.h"
|
|
||||||
#include "../../Buffers.h"
|
|
||||||
|
|
||||||
using namespace tgvoip;
|
|
||||||
|
|
||||||
NetworkSocketWinsock::NetworkSocketWinsock(NetworkProtocol protocol) : NetworkSocket(protocol){
|
|
||||||
needUpdateNat64Prefix=true;
|
|
||||||
nat64Present=false;
|
|
||||||
switchToV6at=0;
|
|
||||||
isV4Available=false;
|
|
||||||
closing=false;
|
|
||||||
fd=INVALID_SOCKET;
|
|
||||||
|
|
||||||
#ifdef TGVOIP_WINXP_COMPAT
|
|
||||||
DWORD version=GetVersion();
|
|
||||||
isAtLeastVista=LOBYTE(LOWORD(version))>=6; // Vista is 6.0, XP is 5.1 and 5.2
|
|
||||||
#else
|
|
||||||
isAtLeastVista=true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
WSADATA wsaData;
|
|
||||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
||||||
LOGD("Initialized winsock, version %d.%d", wsaData.wHighVersion, wsaData.wVersion);
|
|
||||||
|
|
||||||
if(protocol==NetworkProtocol::TCP)
|
|
||||||
timeout=10.0;
|
|
||||||
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkSocketWinsock::~NetworkSocketWinsock(){
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::SetMaxPriority(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::Send(NetworkPacket packet){
|
|
||||||
if(packet.IsEmpty() || (protocol==NetworkProtocol::UDP && packet.address.IsEmpty())){
|
|
||||||
LOGW("tried to send null packet");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int res;
|
|
||||||
if(protocol==NetworkProtocol::UDP){
|
|
||||||
if(isAtLeastVista){
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
if(!packet.address.isIPv6){
|
|
||||||
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_strerrorA(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])=packet.address.addr.ipv4;
|
|
||||||
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{
|
|
||||||
memcpy(addr.sin6_addr.s6_addr, packet.address.addr.ipv6, 16);
|
|
||||||
}
|
|
||||||
addr.sin6_port=htons(packet.port);
|
|
||||||
res=sendto(fd, (const char*)*packet.data, packet.data.Length(), 0, (const sockaddr *) &addr, sizeof(addr));
|
|
||||||
}else{
|
|
||||||
sockaddr_in addr;
|
|
||||||
addr.sin_addr.s_addr=packet.address.addr.ipv4;
|
|
||||||
addr.sin_port=htons(packet.port);
|
|
||||||
addr.sin_family=AF_INET;
|
|
||||||
res=sendto(fd, (const char*)*packet.data, packet.data.Length(), 0, (const sockaddr*)&addr, sizeof(addr));
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
res=send(fd, (const char*)*packet.data, packet.data.Length(), 0);
|
|
||||||
}
|
|
||||||
if(res==SOCKET_ERROR){
|
|
||||||
int error=WSAGetLastError();
|
|
||||||
if(error==WSAEWOULDBLOCK){
|
|
||||||
if(!pendingOutgoingPacket.IsEmpty()){
|
|
||||||
LOGE("Got EAGAIN but there's already a pending packet");
|
|
||||||
failed=true;
|
|
||||||
}else{
|
|
||||||
LOGV("Socket %d not ready to send", fd);
|
|
||||||
pendingOutgoingPacket=std::move(packet);
|
|
||||||
readyToSend=false;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
LOGE("error sending: %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
|
|
||||||
if(error==WSAENETUNREACH && !isV4Available && VoIPController::GetCurrentTime()<switchToV6at){
|
|
||||||
switchToV6at=VoIPController::GetCurrentTime();
|
|
||||||
LOGI("Network unreachable, trying NAT64");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(res<packet.data.Length() && protocol==NetworkProtocol::TCP){
|
|
||||||
if(!pendingOutgoingPacket.IsEmpty()){
|
|
||||||
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=std::move(packet);
|
|
||||||
Buffer pdata=std::move(pendingOutgoingPacket.data);
|
|
||||||
pendingOutgoingPacket.data=Buffer::CopyOf(pdata, res, pdata.Length()-res);
|
|
||||||
readyToSend=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkSocketWinsock::OnReadyToSend(){
|
|
||||||
if(!pendingOutgoingPacket.IsEmpty()){
|
|
||||||
Send(std::move(pendingOutgoingPacket));
|
|
||||||
pendingOutgoingPacket=std::move(NetworkPacket::Empty());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readyToSend=true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket NetworkSocketWinsock::Receive(size_t maxLen){
|
|
||||||
if(maxLen==0)
|
|
||||||
maxLen=UINT32_MAX;
|
|
||||||
if(protocol==NetworkProtocol::UDP){
|
|
||||||
if(isAtLeastVista){
|
|
||||||
int addrLen=sizeof(sockaddr_in6);
|
|
||||||
sockaddr_in6 srcAddr;
|
|
||||||
int res=recvfrom(fd, (char*)*recvBuf, std::min(recvBuf.Length(), maxLen), 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
|
|
||||||
if(res==SOCKET_ERROR){
|
|
||||||
int error=WSAGetLastError();
|
|
||||||
LOGE("error receiving %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
//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");
|
|
||||||
}
|
|
||||||
NetworkAddress addr=NetworkAddress::Empty();
|
|
||||||
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]);
|
|
||||||
addr=NetworkAddress::IPv4(v4addr.s_addr);
|
|
||||||
}else{
|
|
||||||
addr=NetworkAddress::IPv6(srcAddr.sin6_addr.s6_addr);
|
|
||||||
}
|
|
||||||
return NetworkPacket{
|
|
||||||
Buffer::CopyOf(recvBuf, 0, (size_t)res),
|
|
||||||
addr,
|
|
||||||
ntohs(srcAddr.sin6_port),
|
|
||||||
NetworkProtocol::UDP
|
|
||||||
};
|
|
||||||
}else{
|
|
||||||
int addrLen=sizeof(sockaddr_in);
|
|
||||||
sockaddr_in srcAddr;
|
|
||||||
int res=recvfrom(fd, (char*)*recvBuf, std::min(recvBuf.Length(), maxLen), 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
|
|
||||||
if(res==SOCKET_ERROR){
|
|
||||||
LOGE("error receiving %d", WSAGetLastError());
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}
|
|
||||||
return NetworkPacket{
|
|
||||||
Buffer::CopyOf(recvBuf, 0, (size_t)res),
|
|
||||||
NetworkAddress::IPv4(srcAddr.sin_addr.s_addr),
|
|
||||||
ntohs(srcAddr.sin_port),
|
|
||||||
NetworkProtocol::UDP
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}else if(protocol==NetworkProtocol::TCP){
|
|
||||||
int res=recv(fd, (char*)*recvBuf, std::min(recvBuf.Length(), maxLen), 0);
|
|
||||||
if(res==SOCKET_ERROR){
|
|
||||||
int error=WSAGetLastError();
|
|
||||||
LOGE("Error receiving from TCP socket: %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
|
|
||||||
failed=true;
|
|
||||||
return NetworkPacket::Empty();
|
|
||||||
}else{
|
|
||||||
return NetworkPacket{
|
|
||||||
Buffer::CopyOf(recvBuf, 0, (size_t)res),
|
|
||||||
tcpConnectedAddress,
|
|
||||||
tcpConnectedPort,
|
|
||||||
NetworkProtocol::TCP
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::Open(){
|
|
||||||
if(protocol==NetworkProtocol::UDP){
|
|
||||||
fd=socket(isAtLeastVista ? AF_INET6 : AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
||||||
if(fd==INVALID_SOCKET){
|
|
||||||
int error=WSAGetLastError();
|
|
||||||
LOGE("error creating socket: %d", error);
|
|
||||||
failed=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int res;
|
|
||||||
if(isAtLeastVista){
|
|
||||||
DWORD flag=0;
|
|
||||||
res=setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&flag, sizeof(flag));
|
|
||||||
if(res==SOCKET_ERROR){
|
|
||||||
LOGE("error enabling dual stack socket: %d", WSAGetLastError());
|
|
||||||
failed=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u_long one=1;
|
|
||||||
ioctlsocket(fd, FIONBIO, &one);
|
|
||||||
|
|
||||||
SetMaxPriority();
|
|
||||||
|
|
||||||
int tries=0;
|
|
||||||
sockaddr* addr;
|
|
||||||
sockaddr_in addr4;
|
|
||||||
sockaddr_in6 addr6;
|
|
||||||
int addrLen;
|
|
||||||
if(isAtLeastVista){
|
|
||||||
//addr.sin6_addr.s_addr=0;
|
|
||||||
memset(&addr6, 0, sizeof(sockaddr_in6));
|
|
||||||
//addr.sin6_len=sizeof(sa_family_t);
|
|
||||||
addr6.sin6_family=AF_INET6;
|
|
||||||
addr=(sockaddr*)&addr6;
|
|
||||||
addrLen=sizeof(addr6);
|
|
||||||
}else{
|
|
||||||
sockaddr_in addr4;
|
|
||||||
addr4.sin_addr.s_addr=0;
|
|
||||||
addr4.sin_family=AF_INET;
|
|
||||||
addr=(sockaddr*)&addr4;
|
|
||||||
addrLen=sizeof(addr4);
|
|
||||||
}
|
|
||||||
for(tries=0;tries<10;tries++){
|
|
||||||
uint16_t port=htons(GenerateLocalPort());
|
|
||||||
if(isAtLeastVista)
|
|
||||||
((sockaddr_in6*)addr)->sin6_port=port;
|
|
||||||
else
|
|
||||||
((sockaddr_in*)addr)->sin_port=port;
|
|
||||||
res=::bind(fd, addr, addrLen);
|
|
||||||
LOGV("trying bind to port %u", ntohs(port));
|
|
||||||
if(res<0){
|
|
||||||
LOGE("error binding to port %u: %d / %s", ntohs(port), errno, strerror(errno));
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tries==10){
|
|
||||||
if(isAtLeastVista)
|
|
||||||
((sockaddr_in6*)addr)->sin6_port=0;
|
|
||||||
else
|
|
||||||
((sockaddr_in*)addr)->sin_port=0;
|
|
||||||
res=::bind(fd, addr, addrLen);
|
|
||||||
if(res<0){
|
|
||||||
LOGE("error binding to port %u: %d / %s", 0, errno, strerror(errno));
|
|
||||||
//SetState(STATE_FAILED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getsockname(fd, addr, (socklen_t*) &addrLen);
|
|
||||||
uint16_t localUdpPort;
|
|
||||||
if(isAtLeastVista)
|
|
||||||
localUdpPort=ntohs(((sockaddr_in6*)addr)->sin6_port);
|
|
||||||
else
|
|
||||||
localUdpPort=ntohs(((sockaddr_in*)addr)->sin_port);
|
|
||||||
LOGD("Bound to local UDP port %u", localUdpPort);
|
|
||||||
|
|
||||||
needUpdateNat64Prefix=true;
|
|
||||||
isV4Available=false;
|
|
||||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::Close(){
|
|
||||||
closing=true;
|
|
||||||
failed=true;
|
|
||||||
if(fd!=INVALID_SOCKET)
|
|
||||||
closesocket(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::OnActiveInterfaceChanged(){
|
|
||||||
needUpdateNat64Prefix=true;
|
|
||||||
isV4Available=false;
|
|
||||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NetworkSocketWinsock::GetLocalInterfaceInfo(NetworkAddress *v4addr, NetworkAddress *v6addr){
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
Windows::Networking::Connectivity::ConnectionProfile^ profile=Windows::Networking::Connectivity::NetworkInformation::GetInternetConnectionProfile();
|
|
||||||
if(profile){
|
|
||||||
Windows::Foundation::Collections::IVectorView<Windows::Networking::HostName^>^ hostnames=Windows::Networking::Connectivity::NetworkInformation::GetHostNames();
|
|
||||||
for(unsigned int i=0;i<hostnames->Size;i++){
|
|
||||||
Windows::Networking::HostName^ n = hostnames->GetAt(i);
|
|
||||||
if(n->Type!=Windows::Networking::HostNameType::Ipv4 && n->Type!=Windows::Networking::HostNameType::Ipv6)
|
|
||||||
continue;
|
|
||||||
if(n->IPInformation->NetworkAdapter->Equals(profile->NetworkAdapter)){
|
|
||||||
if(v4addr && n->Type==Windows::Networking::HostNameType::Ipv4){
|
|
||||||
char buf[INET_ADDRSTRLEN];
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, n->RawName->Data(), -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
*v4addr=NetworkAddress::IPv4(buf);
|
|
||||||
}else if(v6addr && n->Type==Windows::Networking::HostNameType::Ipv6){
|
|
||||||
char buf[INET6_ADDRSTRLEN];
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, n->RawName->Data(), -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
*v6addr=NetworkAddress::IPv6(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
char buf[128];
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, profile->NetworkAdapter->NetworkAdapterId.ToString()->Data(), -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
#else
|
|
||||||
IP_ADAPTER_ADDRESSES* addrs=(IP_ADAPTER_ADDRESSES*)malloc(15*1024);
|
|
||||||
ULONG size=15*1024;
|
|
||||||
ULONG flags=GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
|
|
||||||
|
|
||||||
ULONG res=GetAdaptersAddresses(AF_UNSPEC, flags, NULL, addrs, &size);
|
|
||||||
if(res==ERROR_BUFFER_OVERFLOW){
|
|
||||||
addrs=(IP_ADAPTER_ADDRESSES*)realloc(addrs, size);
|
|
||||||
res=GetAdaptersAddresses(AF_UNSPEC, flags, NULL, addrs, &size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ULONG bestMetric=0;
|
|
||||||
std::string bestName("");
|
|
||||||
|
|
||||||
if(res==ERROR_SUCCESS){
|
|
||||||
IP_ADAPTER_ADDRESSES* current=addrs;
|
|
||||||
while(current){
|
|
||||||
char* name=current->AdapterName;
|
|
||||||
LOGV("Adapter '%s':", name);
|
|
||||||
IP_ADAPTER_UNICAST_ADDRESS* curAddr=current->FirstUnicastAddress;
|
|
||||||
if(current->OperStatus!=IfOperStatusUp){
|
|
||||||
LOGV("-> (down)");
|
|
||||||
current=current->Next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(current->IfType==IF_TYPE_SOFTWARE_LOOPBACK){
|
|
||||||
LOGV("-> (loopback)");
|
|
||||||
current=current->Next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(isAtLeastVista)
|
|
||||||
LOGV("v4 metric: %u, v6 metric: %u", current->Ipv4Metric, current->Ipv6Metric);
|
|
||||||
while(curAddr){
|
|
||||||
sockaddr* addr=curAddr->Address.lpSockaddr;
|
|
||||||
if(addr->sa_family==AF_INET && v4addr){
|
|
||||||
sockaddr_in* ipv4=(sockaddr_in*)addr;
|
|
||||||
LOGV("-> V4: %s", V4AddressToString(ipv4->sin_addr.s_addr).c_str());
|
|
||||||
uint32_t ip=ntohl(ipv4->sin_addr.s_addr);
|
|
||||||
if((ip & 0xFFFF0000)!=0xA9FE0000){
|
|
||||||
if(isAtLeastVista){
|
|
||||||
if(current->Ipv4Metric>bestMetric){
|
|
||||||
bestMetric=current->Ipv4Metric;
|
|
||||||
bestName=std::string(current->AdapterName);
|
|
||||||
*v4addr=NetworkAddress::IPv4(ipv4->sin_addr.s_addr);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
bestName=std::string(current->AdapterName);
|
|
||||||
*v4addr=NetworkAddress::IPv4(ipv4->sin_addr.s_addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(addr->sa_family==AF_INET6 && v6addr){
|
|
||||||
sockaddr_in6* ipv6=(sockaddr_in6*)addr;
|
|
||||||
LOGV("-> V6: %s", V6AddressToString(ipv6->sin6_addr.s6_addr).c_str());
|
|
||||||
if(!IN6_IS_ADDR_LINKLOCAL(&ipv6->sin6_addr)){
|
|
||||||
*v6addr=NetworkAddress::IPv6(ipv6->sin6_addr.s6_addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curAddr=curAddr->Next;
|
|
||||||
}
|
|
||||||
current=current->Next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(addrs);
|
|
||||||
return bestName;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t NetworkSocketWinsock::GetLocalPort(){
|
|
||||||
if(!isAtLeastVista){
|
|
||||||
sockaddr_in addr;
|
|
||||||
size_t addrLen=sizeof(sockaddr_in);
|
|
||||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*)&addrLen);
|
|
||||||
return ntohs(addr.sin_port);
|
|
||||||
}
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
size_t addrLen=sizeof(sockaddr_in6);
|
|
||||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
|
|
||||||
return ntohs(addr.sin6_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NetworkSocketWinsock::V4AddressToString(uint32_t address){
|
|
||||||
char buf[INET_ADDRSTRLEN];
|
|
||||||
sockaddr_in addr;
|
|
||||||
ZeroMemory(&addr, sizeof(addr));
|
|
||||||
addr.sin_family=AF_INET;
|
|
||||||
addr.sin_addr.s_addr=address;
|
|
||||||
DWORD len=sizeof(buf);
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
wchar_t wbuf[INET_ADDRSTRLEN];
|
|
||||||
ZeroMemory(wbuf, sizeof(wbuf));
|
|
||||||
WSAAddressToStringW((sockaddr*)&addr, sizeof(addr), NULL, wbuf, &len);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
#else
|
|
||||||
WSAAddressToStringA((sockaddr*)&addr, sizeof(addr), NULL, buf, &len);
|
|
||||||
#endif
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NetworkSocketWinsock::V6AddressToString(const unsigned char *address){
|
|
||||||
char buf[INET6_ADDRSTRLEN];
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
ZeroMemory(&addr, sizeof(addr));
|
|
||||||
addr.sin6_family=AF_INET6;
|
|
||||||
memcpy(addr.sin6_addr.s6_addr, address, 16);
|
|
||||||
DWORD len=sizeof(buf);
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
wchar_t wbuf[INET6_ADDRSTRLEN];
|
|
||||||
ZeroMemory(wbuf, sizeof(wbuf));
|
|
||||||
WSAAddressToStringW((sockaddr*)&addr, sizeof(addr), NULL, wbuf, &len);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf), NULL, NULL);
|
|
||||||
#else
|
|
||||||
WSAAddressToStringA((sockaddr*)&addr, sizeof(addr), NULL, buf, &len);
|
|
||||||
#endif
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t NetworkSocketWinsock::StringToV4Address(std::string address){
|
|
||||||
sockaddr_in addr;
|
|
||||||
ZeroMemory(&addr, sizeof(addr));
|
|
||||||
addr.sin_family=AF_INET;
|
|
||||||
int size=sizeof(addr);
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
wchar_t buf[INET_ADDRSTRLEN];
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, address.c_str(), -1, buf, INET_ADDRSTRLEN);
|
|
||||||
WSAStringToAddressW(buf, AF_INET, NULL, (sockaddr*)&addr, &size);
|
|
||||||
#else
|
|
||||||
WSAStringToAddressA((char*)address.c_str(), AF_INET, NULL, (sockaddr*)&addr, &size);
|
|
||||||
#endif
|
|
||||||
return addr.sin_addr.s_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::StringToV6Address(std::string address, unsigned char *out){
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
ZeroMemory(&addr, sizeof(addr));
|
|
||||||
addr.sin6_family=AF_INET6;
|
|
||||||
int size=sizeof(addr);
|
|
||||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
|
||||||
wchar_t buf[INET6_ADDRSTRLEN];
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, address.c_str(), -1, buf, INET6_ADDRSTRLEN);
|
|
||||||
WSAStringToAddressW(buf, AF_INET, NULL, (sockaddr*)&addr, &size);
|
|
||||||
#else
|
|
||||||
WSAStringToAddressA((char*)address.c_str(), AF_INET, NULL, (sockaddr*)&addr, &size);
|
|
||||||
#endif
|
|
||||||
memcpy(out, addr.sin6_addr.s6_addr, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::Connect(const NetworkAddress address, uint16_t port){
|
|
||||||
sockaddr_in v4;
|
|
||||||
sockaddr_in6 v6;
|
|
||||||
sockaddr* addr=NULL;
|
|
||||||
size_t addrLen=0;
|
|
||||||
if(!address.isIPv6){
|
|
||||||
v4.sin_family=AF_INET;
|
|
||||||
v4.sin_addr.s_addr=address.addr.ipv4;
|
|
||||||
v4.sin_port=htons(port);
|
|
||||||
addr=reinterpret_cast<sockaddr*>(&v4);
|
|
||||||
addrLen=sizeof(v4);
|
|
||||||
}else{
|
|
||||||
v6.sin6_family=AF_INET6;
|
|
||||||
memcpy(v6.sin6_addr.s6_addr, address.addr.ipv6, 16);
|
|
||||||
v6.sin6_flowinfo=0;
|
|
||||||
v6.sin6_scope_id=0;
|
|
||||||
v6.sin6_port=htons(port);
|
|
||||||
addr=reinterpret_cast<sockaddr*>(&v6);
|
|
||||||
addrLen=sizeof(v6);
|
|
||||||
}
|
|
||||||
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
|
|
||||||
if(fd==INVALID_SOCKET){
|
|
||||||
LOGE("Error creating TCP socket: %d", WSAGetLastError());
|
|
||||||
failed=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
u_long one=1;
|
|
||||||
ioctlsocket(fd, FIONBIO, &one);
|
|
||||||
int opt=1;
|
|
||||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&opt, sizeof(opt));
|
|
||||||
DWORD timeout=5000;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
||||||
timeout=60000;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
||||||
int res=connect(fd, (const sockaddr*) addr, addrLen);
|
|
||||||
if(res!=0){
|
|
||||||
int error=WSAGetLastError();
|
|
||||||
if(error!=WSAEINPROGRESS && error!=WSAEWOULDBLOCK){
|
|
||||||
LOGW("error connecting TCP socket to %s:%u: %d / %s", address.ToString().c_str(), port, error, WindowsSpecific::GetErrorMessage(error).c_str());
|
|
||||||
closesocket(fd);
|
|
||||||
failed=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tcpConnectedAddress=address;
|
|
||||||
tcpConnectedPort=port;
|
|
||||||
LOGI("successfully connected to %s:%d", tcpConnectedAddress.ToString().c_str(), tcpConnectedPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkSocketWinsock::ResolveDomainName(std::string name){
|
|
||||||
addrinfo* addr0;
|
|
||||||
NetworkAddress ret=NetworkAddress::Empty();
|
|
||||||
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
|
|
||||||
if(res!=0){
|
|
||||||
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerrorA(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=NetworkAddress::IPv4(addr->sin_addr.s_addr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
freeaddrinfo(addr0);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkAddress NetworkSocketWinsock::GetConnectedAddress(){
|
|
||||||
return tcpConnectedAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t NetworkSocketWinsock::GetConnectedPort(){
|
|
||||||
return tcpConnectedPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkSocketWinsock::SetTimeouts(int sendTimeout, int recvTimeout){
|
|
||||||
DWORD timeout=sendTimeout*1000;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
||||||
timeout=recvTimeout*1000;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkSocketWinsock::Select(std::vector<NetworkSocket*> &readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*> &errorFds, SocketSelectCanceller* _canceller){
|
|
||||||
fd_set readSet;
|
|
||||||
fd_set errorSet;
|
|
||||||
fd_set writeSet;
|
|
||||||
SocketSelectCancellerWin32* canceller=dynamic_cast<SocketSelectCancellerWin32*>(_canceller);
|
|
||||||
timeval timeout={0, 10000};
|
|
||||||
bool anyFailed=false;
|
|
||||||
int res=0;
|
|
||||||
|
|
||||||
do{
|
|
||||||
FD_ZERO(&readSet);
|
|
||||||
FD_ZERO(&writeSet);
|
|
||||||
FD_ZERO(&errorSet);
|
|
||||||
|
|
||||||
for(std::vector<NetworkSocket*>::iterator itr=readFds.begin();itr!=readFds.end();++itr){
|
|
||||||
int sfd=GetDescriptorFromSocket(*itr);
|
|
||||||
if(sfd==0){
|
|
||||||
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
FD_SET(sfd, &readSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(NetworkSocket*& s:writeFds){
|
|
||||||
int sfd=GetDescriptorFromSocket(s);
|
|
||||||
if(sfd==0){
|
|
||||||
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
FD_SET(sfd, &writeSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::vector<NetworkSocket*>::iterator itr=errorFds.begin();itr!=errorFds.end();++itr){
|
|
||||||
int sfd=GetDescriptorFromSocket(*itr);
|
|
||||||
if(sfd==0){
|
|
||||||
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if((*itr)->timeout>0 && VoIPController::GetCurrentTime()-(*itr)->lastSuccessfulOperationTime>(*itr)->timeout){
|
|
||||||
LOGW("Socket %d timed out", sfd);
|
|
||||||
(*itr)->failed=true;
|
|
||||||
}
|
|
||||||
anyFailed |= (*itr)->IsFailed();
|
|
||||||
FD_SET(sfd, &errorSet);
|
|
||||||
}
|
|
||||||
if(canceller && canceller->canceled)
|
|
||||||
break;
|
|
||||||
res=select(0, &readSet, &writeSet, &errorSet, &timeout);
|
|
||||||
//LOGV("select result %d", res);
|
|
||||||
if(res==SOCKET_ERROR)
|
|
||||||
LOGE("SELECT ERROR %d", WSAGetLastError());
|
|
||||||
}while(res==0);
|
|
||||||
|
|
||||||
|
|
||||||
if(canceller && canceller->canceled && !anyFailed){
|
|
||||||
canceller->canceled=false;
|
|
||||||
return false;
|
|
||||||
}else if(anyFailed){
|
|
||||||
FD_ZERO(&readSet);
|
|
||||||
FD_ZERO(&errorSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<NetworkSocket*>::iterator itr=readFds.begin();
|
|
||||||
while(itr!=readFds.end()){
|
|
||||||
int sfd=GetDescriptorFromSocket(*itr);
|
|
||||||
if(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(FD_ISSET(sfd, &writeSet)){
|
|
||||||
(*itr)->lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
|
|
||||||
LOGI("Socket %d is ready to send", sfd);
|
|
||||||
}
|
|
||||||
if(sfd==0 || !FD_ISSET(sfd, &writeSet) || !(*itr)->OnReadyToSend()){
|
|
||||||
itr=writeFds.erase(itr);
|
|
||||||
}else{
|
|
||||||
++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, error=%d", readFds.size(), errorFds.size());
|
|
||||||
|
|
||||||
return readFds.size()>0 || errorFds.size()>0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketSelectCancellerWin32::SocketSelectCancellerWin32(){
|
|
||||||
canceled=false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketSelectCancellerWin32::~SocketSelectCancellerWin32(){
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketSelectCancellerWin32::CancelSelect(){
|
|
||||||
canceled=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NetworkSocketWinsock::GetDescriptorFromSocket(NetworkSocket *socket){
|
|
||||||
NetworkSocketWinsock* sp=dynamic_cast<NetworkSocketWinsock*>(socket);
|
|
||||||
if(sp)
|
|
||||||
return sp->fd;
|
|
||||||
NetworkSocketWrapper* sw=dynamic_cast<NetworkSocketWrapper*>(socket);
|
|
||||||
if(sw)
|
|
||||||
return GetDescriptorFromSocket(sw->GetWrapped());
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
//
|
|
||||||
// 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_NETWORKSOCKETWINSOCK_H
|
|
||||||
#define LIBTGVOIP_NETWORKSOCKETWINSOCK_H
|
|
||||||
|
|
||||||
#include "../../NetworkSocket.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace tgvoip {
|
|
||||||
class Buffer;
|
|
||||||
|
|
||||||
class SocketSelectCancellerWin32 : public SocketSelectCanceller{
|
|
||||||
friend class NetworkSocketWinsock;
|
|
||||||
public:
|
|
||||||
SocketSelectCancellerWin32();
|
|
||||||
virtual ~SocketSelectCancellerWin32();
|
|
||||||
virtual void CancelSelect();
|
|
||||||
private:
|
|
||||||
bool canceled;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NetworkSocketWinsock : public NetworkSocket{
|
|
||||||
public:
|
|
||||||
NetworkSocketWinsock(NetworkProtocol protocol);
|
|
||||||
virtual ~NetworkSocketWinsock();
|
|
||||||
virtual void Send(NetworkPacket packet) override;
|
|
||||||
virtual NetworkPacket Receive(size_t maxLen) override;
|
|
||||||
virtual void Open() override;
|
|
||||||
virtual void Close() override;
|
|
||||||
virtual std::string GetLocalInterfaceInfo(NetworkAddress* v4addr, NetworkAddress* v6addr) override;
|
|
||||||
virtual void OnActiveInterfaceChanged() override;
|
|
||||||
virtual uint16_t GetLocalPort() override;
|
|
||||||
virtual void Connect(const NetworkAddress address, uint16_t port) 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 NetworkAddress 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();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int GetDescriptorFromSocket(NetworkSocket* socket);
|
|
||||||
uintptr_t fd;
|
|
||||||
bool needUpdateNat64Prefix;
|
|
||||||
bool nat64Present;
|
|
||||||
double switchToV6at;
|
|
||||||
bool isV4Available;
|
|
||||||
bool isAtLeastVista;
|
|
||||||
bool closing;
|
|
||||||
NetworkAddress tcpConnectedAddress=NetworkAddress::Empty();
|
|
||||||
uint16_t tcpConnectedPort;
|
|
||||||
NetworkPacket pendingOutgoingPacket=NetworkPacket::Empty();
|
|
||||||
Buffer recvBuf=Buffer(2048);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBTGVOIP_NETWORKSOCKETWINSOCK_H
|
|
|
@ -1,68 +0,0 @@
|
||||||
|
|
||||||
//
|
|
||||||
// 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 "WindowsSandboxUtils.h"
|
|
||||||
#include <audioclient.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#ifdef TGVOIP_WP_SILVERLIGHT
|
|
||||||
#include <phoneaudioclient.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace tgvoip;
|
|
||||||
using namespace Microsoft::WRL;
|
|
||||||
|
|
||||||
|
|
||||||
IAudioClient2* WindowsSandboxUtils::ActivateAudioDevice(const wchar_t* devID, HRESULT* callRes, HRESULT* actRes) {
|
|
||||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
|
||||||
// Did I say that I hate pointlessly asynchronous things?
|
|
||||||
HANDLE event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
|
|
||||||
ActivationHandler activationHandler(event);
|
|
||||||
IActivateAudioInterfaceAsyncOperation* actHandler;
|
|
||||||
HRESULT cr = ActivateAudioInterfaceAsync(devID, __uuidof(IAudioClient2), NULL, (IActivateAudioInterfaceCompletionHandler*)&activationHandler, &actHandler);
|
|
||||||
if (callRes)
|
|
||||||
*callRes = cr;
|
|
||||||
DWORD resulttt = WaitForSingleObjectEx(event, INFINITE, false);
|
|
||||||
DWORD last = GetLastError();
|
|
||||||
CloseHandle(event);
|
|
||||||
if (actRes)
|
|
||||||
*actRes = activationHandler.actResult;
|
|
||||||
return activationHandler.client;
|
|
||||||
#else
|
|
||||||
IAudioClient2* client;
|
|
||||||
HRESULT res=ActivateAudioInterface(devID, __uuidof(IAudioClient2), (void**)&client);
|
|
||||||
if(callRes)
|
|
||||||
*callRes=S_OK;
|
|
||||||
if(actRes)
|
|
||||||
*actRes=res;
|
|
||||||
return client;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
|
||||||
ActivationHandler::ActivationHandler(HANDLE _event) : event(_event)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
STDMETHODIMP ActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation * operation)
|
|
||||||
{
|
|
||||||
HRESULT hr = S_OK;
|
|
||||||
HRESULT hrActivateResult = S_OK;
|
|
||||||
IUnknown *punkAudioInterface = nullptr;
|
|
||||||
|
|
||||||
hr = operation->GetActivateResult(&hrActivateResult, &punkAudioInterface);
|
|
||||||
if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult))
|
|
||||||
{
|
|
||||||
punkAudioInterface->QueryInterface(IID_PPV_ARGS(&client));
|
|
||||||
}
|
|
||||||
|
|
||||||
SetEvent(event);
|
|
||||||
|
|
||||||
return hr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,9 +0,0 @@
|
||||||
#include "WindowsSpecific.h"
|
|
||||||
|
|
||||||
using namespace tgvoip;
|
|
||||||
|
|
||||||
std::string WindowsSpecific::GetErrorMessage(DWORD code){
|
|
||||||
char buf[1024]={0};
|
|
||||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buf, sizeof(buf), NULL);
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue