Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,76 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "AudioInputAndroid.h"
#include <stdio.h>
#include "../../logging.h"
#include "JNIUtilities.h"
#include "tgnet/FileLog.h"
extern JavaVM* sharedJVM;
using namespace tgvoip;
using namespace tgvoip::audio;
jmethodID AudioInputAndroid::initMethod=NULL;
jmethodID AudioInputAndroid::releaseMethod=NULL;
jmethodID AudioInputAndroid::startMethod=NULL;
jmethodID AudioInputAndroid::stopMethod=NULL;
jmethodID AudioInputAndroid::getEnabledEffectsMaskMethod=NULL;
jclass AudioInputAndroid::jniClass=NULL;
AudioInputAndroid::AudioInputAndroid(){
jni::DoWithJNI([this](JNIEnv* env){
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
DEBUG_REF("AudioInputAndroid");
javaObject=env->NewGlobalRef(obj);
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
enabledEffects=(unsigned int)env->CallIntMethod(javaObject, getEnabledEffectsMaskMethod);
});
running=false;
}
AudioInputAndroid::~AudioInputAndroid(){
{
MutexGuard guard(mutex);
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, releaseMethod);
DEBUG_DELREF("AudioInputAndroid");
env->DeleteGlobalRef(javaObject);
javaObject=NULL;
});
}
}
void AudioInputAndroid::Start(){
MutexGuard guard(mutex);
jni::DoWithJNI([this](JNIEnv* env){
failed=!env->CallBooleanMethod(javaObject, startMethod);
});
running=true;
}
void AudioInputAndroid::Stop(){
MutexGuard guard(mutex);
running=false;
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, stopMethod);
});
}
void AudioInputAndroid::HandleCallback(JNIEnv* env, jobject buffer){
if(!running)
return;
unsigned char* buf=(unsigned char*) env->GetDirectBufferAddress(buffer);
size_t len=(size_t) env->GetDirectBufferCapacity(buffer);
InvokeCallback(buf, len);
}
unsigned int AudioInputAndroid::GetEnabledEffects(){
return enabledEffects;
}

View file

@ -0,0 +1,43 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTANDROID_H
#define LIBTGVOIP_AUDIOINPUTANDROID_H
#include <jni.h>
#include "../../audio/AudioInput.h"
#include "../../threading.h"
namespace tgvoip{ namespace audio{
class AudioInputAndroid : public AudioInput{
public:
AudioInputAndroid();
virtual ~AudioInputAndroid();
virtual void Start();
virtual void Stop();
void HandleCallback(JNIEnv* env, jobject buffer);
unsigned int GetEnabledEffects();
static jmethodID initMethod;
static jmethodID releaseMethod;
static jmethodID startMethod;
static jmethodID stopMethod;
static jmethodID getEnabledEffectsMaskMethod;
static jclass jniClass;
static constexpr unsigned int EFFECT_AEC=1;
static constexpr unsigned int EFFECT_NS=2;
private:
jobject javaObject;
bool running;
Mutex mutex;
unsigned int enabledEffects=0;
};
}}
#endif //LIBTGVOIP_AUDIOINPUTANDROID_H

View file

@ -0,0 +1,137 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "AudioInputOpenSLES.h"
#include "../../logging.h"
#include "OpenSLEngineWrapper.h"
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return; }
#define BUFFER_SIZE 960 // 20 ms
using namespace tgvoip;
using namespace tgvoip::audio;
unsigned int AudioInputOpenSLES::nativeBufferSize;
AudioInputOpenSLES::AudioInputOpenSLES(){
slEngine=OpenSLEngineWrapper::CreateEngine();
LOGI("Native buffer size is %u samples", nativeBufferSize);
if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
LOGE("20ms is not divisible by native buffer size!!");
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
LOGE("native buffer size is not multiple of 20ms!!");
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
}
if(nativeBufferSize==BUFFER_SIZE)
nativeBufferSize*=2;
LOGI("Adjusted native buffer size is %u", nativeBufferSize);
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
slRecorderObj=NULL;
}
AudioInputOpenSLES::~AudioInputOpenSLES(){
//Stop();
(*slBufferQueue)->Clear(slBufferQueue);
(*slRecorderObj)->Destroy(slRecorderObj);
slRecorderObj=NULL;
slRecorder=NULL;
slBufferQueue=NULL;
slEngine=NULL;
OpenSLEngineWrapper::DestroyEngine();
free(buffer);
buffer=NULL;
free(nativeBuffer);
nativeBuffer=NULL;
}
void AudioInputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
((AudioInputOpenSLES*)context)->HandleSLCallback();
}
void AudioInputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
assert(slRecorderObj==NULL);
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audioSrc = {&loc_dev, NULL};
SLDataLocator_AndroidSimpleBufferQueue loc_bq =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
SLDataSink audioSnk = {&loc_bq, &format_pcm};
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*slEngine)->CreateAudioRecorder(slEngine, &slRecorderObj, &audioSrc, &audioSnk, 2, id, req);
CHECK_SL_ERROR(result, "Error creating recorder");
SLAndroidConfigurationItf recorderConfig;
result = (*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDCONFIGURATION, &recorderConfig);
SLint32 streamType = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
result = (*recorderConfig)->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &streamType, sizeof(SLint32));
result=(*slRecorderObj)->Realize(slRecorderObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing recorder");
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_RECORD, &slRecorder);
CHECK_SL_ERROR(result, "Error getting recorder interface");
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
CHECK_SL_ERROR(result, "Error getting buffer queue");
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioInputOpenSLES::BufferCallback, this);
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}
void AudioInputOpenSLES::Start(){
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_RECORDING);
CHECK_SL_ERROR(result, "Error starting record");
}
void AudioInputOpenSLES::Stop(){
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_STOPPED);
CHECK_SL_ERROR(result, "Error stopping record");
}
void AudioInputOpenSLES::HandleSLCallback(){
//SLmillisecond pMsec = 0;
//(*slRecorder)->GetPosition(slRecorder, &pMsec);
//LOGI("Callback! pos=%lu", pMsec);
//InvokeCallback((unsigned char*)buffer, BUFFER_SIZE*sizeof(int16_t));
//fwrite(nativeBuffer, 1, nativeBufferSize*2, test);
if(nativeBufferSize==BUFFER_SIZE){
//LOGV("nativeBufferSize==BUFFER_SIZE");
InvokeCallback((unsigned char *) nativeBuffer, BUFFER_SIZE*sizeof(int16_t));
}else if(nativeBufferSize<BUFFER_SIZE){
//LOGV("nativeBufferSize<BUFFER_SIZE");
if(positionInBuffer>=BUFFER_SIZE){
InvokeCallback((unsigned char *) buffer, BUFFER_SIZE*sizeof(int16_t));
positionInBuffer=0;
}
memcpy(((unsigned char*)buffer)+positionInBuffer*2, nativeBuffer, (size_t)nativeBufferSize*2);
positionInBuffer+=nativeBufferSize;
}else if(nativeBufferSize>BUFFER_SIZE){
//LOGV("nativeBufferSize>BUFFER_SIZE");
for(unsigned int offset=0;offset<nativeBufferSize;offset+=BUFFER_SIZE){
InvokeCallback(((unsigned char *) nativeBuffer)+offset*2, BUFFER_SIZE*sizeof(int16_t));
}
}
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}

View file

@ -0,0 +1,40 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_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

View file

@ -0,0 +1,110 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "AudioOutputAndroid.h"
#include <stdio.h>
#include "../../logging.h"
#include "tgnet/FileLog.h"
extern JavaVM* sharedJVM;
using namespace tgvoip;
using namespace tgvoip::audio;
jmethodID AudioOutputAndroid::initMethod=NULL;
jmethodID AudioOutputAndroid::releaseMethod=NULL;
jmethodID AudioOutputAndroid::startMethod=NULL;
jmethodID AudioOutputAndroid::stopMethod=NULL;
jclass AudioOutputAndroid::jniClass=NULL;
AudioOutputAndroid::AudioOutputAndroid(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
DEBUG_REF("AudioOutputAndroid");
javaObject=env->NewGlobalRef(obj);
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
running=false;
}
AudioOutputAndroid::~AudioOutputAndroid(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, releaseMethod);
DEBUG_DELREF("AudioOutputAndroid");
env->DeleteGlobalRef(javaObject);
javaObject=NULL;
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
void AudioOutputAndroid::Start(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, startMethod);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
running=true;
}
void AudioOutputAndroid::Stop(){
running=false;
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, stopMethod);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
void AudioOutputAndroid::HandleCallback(JNIEnv* env, jbyteArray buffer){
if(!running)
return;
unsigned char* buf=(unsigned char*) env->GetByteArrayElements(buffer, NULL);
size_t len=(size_t) env->GetArrayLength(buffer);
InvokeCallback(buf, len);
env->ReleaseByteArrayElements(buffer, (jbyte *) buf, 0);
}
bool AudioOutputAndroid::IsPlaying(){
return running;
}

View file

@ -0,0 +1,37 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef 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

View file

@ -0,0 +1,171 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include "AudioOutputOpenSLES.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include "OpenSLEngineWrapper.h"
#include "AudioInputAndroid.h"
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); failed=true; return; }
#define BUFFER_SIZE 960 // 20 ms
using namespace tgvoip;
using namespace tgvoip::audio;
unsigned int AudioOutputOpenSLES::nativeBufferSize;
AudioOutputOpenSLES::AudioOutputOpenSLES(){
SLresult result;
slEngine=OpenSLEngineWrapper::CreateEngine();
const SLInterfaceID pOutputMixIDs[] = {};
const SLboolean pOutputMixRequired[] = {};
result = (*slEngine)->CreateOutputMix(slEngine, &slOutputMixObj, 0, pOutputMixIDs, pOutputMixRequired);
CHECK_SL_ERROR(result, "Error creating output mix");
result = (*slOutputMixObj)->Realize(slOutputMixObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing output mix");
LOGI("Native buffer size is %u samples", nativeBufferSize);
/*if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
LOGE("20ms is not divisible by native buffer size!!");
nativeBufferSize=BUFFER_SIZE;
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
LOGE("native buffer size is not multiple of 20ms!!");
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
}
LOGI("Adjusted native buffer size is %u", nativeBufferSize);*/
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
slPlayerObj=NULL;
remainingDataSize=0;
}
AudioOutputOpenSLES::~AudioOutputOpenSLES(){
if(!stopped)
Stop();
(*slBufferQueue)->Clear(slBufferQueue);
LOGV("destroy slPlayerObj");
(*slPlayerObj)->Destroy(slPlayerObj);
LOGV("destroy slOutputMixObj");
(*slOutputMixObj)->Destroy(slOutputMixObj);
OpenSLEngineWrapper::DestroyEngine();
free(buffer);
free(nativeBuffer);
}
void AudioOutputOpenSLES::SetNativeBufferSize(unsigned int size){
AudioOutputOpenSLES::nativeBufferSize=size;
}
void AudioOutputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
((AudioOutputOpenSLES*)context)->HandleSLCallback();
}
void AudioOutputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
assert(slPlayerObj==NULL);
SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
SLDataFormat_PCM formatPCM = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
SLDataSource audioSrc = {&locatorBufferQueue, &formatPCM};
SLDataLocator_OutputMix locatorOutMix = {SL_DATALOCATOR_OUTPUTMIX, slOutputMixObj};
SLDataSink audioSnk = {&locatorOutMix, NULL};
const SLInterfaceID id[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObj, &audioSrc, &audioSnk, 2, id, req);
CHECK_SL_ERROR(result, "Error creating player");
SLAndroidConfigurationItf playerConfig;
result = (*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
SLint32 streamType = SL_ANDROID_STREAM_VOICE;
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
result=(*slPlayerObj)->Realize(slPlayerObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing player");
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_PLAY, &slPlayer);
CHECK_SL_ERROR(result, "Error getting player interface");
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
CHECK_SL_ERROR(result, "Error getting buffer queue");
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioOutputOpenSLES::BufferCallback, this);
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}
bool AudioOutputOpenSLES::IsPhone(){
return false;
}
void AudioOutputOpenSLES::EnableLoudspeaker(bool enabled){
}
void AudioOutputOpenSLES::Start(){
stopped=false;
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PLAYING);
CHECK_SL_ERROR(result, "Error starting player");
}
void AudioOutputOpenSLES::Stop(){
stopped=true;
LOGV("Stopping OpenSL output");
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PAUSED);
CHECK_SL_ERROR(result, "Error starting player");
}
void AudioOutputOpenSLES::HandleSLCallback(){
/*if(stopped){
//LOGV("left HandleSLCallback early");
return;
}*/
//LOGV("before InvokeCallback");
if(!stopped){
while(remainingDataSize<nativeBufferSize*2){
assert(remainingDataSize+BUFFER_SIZE*2<10240);
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
remainingDataSize+=BUFFER_SIZE*2;
}
memcpy(nativeBuffer, remainingData, nativeBufferSize*2);
remainingDataSize-=nativeBufferSize*2;
if(remainingDataSize>0)
memmove(remainingData, remainingData+nativeBufferSize*2, remainingDataSize);
//InvokeCallback((unsigned char *) nativeBuffer, nativeBufferSize*sizeof(int16_t));
}else{
memset(nativeBuffer, 0, nativeBufferSize*2);
}
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
//LOGV("left HandleSLCallback");
}
bool AudioOutputOpenSLES::IsPlaying(){
if(slPlayer){
uint32_t state;
(*slPlayer)->GetPlayState(slPlayer, &state);
return state==SL_PLAYSTATE_PLAYING;
}
return false;
}
float AudioOutputOpenSLES::GetLevel(){
return 0; // we don't use this anyway
}

View file

@ -0,0 +1,47 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
#define LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "../../audio/AudioOutput.h"
namespace tgvoip{ namespace audio{
class AudioOutputOpenSLES : public AudioOutput{
public:
AudioOutputOpenSLES();
virtual ~AudioOutputOpenSLES();
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
virtual bool IsPhone();
virtual void EnableLoudspeaker(bool enabled);
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
virtual float GetLevel();
static void SetNativeBufferSize(unsigned int size);
static unsigned int nativeBufferSize;
private:
static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
void HandleSLCallback();
SLEngineItf slEngine;
SLObjectItf slPlayerObj;
SLObjectItf slOutputMixObj;
SLPlayItf slPlayer;
SLAndroidSimpleBufferQueueItf slBufferQueue;
int16_t* buffer;
int16_t* nativeBuffer;
bool stopped;
unsigned char remainingData[10240];
size_t remainingDataSize;
};
}}
#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H

View file

@ -0,0 +1,70 @@
//
// Created by Grishka on 15.08.2018.
//
#ifndef LIBTGVOIP_JNIUTILITIES_H
#define LIBTGVOIP_JNIUTILITIES_H
#include <functional>
#include <jni.h>
#include <stdarg.h>
#include <string>
#include "../../Buffers.h"
extern JavaVM* sharedJVM;
namespace tgvoip{
namespace jni{
inline JNIEnv *GetEnv() {
JNIEnv *env = nullptr;
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
return env;
}
inline void DoWithJNI(std::function<void(JNIEnv*)> f){
JNIEnv *env=GetEnv();
bool didAttach=false;
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
f(env);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
inline void AttachAndCallVoidMethod(jmethodID method, jobject obj, ...){
if(!method || !obj)
return;
va_list va;
va_start(va, obj);
DoWithJNI([&va, method, obj](JNIEnv* env){
env->CallVoidMethodV(obj, method, va);
});
va_end(va);
}
inline std::string JavaStringToStdString(JNIEnv* env, jstring jstr){
if(!jstr)
return "";
const char* jchars=env->GetStringUTFChars(jstr, NULL);
std::string str(jchars);
env->ReleaseStringUTFChars(jstr, jchars);
return str;
}
inline jbyteArray BufferToByteArray(JNIEnv* env, Buffer& buf){
jbyteArray arr=env->NewByteArray((jsize)buf.Length());
jbyte* elements=env->GetByteArrayElements(arr, NULL);
memcpy(elements, *buf, buf.Length());
env->ReleaseByteArrayElements(arr, elements, 0);
return arr;
}
}
}
#endif //LIBTGVOIP_JNIUTILITIES_H

View file

@ -0,0 +1,48 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#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;
}

View file

@ -0,0 +1,26 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_OPENSLENGINEWRAPPER_H
#define LIBTGVOIP_OPENSLENGINEWRAPPER_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
namespace tgvoip{ namespace audio{
class OpenSLEngineWrapper{
public:
static SLEngineItf CreateEngine();
static void DestroyEngine();
private:
static SLObjectItf sharedEngineObj;
static SLEngineItf sharedEngine;
static int count;
};
}}
#endif //LIBTGVOIP_OPENSLENGINEWRAPPER_H

View file

@ -0,0 +1,150 @@
//
// Created by Grishka on 12.08.2018.
//
#include "VideoRendererAndroid.h"
#include "JNIUtilities.h"
#include "../../PrivateDefines.h"
#include "../../logging.h"
using namespace tgvoip;
using namespace tgvoip::video;
jmethodID VideoRendererAndroid::resetMethod=NULL;
jmethodID VideoRendererAndroid::decodeAndDisplayMethod=NULL;
jmethodID VideoRendererAndroid::setStreamEnabledMethod=NULL;
jmethodID VideoRendererAndroid::setRotationMethod=NULL;
std::vector<uint32_t> VideoRendererAndroid::availableDecoders;
int VideoRendererAndroid::maxResolution;
extern JavaVM* sharedJVM;
VideoRendererAndroid::VideoRendererAndroid(jobject jobj) : queue(50){
this->jobj=jobj;
}
VideoRendererAndroid::~VideoRendererAndroid(){
running=false;
Request req{
Buffer(0),
Request::Type::Shutdown
};
queue.Put(std::move(req));
thread->Join();
delete thread;
/*decoderThread.Post([this]{
decoderEnv->DeleteGlobalRef(jobj);
});*/
}
void VideoRendererAndroid::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer> &_csd){
csd.clear();
for(Buffer& b:_csd){
csd.push_back(Buffer::CopyOf(b));
}
this->codec=codec;
this->width=width;
this->height=height;
Request req{
Buffer(0),
Request::Type::ResetDecoder
};
queue.Put(std::move(req));
Request req2{
Buffer(0),
Request::Type::UpdateStreamState
};
queue.Put(std::move(req2));
if(!thread){
thread=new Thread(std::bind(&VideoRendererAndroid::RunThread, this));
thread->Start();
}
}
void VideoRendererAndroid::DecodeAndDisplay(Buffer frame, uint32_t pts){
/*decoderThread.Post(std::bind([this](Buffer frame){
}, std::move(frame)));*/
//LOGV("2 before decode %u", (unsigned int)frame.Length());
Request req{
std::move(frame),
Request::Type::DecodeFrame
};
queue.Put(std::move(req));
}
void VideoRendererAndroid::RunThread(){
JNIEnv* env;
sharedJVM->AttachCurrentThread(&env, NULL);
constexpr size_t bufferSize=200*1024;
unsigned char* buf=reinterpret_cast<unsigned char*>(malloc(bufferSize));
jobject jbuf=env->NewDirectByteBuffer(buf, bufferSize);
uint16_t lastSetRotation=0;
while(running){
//LOGV("before get from queue");
Request request=std::move(queue.GetBlocking());
//LOGV("1 before decode %u", (unsigned int)request.Length());
if(request.type==Request::Type::Shutdown){
LOGI("Shutting down video decoder thread");
break;
}else if(request.type==Request::Type::DecodeFrame){
if(request.buffer.Length()>bufferSize){
LOGE("Frame data is too long (%u, max %u)", (int) request.buffer.Length(), (int) bufferSize);
}else{
if(lastSetRotation!=rotation){
lastSetRotation=rotation;
env->CallVoidMethod(jobj, setRotationMethod, (jint)rotation);
}
memcpy(buf, *request.buffer, request.buffer.Length());
env->CallVoidMethod(jobj, decodeAndDisplayMethod, jbuf, (jint) request.buffer.Length(), 0);
}
}else if(request.type==Request::Type::ResetDecoder){
jobjectArray jcsd=NULL;
if(!csd.empty()){
jcsd=env->NewObjectArray((jsize)csd.size(), env->FindClass("[B"), NULL);
jsize i=0;
for(Buffer& b:csd){
env->SetObjectArrayElement(jcsd, i, jni::BufferToByteArray(env, b));
i++;
}
}
std::string codecStr="";
switch(codec){
case CODEC_AVC:
codecStr="video/avc";
break;
case CODEC_HEVC:
codecStr="video/hevc";
break;
case CODEC_VP8:
codecStr="video/x-vnd.on2.vp8";
break;
case CODEC_VP9:
codecStr="video/x-vnd.on2.vp9";
break;
}
env->CallVoidMethod(jobj, resetMethod, env->NewStringUTF(codecStr.c_str()), (jint)width, (jint)height, jcsd);
}else if(request.type==Request::Type::UpdateStreamState){
env->CallVoidMethod(jobj, setStreamEnabledMethod, streamEnabled);
}
}
free(buf);
sharedJVM->DetachCurrentThread();
LOGI("==== decoder thread exiting ====");
}
void VideoRendererAndroid::SetStreamEnabled(bool enabled){
LOGI("Video stream state: %d", enabled);
streamEnabled=enabled;
Request req{
Buffer(0),
Request::Type::UpdateStreamState
};
queue.Put(std::move(req));
}
void VideoRendererAndroid::SetRotation(uint16_t rotation){
this->rotation=rotation;
}

View file

@ -0,0 +1,58 @@
//
// Created by Grishka on 12.08.2018.
//
#ifndef LIBTGVOIP_VIDEORENDERERANDROID_H
#define LIBTGVOIP_VIDEORENDERERANDROID_H
#include "../../video/VideoRenderer.h"
#include "../../MessageThread.h"
#include <jni.h>
#include "../../BlockingQueue.h"
namespace tgvoip{
namespace video{
class VideoRendererAndroid : public VideoRenderer{
public:
VideoRendererAndroid(jobject jobj);
virtual ~VideoRendererAndroid();
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
virtual void SetStreamEnabled(bool enabled) override;
virtual void SetRotation(uint16_t rotation) override;
static jmethodID resetMethod;
static jmethodID decodeAndDisplayMethod;
static jmethodID setStreamEnabledMethod;
static jmethodID setRotationMethod;
static std::vector<uint32_t> availableDecoders;
static int maxResolution;
private:
struct Request{
enum Type{
DecodeFrame,
ResetDecoder,
UpdateStreamState,
Shutdown
};
Buffer buffer;
Type type;
};
void RunThread();
Thread* thread=NULL;
bool running=true;
BlockingQueue<Request> queue;
std::vector<Buffer> csd;
int width;
int height;
bool streamEnabled=true;
uint32_t codec;
uint16_t rotation=0;
jobject jobj;
};
}
}
#endif //LIBTGVOIP_VIDEORENDERERANDROID_H

View file

@ -0,0 +1,90 @@
//
// Created by Grishka on 12.08.2018.
//
#include "VideoSourceAndroid.h"
#include "JNIUtilities.h"
#include "../../logging.h"
#include "../../PrivateDefines.h"
#include "tgnet/FileLog.h"
using namespace tgvoip;
using namespace tgvoip::video;
extern JavaVM* sharedJVM;
std::vector<uint32_t> VideoSourceAndroid::availableEncoders;
VideoSourceAndroid::VideoSourceAndroid(jobject jobj) : javaObject(jobj){
jni::DoWithJNI([&](JNIEnv* env){
jclass cls=env->GetObjectClass(javaObject);
startMethod=env->GetMethodID(cls, "start", "()V");
stopMethod=env->GetMethodID(cls, "stop", "()V");
prepareEncoderMethod=env->GetMethodID(cls, "prepareEncoder", "(Ljava/lang/String;I)V");
requestKeyFrameMethod=env->GetMethodID(cls, "requestKeyFrame", "()V");
setBitrateMethod=env->GetMethodID(cls, "setBitrate", "(I)V");
});
}
VideoSourceAndroid::~VideoSourceAndroid(){
jni::DoWithJNI([this](JNIEnv* env){
DEBUG_DELREF("VideoSourceAndroid");
env->DeleteGlobalRef(javaObject);
});
}
void VideoSourceAndroid::Start(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, startMethod);
});
}
void VideoSourceAndroid::Stop(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, stopMethod);
});
}
void VideoSourceAndroid::SendFrame(Buffer frame, uint32_t flags){
callback(frame, flags, rotation);
}
void VideoSourceAndroid::SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height){
LOGD("Video stream parameters: %d x %d", width, height);
this->width=width;
this->height=height;
this->csd=std::move(csd);
}
void VideoSourceAndroid::Reset(uint32_t codec, int maxResolution){
jni::DoWithJNI([&](JNIEnv* env){
std::string codecStr="";
switch(codec){
case CODEC_AVC:
codecStr="video/avc";
break;
case CODEC_HEVC:
codecStr="video/hevc";
break;
case CODEC_VP8:
codecStr="video/x-vnd.on2.vp8";
break;
case CODEC_VP9:
codecStr="video/x-vnd.on2.vp9";
break;
}
env->CallVoidMethod(javaObject, prepareEncoderMethod, env->NewStringUTF(codecStr.c_str()), maxResolution);
});
}
void VideoSourceAndroid::RequestKeyFrame(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, requestKeyFrameMethod);
});
}
void VideoSourceAndroid::SetBitrate(uint32_t bitrate){
jni::DoWithJNI([&](JNIEnv* env){
env->CallVoidMethod(javaObject, setBitrateMethod, (jint)bitrate);
});
}

View file

@ -0,0 +1,40 @@
//
// Created by Grishka on 12.08.2018.
//
#ifndef LIBTGVOIP_VIDEOSOURCEANDROID_H
#define LIBTGVOIP_VIDEOSOURCEANDROID_H
#include "../../video/VideoSource.h"
#include "../../Buffers.h"
#include <jni.h>
#include <vector>
namespace tgvoip{
namespace video{
class VideoSourceAndroid : public VideoSource{
public:
VideoSourceAndroid(jobject jobj);
virtual ~VideoSourceAndroid();
virtual void Start() override;
virtual void Stop() override;
virtual void Reset(uint32_t codec, int maxResolution) override;
void SendFrame(Buffer frame, uint32_t flags);
void SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height);
virtual void RequestKeyFrame() override;
virtual void SetBitrate(uint32_t bitrate) override;
static std::vector<uint32_t> availableEncoders;
private:
jobject javaObject;
jmethodID prepareEncoderMethod;
jmethodID startMethod;
jmethodID stopMethod;
jmethodID requestKeyFrameMethod;
jmethodID setBitrateMethod;
};
}
}
#endif //LIBTGVOIP_VIDEOSOURCEANDROID_H

View file

@ -0,0 +1,73 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "AudioUnitIO.h"
#include "AudioInputAudioUnit.h"
#include "../../logging.h"
#define BUFFER_SIZE 960
using namespace tgvoip;
using namespace tgvoip::audio;
AudioInputAudioUnit::AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io){
remainingDataSize=0;
isRecording=false;
this->io=io;
#if TARGET_OS_OSX
io->SetCurrentDevice(true, deviceID);
#endif
}
AudioInputAudioUnit::~AudioInputAudioUnit(){
}
void AudioInputAudioUnit::Start(){
isRecording=true;
io->EnableInput(true);
}
void AudioInputAudioUnit::Stop(){
isRecording=false;
io->EnableInput(false);
}
void AudioInputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
int i;
for(i=0;i<ioData->mNumberBuffers;i++){
AudioBuffer buf=ioData->mBuffers[i];
#if TARGET_OS_OSX
assert(remainingDataSize+buf.mDataByteSize/2<10240);
float* src=reinterpret_cast<float*>(buf.mData);
int16_t* dst=reinterpret_cast<int16_t*>(remainingData+remainingDataSize);
for(int j=0;j<buf.mDataByteSize/4;j++){
dst[j]=(int16_t)(src[j]*INT16_MAX);
}
remainingDataSize+=buf.mDataByteSize/2;
#else
assert(remainingDataSize+buf.mDataByteSize<10240);
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
remainingDataSize+=buf.mDataByteSize;
#endif
while(remainingDataSize>=BUFFER_SIZE*2){
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
remainingDataSize-=BUFFER_SIZE*2;
if(remainingDataSize>0){
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
}
}
}
}
#if TARGET_OS_OSX
void AudioInputAudioUnit::SetCurrentDevice(std::string deviceID){
io->SetCurrentDevice(true, deviceID);
}
#endif

View file

@ -0,0 +1,38 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
#include <AudioUnit/AudioUnit.h>
#include "../../audio/AudioInput.h"
#include "../../utils.h"
namespace tgvoip{ namespace audio{
class AudioUnitIO;
class AudioInputAudioUnit : public AudioInput{
public:
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioInputAudioUnit);
AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io);
virtual ~AudioInputAudioUnit();
virtual void Start();
virtual void Stop();
void HandleBufferCallback(AudioBufferList* ioData);
#if TARGET_OS_OSX
virtual void SetCurrentDevice(std::string deviceID);
#endif
private:
unsigned char remainingData[10240];
size_t remainingDataSize;
bool isRecording;
AudioUnitIO* io;
};
}}
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H

View file

@ -0,0 +1,308 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include "AudioInputAudioUnitOSX.h"
#include "../../logging.h"
#include "../../audio/Resampler.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("input: " msg": OSStatus=%d", (int)res); failed=true; return; }
#define kOutputBus 0
#define kInputBus 1
using namespace tgvoip;
using namespace tgvoip::audio;
AudioInputAudioUnitLegacy::AudioInputAudioUnitLegacy(std::string deviceID) : AudioInput(deviceID){
remainingDataSize=0;
isRecording=false;
inBufferList.mBuffers[0].mData=malloc(10240);
inBufferList.mBuffers[0].mDataByteSize=10240;
inBufferList.mNumberBuffers=1;
OSStatus status;
AudioComponentDescription inputDesc={
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
.componentManufacturer = kAudioUnitManufacturer_Apple
};
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
status=AudioComponentInstanceNew(component, &unit);
CHECK_AU_ERROR(status, "Error creating AudioUnit");
UInt32 flag=0;
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
flag=1;
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
SetCurrentDevice(deviceID);
CFRunLoopRef theRunLoop = NULL;
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = AudioInputAudioUnitLegacy::BufferCallback;
callbackStruct.inputProcRefCon=this;
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
CHECK_AU_ERROR(status, "Error setting input buffer callback");
status=AudioUnitInitialize(unit);
CHECK_AU_ERROR(status, "Error initializing unit");
}
AudioInputAudioUnitLegacy::~AudioInputAudioUnitLegacy(){
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
free(inBufferList.mBuffers[0].mData);
}
void AudioInputAudioUnitLegacy::Start(){
isRecording=true;
OSStatus status=AudioOutputUnitStart(unit);
CHECK_AU_ERROR(status, "Error starting AudioUnit");
}
void AudioInputAudioUnitLegacy::Stop(){
isRecording=false;
OSStatus status=AudioOutputUnitStart(unit);
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
}
OSStatus AudioInputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
AudioInputAudioUnitLegacy* input=(AudioInputAudioUnitLegacy*) inRefCon;
input->inBufferList.mBuffers[0].mDataByteSize=10240;
AudioUnitRender(input->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &input->inBufferList);
input->HandleBufferCallback(&input->inBufferList);
return noErr;
}
void AudioInputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
int i;
for(i=0;i<ioData->mNumberBuffers;i++){
AudioBuffer buf=ioData->mBuffers[i];
size_t len=buf.mDataByteSize;
if(hardwareSampleRate!=48000){
len=tgvoip::audio::Resampler::Convert((int16_t*)buf.mData, (int16_t*)(remainingData+remainingDataSize), buf.mDataByteSize/2, (10240-(buf.mDataByteSize+remainingDataSize))/2, 48000, hardwareSampleRate)*2;
}else{
assert(remainingDataSize+buf.mDataByteSize<10240);
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
}
remainingDataSize+=len;
while(remainingDataSize>=BUFFER_SIZE*2){
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
remainingDataSize-=BUFFER_SIZE*2;
if(remainingDataSize>0){
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
}
}
}
}
void AudioInputAudioUnitLegacy::EnumerateDevices(std::vector<AudioInputDevice>& devs){
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
return;
}
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
free(audioDevices);
audioDevices = NULL;
return;
}
// Iterate through all the devices and determine which are input-capable
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
continue;
}
// Query device name
CFStringRef deviceName = NULL;
dataSize = sizeof(deviceName);
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
continue;
}
// Determine if the device is an input device (it is an input device if it has input channels)
dataSize = 0;
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
continue;
}
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
if(kAudioHardwareNoError != status)
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
free(bufferList);
bufferList = NULL;
continue;
}
free(bufferList);
bufferList = NULL;
AudioInputDevice dev;
char buf[1024];
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
dev.displayName=std::string(buf);
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
dev.id=std::string(buf);
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
continue;
devs.push_back(dev);
}
free(audioDevices);
audioDevices = NULL;
}
void AudioInputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
UInt32 size=sizeof(AudioDeviceID);
AudioDeviceID inputDevice=0;
OSStatus status;
if(deviceID=="default"){
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
UInt32 propsize = sizeof(AudioDeviceID);
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &inputDevice);
CHECK_AU_ERROR(status, "Error getting default input device");
}else{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
CHECK_AU_ERROR(status, "Error getting devices size");
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID audioDevices[deviceCount];
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
CHECK_AU_ERROR(status, "Error getting device list");
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
CHECK_AU_ERROR(status, "Error getting device uid");
char buf[1024];
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
if(deviceID==buf){
LOGV("Found device for id %s", buf);
inputDevice=audioDevices[i];
break;
}
}
if(!inputDevice){
LOGW("Requested device not found, using default");
SetCurrentDevice("default");
return;
}
}
status =AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
kInputBus,
&inputDevice,
size);
CHECK_AU_ERROR(status, "Error setting input device");
AudioStreamBasicDescription hardwareFormat;
size=sizeof(hardwareFormat);
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kInputBus, &hardwareFormat, &size);
CHECK_AU_ERROR(status, "Error getting hardware format");
hardwareSampleRate=hardwareFormat.mSampleRate;
AudioStreamBasicDescription desiredFormat={
.mSampleRate=hardwareFormat.mSampleRate, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
};
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &desiredFormat, sizeof(desiredFormat));
CHECK_AU_ERROR(status, "Error setting format");
LOGD("Switched capture device, new sample rate %d", hardwareSampleRate);
this->currentDevice=deviceID;
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyBufferFrameSize,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size=4;
UInt32 bufferFrameSize;
status=AudioObjectGetPropertyData(inputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
if(status==noErr){
estimatedDelay=bufferFrameSize/48;
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
}
}
OSStatus AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
LOGV("System default input device changed");
AudioInputAudioUnitLegacy* self=(AudioInputAudioUnitLegacy*)inClientData;
if(self->currentDevice=="default"){
self->SetCurrentDevice(self->currentDevice);
}
return noErr;
}

View file

@ -0,0 +1,39 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#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

View file

@ -0,0 +1,84 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include "AudioOutputAudioUnit.h"
#include "../../logging.h"
#include "AudioUnitIO.h"
#define BUFFER_SIZE 960
using namespace tgvoip;
using namespace tgvoip::audio;
AudioOutputAudioUnit::AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io){
isPlaying=false;
remainingDataSize=0;
this->io=io;
#if TARGET_OS_OSX
io->SetCurrentDevice(false, deviceID);
#endif
}
AudioOutputAudioUnit::~AudioOutputAudioUnit(){
}
void AudioOutputAudioUnit::Start(){
isPlaying=true;
io->EnableOutput(true);
}
void AudioOutputAudioUnit::Stop(){
isPlaying=false;
io->EnableOutput(false);
}
bool AudioOutputAudioUnit::IsPlaying(){
return isPlaying;
}
void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
int i;
for(i=0;i<ioData->mNumberBuffers;i++){
AudioBuffer buf=ioData->mBuffers[i];
if(!isPlaying){
memset(buf.mData, 0, buf.mDataByteSize);
return;
}
#if TARGET_OS_OSX
unsigned int k;
while(remainingDataSize<buf.mDataByteSize/2){
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
remainingDataSize+=BUFFER_SIZE*2;
}
float* dst=reinterpret_cast<float*>(buf.mData);
int16_t* src=reinterpret_cast<int16_t*>(remainingData);
for(k=0;k<buf.mDataByteSize/4;k++){
dst[k]=src[k]/(float)INT16_MAX;
}
remainingDataSize-=buf.mDataByteSize/2;
memmove(remainingData, remainingData+buf.mDataByteSize/2, remainingDataSize);
#else
while(remainingDataSize<buf.mDataByteSize){
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
remainingDataSize+=BUFFER_SIZE*2;
}
memcpy(buf.mData, remainingData, buf.mDataByteSize);
remainingDataSize-=buf.mDataByteSize;
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
#endif
}
}
#if TARGET_OS_OSX
void AudioOutputAudioUnit::SetCurrentDevice(std::string deviceID){
io->SetCurrentDevice(false, deviceID);
}
#endif

View file

@ -0,0 +1,38 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
#include <AudioUnit/AudioUnit.h>
#include "../../audio/AudioOutput.h"
#include "../../utils.h"
namespace tgvoip{ namespace audio{
class AudioUnitIO;
class AudioOutputAudioUnit : public AudioOutput{
public:
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioOutputAudioUnit);
AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io);
virtual ~AudioOutputAudioUnit();
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
void HandleBufferCallback(AudioBufferList* ioData);
#if TARGET_OS_OSX
virtual void SetCurrentDevice(std::string deviceID);
#endif
private:
bool isPlaying;
unsigned char remainingData[10240];
size_t remainingDataSize;
AudioUnitIO* io;
};
}}
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H

View file

@ -0,0 +1,365 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include <sys/sysctl.h>
#include "AudioOutputAudioUnitOSX.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("output: " msg": OSStatus=%d", (int)res); return; }
#define kOutputBus 0
#define kInputBus 1
using namespace tgvoip;
using namespace tgvoip::audio;
AudioOutputAudioUnitLegacy::AudioOutputAudioUnitLegacy(std::string deviceID){
remainingDataSize=0;
isPlaying=false;
sysDevID=0;
OSStatus status;
AudioComponentDescription inputDesc={
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
.componentManufacturer = kAudioUnitManufacturer_Apple
};
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
status=AudioComponentInstanceNew(component, &unit);
CHECK_AU_ERROR(status, "Error creating AudioUnit");
UInt32 flag=1;
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
flag=0;
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
char model[128];
memset(model, 0, sizeof(model));
size_t msize=sizeof(model);
int mres=sysctlbyname("hw.model", model, &msize, NULL, 0);
if(mres==0){
LOGV("Mac model: %s", model);
isMacBookPro=(strncmp("MacBookPro", model, 10)==0);
}
SetCurrentDevice(deviceID);
CFRunLoopRef theRunLoop = NULL;
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
AudioStreamBasicDescription desiredFormat={
.mSampleRate=/*hardwareFormat.mSampleRate*/48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
};
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
CHECK_AU_ERROR(status, "Error setting format");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = AudioOutputAudioUnitLegacy::BufferCallback;
callbackStruct.inputProcRefCon=this;
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
CHECK_AU_ERROR(status, "Error setting input buffer callback");
status=AudioUnitInitialize(unit);
CHECK_AU_ERROR(status, "Error initializing unit");
}
AudioOutputAudioUnitLegacy::~AudioOutputAudioUnitLegacy(){
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
AudioObjectPropertyAddress dataSourceProp={
kAudioDevicePropertyDataSource,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
}
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
}
void AudioOutputAudioUnitLegacy::Start(){
isPlaying=true;
OSStatus status=AudioOutputUnitStart(unit);
CHECK_AU_ERROR(status, "Error starting AudioUnit");
}
void AudioOutputAudioUnitLegacy::Stop(){
isPlaying=false;
OSStatus status=AudioOutputUnitStart(unit);
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
}
OSStatus AudioOutputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
AudioOutputAudioUnitLegacy* input=(AudioOutputAudioUnitLegacy*) inRefCon;
input->HandleBufferCallback(ioData);
return noErr;
}
bool AudioOutputAudioUnitLegacy::IsPlaying(){
return isPlaying;
}
void AudioOutputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
int i;
for(i=0;i<ioData->mNumberBuffers;i++){
AudioBuffer buf=ioData->mBuffers[i];
if(!isPlaying){
memset(buf.mData, 0, buf.mDataByteSize);
return;
}
while(remainingDataSize<buf.mDataByteSize){
assert(remainingDataSize+BUFFER_SIZE*2<10240);
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
remainingDataSize+=BUFFER_SIZE*2;
}
memcpy(buf.mData, remainingData, buf.mDataByteSize);
remainingDataSize-=buf.mDataByteSize;
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
}
}
void AudioOutputAudioUnitLegacy::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
return;
}
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
free(audioDevices);
audioDevices = NULL;
return;
}
// Iterate through all the devices and determine which are input-capable
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
continue;
}
// Query device name
CFStringRef deviceName = NULL;
dataSize = sizeof(deviceName);
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
continue;
}
// Determine if the device is an input device (it is an input device if it has input channels)
dataSize = 0;
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status) {
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
continue;
}
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
if(kAudioHardwareNoError != status)
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
free(bufferList);
bufferList = NULL;
continue;
}
free(bufferList);
bufferList = NULL;
AudioOutputDevice dev;
char buf[1024];
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
dev.displayName=std::string(buf);
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
dev.id=std::string(buf);
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
continue;
devs.push_back(dev);
}
free(audioDevices);
audioDevices = NULL;
}
void AudioOutputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
UInt32 size=sizeof(AudioDeviceID);
AudioDeviceID outputDevice=0;
OSStatus status;
AudioObjectPropertyAddress dataSourceProp={
kAudioDevicePropertyDataSource,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
}
if(deviceID=="default"){
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
UInt32 propsize = sizeof(AudioDeviceID);
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &outputDevice);
CHECK_AU_ERROR(status, "Error getting default input device");
}else{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
CHECK_AU_ERROR(status, "Error getting devices size");
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID audioDevices[deviceCount];
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
CHECK_AU_ERROR(status, "Error getting device list");
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
CHECK_AU_ERROR(status, "Error getting device uid");
char buf[1024];
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
if(deviceID==buf){
LOGV("Found device for id %s", buf);
outputDevice=audioDevices[i];
break;
}
}
if(!outputDevice){
LOGW("Requested device not found, using default");
SetCurrentDevice("default");
return;
}
}
status =AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
kOutputBus,
&outputDevice,
size);
CHECK_AU_ERROR(status, "Error setting output device");
AudioStreamBasicDescription hardwareFormat;
size=sizeof(hardwareFormat);
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &hardwareFormat, &size);
CHECK_AU_ERROR(status, "Error getting hardware format");
hardwareSampleRate=hardwareFormat.mSampleRate;
AudioStreamBasicDescription desiredFormat={
.mSampleRate=48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
};
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
CHECK_AU_ERROR(status, "Error setting format");
LOGD("Switched playback device, new sample rate %d", hardwareSampleRate);
this->currentDevice=deviceID;
sysDevID=outputDevice;
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyBufferFrameSize,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size=4;
UInt32 bufferFrameSize;
status=AudioObjectGetPropertyData(outputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
if(status==noErr){
estimatedDelay=bufferFrameSize/48;
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
}
if(isMacBookPro){
if(AudioObjectHasProperty(outputDevice, &dataSourceProp)){
UInt32 dataSource;
size=4;
AudioObjectGetPropertyData(outputDevice, &dataSourceProp, 0, NULL, &size, &dataSource);
SetPanRight(dataSource=='ispk');
AudioObjectAddPropertyListener(outputDevice, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
}else{
SetPanRight(false);
}
}
}
void AudioOutputAudioUnitLegacy::SetPanRight(bool panRight){
LOGI("%sabling pan right on macbook pro", panRight ? "En" : "Dis");
int32_t channelMap[]={panRight ? -1 : 0, 0};
OSStatus status=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, kOutputBus, channelMap, sizeof(channelMap));
CHECK_AU_ERROR(status, "Error setting channel map");
}
OSStatus AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
AudioOutputAudioUnitLegacy* self=(AudioOutputAudioUnitLegacy*)inClientData;
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
LOGV("System default input device changed");
if(self->currentDevice=="default"){
self->SetCurrentDevice(self->currentDevice);
}
}else if(inAddresses[0].mSelector==kAudioDevicePropertyDataSource){
UInt32 dataSource;
UInt32 size=4;
AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &size, &dataSource);
self->SetPanRight(dataSource=='ispk');
}
return noErr;
}

View file

@ -0,0 +1,42 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_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

View file

@ -0,0 +1,321 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdio.h>
#include "AudioUnitIO.h"
#include "AudioInputAudioUnit.h"
#include "AudioOutputAudioUnit.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include "../../VoIPServerConfig.h"
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); failed=true; return; }
#define BUFFER_SIZE 960 // 20 ms
#if TARGET_OS_OSX
#define INPUT_BUFFER_SIZE 20480
#else
#define INPUT_BUFFER_SIZE 10240
#endif
#define kOutputBus 0
#define kInputBus 1
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
extern "C" {
OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
Float32 inDuckedLevel,
const AudioTimeStamp* __nullable inStartTime,
Float32 inRampDuration) __attribute__((weak_import));
}
#endif
using namespace tgvoip;
using namespace tgvoip::audio;
AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID){
input=NULL;
output=NULL;
inputEnabled=false;
outputEnabled=false;
failed=false;
started=false;
inBufferList.mBuffers[0].mData=malloc(INPUT_BUFFER_SIZE);
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
inBufferList.mNumberBuffers=1;
#if TARGET_OS_IPHONE
DarwinSpecific::ConfigureAudioSession();
#endif
OSStatus status;
AudioComponentDescription desc;
AudioComponent inputComponent;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
inputComponent = AudioComponentFindNext(NULL, &desc);
status = AudioComponentInstanceNew(inputComponent, &unit);
UInt32 flag=1;
#if TARGET_OS_IPHONE
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
#endif
#if TARGET_OS_IPHONE
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0;
#else
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0;
#endif
status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
CHECK_AU_ERROR(status, "Error disabling AGC");
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = 48000;
audioFormat.mFormatID = kAudioFormatLinearPCM;
#if TARGET_OS_IPHONE
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
#else // OS X
audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
audioFormat.mBitsPerChannel = 32;
audioFormat.mBytesPerPacket = 4;
audioFormat.mBytesPerFrame = 4;
#endif
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
CHECK_AU_ERROR(status, "Error setting output format");
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat));
CHECK_AU_ERROR(status, "Error setting input format");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = AudioUnitIO::BufferCallback;
callbackStruct.inputProcRefCon = this;
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
CHECK_AU_ERROR(status, "Error setting output buffer callback");
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
CHECK_AU_ERROR(status, "Error setting input buffer callback");
#if TARGET_OS_OSX
CFRunLoopRef theRunLoop = NULL;
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
#endif
input=new AudioInputAudioUnit(inputDeviceID, this);
output=new AudioOutputAudioUnit(outputDeviceID, this);
}
AudioUnitIO::~AudioUnitIO(){
#if TARGET_OS_OSX
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
#endif
AudioOutputUnitStop(unit);
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
free(inBufferList.mBuffers[0].mData);
delete input;
delete output;
}
OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
return noErr;
}
void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){
if(bus==kOutputBus){
if(output && outputEnabled){
output->HandleBufferCallback(ioData);
}else{
memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
}
}else if(bus==kInputBus){
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
if(input && inputEnabled){
input->HandleBufferCallback(&inBufferList);
}
}
}
void AudioUnitIO::EnableInput(bool enabled){
inputEnabled=enabled;
StartIfNeeded();
}
void AudioUnitIO::EnableOutput(bool enabled){
outputEnabled=enabled;
StartIfNeeded();
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
if(actualDuckingEnabled!=duckingEnabled){
actualDuckingEnabled=duckingEnabled;
AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, NULL, 0.1f);
}
#endif
}
void AudioUnitIO::StartIfNeeded(){
if(started)
return;
started=true;
OSStatus status = AudioUnitInitialize(unit);
CHECK_AU_ERROR(status, "Error initializing AudioUnit");
status=AudioOutputUnitStart(unit);
CHECK_AU_ERROR(status, "Error starting AudioUnit");
}
AudioInput* AudioUnitIO::GetInput(){
return input;
}
AudioOutput* AudioUnitIO::GetOutput(){
return output;
}
#if TARGET_OS_OSX
OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
AudioUnitIO* self=(AudioUnitIO*)inClientData;
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
LOGV("System default output device changed");
if(self->currentOutputDevice=="default"){
self->SetCurrentDevice(false, self->currentOutputDevice);
}
}else if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultInputDevice){
LOGV("System default input device changed");
if(self->currentInputDevice=="default"){
self->SetCurrentDevice(true, self->currentInputDevice);
}
}
return noErr;
}
void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID){
LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str());
if(started){
AudioOutputUnitStop(unit);
AudioUnitUninitialize(unit);
}
UInt32 size=sizeof(AudioDeviceID);
AudioDeviceID device=0;
OSStatus status;
if(deviceID=="default"){
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
UInt32 propsize = sizeof(AudioDeviceID);
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &device);
CHECK_AU_ERROR(status, "Error getting default device");
}else{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
CHECK_AU_ERROR(status, "Error getting devices size");
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
AudioDeviceID audioDevices[deviceCount];
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
CHECK_AU_ERROR(status, "Error getting device list");
for(UInt32 i = 0; i < deviceCount; ++i) {
// Query device UID
CFStringRef deviceUID = NULL;
dataSize = sizeof(deviceUID);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
CHECK_AU_ERROR(status, "Error getting device uid");
char buf[1024];
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
if(deviceID==buf){
LOGV("Found device for id %s", buf);
device=audioDevices[i];
break;
}
}
if(!device){
LOGW("Requested device not found, using default");
SetCurrentDevice(input, "default");
return;
}
}
status=AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
input ? kInputBus : kOutputBus,
&device,
size);
CHECK_AU_ERROR(status, "Error setting input device");
if(input)
currentInputDevice=deviceID;
else
currentOutputDevice=deviceID;
/*AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyBufferFrameSize,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size=4;
UInt32 bufferFrameSize;
status=AudioObjectGetPropertyData(device, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
if(status==noErr){
estimatedDelay=bufferFrameSize/48;
LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
}*/
if(started){
started=false;
StartIfNeeded();
}
if(!input){
currentOutputDeviceID=device;
}
LOGV("Set current %sput device done", input ? "in" : "out");
}
void AudioUnitIO::SetDuckingEnabled(bool enabled){
duckingEnabled=enabled;
#ifndef TGVOIP_NO_OSX_PRIVATE_API
if(outputEnabled && duckingEnabled!=actualDuckingEnabled){
actualDuckingEnabled=enabled;
AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, NULL, 0.1f);
}
#endif
}
#endif

View file

@ -0,0 +1,57 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOUNITIO_H
#define LIBTGVOIP_AUDIOUNITIO_H
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include "../../threading.h"
#include <string>
#include "../../audio/AudioIO.h"
namespace tgvoip{ namespace audio{
class AudioInputAudioUnit;
class AudioOutputAudioUnit;
class AudioUnitIO : public AudioIO{
public:
AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID);
~AudioUnitIO();
void EnableInput(bool enabled);
void EnableOutput(bool enabled);
virtual AudioInput* GetInput();
virtual AudioOutput* GetOutput();
#if TARGET_OS_OSX
void SetCurrentDevice(bool input, std::string deviceID);
void SetDuckingEnabled(bool enabled);
#endif
private:
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
void BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList* ioData);
void StartIfNeeded();
#if TARGET_OS_OSX
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
std::string currentInputDevice;
std::string currentOutputDevice;
bool duckingEnabled=true;
#ifndef TGVOIP_NO_OSX_PRIVATE_API
bool actualDuckingEnabled=true;
#endif // TGVOIP_NO_OSX_PRIVATE_API
AudioDeviceID currentOutputDeviceID;
#endif
AudioComponentInstance unit;
AudioInputAudioUnit* input;
AudioOutputAudioUnit* output;
AudioBufferList inBufferList;
bool inputEnabled;
bool outputEnabled;
bool started;
};
}}
#endif /* LIBTGVOIP_AUDIOUNITIO_H */

View file

@ -0,0 +1,32 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef TGVOIP_DARWINSPECIFIC_H
#define TGVOIP_DARWINSPECIFIC_H
#include <string>
namespace tgvoip {
struct CellularCarrierInfo;
class DarwinSpecific{
public:
enum{
THREAD_PRIO_USER_INTERACTIVE,
THREAD_PRIO_USER_INITIATED,
THREAD_PRIO_UTILITY,
THREAD_PRIO_BACKGROUND,
THREAD_PRIO_DEFAULT
};
static void GetSystemName(char* buf, size_t len);
static void SetCurrentThreadPriority(int priority);
static CellularCarrierInfo GetCarrierInfo();
static void ConfigureAudioSession();
};
}
#endif //TGVOIP_DARWINSPECIFIC_H

View file

@ -0,0 +1,110 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "DarwinSpecific.h"
#include "../../VoIPController.h"
#include "../../logging.h"
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
#import <AVFoundation/AVFoundation.h>
#endif
using namespace tgvoip;
void DarwinSpecific::GetSystemName(char* buf, size_t len){
NSString* v=[[NSProcessInfo processInfo] operatingSystemVersionString];
strcpy(buf, [v UTF8String]);
//[v getCString:buf maxLength:sizeof(buf) encoding:NSUTF8StringEncoding];
}
void DarwinSpecific::SetCurrentThreadPriority(int priority){
NSThread* thread=[NSThread currentThread];
if([thread respondsToSelector:@selector(setQualityOfService:)]){
NSQualityOfService qos;
switch(priority){
case THREAD_PRIO_USER_INTERACTIVE:
qos=NSQualityOfServiceUserInteractive;
break;
case THREAD_PRIO_USER_INITIATED:
qos=NSQualityOfServiceUserInitiated;
break;
case THREAD_PRIO_UTILITY:
qos=NSQualityOfServiceUtility;
break;
case THREAD_PRIO_BACKGROUND:
qos=NSQualityOfServiceBackground;
break;
case THREAD_PRIO_DEFAULT:
default:
qos=NSQualityOfServiceDefault;
break;
}
[thread setQualityOfService:qos];
}else{
double p;
switch(priority){
case THREAD_PRIO_USER_INTERACTIVE:
p=1.0;
break;
case THREAD_PRIO_USER_INITIATED:
p=0.8;
break;
case THREAD_PRIO_UTILITY:
p=0.4;
break;
case THREAD_PRIO_BACKGROUND:
p=0.2;
break;
case THREAD_PRIO_DEFAULT:
default:
p=0.5;
break;
}
[NSThread setThreadPriority:p];
}
}
CellularCarrierInfo DarwinSpecific::GetCarrierInfo(){
CellularCarrierInfo info;
#if TARGET_OS_IOS
CTTelephonyNetworkInfo* netinfo=[CTTelephonyNetworkInfo new];
CTCarrier* carrier=[netinfo subscriberCellularProvider];
if(carrier){
NSString* name=[carrier carrierName];
NSString* mcc=[carrier mobileCountryCode];
NSString* mnc=[carrier mobileNetworkCode];
NSString* countryCode=[carrier isoCountryCode];
if(name && mcc && mnc && countryCode){
info.name=[name cStringUsingEncoding:NSUTF8StringEncoding];
info.mcc=[mcc cStringUsingEncoding:NSUTF8StringEncoding];
info.mnc=[mnc cStringUsingEncoding:NSUTF8StringEncoding];
info.countryCode=[[countryCode uppercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
}
}
#endif
return info;
}
void DarwinSpecific::ConfigureAudioSession(){
#if TARGET_OS_IOS
AVAudioSession* session=[AVAudioSession sharedInstance];
NSError* error=nil;
[session setPreferredSampleRate:48000.0 error:&error];
if(error){
LOGE("Failed to set preferred sample rate on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
return;
}
[session setPreferredIOBufferDuration:0.020 error:&error];
if(error){
LOGE("Failed to set preferred IO buffer duration on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
return;
}
LOGI("Configured AVAudioSession: sampleRate=%f, IOBufferDuration=%f", session.sampleRate, session.IOBufferDuration);
#endif
}

View file

@ -0,0 +1,40 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
#define TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
#include "../../video/VideoRenderer.h"
#include <vector>
#include <objc/objc.h>
#include <VideoToolbox/VideoToolbox.h>
#ifdef __OBJC__
@class TGVVideoRenderer;
#else
typedef struct objc_object TGVVideoRenderer;
#endif
namespace tgvoip{
namespace video{
class SampleBufferDisplayLayerRenderer : public VideoRenderer{
public:
SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer);
virtual ~SampleBufferDisplayLayerRenderer();
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
virtual void SetStreamEnabled(bool enabled) override;
static int GetMaximumResolution();
static std::vector<uint32_t> GetAvailableDecoders();
private:
TGVVideoRenderer* renderer;
CMFormatDescriptionRef formatDesc=NULL;
bool needReset=false;
};
}
}
#endif /* TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER */

View file

@ -0,0 +1,144 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#include <UIKit/UIKit.h>
#endif
#include "SampleBufferDisplayLayerRenderer.h"
#include "../../PrivateDefines.h"
#include "../../logging.h"
#include "TGVVideoRenderer.h"
using namespace tgvoip;
using namespace tgvoip::video;
SampleBufferDisplayLayerRenderer::SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer) : renderer(renderer){
}
SampleBufferDisplayLayerRenderer::~SampleBufferDisplayLayerRenderer(){
}
void SampleBufferDisplayLayerRenderer::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd){
LOGI("video renderer reset: %d x %d", width, height);
if(formatDesc){
CFRelease(formatDesc);
}
if(codec==CODEC_AVC){
if(csd.size()!=2){
LOGE("H264 requires exactly 2 CSD buffers");
return;
}
const uint8_t* params[]={*csd[0]+4, *csd[1]+4};
size_t paramSizes[]={csd[0].Length()-4, csd[1].Length()-4};
OSStatus status=CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, params, paramSizes, 4, &formatDesc);
if(status!=noErr){
LOGE("CMVideoFormatDescriptionCreateFromH264ParameterSets failed: %d", status);
return;
}
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
}else if(codec==CODEC_HEVC){
if(@available(iOS 11.0, *)){
if(csd.size()!=1){
LOGE("HEVC requires exactly 1 CSD buffer");
return;
}
int offsets[]={0, 0, 0};
Buffer& buf=csd[0];
int currentNAL=0;
for(int i=0;i<buf.Length()-4;i++){
if(buf[i]==0 && buf[i+1]==0 && buf[i+2]==0 && buf[i+3]==1){
offsets[currentNAL]=i+4;
currentNAL++;
}
}
LOGV("CSD NAL offsets: %d %d %d", offsets[0], offsets[1], offsets[2]);
if(offsets[0]==0 || offsets[1]==0 || offsets[2]==0){
LOGE("Error splitting CSD buffer");
return;
}
const uint8_t* params[]={*buf+offsets[0], *buf+offsets[1], *buf+offsets[2]};
size_t paramSizes[]={(size_t)((offsets[1]-4)-offsets[0]), (size_t)((offsets[2]-4)-offsets[1]), (size_t)(buf.Length()-offsets[2])};
OSStatus status=CMVideoFormatDescriptionCreateFromHEVCParameterSets(NULL, 3, params, paramSizes, 4, NULL, &formatDesc);
if(status!=noErr){
LOGE("CMVideoFormatDescriptionCreateFromHEVCParameterSets failed: %d", status);
return;
}
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
}else{
LOGE("HEVC not available on this OS");
}
}
needReset=true;
}
void SampleBufferDisplayLayerRenderer::DecodeAndDisplay(Buffer frame, uint32_t pts){
CMBlockBufferRef blockBuffer;
OSStatus status=CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, *frame, frame.Length(), kCFAllocatorNull, NULL, 0, frame.Length(), 0, &blockBuffer);
if(status!=noErr){
LOGE("CMBlockBufferCreateWithMemoryBlock failed: %d", status);
return;
}
uint32_t _len=(uint32_t)(frame.Length()-4);
uint8_t lenBytes[]={(uint8_t)(_len >> 24), (uint8_t)(_len >> 16), (uint8_t)(_len >> 8), (uint8_t)_len};
status=CMBlockBufferReplaceDataBytes(lenBytes, blockBuffer, 0, 4);
if(status!=noErr){
LOGE("CMBlockBufferReplaceDataBytes failed: %d", status);
return;
}
CMSampleBufferRef sampleBuffer;
status=CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, formatDesc, 1, 0, NULL, 0, NULL, &sampleBuffer);
if(status!=noErr){
LOGE("CMSampleBufferCreate failed: %d", status);
return;
}
CFRelease(blockBuffer);
CFArrayRef attachments=CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
CFMutableDictionaryRef dict=(CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
[renderer _enqueueBuffer:sampleBuffer reset:needReset];
needReset=false;
CFRelease(sampleBuffer);
}
void SampleBufferDisplayLayerRenderer::SetStreamEnabled(bool enabled){
}
int SampleBufferDisplayLayerRenderer::GetMaximumResolution(){
#if TARGET_OS_IPHONE
CGRect screenSize=[UIScreen mainScreen].nativeBounds;
CGFloat minSize=std::min(screenSize.size.width, screenSize.size.height);
if(minSize>720.f){
return INIT_VIDEO_RES_1080;
}else if(minSize>480.f){
return INIT_VIDEO_RES_720;
}else{
return INIT_VIDEO_RES_480;
}
#else // OS X
// TODO support OS X
#endif
return INIT_VIDEO_RES_1080;
}
std::vector<uint32_t> SampleBufferDisplayLayerRenderer::GetAvailableDecoders(){
std::vector<uint32_t> res;
res.push_back(CODEC_AVC);
if(@available(iOS 11.0, *)){
if(VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)){
res.push_back(CODEC_HEVC);
}
}
return res;
}

View file

@ -0,0 +1,3 @@
#import <Foundation/Foundation.h>
extern void (*TGVoipLoggingFunction)(NSString *);

View file

@ -0,0 +1,20 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef TGVOIP_TGLOGWRAPPER_H
#define TGVOIP_TGLOGWRAPPER_H
#if defined __cplusplus
extern "C" {
#endif
void __tgvoip_call_tglog(const char* format, ...);
#if defined __cplusplus
};
#endif
#endif //TGVOIP_TGLOGWRAPPER_H

View file

@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>
void (*TGVoipLoggingFunction)(NSString *) = NULL;
void __tgvoip_call_tglog(const char* format, ...){
va_list args;
va_start(args, format);
NSString *string = [[NSString alloc] initWithFormat:[[NSString alloc]initWithUTF8String:format] arguments:args];
va_end(args);
if (TGVoipLoggingFunction) {
TGVoipLoggingFunction(string);
}
}

View file

@ -0,0 +1,43 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
namespace tgvoip{
namespace video{
class VideoRenderer;
}
}
typedef NS_ENUM(int, TGVStreamPauseReason){
TGVStreamPauseReasonBackground,
TGVStreamPauseReasonPoorConnection
};
typedef NS_ENUM(int, TGVStreamStopReason){
TGVStreamStopReasonUser,
TGVStreamStopReasonPoorConnection
};
@protocol TGVVideoRendererDelegate <NSObject>
- (void)incomingVideoRotationDidChange: (int)rotation;
- (void)incomingVideoStreamWillStartWithFrameSize: (CGSize)size;
- (void)incomingVideoStreamDidStopWithReason: (TGVStreamStopReason)reason;
- (void)incomingVideoStreamDidPauseWithReason: (TGVStreamPauseReason)reason;
- (void)incomingVideoStreamWillResume;
@end
@interface TGVVideoRenderer : NSObject
- (instancetype)initWithDisplayLayer: (AVSampleBufferDisplayLayer *)layer delegate: (id<TGVVideoRendererDelegate>)delegate;
- (tgvoip::video::VideoRenderer*)nativeVideoRenderer;
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset;
@end

View file

@ -0,0 +1,47 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#import "TGVVideoRenderer.h"
#include "SampleBufferDisplayLayerRenderer.h"
#include "../../logging.h"
@implementation TGVVideoRenderer{
AVSampleBufferDisplayLayer* layer;
id<TGVVideoRendererDelegate> delegate;
tgvoip::video::SampleBufferDisplayLayerRenderer* nativeRenderer;
}
- (instancetype)initWithDisplayLayer:(AVSampleBufferDisplayLayer *)layer delegate:(nonnull id<TGVVideoRendererDelegate>)delegate{
self=[super init];
self->layer=layer;
self->delegate=delegate;
nativeRenderer=new tgvoip::video::SampleBufferDisplayLayerRenderer(self);
layer.videoGravity=AVLayerVideoGravityResizeAspect;
return self;
}
- (void)dealloc{
delete nativeRenderer;
}
- (tgvoip::video::VideoRenderer *)nativeVideoRenderer{
return nativeRenderer;
}
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset{
if(reset){
LOGV("Resetting layer");
[layer flush];
}
LOGV("Enqueue buffer");
[layer enqueueSampleBuffer:buffer];
NSError* error=[layer error];
if(error){
LOGE("enqueueSampleBuffer failed: %s", [error.description cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
@end

View file

@ -0,0 +1,39 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
namespace tgvoip{
namespace video{
class VideoSource;
}
}
typedef NS_ENUM(int, TGVVideoResolution){
TGVVideoResolution1080,
TGVVideoResolution720,
TGVVideoResolution480,
TGVVideoResolution360
};
@protocol TGVVideoSourceDelegate <NSObject>
- (void)setFrameRate: (unsigned int)frameRate;
@end
@interface TGVVideoSource : NSObject
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate;
- (void)sendVideoFrame: (CMSampleBufferRef)buffer;
- (TGVVideoResolution)maximumSupportedVideoResolution;
- (void)setVideoRotation: (int)rotation;
- (void)pauseStream;
- (void)resumeStream;
- (tgvoip::video::VideoSource*)nativeVideoSource;
@end

View file

@ -0,0 +1,51 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#import "TGVVideoSource.h"
#include "VideoToolboxEncoderSource.h"
@implementation TGVVideoSource{
tgvoip::video::VideoToolboxEncoderSource* nativeSource;
id<TGVVideoSourceDelegate> delegate;
}
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate{
self=[super init];
nativeSource=new tgvoip::video::VideoToolboxEncoderSource();
self->delegate=delegate;
return self;
}
- (void)dealloc{
delete nativeSource;
}
- (void)sendVideoFrame: (CMSampleBufferRef)buffer{
nativeSource->EncodeFrame(buffer);
}
- (TGVVideoResolution)maximumSupportedVideoResolution{
return TGVVideoResolution1080;
}
- (void)setVideoRotation: (int)rotation{
}
- (void)pauseStream{
}
- (void)resumeStream{
}
- (tgvoip::video::VideoSource*)nativeVideoSource{
return nativeSource;
}
@end

View file

@ -0,0 +1,41 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
#define LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
#include "../../video/VideoSource.h"
#include <CoreMedia/CoreMedia.h>
#include <VideoToolbox/VideoToolbox.h>
#include <vector>
namespace tgvoip{
namespace video{
class VideoToolboxEncoderSource : public VideoSource{
public:
VideoToolboxEncoderSource();
virtual ~VideoToolboxEncoderSource();
virtual void Start() override;
virtual void Stop() override;
virtual void Reset(uint32_t codec, int maxResolution) override;
virtual void RequestKeyFrame() override;
virtual void SetBitrate(uint32_t bitrate) override;
void EncodeFrame(CMSampleBufferRef frame);
static std::vector<uint32_t> GetAvailableEncoders();
private:
void EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags);
void SetEncoderBitrateAndLimit(uint32_t bitrate);
bool needUpdateStreamParams=true;
uint32_t codec=0;
VTCompressionSessionRef session=NULL;
bool keyframeRequested=false;
uint32_t bitrateChangeRequested=0;
uint32_t lastBitrate=512*1024;
};
}
}
#endif /* LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE */

View file

@ -0,0 +1,255 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#import <Foundation/Foundation.h>
#include "VideoToolboxEncoderSource.h"
#include "../../PrivateDefines.h"
#include "../../logging.h"
using namespace tgvoip;
using namespace tgvoip::video;
#define CHECK_ERR(err, msg) if(err!=noErr){LOGE("VideoToolboxEncoder: " msg " failed: %d", err); return;}
VideoToolboxEncoderSource::VideoToolboxEncoderSource(){
}
VideoToolboxEncoderSource::~VideoToolboxEncoderSource(){
if(session){
CFRelease(session);
session=NULL;
}
}
void VideoToolboxEncoderSource::Start(){
}
void VideoToolboxEncoderSource::Stop(){
}
void VideoToolboxEncoderSource::Reset(uint32_t codec, int maxResolution){
if(session){
LOGV("Releasing old compression session");
//VTCompressionSessionCompleteFrames(session, kCMTimeInvalid);
VTCompressionSessionInvalidate(session);
CFRelease(session);
session=NULL;
LOGV("Released compression session");
}
CMVideoCodecType codecType;
switch(codec){
case CODEC_AVC:
codecType=kCMVideoCodecType_H264;
break;
case CODEC_HEVC:
codecType=kCMVideoCodecType_HEVC;
break;
default:
LOGE("VideoToolboxEncoder: Unsupported codec");
return;
}
needUpdateStreamParams=true;
this->codec=codec;
// typedef void (*VTCompressionOutputCallback)(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer);
uint32_t width, height;
switch(maxResolution){
case INIT_VIDEO_RES_1080:
width=1920;
height=1080;
break;
case INIT_VIDEO_RES_720:
width=1280;
height=720;
break;
case INIT_VIDEO_RES_480:
width=854;
height=480;
break;
case INIT_VIDEO_RES_360:
default:
width=640;
height=360;
break;
}
OSStatus status=VTCompressionSessionCreate(NULL, width, height, codecType, NULL, NULL, NULL, [](void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer){
reinterpret_cast<VideoToolboxEncoderSource*>(outputCallbackRefCon)->EncoderCallback(status, sampleBuffer, infoFlags);
}, this, &session);
if(status!=noErr){
LOGE("VTCompressionSessionCreate failed: %d", status);
return;
}
LOGD("Created VTCompressionSession");
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
CHECK_ERR(status, "VTSessionSetProperty(AllowFrameReordering)");
int64_t interval=15;
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(interval));
CHECK_ERR(status, "VTSessionSetProperty(MaxKeyFrameIntervalDuration)");
SetEncoderBitrateAndLimit(lastBitrate);
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
CHECK_ERR(status, "VTSessionSetProperty(RealTime)");
LOGD("VTCompressionSession initialized");
// TODO change camera frame rate dynamically based on resolution + codec
}
void VideoToolboxEncoderSource::RequestKeyFrame(){
keyframeRequested=true;
}
void VideoToolboxEncoderSource::EncodeFrame(CMSampleBufferRef frame){
if(!session)
return;
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(frame);
CMMediaType type=CMFormatDescriptionGetMediaType(format);
if(type!=kCMMediaType_Video){
//LOGW("Received non-video CMSampleBuffer");
return;
}
if(bitrateChangeRequested){
LOGI("VideoToolboxEocnder: setting bitrate to %u", bitrateChangeRequested);
SetEncoderBitrateAndLimit(bitrateChangeRequested);
lastBitrate=bitrateChangeRequested;
bitrateChangeRequested=0;
}
CFDictionaryRef frameProps=NULL;
if(keyframeRequested){
LOGI("VideoToolboxEncoder: requesting keyframe");
const void* keys[]={kVTEncodeFrameOptionKey_ForceKeyFrame};
const void* values[]={kCFBooleanTrue};
frameProps=CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
keyframeRequested=false;
}
//CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
//LOGD("EncodeFrame %d x %d", size.width, size.height);
CVImageBufferRef imgBuffer=CMSampleBufferGetImageBuffer(frame);
//OSType pixFmt=CVPixelBufferGetPixelFormatType(imgBuffer);
//LOGV("pixel format: %c%c%c%c", PRINT_FOURCC(pixFmt));
OSStatus status=VTCompressionSessionEncodeFrame(session, imgBuffer, CMSampleBufferGetPresentationTimeStamp(frame), CMSampleBufferGetDuration(frame), frameProps, NULL, NULL);
CHECK_ERR(status, "VTCompressionSessionEncodeFrame");
if(frameProps)
CFRelease(frameProps);
}
void VideoToolboxEncoderSource::SetBitrate(uint32_t bitrate){
bitrateChangeRequested=bitrate;
}
void VideoToolboxEncoderSource::EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags){
if(status!=noErr){
LOGE("EncoderCallback error: %d", status);
return;
}
if(flags & kVTEncodeInfo_FrameDropped){
LOGW("VideoToolboxEncoder: Frame dropped");
}
if(!CMSampleBufferGetNumSamples(buffer)){
LOGW("Empty CMSampleBuffer");
return;
}
const uint8_t startCode[]={0, 0, 0, 1};
if(needUpdateStreamParams){
LOGI("VideoToolboxEncoder: Updating stream params");
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(buffer);
CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
width=size.width;
height=size.height;
csd.clear();
if(codec==CODEC_AVC){
for(size_t i=0;i<2;i++){
const uint8_t* ps=NULL;
size_t pl=0;
status=CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
CHECK_ERR(status, "CMVideoFormatDescriptionGetH264ParameterSetAtIndex");
Buffer b(pl+4);
b.CopyFrom(ps, 4, pl);
b.CopyFrom(startCode, 0, 4);
csd.push_back(std::move(b));
}
}else if(codec==CODEC_HEVC){
LOGD("here1");
BufferOutputStream csdBuf(1024);
for(size_t i=0;i<3;i++){
const uint8_t* ps=NULL;
size_t pl=0;
status=CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
CHECK_ERR(status, "CMVideoFormatDescriptionGetHEVCParameterSetAtIndex");
csdBuf.WriteBytes(startCode, 4);
csdBuf.WriteBytes(ps, pl);
}
csd.push_back(std::move(csdBuf));
}
needUpdateStreamParams=false;
}
CMBlockBufferRef blockBuffer=CMSampleBufferGetDataBuffer(buffer);
size_t len=CMBlockBufferGetDataLength(blockBuffer);
int frameFlags=0;
CFArrayRef attachmentsArray=CMSampleBufferGetSampleAttachmentsArray(buffer, 0);
if(attachmentsArray && CFArrayGetCount(attachmentsArray)){
CFBooleanRef notSync;
CFDictionaryRef dict=(CFDictionaryRef)CFArrayGetValueAtIndex(attachmentsArray, 0);
BOOL keyExists=CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, (const void **)&notSync);
if(!keyExists || !CFBooleanGetValue(notSync)){
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
}
}else{
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
}
Buffer frame(len);
CMBlockBufferCopyDataBytes(blockBuffer, 0, len, *frame);
uint32_t offset=0;
while(offset<len){
uint32_t nalLen=CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(*frame+offset));
//LOGV("NAL length %u", nalLen);
frame.CopyFrom(startCode, offset, 4);
offset+=nalLen+4;
}
callback(std::move(frame), frameFlags);
//LOGV("EncoderCallback: %u bytes total", (unsigned int)len);
}
void VideoToolboxEncoderSource::SetEncoderBitrateAndLimit(uint32_t bitrate){
OSStatus status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bitrate));
CHECK_ERR(status, "VTSessionSetProperty(AverageBitRate)");
int64_t dataLimitValue=(int64_t)(bitrate/8);
CFNumberRef bytesPerSecond=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitValue);
int64_t oneValue=1;
CFNumberRef one=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneValue);
const void* numbers[]={bytesPerSecond, one};
CFArrayRef limits=CFArrayCreate(NULL, numbers, 2, &kCFTypeArrayCallBacks);
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, limits);
CFRelease(bytesPerSecond);
CFRelease(one);
CFRelease(limits);
CHECK_ERR(status, "VTSessionSetProperty(DataRateLimits");
}
std::vector<uint32_t> VideoToolboxEncoderSource::GetAvailableEncoders(){
std::vector<uint32_t> res;
res.push_back(CODEC_AVC);
CFArrayRef encoders;
OSStatus status=VTCopyVideoEncoderList(NULL, &encoders);
for(CFIndex i=0;i<CFArrayGetCount(encoders);i++){
CFDictionaryRef v=(CFDictionaryRef)CFArrayGetValueAtIndex(encoders, i);
NSDictionary* encoder=(__bridge NSDictionary*)v;
//NSString* name=(NSString*)CFDictionaryGetValue(v, kVTVideoEncoderList_EncoderName);
uint32_t codecType=[(NSNumber*)encoder[(NSString*)kVTVideoEncoderList_CodecType] intValue];
//LOGV("Encoders[%u]: %s, %c%c%c%c", i, [(NSString*)encoder[(NSString*)kVTVideoEncoderList_EncoderName] cStringUsingEncoding:NSUTF8StringEncoding], PRINT_FOURCC(codecType));
if(codecType==kCMVideoCodecType_HEVC){
res.push_back(CODEC_HEVC);
break;
}
}
CFRelease(encoders);
return res;
}

View file

@ -0,0 +1,175 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <dlfcn.h>
#include "AudioInputALSA.h"
#include "../../logging.h"
#include "../../VoIPController.h"
using namespace tgvoip::audio;
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
AudioInputALSA::AudioInputALSA(std::string devID){
isRecording=false;
handle=NULL;
lib=dlopen("libasound.so.2", RTLD_LAZY);
if(!lib)
lib=dlopen("libasound.so", RTLD_LAZY);
if(!lib){
LOGE("Error loading libasound: %s", dlerror());
failed=true;
return;
}
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
LOAD_FUNCTION(lib, "snd_pcm_readi", _snd_pcm_readi);
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
SetCurrentDevice(devID);
}
AudioInputALSA::~AudioInputALSA(){
if(handle)
_snd_pcm_close(handle);
if(lib)
dlclose(lib);
}
void AudioInputALSA::Start(){
if(failed || isRecording)
return;
isRecording=true;
thread=new Thread(std::bind(&AudioInputALSA::RunThread, this));
thread->SetName("AudioInputALSA");
thread->Start();
}
void AudioInputALSA::Stop(){
if(!isRecording)
return;
isRecording=false;
thread->Join();
delete thread;
thread=NULL;
}
void AudioInputALSA::RunThread(){
unsigned char buffer[BUFFER_SIZE*2];
snd_pcm_sframes_t frames;
while(isRecording){
frames=_snd_pcm_readi(handle, buffer, BUFFER_SIZE);
if (frames < 0){
frames = _snd_pcm_recover(handle, frames, 0);
}
if (frames < 0) {
LOGE("snd_pcm_readi failed: %s\n", _snd_strerror(frames));
break;
}
InvokeCallback(buffer, sizeof(buffer));
}
}
void AudioInputALSA::SetCurrentDevice(std::string devID){
bool wasRecording=isRecording;
isRecording=false;
if(handle){
thread->Join();
_snd_pcm_close(handle);
}
currentDevice=devID;
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_CAPTURE, 0);
if(res<0)
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
CHECK_ERROR(res, "snd_pcm_open failed");
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
CHECK_ERROR(res, "snd_pcm_set_params failed");
if(wasRecording){
isRecording=true;
thread->Start();
}
}
void AudioInputALSA::EnumerateDevices(std::vector<AudioInputDevice>& devs){
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
int (*_snd_device_name_free_hint)(void** hinst);
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
if(!lib)
dlopen("libasound.so", RTLD_LAZY);
if(!lib)
return;
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
dlclose(lib);
return;
}
char** hints;
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
if(err!=0){
dlclose(lib);
return;
}
char** n=hints;
while(*n){
char* name=_snd_device_name_get_hint(*n, "NAME");
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
free(name);
n++;
continue;
}
char* desc=_snd_device_name_get_hint(*n, "DESC");
char* ioid=_snd_device_name_get_hint(*n, "IOID");
if(!ioid || strcmp(ioid, "Input")==0){
char* l1=strtok(desc, "\n");
char* l2=strtok(NULL, "\n");
char* tmp=strtok(l1, ",");
char* actualName=tmp;
while((tmp=strtok(NULL, ","))){
actualName=tmp;
}
if(actualName[0]==' ')
actualName++;
AudioInputDevice dev;
dev.id=std::string(name);
if(l2){
char buf[256];
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
dev.displayName=std::string(buf);
}else{
dev.displayName=std::string(actualName);
}
devs.push_back(dev);
}
free(name);
free(desc);
free(ioid);
n++;
}
dlclose(lib);
}

View file

@ -0,0 +1,46 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTALSA_H
#define LIBTGVOIP_AUDIOINPUTALSA_H
#include "../../audio/AudioInput.h"
#include "../../threading.h"
#include <alsa/asoundlib.h>
namespace tgvoip{
namespace audio{
class AudioInputALSA : public AudioInput{
public:
AudioInputALSA(std::string devID);
virtual ~AudioInputALSA();
virtual void Start();
virtual void Stop();
virtual void SetCurrentDevice(std::string devID);
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
private:
void RunThread();
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
int (*_snd_pcm_close)(snd_pcm_t* pcm);
snd_pcm_sframes_t (*_snd_pcm_readi)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
const char* (*_snd_strerror)(int errnum);
void* lib;
snd_pcm_t* handle;
Thread* thread;
bool isRecording;
};
}
}
#endif //LIBTGVOIP_AUDIOINPUTALSA_H

View file

@ -0,0 +1,204 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include <dlfcn.h>
#include <unistd.h>
#include "AudioInputPulse.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include "AudioPulse.h"
#include "PulseFunctions.h"
#if !defined(__GLIBC__)
#include <libgen.h>
#endif
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
using namespace tgvoip::audio;
AudioInputPulse::AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
isRecording=false;
isConnected=false;
didStart=false;
this->mainloop=mainloop;
this->context=context;
stream=NULL;
remainingDataSize=0;
pa_threaded_mainloop_lock(mainloop);
stream=CreateAndInitStream();
pa_threaded_mainloop_unlock(mainloop);
isLocked=false;
if(!stream){
return;
}
SetCurrentDevice(devID);
}
AudioInputPulse::~AudioInputPulse(){
if(stream){
pa_stream_disconnect(stream);
pa_stream_unref(stream);
}
}
pa_stream* AudioInputPulse::CreateAndInitStream(){
pa_sample_spec sampleSpec{
.format=PA_SAMPLE_S16LE,
.rate=48000,
.channels=1
};
pa_proplist* proplist=pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip capture", &sampleSpec, NULL, proplist);
pa_proplist_free(proplist);
if(!stream){
LOGE("Error initializing PulseAudio (pa_stream_new)");
failed=true;
return NULL;
}
pa_stream_set_state_callback(stream, AudioInputPulse::StreamStateCallback, this);
pa_stream_set_read_callback(stream, AudioInputPulse::StreamReadCallback, this);
return stream;
}
void AudioInputPulse::Start(){
if(failed || isRecording)
return;
pa_threaded_mainloop_lock(mainloop);
isRecording=true;
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
pa_threaded_mainloop_unlock(mainloop);
}
void AudioInputPulse::Stop(){
if(!isRecording)
return;
isRecording=false;
pa_threaded_mainloop_lock(mainloop);
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
pa_threaded_mainloop_unlock(mainloop);
}
bool AudioInputPulse::IsRecording(){
return isRecording;
}
void AudioInputPulse::SetCurrentDevice(std::string devID){
pa_threaded_mainloop_lock(mainloop);
currentDevice=devID;
if(isRecording && isConnected){
pa_stream_disconnect(stream);
pa_stream_unref(stream);
isConnected=false;
stream=CreateAndInitStream();
}
pa_buffer_attr bufferAttr={
.maxlength=(uint32_t)-1,
.tlength=(uint32_t)-1,
.prebuf=(uint32_t)-1,
.minreq=(uint32_t)-1,
.fragsize=960*2
};
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
int err=pa_stream_connect_record(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags);
if(err!=0){
pa_threaded_mainloop_unlock(mainloop);
/*if(devID!="default"){
SetCurrentDevice("default");
return;
}*/
}
CHECK_ERROR(err, "pa_stream_connect_record");
while(true){
pa_stream_state_t streamState=pa_stream_get_state(stream);
if(!PA_STREAM_IS_GOOD(streamState)){
LOGE("Error connecting to audio device '%s'", devID.c_str());
pa_threaded_mainloop_unlock(mainloop);
failed=true;
return;
}
if(streamState==PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(mainloop);
}
isConnected=true;
if(isRecording){
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
}
pa_threaded_mainloop_unlock(mainloop);
}
bool AudioInputPulse::EnumerateDevices(std::vector<AudioInputDevice>& devs){
return AudioPulse::DoOneOperation([&](pa_context* ctx){
return pa_context_get_source_info_list(ctx, [](pa_context* ctx, const pa_source_info* info, int eol, void* userdata){
if(eol>0)
return;
std::vector<AudioInputDevice>* devs=(std::vector<AudioInputDevice>*)userdata;
AudioInputDevice dev;
dev.id=std::string(info->name);
dev.displayName=std::string(info->description);
devs->push_back(dev);
}, &devs);
});
}
void AudioInputPulse::StreamStateCallback(pa_stream *s, void* arg) {
AudioInputPulse* self=(AudioInputPulse*) arg;
pa_threaded_mainloop_signal(self->mainloop, 0);
}
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
((AudioInputPulse*)userdata)->StreamReadCallback(stream, requestedBytes);
}
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes) {
size_t bytesRemaining = requestedBytes;
uint8_t *buffer = NULL;
pa_usec_t latency;
if(pa_stream_get_latency(stream, &latency, NULL)==0){
estimatedDelay=(int32_t)(latency/100);
}
while (bytesRemaining > 0) {
size_t bytesToFill = 102400;
if (bytesToFill > bytesRemaining) bytesToFill = bytesRemaining;
int err=pa_stream_peek(stream, (const void**) &buffer, &bytesToFill);
CHECK_ERROR(err, "pa_stream_peek");
if(isRecording){
if(remainingDataSize+bytesToFill>sizeof(remainingData)){
LOGE("Capture buffer is too big (%d)", (int)bytesToFill);
}
memcpy(remainingData+remainingDataSize, buffer, bytesToFill);
remainingDataSize+=bytesToFill;
while(remainingDataSize>=960*2){
InvokeCallback(remainingData, 960*2);
memmove(remainingData, remainingData+960*2, remainingDataSize-960*2);
remainingDataSize-=960*2;
}
}
err=pa_stream_drop(stream);
CHECK_ERROR(err, "pa_stream_drop");
bytesRemaining -= bytesToFill;
}
}

View file

@ -0,0 +1,52 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTPULSE_H
#define LIBTGVOIP_AUDIOINPUTPULSE_H
#include "../../audio/AudioInput.h"
#include "../../threading.h"
#include <pulse/pulseaudio.h>
#define DECLARE_DL_FUNCTION(name) typeof(name)* _import_##name
namespace tgvoip{
namespace audio{
class AudioInputPulse : public AudioInput{
public:
AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
virtual ~AudioInputPulse();
virtual void Start();
virtual void Stop();
virtual bool IsRecording();
virtual void SetCurrentDevice(std::string devID);
static bool EnumerateDevices(std::vector<AudioInputDevice>& devs);
private:
static void StreamStateCallback(pa_stream* s, void* arg);
static void StreamReadCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
void StreamReadCallback(pa_stream* stream, size_t requestedBytes);
pa_stream* CreateAndInitStream();
pa_threaded_mainloop* mainloop;
pa_context* context;
pa_stream* stream;
bool isRecording;
bool isConnected;
bool didStart;
bool isLocked;
unsigned char remainingData[960*8*2];
size_t remainingDataSize;
};
}
}
#undef DECLARE_DL_FUNCTION
#endif //LIBTGVOIP_AUDIOINPUTPULSE_H

View file

@ -0,0 +1,177 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include <dlfcn.h>
#include "AudioOutputALSA.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
using namespace tgvoip::audio;
AudioOutputALSA::AudioOutputALSA(std::string devID){
isPlaying=false;
handle=NULL;
lib=dlopen("libasound.so.2", RTLD_LAZY);
if(!lib)
lib=dlopen("libasound.so", RTLD_LAZY);
if(!lib){
LOGE("Error loading libasound: %s", dlerror());
failed=true;
return;
}
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
LOAD_FUNCTION(lib, "snd_pcm_writei", _snd_pcm_writei);
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
SetCurrentDevice(devID);
}
AudioOutputALSA::~AudioOutputALSA(){
if(handle)
_snd_pcm_close(handle);
if(lib)
dlclose(lib);
}
void AudioOutputALSA::Start(){
if(failed || isPlaying)
return;
isPlaying=true;
thread=new Thread(std::bind(&AudioOutputALSA::RunThread, this));
thread->SetName("AudioOutputALSA");
thread->Start();
}
void AudioOutputALSA::Stop(){
if(!isPlaying)
return;
isPlaying=false;
thread->Join();
delete thread;
thread=NULL;
}
bool AudioOutputALSA::IsPlaying(){
return isPlaying;
}
void AudioOutputALSA::RunThread(){
unsigned char buffer[BUFFER_SIZE*2];
snd_pcm_sframes_t frames;
while(isPlaying){
InvokeCallback(buffer, sizeof(buffer));
frames=_snd_pcm_writei(handle, buffer, BUFFER_SIZE);
if (frames < 0){
frames = _snd_pcm_recover(handle, frames, 0);
}
if (frames < 0) {
LOGE("snd_pcm_writei failed: %s\n", _snd_strerror(frames));
break;
}
}
}
void AudioOutputALSA::SetCurrentDevice(std::string devID){
bool wasPlaying=isPlaying;
isPlaying=false;
if(handle){
thread->Join();
_snd_pcm_close(handle);
}
currentDevice=devID;
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
if(res<0)
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
CHECK_ERROR(res, "snd_pcm_open failed");
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
CHECK_ERROR(res, "snd_pcm_set_params failed");
if(wasPlaying){
isPlaying=true;
thread->Start();
}
}
void AudioOutputALSA::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
int (*_snd_device_name_free_hint)(void** hinst);
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
if(!lib)
dlopen("libasound.so", RTLD_LAZY);
if(!lib)
return;
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
dlclose(lib);
return;
}
char** hints;
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
if(err!=0){
dlclose(lib);
return;
}
char** n=hints;
while(*n){
char* name=_snd_device_name_get_hint(*n, "NAME");
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
free(name);
n++;
continue;
}
char* desc=_snd_device_name_get_hint(*n, "DESC");
char* ioid=_snd_device_name_get_hint(*n, "IOID");
if(!ioid || strcmp(ioid, "Output")==0){
char* l1=strtok(desc, "\n");
char* l2=strtok(NULL, "\n");
char* tmp=strtok(l1, ",");
char* actualName=tmp;
while((tmp=strtok(NULL, ","))){
actualName=tmp;
}
if(actualName[0]==' ')
actualName++;
AudioOutputDevice dev;
dev.id=std::string(name);
if(l2){
char buf[256];
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
dev.displayName=std::string(buf);
}else{
dev.displayName=std::string(actualName);
}
devs.push_back(dev);
}
free(name);
free(desc);
free(ioid);
n++;
}
dlclose(lib);
}

View file

@ -0,0 +1,46 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTALSA_H
#define LIBTGVOIP_AUDIOOUTPUTALSA_H
#include "../../audio/AudioOutput.h"
#include "../../threading.h"
#include <alsa/asoundlib.h>
namespace tgvoip{
namespace audio{
class AudioOutputALSA : public AudioOutput{
public:
AudioOutputALSA(std::string devID);
virtual ~AudioOutputALSA();
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
virtual void SetCurrentDevice(std::string devID);
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
private:
void RunThread();
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
int (*_snd_pcm_close)(snd_pcm_t* pcm);
snd_pcm_sframes_t (*_snd_pcm_writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
const char* (*_snd_strerror)(int errnum);
void* lib;
snd_pcm_t* handle;
Thread* thread;
bool isPlaying;
};
}
}
#endif //LIBTGVOIP_AUDIOOUTPUTALSA_H

View file

@ -0,0 +1,187 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include <dlfcn.h>
#include <unistd.h>
#include "AudioOutputPulse.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include "AudioPulse.h"
#include "PulseFunctions.h"
#if !defined(__GLIBC__)
#include <libgen.h>
#endif
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
using namespace tgvoip;
using namespace tgvoip::audio;
AudioOutputPulse::AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
isPlaying=false;
isConnected=false;
didStart=false;
isLocked=false;
this->mainloop=mainloop;
this->context=context;
stream=NULL;
remainingDataSize=0;
pa_threaded_mainloop_lock(mainloop);
stream=CreateAndInitStream();
pa_threaded_mainloop_unlock(mainloop);
SetCurrentDevice(devID);
}
AudioOutputPulse::~AudioOutputPulse(){
if(stream){
pa_stream_disconnect(stream);
pa_stream_unref(stream);
}
}
pa_stream* AudioOutputPulse::CreateAndInitStream(){
pa_sample_spec sampleSpec{
.format=PA_SAMPLE_S16LE,
.rate=48000,
.channels=1
};
pa_proplist* proplist=pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip playback", &sampleSpec, NULL, proplist);
pa_proplist_free(proplist);
if(!stream){
LOGE("Error initializing PulseAudio (pa_stream_new)");
failed=true;
return NULL;
}
pa_stream_set_state_callback(stream, AudioOutputPulse::StreamStateCallback, this);
pa_stream_set_write_callback(stream, AudioOutputPulse::StreamWriteCallback, this);
return stream;
}
void AudioOutputPulse::Start(){
if(failed || isPlaying)
return;
isPlaying=true;
pa_threaded_mainloop_lock(mainloop);
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
pa_threaded_mainloop_unlock(mainloop);
}
void AudioOutputPulse::Stop(){
if(!isPlaying)
return;
isPlaying=false;
pa_threaded_mainloop_lock(mainloop);
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
pa_threaded_mainloop_unlock(mainloop);
}
bool AudioOutputPulse::IsPlaying(){
return isPlaying;
}
void AudioOutputPulse::SetCurrentDevice(std::string devID){
pa_threaded_mainloop_lock(mainloop);
currentDevice=devID;
if(isPlaying && isConnected){
pa_stream_disconnect(stream);
pa_stream_unref(stream);
isConnected=false;
stream=CreateAndInitStream();
}
pa_buffer_attr bufferAttr={
.maxlength=(uint32_t)-1,
.tlength=960*2,
.prebuf=(uint32_t)-1,
.minreq=(uint32_t)-1,
.fragsize=(uint32_t)-1
};
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
int err=pa_stream_connect_playback(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags, NULL, NULL);
if(err!=0 && devID!="default"){
SetCurrentDevice("default");
return;
}
CHECK_ERROR(err, "pa_stream_connect_playback");
while(true){
pa_stream_state_t streamState=pa_stream_get_state(stream);
if(!PA_STREAM_IS_GOOD(streamState)){
LOGE("Error connecting to audio device '%s'", devID.c_str());
failed=true;
return;
}
if(streamState==PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(mainloop);
}
isConnected=true;
if(isPlaying){
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
}
pa_threaded_mainloop_unlock(mainloop);
}
bool AudioOutputPulse::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
return AudioPulse::DoOneOperation([&](pa_context* ctx){
return pa_context_get_sink_info_list(ctx, [](pa_context* ctx, const pa_sink_info* info, int eol, void* userdata){
if(eol>0)
return;
std::vector<AudioOutputDevice>* devs=(std::vector<AudioOutputDevice>*)userdata;
AudioOutputDevice dev;
dev.id=std::string(info->name);
dev.displayName=std::string(info->description);
devs->push_back(dev);
}, &devs);
});
}
void AudioOutputPulse::StreamStateCallback(pa_stream *s, void* arg) {
AudioOutputPulse* self=(AudioOutputPulse*) arg;
pa_threaded_mainloop_signal(self->mainloop, 0);
}
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
((AudioOutputPulse*)userdata)->StreamWriteCallback(stream, requestedBytes);
}
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes) {
//assert(requestedBytes<=sizeof(remainingData));
if(requestedBytes>sizeof(remainingData)){
requestedBytes=960*2; // force buffer size to 20ms. This probably wrecks the jitter buffer, but still better than crashing
}
pa_usec_t latency;
if(pa_stream_get_latency(stream, &latency, NULL)==0){
estimatedDelay=(int32_t)(latency/100);
}
while(requestedBytes>remainingDataSize){
if(isPlaying){
InvokeCallback(remainingData+remainingDataSize, 960*2);
remainingDataSize+=960*2;
}else{
memset(remainingData+remainingDataSize, 0, requestedBytes-remainingDataSize);
remainingDataSize=requestedBytes;
}
}
int err=pa_stream_write(stream, remainingData, requestedBytes, NULL, 0, PA_SEEK_RELATIVE);
CHECK_ERROR(err, "pa_stream_write");
remainingDataSize-=requestedBytes;
if(remainingDataSize>0)
memmove(remainingData, remainingData+requestedBytes, remainingDataSize);
}

View file

@ -0,0 +1,48 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTPULSE_H
#define LIBTGVOIP_AUDIOOUTPUTPULSE_H
#include "../../audio/AudioOutput.h"
#include "../../threading.h"
#include <pulse/pulseaudio.h>
namespace tgvoip{
namespace audio{
class AudioOutputPulse : public AudioOutput{
public:
AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
virtual ~AudioOutputPulse();
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
virtual void SetCurrentDevice(std::string devID);
static bool EnumerateDevices(std::vector<AudioOutputDevice>& devs);
private:
static void StreamStateCallback(pa_stream* s, void* arg);
static void StreamWriteCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
void StreamWriteCallback(pa_stream* stream, size_t requestedBytes);
pa_stream* CreateAndInitStream();
pa_threaded_mainloop* mainloop;
pa_context* context;
pa_stream* stream;
bool isPlaying;
bool isConnected;
bool didStart;
bool isLocked;
unsigned char remainingData[960*8*2];
size_t remainingDataSize;
};
}
}
#endif //LIBTGVOIP_AUDIOOUTPUTPULSE_H

View file

@ -0,0 +1,288 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "AudioPulse.h"
#include <dlfcn.h>
#include "../../logging.h"
#define DECLARE_DL_FUNCTION(name) typeof(name)* AudioPulse::_import_##name=NULL
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); return false;}
#define LOAD_DL_FUNCTION(name) {_import_##name=(typeof(_import_##name))dlsym(lib, #name); CHECK_DL_ERROR(_import_##name, "Error getting entry point for " #name);}
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
using namespace tgvoip;
using namespace tgvoip::audio;
bool AudioPulse::loaded=false;
void* AudioPulse::lib=NULL;
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
DECLARE_DL_FUNCTION(pa_context_new);
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
DECLARE_DL_FUNCTION(pa_context_connect);
DECLARE_DL_FUNCTION(pa_context_get_state);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
DECLARE_DL_FUNCTION(pa_operation_unref);
DECLARE_DL_FUNCTION(pa_stream_cork);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
DECLARE_DL_FUNCTION(pa_stream_disconnect);
DECLARE_DL_FUNCTION(pa_stream_unref);
DECLARE_DL_FUNCTION(pa_context_disconnect);
DECLARE_DL_FUNCTION(pa_context_unref);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
DECLARE_DL_FUNCTION(pa_stream_begin_write);
DECLARE_DL_FUNCTION(pa_stream_write);
DECLARE_DL_FUNCTION(pa_stream_get_state);
DECLARE_DL_FUNCTION(pa_strerror);
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
DECLARE_DL_FUNCTION(pa_stream_connect_record);
DECLARE_DL_FUNCTION(pa_stream_peek);
DECLARE_DL_FUNCTION(pa_stream_drop);
DECLARE_DL_FUNCTION(pa_mainloop_new);
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
DECLARE_DL_FUNCTION(pa_mainloop_free);
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
DECLARE_DL_FUNCTION(pa_operation_get_state);
DECLARE_DL_FUNCTION(pa_proplist_new);
DECLARE_DL_FUNCTION(pa_proplist_sets);
DECLARE_DL_FUNCTION(pa_proplist_free);
DECLARE_DL_FUNCTION(pa_stream_get_latency);
#include "PulseFunctions.h"
bool AudioPulse::Load(){
if(loaded)
return true;
lib=dlopen("libpulse.so.0", RTLD_LAZY);
if(!lib)
lib=dlopen("libpulse.so", RTLD_LAZY);
if(!lib){
LOGE("Error loading libpulse: %s", dlerror());
return false;
}
LOAD_DL_FUNCTION(pa_threaded_mainloop_new);
LOAD_DL_FUNCTION(pa_threaded_mainloop_get_api);
LOAD_DL_FUNCTION(pa_context_new);
LOAD_DL_FUNCTION(pa_context_new_with_proplist);
LOAD_DL_FUNCTION(pa_context_set_state_callback);
LOAD_DL_FUNCTION(pa_threaded_mainloop_lock);
LOAD_DL_FUNCTION(pa_threaded_mainloop_unlock);
LOAD_DL_FUNCTION(pa_threaded_mainloop_start);
LOAD_DL_FUNCTION(pa_context_connect);
LOAD_DL_FUNCTION(pa_context_get_state);
LOAD_DL_FUNCTION(pa_threaded_mainloop_wait);
LOAD_DL_FUNCTION(pa_stream_new_with_proplist);
LOAD_DL_FUNCTION(pa_stream_set_state_callback);
LOAD_DL_FUNCTION(pa_stream_set_write_callback);
LOAD_DL_FUNCTION(pa_stream_connect_playback);
LOAD_DL_FUNCTION(pa_operation_unref);
LOAD_DL_FUNCTION(pa_stream_cork);
LOAD_DL_FUNCTION(pa_threaded_mainloop_stop);
LOAD_DL_FUNCTION(pa_stream_disconnect);
LOAD_DL_FUNCTION(pa_stream_unref);
LOAD_DL_FUNCTION(pa_context_disconnect);
LOAD_DL_FUNCTION(pa_context_unref);
LOAD_DL_FUNCTION(pa_threaded_mainloop_free);
LOAD_DL_FUNCTION(pa_threaded_mainloop_signal);
LOAD_DL_FUNCTION(pa_stream_begin_write);
LOAD_DL_FUNCTION(pa_stream_write);
LOAD_DL_FUNCTION(pa_stream_get_state);
LOAD_DL_FUNCTION(pa_strerror);
LOAD_DL_FUNCTION(pa_stream_set_read_callback);
LOAD_DL_FUNCTION(pa_stream_connect_record);
LOAD_DL_FUNCTION(pa_stream_peek);
LOAD_DL_FUNCTION(pa_stream_drop);
LOAD_DL_FUNCTION(pa_mainloop_new);
LOAD_DL_FUNCTION(pa_mainloop_get_api);
LOAD_DL_FUNCTION(pa_mainloop_iterate);
LOAD_DL_FUNCTION(pa_mainloop_free);
LOAD_DL_FUNCTION(pa_context_get_sink_info_list);
LOAD_DL_FUNCTION(pa_context_get_source_info_list);
LOAD_DL_FUNCTION(pa_operation_get_state);
LOAD_DL_FUNCTION(pa_proplist_new);
LOAD_DL_FUNCTION(pa_proplist_sets);
LOAD_DL_FUNCTION(pa_proplist_free);
LOAD_DL_FUNCTION(pa_stream_get_latency);
loaded=true;
return true;
}
AudioPulse::AudioPulse(std::string inputDevice, std::string outputDevice){
if(!Load()){
failed=true;
LOGE("Failed to load libpulse");
return;
}
mainloop=pa_threaded_mainloop_new();
if(!mainloop){
LOGE("Error initializing PulseAudio (pa_threaded_mainloop_new)");
failed=true;
return;
}
mainloopApi=pa_threaded_mainloop_get_api(mainloop);
#ifndef MAXPATHLEN
char exeName[20];
#else
char exePath[MAXPATHLEN];
char exeName[MAXPATHLEN];
ssize_t lres=readlink("/proc/self/exe", exePath, sizeof(exePath));
if(lres==-1)
lres=readlink("/proc/curproc/file", exePath, sizeof(exePath));
if(lres==-1)
lres=readlink("/proc/curproc/exe", exePath, sizeof(exePath));
if(lres>0){
strcpy(exeName, basename(exePath));
}else
#endif
{
snprintf(exeName, sizeof(exeName), "Process %d", getpid());
}
pa_proplist* proplist=pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "phone");
context=pa_context_new_with_proplist(mainloopApi, exeName, proplist);
pa_proplist_free(proplist);
if(!context){
LOGE("Error initializing PulseAudio (pa_context_new)");
failed=true;
return;
}
pa_context_set_state_callback(context, [](pa_context* context, void* arg){
AudioPulse* self=reinterpret_cast<AudioPulse*>(arg);
pa_threaded_mainloop_signal(self->mainloop, 0);
}, this);
pa_threaded_mainloop_lock(mainloop);
isLocked=true;
int err=pa_threaded_mainloop_start(mainloop);
CHECK_ERROR(err, "pa_threaded_mainloop_start");
didStart=true;
err=pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
CHECK_ERROR(err, "pa_context_connect");
while(true){
pa_context_state_t contextState=pa_context_get_state(context);
if(!PA_CONTEXT_IS_GOOD(contextState)){
LOGE("Error initializing PulseAudio (PA_CONTEXT_IS_GOOD)");
failed=true;
return;
}
if(contextState==PA_CONTEXT_READY)
break;
pa_threaded_mainloop_wait(mainloop);
}
pa_threaded_mainloop_unlock(mainloop);
isLocked=false;
output=new AudioOutputPulse(context, mainloop, outputDevice);
input=new AudioInputPulse(context, mainloop, inputDevice);
}
AudioPulse::~AudioPulse(){
if(mainloop && didStart){
if(isLocked)
pa_threaded_mainloop_unlock(mainloop);
pa_threaded_mainloop_stop(mainloop);
}
if(input)
delete input;
if(output)
delete output;
if(context){
pa_context_disconnect(context);
pa_context_unref(context);
}
if(mainloop)
pa_threaded_mainloop_free(mainloop);
}
AudioOutput* AudioPulse::GetOutput(){
return output;
}
AudioInput* AudioPulse::GetInput(){
return input;
}
bool AudioPulse::DoOneOperation(std::function<pa_operation*(pa_context*)> f){
if(!Load())
return false;
pa_mainloop* ml;
pa_mainloop_api* mlAPI;
pa_context* ctx;
pa_operation* op=NULL;
int paReady=0;
ml=pa_mainloop_new();
mlAPI=pa_mainloop_get_api(ml);
ctx=pa_context_new(mlAPI, "libtgvoip");
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
pa_context_set_state_callback(ctx, [](pa_context* context, void* arg){
pa_context_state_t state;
int* pa_ready=(int*)arg;
state=pa_context_get_state(context);
switch(state){
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*pa_ready=2;
break;
case PA_CONTEXT_READY:
*pa_ready=1;
break;
}
}, &paReady);
while(true){
if(paReady==0){
pa_mainloop_iterate(ml, 1, NULL);
continue;
}
if(paReady==2){
pa_context_disconnect(ctx);
pa_context_unref(ctx);
pa_mainloop_free(ml);
return false;
}
if(!op){
op=f(ctx);
continue;
}
if(pa_operation_get_state(op)==PA_OPERATION_DONE){
pa_operation_unref(op);
pa_context_disconnect(ctx);
pa_context_unref(ctx);
pa_mainloop_free(ml);
return true;
}
pa_mainloop_iterate(ml, 1, NULL);
}
}

View file

@ -0,0 +1,95 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_PULSEAUDIOLOADER_H
#define LIBTGVOIP_PULSEAUDIOLOADER_H
#include <string>
#include <functional>
#include <pulse/pulseaudio.h>
#include "../../audio/AudioIO.h"
#include "AudioInputPulse.h"
#include "AudioOutputPulse.h"
#define DECLARE_DL_FUNCTION(name) static typeof(name)* _import_##name
namespace tgvoip{
namespace audio{
class AudioPulse : public AudioIO{
public:
AudioPulse(std::string inputDevice, std::string outputDevice);
virtual ~AudioPulse();
virtual AudioInput* GetInput();
virtual AudioOutput* GetOutput();
static bool Load();
static bool DoOneOperation(std::function<pa_operation*(pa_context*)> f);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
DECLARE_DL_FUNCTION(pa_context_new);
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
DECLARE_DL_FUNCTION(pa_context_connect);
DECLARE_DL_FUNCTION(pa_context_get_state);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
DECLARE_DL_FUNCTION(pa_operation_unref);
DECLARE_DL_FUNCTION(pa_stream_cork);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
DECLARE_DL_FUNCTION(pa_stream_disconnect);
DECLARE_DL_FUNCTION(pa_stream_unref);
DECLARE_DL_FUNCTION(pa_context_disconnect);
DECLARE_DL_FUNCTION(pa_context_unref);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
DECLARE_DL_FUNCTION(pa_stream_begin_write);
DECLARE_DL_FUNCTION(pa_stream_write);
DECLARE_DL_FUNCTION(pa_stream_get_state);
DECLARE_DL_FUNCTION(pa_strerror);
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
DECLARE_DL_FUNCTION(pa_stream_connect_record);
DECLARE_DL_FUNCTION(pa_stream_peek);
DECLARE_DL_FUNCTION(pa_stream_drop);
DECLARE_DL_FUNCTION(pa_mainloop_new);
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
DECLARE_DL_FUNCTION(pa_mainloop_free);
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
DECLARE_DL_FUNCTION(pa_operation_get_state);
DECLARE_DL_FUNCTION(pa_proplist_new);
DECLARE_DL_FUNCTION(pa_proplist_sets);
DECLARE_DL_FUNCTION(pa_proplist_free);
DECLARE_DL_FUNCTION(pa_stream_get_latency);
private:
static void* lib;
static bool loaded;
AudioInputPulse* input=NULL;
AudioOutputPulse* output=NULL;
pa_threaded_mainloop* mainloop;
pa_mainloop_api* mainloopApi;
pa_context* context;
bool isLocked=false;
bool didStart=false;
};
}
}
#undef DECLARE_DL_FUNCTION
#endif // LIBTGVOIP_PULSEAUDIOLOADER_H

View file

@ -0,0 +1,48 @@
#ifndef LIBTGVOIP_PULSE_FUNCTIONS_H
#define LIBTGVOIP_PULSE_FUNCTIONS_H
#define pa_threaded_mainloop_new AudioPulse::_import_pa_threaded_mainloop_new
#define pa_threaded_mainloop_get_api AudioPulse::_import_pa_threaded_mainloop_get_api
#define pa_context_new AudioPulse::_import_pa_context_new
#define pa_context_new_with_proplist AudioPulse::_import_pa_context_new_with_proplist
#define pa_context_set_state_callback AudioPulse::_import_pa_context_set_state_callback
#define pa_threaded_mainloop_lock AudioPulse::_import_pa_threaded_mainloop_lock
#define pa_threaded_mainloop_unlock AudioPulse::_import_pa_threaded_mainloop_unlock
#define pa_threaded_mainloop_start AudioPulse::_import_pa_threaded_mainloop_start
#define pa_context_connect AudioPulse::_import_pa_context_connect
#define pa_context_get_state AudioPulse::_import_pa_context_get_state
#define pa_threaded_mainloop_wait AudioPulse::_import_pa_threaded_mainloop_wait
#define pa_stream_new_with_proplist AudioPulse::_import_pa_stream_new_with_proplist
#define pa_stream_set_state_callback AudioPulse::_import_pa_stream_set_state_callback
#define pa_stream_set_write_callback AudioPulse::_import_pa_stream_set_write_callback
#define pa_stream_connect_playback AudioPulse::_import_pa_stream_connect_playback
#define pa_operation_unref AudioPulse::_import_pa_operation_unref
#define pa_stream_cork AudioPulse::_import_pa_stream_cork
#define pa_threaded_mainloop_stop AudioPulse::_import_pa_threaded_mainloop_stop
#define pa_stream_disconnect AudioPulse::_import_pa_stream_disconnect
#define pa_stream_unref AudioPulse::_import_pa_stream_unref
#define pa_context_disconnect AudioPulse::_import_pa_context_disconnect
#define pa_context_unref AudioPulse::_import_pa_context_unref
#define pa_threaded_mainloop_free AudioPulse::_import_pa_threaded_mainloop_free
#define pa_threaded_mainloop_signal AudioPulse::_import_pa_threaded_mainloop_signal
#define pa_stream_begin_write AudioPulse::_import_pa_stream_begin_write
#define pa_stream_write AudioPulse::_import_pa_stream_write
#define pa_strerror AudioPulse::_import_pa_strerror
#define pa_stream_get_state AudioPulse::_import_pa_stream_get_state
#define pa_stream_set_read_callback AudioPulse::_import_pa_stream_set_read_callback
#define pa_stream_connect_record AudioPulse::_import_pa_stream_connect_record
#define pa_stream_peek AudioPulse::_import_pa_stream_peek
#define pa_stream_drop AudioPulse::_import_pa_stream_drop
#define pa_mainloop_new AudioPulse::_import_pa_mainloop_new
#define pa_mainloop_get_api AudioPulse::_import_pa_mainloop_get_api
#define pa_mainloop_iterate AudioPulse::_import_pa_mainloop_iterate
#define pa_mainloop_free AudioPulse::_import_pa_mainloop_free
#define pa_context_get_sink_info_list AudioPulse::_import_pa_context_get_sink_info_list
#define pa_context_get_source_info_list AudioPulse::_import_pa_context_get_source_info_list
#define pa_operation_get_state AudioPulse::_import_pa_operation_get_state
#define pa_proplist_new AudioPulse::_import_pa_proplist_new
#define pa_proplist_sets AudioPulse::_import_pa_proplist_sets
#define pa_proplist_free AudioPulse::_import_pa_proplist_free
#define pa_stream_get_latency AudioPulse::_import_pa_stream_get_latency
#endif //LIBTGVOIP_PULSE_FUNCTIONS_H

View file

@ -0,0 +1,644 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "NetworkSocketPosix.h"
#include <sys/socket.h>
#include <errno.h>
#include <assert.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include "../../logging.h"
#include "../../VoIPController.h"
#include "../../Buffers.h"
#ifdef __ANDROID__
#include <jni.h>
#include <sys/system_properties.h>
extern JavaVM* sharedJVM;
extern jclass jniUtilitiesClass;
#else
#include <ifaddrs.h>
#endif
using namespace tgvoip;
NetworkSocketPosix::NetworkSocketPosix(NetworkProtocol protocol) : NetworkSocket(protocol), lastRecvdV4(0), lastRecvdV6("::0"){
needUpdateNat64Prefix=true;
nat64Present=false;
switchToV6at=0;
isV4Available=false;
fd=-1;
useTCP=false;
closing=false;
tcpConnectedAddress=NULL;
tcpConnectedPort=0;
if(protocol==PROTO_TCP)
timeout=10.0;
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
}
NetworkSocketPosix::~NetworkSocketPosix(){
if(fd>=0){
Close();
}
if(tcpConnectedAddress)
delete tcpConnectedAddress;
if(pendingOutgoingPacket)
delete pendingOutgoingPacket;
}
void NetworkSocketPosix::SetMaxPriority(){
#ifdef __APPLE__
int prio=NET_SERVICE_TYPE_VO;
int res=setsockopt(fd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &prio, sizeof(prio));
if(res<0){
LOGE("error setting darwin-specific net priority: %d / %s", errno, strerror(errno));
}
#elif defined(__linux__)
int prio=6;
int res=setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
if(res<0){
LOGE("error setting priority: %d / %s", errno, strerror(errno));
}
prio=46 << 2;
res=setsockopt(fd, SOL_IP, IP_TOS, &prio, sizeof(prio));
if(res<0){
LOGE("error setting ip tos: %d / %s", errno, strerror(errno));
}
#else
LOGI("cannot set socket priority");
#endif
}
void NetworkSocketPosix::Send(NetworkPacket *packet){
if(!packet || (protocol==PROTO_UDP && !packet->address)){
LOGW("tried to send null packet");
return;
}
int res;
if(protocol==PROTO_UDP){
sockaddr_in6 addr;
IPv4Address *v4addr=dynamic_cast<IPv4Address *>(packet->address);
if(v4addr){
if(needUpdateNat64Prefix && !isV4Available && VoIPController::GetCurrentTime()>switchToV6at && switchToV6at!=0){
LOGV("Updating NAT64 prefix");
nat64Present=false;
addrinfo *addr0;
int res=getaddrinfo("ipv4only.arpa", NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo *addrPtr;
unsigned char *addr170=NULL;
unsigned char *addr171=NULL;
for(addrPtr=addr0; addrPtr; addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET6){
sockaddr_in6 *translatedAddr=(sockaddr_in6 *) addrPtr->ai_addr;
uint32_t v4part=*((uint32_t *) &translatedAddr->sin6_addr.s6_addr[12]);
if(v4part==0xAA0000C0 && !addr170){
addr170=translatedAddr->sin6_addr.s6_addr;
}
if(v4part==0xAB0000C0 && !addr171){
addr171=translatedAddr->sin6_addr.s6_addr;
}
char buf[INET6_ADDRSTRLEN];
LOGV("Got translated address: %s", inet_ntop(AF_INET6, &translatedAddr->sin6_addr, buf, sizeof(buf)));
}
}
if(addr170 && addr171 && memcmp(addr170, addr171, 12)==0){
nat64Present=true;
memcpy(nat64Prefix, addr170, 12);
char buf[INET6_ADDRSTRLEN];
LOGV("Found nat64 prefix from %s", inet_ntop(AF_INET6, addr170, buf, sizeof(buf)));
}else{
LOGV("Didn't find nat64");
}
freeaddrinfo(addr0);
}
needUpdateNat64Prefix=false;
}
memset(&addr, 0, sizeof(sockaddr_in6));
addr.sin6_family=AF_INET6;
*((uint32_t *) &addr.sin6_addr.s6_addr[12])=v4addr->GetAddress();
if(nat64Present)
memcpy(addr.sin6_addr.s6_addr, nat64Prefix, 12);
else
addr.sin6_addr.s6_addr[11]=addr.sin6_addr.s6_addr[10]=0xFF;
}else{
IPv6Address *v6addr=dynamic_cast<IPv6Address *>(packet->address);
assert(v6addr!=NULL);
memcpy(addr.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
addr.sin6_family=AF_INET6;
}
addr.sin6_port=htons(packet->port);
res=(int)sendto(fd, packet->data, packet->length, 0, (const sockaddr *) &addr, sizeof(addr));
}else{
res=(int)send(fd, packet->data, packet->length, 0);
}
if(res<=0){
if(errno==EAGAIN || errno==EWOULDBLOCK){
if(pendingOutgoingPacket){
LOGE("Got EAGAIN but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length);
pendingOutgoingPacket->CopyFrom(packet->data, 0, packet->length);
readyToSend=false;
}
}else{
LOGE("error sending: %d / %s", errno, strerror(errno));
if(errno==ENETUNREACH && !isV4Available && VoIPController::GetCurrentTime()<switchToV6at){
switchToV6at=VoIPController::GetCurrentTime();
LOGI("Network unreachable, trying NAT64");
}
}
}else if((size_t)res!=packet->length && packet->protocol==PROTO_TCP){
if(pendingOutgoingPacket){
LOGE("send returned less than packet length but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length-res);
pendingOutgoingPacket->CopyFrom(packet->data+res, 0, packet->length-res);
readyToSend=false;
}
}
}
bool NetworkSocketPosix::OnReadyToSend(){
if(pendingOutgoingPacket){
NetworkPacket pkt={0};
pkt.data=**pendingOutgoingPacket;
pkt.length=pendingOutgoingPacket->Length();
Send(&pkt);
delete pendingOutgoingPacket;
pendingOutgoingPacket=NULL;
return false;
}
readyToSend=true;
return true;
}
void NetworkSocketPosix::Receive(NetworkPacket *packet){
if(failed){
packet->length=0;
return;
}
if(protocol==PROTO_UDP){
int addrLen=sizeof(sockaddr_in6);
sockaddr_in6 srcAddr;
ssize_t len=recvfrom(fd, packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
if(len>0)
packet->length=(size_t) len;
else{
LOGE("error receiving %d / %s", errno, strerror(errno));
packet->length=0;
return;
}
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
if(!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)){
isV4Available=true;
LOGI("Detected IPv4 connectivity, will not try IPv6");
}
if(IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12)==0)){
in_addr v4addr=*((in_addr *) &srcAddr.sin6_addr.s6_addr[12]);
lastRecvdV4=IPv4Address(v4addr.s_addr);
packet->address=&lastRecvdV4;
}else{
lastRecvdV6=IPv6Address(srcAddr.sin6_addr.s6_addr);
packet->address=&lastRecvdV6;
}
packet->protocol=PROTO_UDP;
packet->port=ntohs(srcAddr.sin6_port);
}else if(protocol==PROTO_TCP){
int res=(int)recv(fd, packet->data, packet->length, 0);
if(res<=0){
LOGE("Error receiving from TCP socket: %d / %s", errno, strerror(errno));
failed=true;
packet->length=0;
}else{
packet->length=(size_t)res;
packet->address=tcpConnectedAddress;
packet->port=tcpConnectedPort;
packet->protocol=PROTO_TCP;
}
}
}
void NetworkSocketPosix::Open(){
if(protocol!=PROTO_UDP)
return;
fd=socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if(fd<0){
LOGE("error creating socket: %d / %s", errno, strerror(errno));
failed=true;
return;
}
int flag=0;
int res=setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag));
if(res<0){
LOGE("error enabling dual stack socket: %d / %s", errno, strerror(errno));
failed=true;
return;
}
SetMaxPriority();
fcntl(fd, F_SETFL, O_NONBLOCK);
int tries=0;
sockaddr_in6 addr;
//addr.sin6_addr.s_addr=0;
memset(&addr, 0, sizeof(sockaddr_in6));
//addr.sin6_len=sizeof(sa_family_t);
addr.sin6_family=AF_INET6;
for(tries=0;tries<10;tries++){
addr.sin6_port=htons(GenerateLocalPort());
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
LOGV("trying bind to port %u", ntohs(addr.sin6_port));
if(res<0){
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
}else{
break;
}
}
if(tries==10){
addr.sin6_port=0;
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
if(res<0){
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
//SetState(STATE_FAILED);
failed=true;
return;
}
}
size_t addrLen=sizeof(sockaddr_in6);
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
LOGD("Bound to local UDP port %u", ntohs(addr.sin6_port));
needUpdateNat64Prefix=true;
isV4Available=false;
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
}
void NetworkSocketPosix::Close(){
closing=true;
failed=true;
if (fd>=0) {
shutdown(fd, SHUT_RDWR);
close(fd);
fd=-1;
}
}
void NetworkSocketPosix::Connect(const NetworkAddress *address, uint16_t port){
const IPv4Address* v4addr=dynamic_cast<const IPv4Address*>(address);
const IPv6Address* v6addr=dynamic_cast<const IPv6Address*>(address);
struct sockaddr_in v4={0};
struct sockaddr_in6 v6={0};
struct sockaddr* addr=NULL;
size_t addrLen=0;
if(v4addr){
v4.sin_family=AF_INET;
v4.sin_addr.s_addr=v4addr->GetAddress();
v4.sin_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v4);
addrLen=sizeof(v4);
}else if(v6addr){
v6.sin6_family=AF_INET6;
memcpy(v6.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
v6.sin6_flowinfo=0;
v6.sin6_scope_id=0;
v6.sin6_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v6);
addrLen=sizeof(v6);
}else{
LOGE("Unknown address type in TCP connect");
failed=true;
return;
}
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
if(fd<0){
LOGE("Error creating TCP socket: %d / %s", errno, strerror(errno));
failed=true;
return;
}
int opt=1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
timeval timeout;
timeout.tv_sec=5;
timeout.tv_usec=0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
timeout.tv_sec=60;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
fcntl(fd, F_SETFL, O_NONBLOCK);
int res=(int)connect(fd, (const sockaddr*) addr, (socklen_t)addrLen);
if(res!=0 && errno!=EINVAL && errno!=EINPROGRESS){
LOGW("error connecting TCP socket to %s:%u: %d / %s; %d / %s", address->ToString().c_str(), port, res, strerror(res), errno, strerror(errno));
close(fd);
failed=true;
return;
}
tcpConnectedAddress=v4addr ? (NetworkAddress*)new IPv4Address(*v4addr) : (NetworkAddress*)new IPv6Address(*v6addr);
tcpConnectedPort=port;
LOGI("successfully connected to %s:%d", tcpConnectedAddress->ToString().c_str(), tcpConnectedPort);
}
void NetworkSocketPosix::OnActiveInterfaceChanged(){
needUpdateNat64Prefix=true;
isV4Available=false;
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
}
std::string NetworkSocketPosix::GetLocalInterfaceInfo(IPv4Address *v4addr, IPv6Address *v6addr){
std::string name="";
// Android doesn't support ifaddrs
#ifdef __ANDROID__
JNIEnv *env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
jmethodID getLocalNetworkAddressesAndInterfaceNameMethod=env->GetStaticMethodID(jniUtilitiesClass, "getLocalNetworkAddressesAndInterfaceName", "()[Ljava/lang/String;");
jobjectArray jinfo=(jobjectArray) env->CallStaticObjectMethod(jniUtilitiesClass, getLocalNetworkAddressesAndInterfaceNameMethod);
if(jinfo){
jstring jitfName=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 0));
jstring jipv4=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 1));
jstring jipv6=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 2));
if(jitfName){
const char *itfchars=env->GetStringUTFChars(jitfName, NULL);
name=std::string(itfchars);
env->ReleaseStringUTFChars(jitfName, itfchars);
}
if(v4addr && jipv4){
const char* ipchars=env->GetStringUTFChars(jipv4, NULL);
*v4addr=IPv4Address(ipchars);
env->ReleaseStringUTFChars(jipv4, ipchars);
}
if(v6addr && jipv6){
const char* ipchars=env->GetStringUTFChars(jipv6, NULL);
*v6addr=IPv6Address(ipchars);
env->ReleaseStringUTFChars(jipv6, ipchars);
}
}else{
LOGW("Failed to get android network interface info");
}
if(didAttach){
sharedJVM->DetachCurrentThread();
}
#else
struct ifaddrs* interfaces;
if(!getifaddrs(&interfaces)){
struct ifaddrs* interface;
for(interface=interfaces;interface;interface=interface->ifa_next){
if(!(interface->ifa_flags & IFF_UP) || !(interface->ifa_flags & IFF_RUNNING) || (interface->ifa_flags & IFF_LOOPBACK))
continue;
const struct sockaddr_in* addr=(const struct sockaddr_in*)interface->ifa_addr;
if(addr){
if(addr->sin_family==AF_INET){
if((ntohl(addr->sin_addr.s_addr) & 0xFFFF0000)==0xA9FE0000)
continue;
if(v4addr)
*v4addr=IPv4Address(addr->sin_addr.s_addr);
name=interface->ifa_name;
}else if(addr->sin_family==AF_INET6){
const struct sockaddr_in6* addr6=(const struct sockaddr_in6*)addr;
if((addr6->sin6_addr.s6_addr[0] & 0xF0)==0xF0)
continue;
if(v6addr)
*v6addr=IPv6Address(addr6->sin6_addr.s6_addr);
name=interface->ifa_name;
}
}
}
freeifaddrs(interfaces);
}
#endif
return name;
}
uint16_t NetworkSocketPosix::GetLocalPort(){
sockaddr_in6 addr;
size_t addrLen=sizeof(sockaddr_in6);
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
return ntohs(addr.sin6_port);
}
std::string NetworkSocketPosix::V4AddressToString(uint32_t address){
char buf[INET_ADDRSTRLEN];
in_addr addr;
addr.s_addr=address;
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
return std::string(buf);
}
std::string NetworkSocketPosix::V6AddressToString(const unsigned char *address){
char buf[INET6_ADDRSTRLEN];
in6_addr addr;
memcpy(addr.s6_addr, address, 16);
inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
return std::string(buf);
}
uint32_t NetworkSocketPosix::StringToV4Address(std::string address){
in_addr addr;
inet_pton(AF_INET, address.c_str(), &addr);
return addr.s_addr;
}
void NetworkSocketPosix::StringToV6Address(std::string address, unsigned char *out){
in6_addr addr;
inet_pton(AF_INET6, address.c_str(), &addr);
memcpy(out, addr.s6_addr, 16);
}
IPv4Address *NetworkSocketPosix::ResolveDomainName(std::string name){
addrinfo* addr0;
IPv4Address* ret=NULL;
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo* addrPtr;
for(addrPtr=addr0;addrPtr;addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET){
sockaddr_in* addr=(sockaddr_in*)addrPtr->ai_addr;
ret=new IPv4Address(addr->sin_addr.s_addr);
break;
}
}
freeaddrinfo(addr0);
}
return ret;
}
NetworkAddress *NetworkSocketPosix::GetConnectedAddress(){
return tcpConnectedAddress;
}
uint16_t NetworkSocketPosix::GetConnectedPort(){
return tcpConnectedPort;
}
void NetworkSocketPosix::SetTimeouts(int sendTimeout, int recvTimeout){
timeval timeout;
timeout.tv_sec=sendTimeout;
timeout.tv_usec=0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
timeout.tv_sec=recvTimeout;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
}
bool NetworkSocketPosix::Select(std::vector<NetworkSocket *> &readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket *> &errorFds, SocketSelectCanceller* _canceller) {
fd_set readSet;
fd_set writeSet;
fd_set errorSet;
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_ZERO(&errorSet);
SocketSelectCancellerPosix *canceller = dynamic_cast<SocketSelectCancellerPosix *>(_canceller);
if (canceller)
FD_SET(canceller->pipeRead, &readSet);
int maxfd = canceller ? canceller->pipeRead : 0;
for (NetworkSocket *&s:readFds) {
int sfd = GetDescriptorFromSocket(s);
if (sfd <= 0) {
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
continue;
}
FD_SET(sfd, &readSet);
if (maxfd < sfd)
maxfd = sfd;
}
for (NetworkSocket *&s:writeFds) {
int sfd = GetDescriptorFromSocket(s);
if (sfd <= 0) {
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
continue;
}
FD_SET(sfd, &writeSet);
if (maxfd < sfd)
maxfd = sfd;
}
bool anyFailed = false;
for (NetworkSocket *&s:errorFds) {
int sfd = GetDescriptorFromSocket(s);
if (sfd <= 0) {
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
continue;
}
if (s->timeout > 0 && VoIPController::GetCurrentTime() - s->lastSuccessfulOperationTime > s->timeout) {
LOGW("Socket %d timed out", sfd);
s->failed = true;
}
anyFailed |= s->IsFailed();
FD_SET(sfd, &errorSet);
if (maxfd < sfd)
maxfd = sfd;
}
select(maxfd + 1, &readSet, &writeSet, &errorSet, NULL);
if (canceller && FD_ISSET(canceller->pipeRead, &readSet) && !anyFailed) {
char c;
(void) read(canceller->pipeRead, &c, 1);
return false;
} else if (anyFailed) {
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
}
std::vector<NetworkSocket *>::iterator itr = readFds.begin();
while (itr != readFds.end()) {
int sfd = GetDescriptorFromSocket(*itr);
if (sfd > 0 && FD_ISSET(sfd, &readSet)) {
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
}
if (sfd <= 0 || !FD_ISSET(sfd, &readSet) || !(*itr)->OnReadyToReceive()) {
itr = readFds.erase(itr);
} else {
++itr;
}
}
itr = writeFds.begin();
while (itr != writeFds.end()) {
int sfd = GetDescriptorFromSocket(*itr);
if (sfd <= 0 || !FD_ISSET(sfd, &writeSet)) {
itr = writeFds.erase(itr);
} else {
LOGV("Socket %d is ready to send", sfd);
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
if ((*itr)->OnReadyToSend())
++itr;
else
itr = writeFds.erase(itr);
}
}
itr = errorFds.begin();
while (itr != errorFds.end()) {
int sfd = GetDescriptorFromSocket(*itr);
if ((sfd <= 0 || !FD_ISSET(sfd, &errorSet)) && !(*itr)->IsFailed()) {
itr = errorFds.erase(itr);
} else {
++itr;
}
}
//LOGV("select fds left: read=%d, write=%d, error=%d", (int)readFds.size(), (int)writeFds.size(), (int)errorFds.size());
return readFds.size() > 0 || errorFds.size() > 0 || writeFds.size() > 0;
}
SocketSelectCancellerPosix::SocketSelectCancellerPosix(){
int p[2];
int pipeRes=pipe(p);
if(pipeRes!=0){
LOGE("pipe() failed");
abort();
}
pipeRead=p[0];
pipeWrite=p[1];
}
SocketSelectCancellerPosix::~SocketSelectCancellerPosix(){
close(pipeRead);
close(pipeWrite);
}
void SocketSelectCancellerPosix::CancelSelect(){
char c=1;
(void) write(pipeWrite, &c, 1);
}
int NetworkSocketPosix::GetDescriptorFromSocket(NetworkSocket *socket){
NetworkSocketPosix* sp=dynamic_cast<NetworkSocketPosix*>(socket);
if(sp)
return sp->fd;
NetworkSocketWrapper* sw=dynamic_cast<NetworkSocketWrapper*>(socket);
if(sw)
return GetDescriptorFromSocket(sw->GetWrapped());
return 0;
}

View file

@ -0,0 +1,77 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_NETWORKSOCKETPOSIX_H
#define LIBTGVOIP_NETWORKSOCKETPOSIX_H
#include "../../NetworkSocket.h"
#include "../../Buffers.h"
#include <vector>
#include <sys/select.h>
#include <pthread.h>
namespace tgvoip {
class SocketSelectCancellerPosix : public SocketSelectCanceller{
friend class NetworkSocketPosix;
public:
SocketSelectCancellerPosix();
virtual ~SocketSelectCancellerPosix();
virtual void CancelSelect();
private:
int pipeRead;
int pipeWrite;
};
class NetworkSocketPosix : public NetworkSocket{
public:
NetworkSocketPosix(NetworkProtocol protocol);
virtual ~NetworkSocketPosix();
virtual void Send(NetworkPacket* packet) override;
virtual void Receive(NetworkPacket* packet) override;
virtual void Open() override;
virtual void Close() override;
virtual void Connect(const NetworkAddress* address, uint16_t port) override;
virtual std::string GetLocalInterfaceInfo(IPv4Address* v4addr, IPv6Address* v6addr) override;
virtual void OnActiveInterfaceChanged() override;
virtual uint16_t GetLocalPort() override;
static std::string V4AddressToString(uint32_t address);
static std::string V6AddressToString(const unsigned char address[16]);
static uint32_t StringToV4Address(std::string address);
static void StringToV6Address(std::string address, unsigned char* out);
static IPv4Address* ResolveDomainName(std::string name);
static bool Select(std::vector<NetworkSocket*>& readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*>& errorFds, SocketSelectCanceller* canceller);
virtual NetworkAddress *GetConnectedAddress() override;
virtual uint16_t GetConnectedPort() override;
virtual void SetTimeouts(int sendTimeout, int recvTimeout) override;
virtual bool OnReadyToSend() override;
protected:
virtual void SetMaxPriority() override;
private:
static int GetDescriptorFromSocket(NetworkSocket* socket);
int fd;
bool needUpdateNat64Prefix;
bool nat64Present;
double switchToV6at;
bool isV4Available;
bool useTCP;
bool closing;
IPv4Address lastRecvdV4;
IPv6Address lastRecvdV6;
NetworkAddress* tcpConnectedAddress;
uint16_t tcpConnectedPort;
Buffer* pendingOutgoingPacket=NULL;
};
}
#endif //LIBTGVOIP_NETWORKSOCKETPOSIX_H

View file

@ -0,0 +1,459 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include "AudioInputWASAPI.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
template <class T> void SafeRelease(T **ppT)
{
if(*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
using namespace tgvoip::audio;
AudioInputWASAPI::AudioInputWASAPI(std::string deviceID){
isRecording=false;
remainingDataLen=0;
refCount=1;
HRESULT res;
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
CHECK_RES(res, "CoInitializeEx");
}
#ifdef TGVOIP_WINXP_COMPAT
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
#undef CreateEventEx
#define CreateEventEx __CreateEventExA
#endif
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
ZeroMemory(&format, sizeof(format));
format.wFormatTag=WAVE_FORMAT_PCM;
format.nChannels=1;
format.nSamplesPerSec=48000;
format.nBlockAlign=2;
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
format.wBitsPerSample=16;
#ifdef TGVOIP_WINDOWS_DESKTOP
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=enumerator->RegisterEndpointNotificationCallback(this);
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
audioSessionControl=NULL;
device=NULL;
#endif
audioClient=NULL;
captureClient=NULL;
thread=NULL;
started=false;
SetCurrentDevice(deviceID);
}
AudioInputWASAPI::~AudioInputWASAPI(){
if(audioClient && started){
audioClient->Stop();
}
#ifdef TGVOIP_WINDOWS_DESKTOP
if(audioSessionControl){
audioSessionControl->UnregisterAudioSessionNotification(this);
}
#endif
SetEvent(shutdownEvent);
if(thread){
WaitForSingleObjectEx(thread, INFINITE, false);
CloseHandle(thread);
}
#ifdef TGVOIP_WINDOWS_DESKTOP
SafeRelease(&audioSessionControl);
#endif
SafeRelease(&captureClient);
SafeRelease(&audioClient);
#ifdef TGVOIP_WINDOWS_DESKTOP
SafeRelease(&device);
#endif
CloseHandle(shutdownEvent);
CloseHandle(audioSamplesReadyEvent);
CloseHandle(streamSwitchEvent);
#ifdef TGVOIP_WINDOWS_DESKTOP
if(enumerator)
enumerator->UnregisterEndpointNotificationCallback(this);
SafeRelease(&enumerator);
#endif
}
void AudioInputWASAPI::Start(){
isRecording=true;
if(!thread){
thread=CreateThread(NULL, 0, AudioInputWASAPI::StartThread, this, 0, NULL);
}
if(audioClient && !started){
LOGI("audioClient->Start");
audioClient->Start();
started=true;
}
}
void AudioInputWASAPI::Stop(){
isRecording=false;
}
bool AudioInputWASAPI::IsRecording(){
return isRecording;
}
void AudioInputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
#ifdef TGVOIP_WINDOWS_DESKTOP
HRESULT res;
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
SCHECK_RES(res, "CoInitializeEx");
}
IMMDeviceEnumerator *deviceEnumerator = NULL;
IMMDeviceCollection *deviceCollection = NULL;
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
SCHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
SCHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
SCHECK_RES(res, "GetDeviceItem");
wchar_t* devID;
res=device->GetId(&devID);
SCHECK_RES(res, "get device id");
IPropertyStore* propStore;
res=device->OpenPropertyStore(STGM_READ, &propStore);
SafeRelease(&device);
SCHECK_RES(res, "OpenPropertyStore");
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propStore);
AudioInputDevice dev;
wchar_t actualFriendlyName[128];
if(friendlyName.vt==VT_LPWSTR){
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
}else{
wcscpy(actualFriendlyName, L"Unknown");
}
PropVariantClear(&friendlyName);
char buf[256];
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
dev.id=buf;
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
dev.displayName=buf;
devs.push_back(dev);
CoTaskMemFree(devID);
}
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
#endif
}
void AudioInputWASAPI::SetCurrentDevice(std::string deviceID){
if(thread){
streamChangeToDevice=deviceID;
SetEvent(streamSwitchEvent);
}else{
ActuallySetCurrentDevice(deviceID);
}
}
void AudioInputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
currentDevice=deviceID;
HRESULT res;
if(audioClient){
res=audioClient->Stop();
CHECK_RES(res, "audioClient->Stop");
}
#ifdef TGVOIP_WINDOWS_DESKTOP
if(audioSessionControl){
res=audioSessionControl->UnregisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
}
SafeRelease(&audioSessionControl);
#endif
SafeRelease(&captureClient);
SafeRelease(&audioClient);
#ifdef TGVOIP_WINDOWS_DESKTOP
SafeRelease(&device);
IMMDeviceCollection *deviceCollection = NULL;
if(deviceID=="default"){
isDefaultDevice=true;
res=enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device);
CHECK_RES(res, "GetDefaultAudioEndpoint");
}else{
isDefaultDevice=false;
res=enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
CHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
CHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
CHECK_RES(res, "GetDeviceItem");
wchar_t* _devID;
res=device->GetId(&_devID);
CHECK_RES(res, "get device id");
char devID[128];
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
CoTaskMemFree(_devID);
if(deviceID==devID){
this->device=device;
//device->AddRef();
break;
}
}
}
if(deviceCollection)
SafeRelease(&deviceCollection);
if(!device){
LOGE("Didn't find capture device; failing");
failed=true;
return;
}
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
CHECK_RES(res, "device->Activate");
#else
std::wstring devID;
if (deviceID=="default"){
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId(Windows::Media::Devices::AudioDeviceRole::Communications);
if(defaultDevID==nullptr){
LOGE("Didn't find capture device; failing");
failed=true;
return;
}else{
isDefaultDevice=true;
devID=defaultDevID->Data();
}
}else{
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
wchar_t* wstr=new wchar_t[wchars_num];
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
devID=wstr;
}
HRESULT res1, res2;
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
CHECK_RES(res1, "activate1");
CHECK_RES(res2, "activate2");
AudioClientProperties properties={};
properties.cbSize=sizeof AudioClientProperties;
properties.eCategory=AudioCategory_Communications;
res = audioClient2->SetClientProperties(&properties);
CHECK_RES(res, "audioClient2->SetClientProperties");
audioClient=audioClient2;
#endif
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
// Use 1000ms buffer to avoid resampling glitches on Windows 8.1 and older. This should not increase latency.
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 1000*10000, 0, &format, &guid);
CHECK_RES(res, "audioClient->Initialize");
uint32_t bufSize;
res = audioClient->GetBufferSize(&bufSize);
CHECK_RES(res, "audioClient->GetBufferSize");
LOGV("buffer size: %u", bufSize);
estimatedDelay=0;
REFERENCE_TIME latency, devicePeriod;
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
}
}
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
CHECK_RES(res, "audioClient->SetEventHandle");
res = audioClient->GetService(IID_PPV_ARGS(&captureClient));
CHECK_RES(res, "audioClient->GetService");
#ifdef TGVOIP_WINDOWS_DESKTOP
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
res=audioSessionControl->RegisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
#endif
if(isRecording)
audioClient->Start();
LOGV("set current input device done");
}
DWORD WINAPI AudioInputWASAPI::StartThread(void* arg) {
LOGV("WASAPI capture thread starting");
((AudioInputWASAPI*)arg)->RunThread();
return 0;
}
void AudioInputWASAPI::RunThread() {
if(failed)
return;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
CHECK_RES(res, "CoInitializeEx in capture thread");
uint32_t bufferSize=0;
uint64_t framesWritten=0;
bool running=true;
//double prevCallback=VoIPController::GetCurrentTime();
while(running){
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
LOGV("capture thread shutting down");
running=false;
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
LOGV("stream switch");
ActuallySetCurrentDevice(streamChangeToDevice);
ResetEvent(streamSwitchEvent);
bufferSize=0;
LOGV("stream switch done");
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
if(!audioClient)
continue;
res=captureClient->GetNextPacketSize(&bufferSize);
CHECK_RES(res, "captureClient->GetNextPacketSize");
BYTE* data;
uint32_t framesAvailable=0;
DWORD flags;
res=captureClient->GetBuffer(&data, &framesAvailable, &flags, NULL, NULL);
CHECK_RES(res, "captureClient->GetBuffer");
size_t dataLen=framesAvailable*2;
assert(remainingDataLen+dataLen<sizeof(remainingData));
if(flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY){
LOGW("Audio capture data discontinuity");
}
//double t=VoIPController::GetCurrentTime();
//LOGV("audio capture: %u, time %f, flags %u", framesAvailable, t-prevCallback, flags);
//prevCallback=t;
memcpy(remainingData+remainingDataLen, data, dataLen);
remainingDataLen+=dataLen;
while(remainingDataLen>960*2){
if(isRecording)
InvokeCallback(remainingData, 960*2);
//LOGV("remaining data len %u", remainingDataLen);
memmove(remainingData, remainingData+(960*2), remainingDataLen-960*2);
remainingDataLen-=960*2;
}
res=captureClient->ReleaseBuffer(framesAvailable);
CHECK_RES(res, "captureClient->ReleaseBuffer");
//estimatedDelay=(int32_t)((devicePosition-framesWritten)/48);
framesWritten+=framesAvailable;
}
}
}
#ifdef TGVOIP_WINDOWS_DESKTOP
HRESULT AudioInputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
if(!isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
HRESULT AudioInputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
if(flow==eCapture && role==eCommunications && isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
ULONG AudioInputWASAPI::AddRef(){
return InterlockedIncrement(&refCount);
}
ULONG AudioInputWASAPI::Release(){
return InterlockedDecrement(&refCount);
}
HRESULT AudioInputWASAPI::QueryInterface(REFIID iid, void** obj){
if(!obj){
return E_POINTER;
}
*obj=NULL;
if(iid==IID_IUnknown){
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
AddRef();
}else if(iid==__uuidof(IMMNotificationClient)){
*obj=static_cast<IMMNotificationClient*>(this);
AddRef();
}else if(iid==__uuidof(IAudioSessionEvents)){
*obj=static_cast<IAudioSessionEvents*>(this);
AddRef();
}else{
return E_NOINTERFACE;
}
return S_OK;
}
#endif

View file

@ -0,0 +1,105 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTWASAPI_H
#define LIBTGVOIP_AUDIOINPUTWASAPI_H
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
#define TGVOIP_WINDOWS_PHONE
#endif
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
#define TGVOIP_WINDOWS_DESKTOP
#endif
#include <windows.h>
#include <string>
#include <vector>
#pragma warning(push)
#pragma warning(disable : 4201)
#ifndef TGVOIP_WP_SILVERLIGHT
#include <mmdeviceapi.h>
#endif
#ifdef TGVOIP_WINDOWS_DESKTOP
#include <audiopolicy.h>
#include <functiondiscoverykeys.h>
#else
#include <audioclient.h>
#include "WindowsSandboxUtils.h"
#endif
#pragma warning(pop)
#include "../../audio/AudioInput.h"
namespace tgvoip{
namespace audio{
#ifdef TGVOIP_WINDOWS_DESKTOP
class AudioInputWASAPI : public AudioInput, IMMNotificationClient, IAudioSessionEvents{
#else
class AudioInputWASAPI : public AudioInput{
#endif
public:
AudioInputWASAPI(std::string deviceID);
virtual ~AudioInputWASAPI();
virtual void Start();
virtual void Stop();
virtual bool IsRecording();
virtual void SetCurrentDevice(std::string deviceID);
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
#ifdef TGVOIP_WINDOWS_DESKTOP
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
#endif
private:
void ActuallySetCurrentDevice(std::string deviceID);
static DWORD WINAPI StartThread(void* arg);
void RunThread();
WAVEFORMATEX format;
bool isRecording;
HANDLE shutdownEvent;
HANDLE audioSamplesReadyEvent;
HANDLE streamSwitchEvent;
HANDLE thread;
IAudioClient* audioClient=NULL;
IAudioCaptureClient* captureClient=NULL;
#ifdef TGVOIP_WINDOWS_DESKTOP
IMMDeviceEnumerator* enumerator;
IAudioSessionControl* audioSessionControl;
IMMDevice* device;
#endif
unsigned char remainingData[10240];
size_t remainingDataLen;
bool isDefaultDevice;
ULONG refCount;
std::string streamChangeToDevice;
bool started;
#ifdef TGVOIP_WINDOWS_DESKTOP
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
//
// IUnknown
//
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
#endif
};
}
}
#endif //LIBTGVOIP_AUDIOINPUTWASAPI_H

View file

@ -0,0 +1,170 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "AudioInputWave.h"
#include "../../logging.h"
#include "../../VoIPController.h"
using namespace tgvoip::audio;
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res!=MMSYSERR_NOERROR){wchar_t _buf[1024]; waveInGetErrorTextW(res, _buf, 1024); LOGE(msg ": %ws (MMRESULT=0x%08X)", _buf, res); failed=true;}
AudioInputWave::AudioInputWave(std::string deviceID){
isRecording=false;
for(int i=0;i<4;i++){
ZeroMemory(&buffers[i], sizeof(WAVEHDR));
buffers[i].dwBufferLength=960*2;
buffers[i].lpData=(char*)malloc(960*2);
}
hWaveIn=NULL;
SetCurrentDevice(deviceID);
}
AudioInputWave::~AudioInputWave(){
for(int i=0;i<4;i++){
free(buffers[i].lpData);
}
waveInClose(hWaveIn);
}
void AudioInputWave::Start(){
if(!isRecording){
isRecording=true;
MMRESULT res;
for(int i=0;i<4;i++){
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInPrepareHeader failed");
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInAddBuffer failed");
}
res=waveInStart(hWaveIn);
CHECK_ERROR(res, "waveInStart failed");
}
}
void AudioInputWave::Stop(){
if(isRecording){
isRecording=false;
MMRESULT res=waveInStop(hWaveIn);
CHECK_ERROR(res, "waveInStop failed");
res=waveInReset(hWaveIn);
CHECK_ERROR(res, "waveInReset failed");
for(int i=0;i<4;i++){
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInUnprepareHeader failed");
}
}
}
void CALLBACK AudioInputWave::WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2){
if(uMsg==WIM_DATA){
((AudioInputWave*)dwInstance)->OnData((WAVEHDR*)dwParam1);
}
}
void AudioInputWave::OnData(WAVEHDR* hdr){
if(!isRecording)
return;
InvokeCallback((unsigned char*)hdr->lpData, hdr->dwBufferLength);
hdr->dwFlags&= ~WHDR_DONE;
MMRESULT res=waveInAddBuffer(hWaveIn, hdr, sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInAddBuffer failed");
}
void AudioInputWave::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
UINT num=waveInGetNumDevs();
WAVEINCAPSW caps;
char nameBuf[512];
for(UINT i=0;i<num;i++){
waveInGetDevCapsW(i, &caps, sizeof(caps));
AudioInputDevice dev;
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
dev.displayName=std::string(nameBuf);
dev.id=std::string(nameBuf);
devs.push_back(dev);
}
}
void AudioInputWave::SetCurrentDevice(std::string deviceID){
currentDevice=deviceID;
bool wasRecording=isRecording;
isRecording=false;
if(hWaveIn){
MMRESULT res;
if(isRecording){
res=waveInStop(hWaveIn);
CHECK_ERROR(res, "waveInStop failed");
res=waveInReset(hWaveIn);
CHECK_ERROR(res, "waveInReset failed");
for(int i=0;i<4;i++){
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInUnprepareHeader failed");
}
}
res=waveInClose(hWaveIn);
CHECK_ERROR(res, "waveInClose failed");
}
ZeroMemory(&format, sizeof(format));
format.cbSize=0;
format.wFormatTag=WAVE_FORMAT_PCM;
format.nSamplesPerSec=48000;
format.wBitsPerSample=16;
format.nChannels=1;
format.nBlockAlign=2;
LOGV("before open device %s", deviceID.c_str());
if(deviceID=="default"){
MMRESULT res=waveInOpen(&hWaveIn, WAVE_MAPPER, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
CHECK_ERROR(res, "waveInOpen failed");
}else{
UINT num=waveInGetNumDevs();
WAVEINCAPSW caps;
char nameBuf[512];
hWaveIn=NULL;
for(UINT i=0;i<num;i++){
waveInGetDevCapsW(i, &caps, sizeof(caps));
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
std::string name=std::string(nameBuf);
if(name==deviceID){
MMRESULT res=waveInOpen(&hWaveIn, i, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION | WAVE_MAPPED);
CHECK_ERROR(res, "waveInOpen failed");
LOGD("Opened device %s", nameBuf);
break;
}
}
if(!hWaveIn){
SetCurrentDevice("default");
return;
}
}
isRecording=wasRecording;
if(isRecording){
MMRESULT res;
for(int i=0;i<4;i++){
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInPrepareHeader failed");
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveInAddBuffer failed");
}
res=waveInStart(hWaveIn);
CHECK_ERROR(res, "waveInStart failed");
}
}

View file

@ -0,0 +1,40 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOINPUTWAVE_H
#define LIBTGVOIP_AUDIOINPUTWAVE_H
#include <windows.h>
#include <string>
#include <vector>
#include "../../audio/AudioInput.h"
namespace tgvoip{
namespace audio{
class AudioInputWave : public AudioInput{
public:
AudioInputWave(std::string deviceID);
virtual ~AudioInputWave();
virtual void Start();
virtual void Stop();
virtual void SetCurrentDevice(std::string deviceID);
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
private:
static void CALLBACK WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
void OnData(WAVEHDR* hdr);
HWAVEIN hWaveIn;
WAVEFORMATEX format;
WAVEHDR buffers[4];
bool isRecording;
};
}
}
#endif //LIBTGVOIP_AUDIOINPUTWAVE_H

View file

@ -0,0 +1,455 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include "AudioOutputWASAPI.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
template <class T> void SafeRelease(T **ppT)
{
if(*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
#ifdef TGVOIP_WINXP_COMPAT
#endif
using namespace tgvoip::audio;
AudioOutputWASAPI::AudioOutputWASAPI(std::string deviceID){
isPlaying=false;
remainingDataLen=0;
refCount=1;
HRESULT res;
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
CHECK_RES(res, "CoInitializeEx");
}
#ifdef TGVOIP_WINXP_COMPAT
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
#undef CreateEventEx
#define CreateEventEx __CreateEventExA
#endif
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
ZeroMemory(&format, sizeof(format));
format.wFormatTag=WAVE_FORMAT_PCM;
format.nChannels=1;
format.nSamplesPerSec=48000;
format.nBlockAlign=2;
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
format.wBitsPerSample=16;
#ifdef TGVOIP_WINDOWS_DESKTOP
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=enumerator->RegisterEndpointNotificationCallback(this);
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
audioSessionControl=NULL;
device=NULL;
#endif
audioClient=NULL;
renderClient=NULL;
thread=NULL;
SetCurrentDevice(deviceID);
}
AudioOutputWASAPI::~AudioOutputWASAPI(){
if(audioClient){
audioClient->Stop();
}
#ifdef TGVOIP_WINDOWS_DESKTOP
if(audioSessionControl){
audioSessionControl->UnregisterAudioSessionNotification(this);
}
#endif
SetEvent(shutdownEvent);
if(thread){
WaitForSingleObjectEx(thread, INFINITE, false);
CloseHandle(thread);
}
SafeRelease(&renderClient);
SafeRelease(&audioClient);
#ifdef TGVOIP_WINDOWS_DESKTOP
SafeRelease(&device);
SafeRelease(&audioSessionControl);
#endif
CloseHandle(shutdownEvent);
CloseHandle(audioSamplesReadyEvent);
CloseHandle(streamSwitchEvent);
#ifdef TGVOIP_WINDOWS_DESKTOP
if(enumerator)
enumerator->UnregisterEndpointNotificationCallback(this);
SafeRelease(&enumerator);
#endif
}
void AudioOutputWASAPI::Start(){
isPlaying=true;
if(!thread){
thread=CreateThread(NULL, 0, AudioOutputWASAPI::StartThread, this, 0, NULL);
}
}
void AudioOutputWASAPI::Stop(){
isPlaying=false;
}
bool AudioOutputWASAPI::IsPlaying(){
return isPlaying;
}
void AudioOutputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
#ifdef TGVOIP_WINDOWS_DESKTOP
HRESULT res;
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
SCHECK_RES(res, "CoInitializeEx");
}
IMMDeviceEnumerator *deviceEnumerator = NULL;
IMMDeviceCollection *deviceCollection = NULL;
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
SCHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
SCHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
SCHECK_RES(res, "GetDeviceItem");
wchar_t* devID;
res=device->GetId(&devID);
SCHECK_RES(res, "get device id");
IPropertyStore* propStore;
res=device->OpenPropertyStore(STGM_READ, &propStore);
SafeRelease(&device);
SCHECK_RES(res, "OpenPropertyStore");
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propStore);
AudioOutputDevice dev;
wchar_t actualFriendlyName[128];
if(friendlyName.vt==VT_LPWSTR){
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
}else{
wcscpy(actualFriendlyName, L"Unknown");
}
PropVariantClear(&friendlyName);
char buf[256];
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
dev.id=buf;
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
dev.displayName=buf;
devs.push_back(dev);
CoTaskMemFree(devID);
}
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
#endif
}
void AudioOutputWASAPI::SetCurrentDevice(std::string deviceID){
if(thread){
streamChangeToDevice=deviceID;
SetEvent(streamSwitchEvent);
}else{
ActuallySetCurrentDevice(deviceID);
}
}
void AudioOutputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
currentDevice=deviceID;
HRESULT res;
if(audioClient){
res=audioClient->Stop();
CHECK_RES(res, "audioClient->Stop");
}
#ifdef TGVOIP_WINDOWS_DESKTOP
if(audioSessionControl){
res=audioSessionControl->UnregisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
}
SafeRelease(&audioSessionControl);
#endif
SafeRelease(&renderClient);
SafeRelease(&audioClient);
#ifdef TGVOIP_WINDOWS_DESKTOP
SafeRelease(&device);
IMMDeviceCollection *deviceCollection = NULL;
if(deviceID=="default"){
isDefaultDevice=true;
res=enumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &device);
CHECK_RES(res, "GetDefaultAudioEndpoint");
}else{
isDefaultDevice=false;
res=enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
CHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
CHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
CHECK_RES(res, "GetDeviceItem");
wchar_t* _devID;
res=device->GetId(&_devID);
CHECK_RES(res, "get device id");
char devID[128];
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
CoTaskMemFree(_devID);
if(deviceID==devID){
this->device=device;
break;
}
}
}
if(deviceCollection)
SafeRelease(&deviceCollection);
if(!device){
LOGE("Didn't find playback device; failing");
failed=true;
return;
}
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
CHECK_RES(res, "device->Activate");
#else
std::wstring devID;
if (deviceID=="default"){
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId(Windows::Media::Devices::AudioDeviceRole::Communications);
if(defaultDevID==nullptr){
LOGE("Didn't find playback device; failing");
failed=true;
return;
}else{
isDefaultDevice=true;
devID=defaultDevID->Data();
}
}else{
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
wchar_t* wstr=new wchar_t[wchars_num];
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
devID=wstr;
}
HRESULT res1, res2;
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
CHECK_RES(res1, "activate1");
CHECK_RES(res2, "activate2");
AudioClientProperties properties={};
properties.cbSize=sizeof AudioClientProperties;
properties.eCategory=AudioCategory_Communications;
res = audioClient2->SetClientProperties(&properties);
CHECK_RES(res, "audioClient2->SetClientProperties");
audioClient = audioClient2;
#endif
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 60 * 10000, 0, &format, &guid);
CHECK_RES(res, "audioClient->Initialize");
uint32_t bufSize;
res = audioClient->GetBufferSize(&bufSize);
CHECK_RES(res, "audioClient->GetBufferSize");
LOGV("buffer size: %u", bufSize);
estimatedDelay=0;
REFERENCE_TIME latency, devicePeriod;
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
}
}
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
CHECK_RES(res, "audioClient->SetEventHandle");
res = audioClient->GetService(IID_PPV_ARGS(&renderClient));
CHECK_RES(res, "audioClient->GetService");
BYTE* data;
res = renderClient->GetBuffer(bufSize, &data);
CHECK_RES(res, "renderClient->GetBuffer");
res = renderClient->ReleaseBuffer(bufSize, AUDCLNT_BUFFERFLAGS_SILENT);
CHECK_RES(res, "renderClient->ReleaseBuffer");
#ifdef TGVOIP_WINDOWS_DESKTOP
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
res=audioSessionControl->RegisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
#endif
audioClient->Start();
LOGV("set current output device done");
}
DWORD WINAPI AudioOutputWASAPI::StartThread(void* arg) {
((AudioOutputWASAPI*)arg)->RunThread();
return 0;
}
void AudioOutputWASAPI::RunThread() {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
CHECK_RES(res, "CoInitializeEx in render thread");
uint32_t bufferSize;
res=audioClient->GetBufferSize(&bufferSize);
CHECK_RES(res, "audioClient->GetBufferSize");
uint64_t framesWritten=0;
bool running=true;
//double prevCallback=VoIPController::GetCurrentTime();
while(running){
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
LOGV("render thread shutting down");
running=false;
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
LOGV("stream switch");
ActuallySetCurrentDevice(streamChangeToDevice);
ResetEvent(streamSwitchEvent);
LOGV("stream switch done");
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
if(!audioClient)
continue;
BYTE* data;
uint32_t padding;
uint32_t framesAvailable;
res=audioClient->GetCurrentPadding(&padding);
CHECK_RES(res, "audioClient->GetCurrentPadding");
framesAvailable=bufferSize-padding;
res=renderClient->GetBuffer(framesAvailable, &data);
CHECK_RES(res, "renderClient->GetBuffer");
//double t=VoIPController::GetCurrentTime();
//LOGV("framesAvail: %u, time: %f, isPlaying: %d", framesAvailable, t-prevCallback, isPlaying);
//prevCallback=t;
size_t bytesAvailable=framesAvailable*2;
while(bytesAvailable>remainingDataLen){
if(isPlaying){
InvokeCallback(remainingData+remainingDataLen, 960*2);
}else{
memset(remainingData+remainingDataLen, 0, 960*2);
}
remainingDataLen+=960*2;
}
memcpy(data, remainingData, bytesAvailable);
if(remainingDataLen>bytesAvailable){
memmove(remainingData, remainingData+bytesAvailable, remainingDataLen-bytesAvailable);
}
remainingDataLen-=bytesAvailable;
res=renderClient->ReleaseBuffer(framesAvailable, 0);
CHECK_RES(res, "renderClient->ReleaseBuffer");
framesWritten+=framesAvailable;
}
}
}
#ifdef TGVOIP_WINDOWS_DESKTOP
HRESULT AudioOutputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
if(!isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
HRESULT AudioOutputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
if(flow==eRender && role==eCommunications && isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
ULONG AudioOutputWASAPI::AddRef(){
return InterlockedIncrement(&refCount);
}
ULONG AudioOutputWASAPI::Release(){
return InterlockedDecrement(&refCount);
}
HRESULT AudioOutputWASAPI::QueryInterface(REFIID iid, void** obj){
if(!obj){
return E_POINTER;
}
*obj=NULL;
if(iid==IID_IUnknown){
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
AddRef();
}else if(iid==__uuidof(IMMNotificationClient)){
*obj=static_cast<IMMNotificationClient*>(this);
AddRef();
}else if(iid==__uuidof(IAudioSessionEvents)){
*obj=static_cast<IAudioSessionEvents*>(this);
AddRef();
}else{
return E_NOINTERFACE;
}
return S_OK;
}
#endif

View file

@ -0,0 +1,103 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTWASAPI_H
#define LIBTGVOIP_AUDIOOUTPUTWASAPI_H
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
#define TGVOIP_WINDOWS_PHONE
#endif
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
#define TGVOIP_WINDOWS_DESKTOP
#endif
#include <windows.h>
#include <string>
#include <vector>
#pragma warning(push)
#pragma warning(disable : 4201)
#ifndef TGVOIP_WP_SILVERLIGHT
#include <mmdeviceapi.h>
#endif
#ifdef TGVOIP_WINDOWS_DESKTOP
#include <audiopolicy.h>
#include <functiondiscoverykeys.h>
#else
#include <audioclient.h>
#include "WindowsSandboxUtils.h"
#endif
#pragma warning(pop)
#include "../../audio/AudioOutput.h"
namespace tgvoip{
namespace audio{
#ifdef TGVOIP_WINDOWS_DESKTOP
class AudioOutputWASAPI : public AudioOutput, IMMNotificationClient, IAudioSessionEvents{
#else
class AudioOutputWASAPI : public AudioOutput{
#endif
public:
AudioOutputWASAPI(std::string deviceID);
virtual ~AudioOutputWASAPI();
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
virtual void SetCurrentDevice(std::string deviceID);
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
#ifdef TGVOIP_WINDOWS_DESKTOP
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
#endif
private:
void ActuallySetCurrentDevice(std::string deviceID);
static DWORD WINAPI StartThread(void* arg);
void RunThread();
WAVEFORMATEX format;
bool isPlaying;
HANDLE shutdownEvent;
HANDLE audioSamplesReadyEvent;
HANDLE streamSwitchEvent;
HANDLE thread;
IAudioClient* audioClient=NULL;
IAudioRenderClient* renderClient=NULL;
#ifdef TGVOIP_WINDOWS_DESKTOP
IMMDeviceEnumerator* enumerator;
IAudioSessionControl* audioSessionControl;
IMMDevice* device;
#endif
unsigned char remainingData[10240];
size_t remainingDataLen;
bool isDefaultDevice;
ULONG refCount;
std::string streamChangeToDevice;
#ifdef TGVOIP_WINDOWS_DESKTOP
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
//
// IUnknown
//
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
#endif
};
}
}
#endif //LIBTGVOIP_AUDIOOUTPUTWASAPI_H

View file

@ -0,0 +1,165 @@
//
// 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");
}
}
}

View file

@ -0,0 +1,40 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_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

View file

@ -0,0 +1,469 @@
#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 = IPv4Address(buf);
}
if (_ep->ipv6){
WideCharToMultiByte(CP_UTF8, 0, _ep->ipv6->Data(), -1, buf, sizeof(buf), NULL, NULL);
ep.v6address = IPv6Address(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;
}*/

View file

@ -0,0 +1,273 @@
#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;
};
}

View file

@ -0,0 +1,719 @@
//
// 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), lastRecvdV4(0), lastRecvdV6("::0"){
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);
tcpConnectedAddress=NULL;
if(protocol==PROTO_TCP)
timeout=10.0;
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
}
NetworkSocketWinsock::~NetworkSocketWinsock(){
if(tcpConnectedAddress)
delete tcpConnectedAddress;
if(pendingOutgoingPacket)
delete pendingOutgoingPacket;
}
void NetworkSocketWinsock::SetMaxPriority(){
}
void NetworkSocketWinsock::Send(NetworkPacket *packet){
if(!packet || (protocol==PROTO_UDP && !packet->address)){
LOGW("tried to send null packet");
return;
}
int res;
if(protocol==PROTO_UDP){
IPv4Address *v4addr=dynamic_cast<IPv4Address *>(packet->address);
if(isAtLeastVista){
sockaddr_in6 addr;
if(v4addr){
if(needUpdateNat64Prefix && !isV4Available && VoIPController::GetCurrentTime()>switchToV6at && switchToV6at!=0){
LOGV("Updating NAT64 prefix");
nat64Present=false;
addrinfo *addr0;
int res=getaddrinfo("ipv4only.arpa", NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo *addrPtr;
unsigned char *addr170=NULL;
unsigned char *addr171=NULL;
for(addrPtr=addr0; addrPtr; addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET6){
sockaddr_in6 *translatedAddr=(sockaddr_in6 *) addrPtr->ai_addr;
uint32_t v4part=*((uint32_t *) &translatedAddr->sin6_addr.s6_addr[12]);
if(v4part==0xAA0000C0 && !addr170){
addr170=translatedAddr->sin6_addr.s6_addr;
}
if(v4part==0xAB0000C0 && !addr171){
addr171=translatedAddr->sin6_addr.s6_addr;
}
char buf[INET6_ADDRSTRLEN];
//LOGV("Got translated address: %s", inet_ntop(AF_INET6, &translatedAddr->sin6_addr, buf, sizeof(buf)));
}
}
if(addr170 && addr171 && memcmp(addr170, addr171, 12)==0){
nat64Present=true;
memcpy(nat64Prefix, addr170, 12);
char buf[INET6_ADDRSTRLEN];
//LOGV("Found nat64 prefix from %s", inet_ntop(AF_INET6, addr170, buf, sizeof(buf)));
}else{
LOGV("Didn't find nat64");
}
freeaddrinfo(addr0);
}
needUpdateNat64Prefix=false;
}
memset(&addr, 0, sizeof(sockaddr_in6));
addr.sin6_family=AF_INET6;
*((uint32_t *) &addr.sin6_addr.s6_addr[12])=v4addr->GetAddress();
if(nat64Present)
memcpy(addr.sin6_addr.s6_addr, nat64Prefix, 12);
else
addr.sin6_addr.s6_addr[11]=addr.sin6_addr.s6_addr[10]=0xFF;
}else{
IPv6Address *v6addr=dynamic_cast<IPv6Address *>(packet->address);
assert(v6addr!=NULL);
memcpy(addr.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
}
addr.sin6_port=htons(packet->port);
res=sendto(fd, (const char*)packet->data, packet->length, 0, (const sockaddr *) &addr, sizeof(addr));
}else if(v4addr){
sockaddr_in addr;
addr.sin_addr.s_addr=v4addr->GetAddress();
addr.sin_port=htons(packet->port);
addr.sin_family=AF_INET;
res=sendto(fd, (const char*)packet->data, packet->length, 0, (const sockaddr*)&addr, sizeof(addr));
}
}else{
res=send(fd, (const char*)packet->data, packet->length, 0);
}
if(res==SOCKET_ERROR){
int error=WSAGetLastError();
if(error==WSAEWOULDBLOCK){
if(pendingOutgoingPacket){
LOGE("Got EAGAIN but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length);
pendingOutgoingPacket->CopyFrom(packet->data, 0, packet->length);
readyToSend=false;
}
}else{
LOGE("error sending: %d / %s", 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->length && protocol==PROTO_TCP){
if(pendingOutgoingPacket){
LOGE("send returned less than packet length but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length-res);
pendingOutgoingPacket->CopyFrom(packet->data+res, 0, packet->length-res);
readyToSend=false;
}
}
}
bool NetworkSocketWinsock::OnReadyToSend(){
if(pendingOutgoingPacket){
NetworkPacket pkt={0};
pkt.data=**pendingOutgoingPacket;
pkt.length=pendingOutgoingPacket->Length();
Send(&pkt);
delete pendingOutgoingPacket;
pendingOutgoingPacket=NULL;
return false;
}
readyToSend=true;
return true;
}
void NetworkSocketWinsock::Receive(NetworkPacket *packet){
if(protocol==PROTO_UDP){
if(isAtLeastVista){
int addrLen=sizeof(sockaddr_in6);
sockaddr_in6 srcAddr;
int res=recvfrom(fd, (char*)packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
if(res!=SOCKET_ERROR)
packet->length=(size_t) res;
else{
int error=WSAGetLastError();
LOGE("error receiving %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
packet->length=0;
return;
}
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
if(!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)){
isV4Available=true;
LOGI("Detected IPv4 connectivity, will not try IPv6");
}
if(IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12)==0)){
in_addr v4addr=*((in_addr *) &srcAddr.sin6_addr.s6_addr[12]);
lastRecvdV4=IPv4Address(v4addr.s_addr);
packet->address=&lastRecvdV4;
}else{
lastRecvdV6=IPv6Address(srcAddr.sin6_addr.s6_addr);
packet->address=&lastRecvdV6;
}
packet->port=ntohs(srcAddr.sin6_port);
}else{
int addrLen=sizeof(sockaddr_in);
sockaddr_in srcAddr;
int res=recvfrom(fd, (char*)packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
if(res!=SOCKET_ERROR)
packet->length=(size_t) res;
else{
LOGE("error receiving %d", WSAGetLastError());
packet->length=0;
return;
}
lastRecvdV4=IPv4Address(srcAddr.sin_addr.s_addr);
packet->address=&lastRecvdV4;
packet->port=ntohs(srcAddr.sin_port);
}
packet->protocol=PROTO_UDP;
}else if(protocol==PROTO_TCP){
int res=recv(fd, (char*)packet->data, packet->length, 0);
if(res==SOCKET_ERROR){
int error=WSAGetLastError();
LOGE("Error receiving from TCP socket: %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
failed=true;
}else{
packet->length=(size_t)res;
packet->address=tcpConnectedAddress;
packet->port=tcpConnectedPort;
packet->protocol=PROTO_TCP;
}
}
}
void NetworkSocketWinsock::Open(){
if(protocol==PROTO_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(IPv4Address *v4addr, IPv6Address *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=IPv4Address(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=IPv6Address(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=IPv4Address(ipv4->sin_addr.s_addr);
}
}else{
bestName=std::string(current->AdapterName);
*v4addr=IPv4Address(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=IPv6Address(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){
const IPv4Address* v4addr=dynamic_cast<const IPv4Address*>(address);
const IPv6Address* v6addr=dynamic_cast<const IPv6Address*>(address);
sockaddr_in v4;
sockaddr_in6 v6;
sockaddr* addr=NULL;
size_t addrLen=0;
if(v4addr){
v4.sin_family=AF_INET;
v4.sin_addr.s_addr=v4addr->GetAddress();
v4.sin_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v4);
addrLen=sizeof(v4);
}else if(v6addr){
v6.sin6_family=AF_INET6;
memcpy(v6.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
v6.sin6_flowinfo=0;
v6.sin6_scope_id=0;
v6.sin6_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v6);
addrLen=sizeof(v6);
}else{
LOGE("Unknown address type in TCP connect");
failed=true;
return;
}
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
if(fd==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=v4addr ? (NetworkAddress*)new IPv4Address(*v4addr) : (NetworkAddress*)new IPv6Address(*v6addr);
tcpConnectedPort=port;
LOGI("successfully connected to %s:%d", tcpConnectedAddress->ToString().c_str(), tcpConnectedPort);
}
IPv4Address *NetworkSocketWinsock::ResolveDomainName(std::string name){
addrinfo* addr0;
IPv4Address* ret=NULL;
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo* addrPtr;
for(addrPtr=addr0;addrPtr;addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET){
sockaddr_in* addr=(sockaddr_in*)addrPtr->ai_addr;
ret=new IPv4Address(addr->sin_addr.s_addr);
break;
}
}
freeaddrinfo(addr0);
}
return ret;
}
NetworkAddress *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;
}

View file

@ -0,0 +1,72 @@
//
// 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);
virtual void Receive(NetworkPacket* packet);
virtual void Open();
virtual void Close();
virtual std::string GetLocalInterfaceInfo(IPv4Address* v4addr, IPv6Address* v6addr);
virtual void OnActiveInterfaceChanged();
virtual uint16_t GetLocalPort();
virtual void Connect(const NetworkAddress* address, uint16_t port);
static std::string V4AddressToString(uint32_t address);
static std::string V6AddressToString(const unsigned char address[16]);
static uint32_t StringToV4Address(std::string address);
static void StringToV6Address(std::string address, unsigned char* out);
static IPv4Address* ResolveDomainName(std::string name);
static bool Select(std::vector<NetworkSocket*>& readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*>& errorFds, SocketSelectCanceller* canceller);
virtual NetworkAddress *GetConnectedAddress();
virtual uint16_t GetConnectedPort();
virtual void SetTimeouts(int sendTimeout, int recvTimeout);
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;
IPv4Address lastRecvdV4;
IPv6Address lastRecvdV6;
bool isAtLeastVista;
bool closing;
NetworkAddress* tcpConnectedAddress;
uint16_t tcpConnectedPort;
Buffer* pendingOutgoingPacket=NULL;
};
}
#endif //LIBTGVOIP_NETWORKSOCKETWINSOCK_H

View file

@ -0,0 +1,68 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#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

View file

@ -0,0 +1,42 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#ifndef LIBTGVOIP_WINDOWS_SANDBOX_UTILS
#define LIBTGVOIP_WINDOWS_SANDBOX_UTILS
#include <audioclient.h>
#include <windows.h>
#ifndef TGVOIP_WP_SILVERLIGHT
#include <mmdeviceapi.h>
#endif
#include <wrl.h>
#include <wrl/implements.h>
using namespace Microsoft::WRL;
namespace tgvoip {
#ifndef TGVOIP_WP_SILVERLIGHT
class ActivationHandler :
public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler >
{
public:
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
ActivationHandler(HANDLE _event);
HANDLE event;
IAudioClient2* client;
HRESULT actResult;
};
#endif
class WindowsSandboxUtils {
public:
static IAudioClient2* ActivateAudioDevice(const wchar_t* devID, HRESULT* callResult, HRESULT* actResult);
};
}
#endif // LIBTGVOIP_WINDOWS_SANDBOX_UTILS

View file

@ -0,0 +1,9 @@
#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);
}

View file

@ -0,0 +1,16 @@
#ifndef LIBTGVOIP_WINDOWS_SPECIFIC_H
#define LIBTGVOIP_WINDOWS_SPECIFIC_H
#include <string>
#include <Windows.h>
namespace tgvoip{
class WindowsSpecific{
public:
static std::string GetErrorMessage(DWORD code);
};
}
#endif // LIBTGVOIP_WINDOWS_SPECIFIC_H