Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "AudioInputAndroid.h"
|
||||
#include <stdio.h>
|
||||
#include "../../logging.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
jmethodID AudioInputAndroid::initMethod=NULL;
|
||||
jmethodID AudioInputAndroid::releaseMethod=NULL;
|
||||
jmethodID AudioInputAndroid::startMethod=NULL;
|
||||
jmethodID AudioInputAndroid::stopMethod=NULL;
|
||||
jmethodID AudioInputAndroid::getEnabledEffectsMaskMethod=NULL;
|
||||
jclass AudioInputAndroid::jniClass=NULL;
|
||||
|
||||
AudioInputAndroid::AudioInputAndroid(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
|
||||
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
|
||||
DEBUG_REF("AudioInputAndroid");
|
||||
javaObject=env->NewGlobalRef(obj);
|
||||
|
||||
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
|
||||
enabledEffects=(unsigned int)env->CallIntMethod(javaObject, getEnabledEffectsMaskMethod);
|
||||
});
|
||||
running=false;
|
||||
}
|
||||
|
||||
AudioInputAndroid::~AudioInputAndroid(){
|
||||
{
|
||||
MutexGuard guard(mutex);
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, releaseMethod);
|
||||
DEBUG_DELREF("AudioInputAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
javaObject=NULL;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputAndroid::Start(){
|
||||
MutexGuard guard(mutex);
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
failed=!env->CallBooleanMethod(javaObject, startMethod);
|
||||
});
|
||||
running=true;
|
||||
}
|
||||
|
||||
void AudioInputAndroid::Stop(){
|
||||
MutexGuard guard(mutex);
|
||||
running=false;
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, stopMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioInputAndroid::HandleCallback(JNIEnv* env, jobject buffer){
|
||||
if(!running)
|
||||
return;
|
||||
unsigned char* buf=(unsigned char*) env->GetDirectBufferAddress(buffer);
|
||||
size_t len=(size_t) env->GetDirectBufferCapacity(buffer);
|
||||
InvokeCallback(buf, len);
|
||||
}
|
||||
|
||||
unsigned int AudioInputAndroid::GetEnabledEffects(){
|
||||
return enabledEffects;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTANDROID_H
|
||||
#define LIBTGVOIP_AUDIOINPUTANDROID_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAndroid : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputAndroid();
|
||||
virtual ~AudioInputAndroid();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleCallback(JNIEnv* env, jobject buffer);
|
||||
unsigned int GetEnabledEffects();
|
||||
static jmethodID initMethod;
|
||||
static jmethodID releaseMethod;
|
||||
static jmethodID startMethod;
|
||||
static jmethodID stopMethod;
|
||||
static jmethodID getEnabledEffectsMaskMethod;
|
||||
static jclass jniClass;
|
||||
|
||||
static constexpr unsigned int EFFECT_AEC=1;
|
||||
static constexpr unsigned int EFFECT_NS=2;
|
||||
|
||||
private:
|
||||
jobject javaObject;
|
||||
bool running;
|
||||
Mutex mutex;
|
||||
unsigned int enabledEffects=0;
|
||||
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTANDROID_H
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "AudioInputOpenSLES.h"
|
||||
#include "../../logging.h"
|
||||
#include "OpenSLEngineWrapper.h"
|
||||
|
||||
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
unsigned int AudioInputOpenSLES::nativeBufferSize;
|
||||
|
||||
AudioInputOpenSLES::AudioInputOpenSLES(){
|
||||
slEngine=OpenSLEngineWrapper::CreateEngine();
|
||||
|
||||
LOGI("Native buffer size is %u samples", nativeBufferSize);
|
||||
if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
|
||||
LOGE("20ms is not divisible by native buffer size!!");
|
||||
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
|
||||
LOGE("native buffer size is not multiple of 20ms!!");
|
||||
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
|
||||
}
|
||||
if(nativeBufferSize==BUFFER_SIZE)
|
||||
nativeBufferSize*=2;
|
||||
LOGI("Adjusted native buffer size is %u", nativeBufferSize);
|
||||
|
||||
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
|
||||
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
|
||||
slRecorderObj=NULL;
|
||||
}
|
||||
|
||||
AudioInputOpenSLES::~AudioInputOpenSLES(){
|
||||
//Stop();
|
||||
(*slBufferQueue)->Clear(slBufferQueue);
|
||||
(*slRecorderObj)->Destroy(slRecorderObj);
|
||||
slRecorderObj=NULL;
|
||||
slRecorder=NULL;
|
||||
slBufferQueue=NULL;
|
||||
slEngine=NULL;
|
||||
OpenSLEngineWrapper::DestroyEngine();
|
||||
free(buffer);
|
||||
buffer=NULL;
|
||||
free(nativeBuffer);
|
||||
nativeBuffer=NULL;
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
|
||||
((AudioInputOpenSLES*)context)->HandleSLCallback();
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
|
||||
assert(slRecorderObj==NULL);
|
||||
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
|
||||
SL_IODEVICE_AUDIOINPUT,
|
||||
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
|
||||
SLDataSource audioSrc = {&loc_dev, NULL};
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq =
|
||||
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
|
||||
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
|
||||
SLDataSink audioSnk = {&loc_bq, &format_pcm};
|
||||
|
||||
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
SLresult result = (*slEngine)->CreateAudioRecorder(slEngine, &slRecorderObj, &audioSrc, &audioSnk, 2, id, req);
|
||||
CHECK_SL_ERROR(result, "Error creating recorder");
|
||||
|
||||
SLAndroidConfigurationItf recorderConfig;
|
||||
result = (*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDCONFIGURATION, &recorderConfig);
|
||||
SLint32 streamType = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
|
||||
result = (*recorderConfig)->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &streamType, sizeof(SLint32));
|
||||
|
||||
result=(*slRecorderObj)->Realize(slRecorderObj, SL_BOOLEAN_FALSE);
|
||||
CHECK_SL_ERROR(result, "Error realizing recorder");
|
||||
|
||||
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_RECORD, &slRecorder);
|
||||
CHECK_SL_ERROR(result, "Error getting recorder interface");
|
||||
|
||||
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
|
||||
CHECK_SL_ERROR(result, "Error getting buffer queue");
|
||||
|
||||
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioInputOpenSLES::BufferCallback, this);
|
||||
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::Start(){
|
||||
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_RECORDING);
|
||||
CHECK_SL_ERROR(result, "Error starting record");
|
||||
}
|
||||
|
||||
void AudioInputOpenSLES::Stop(){
|
||||
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_STOPPED);
|
||||
CHECK_SL_ERROR(result, "Error stopping record");
|
||||
}
|
||||
|
||||
|
||||
void AudioInputOpenSLES::HandleSLCallback(){
|
||||
//SLmillisecond pMsec = 0;
|
||||
//(*slRecorder)->GetPosition(slRecorder, &pMsec);
|
||||
//LOGI("Callback! pos=%lu", pMsec);
|
||||
//InvokeCallback((unsigned char*)buffer, BUFFER_SIZE*sizeof(int16_t));
|
||||
//fwrite(nativeBuffer, 1, nativeBufferSize*2, test);
|
||||
|
||||
if(nativeBufferSize==BUFFER_SIZE){
|
||||
//LOGV("nativeBufferSize==BUFFER_SIZE");
|
||||
InvokeCallback((unsigned char *) nativeBuffer, BUFFER_SIZE*sizeof(int16_t));
|
||||
}else if(nativeBufferSize<BUFFER_SIZE){
|
||||
//LOGV("nativeBufferSize<BUFFER_SIZE");
|
||||
if(positionInBuffer>=BUFFER_SIZE){
|
||||
InvokeCallback((unsigned char *) buffer, BUFFER_SIZE*sizeof(int16_t));
|
||||
positionInBuffer=0;
|
||||
}
|
||||
memcpy(((unsigned char*)buffer)+positionInBuffer*2, nativeBuffer, (size_t)nativeBufferSize*2);
|
||||
positionInBuffer+=nativeBufferSize;
|
||||
}else if(nativeBufferSize>BUFFER_SIZE){
|
||||
//LOGV("nativeBufferSize>BUFFER_SIZE");
|
||||
for(unsigned int offset=0;offset<nativeBufferSize;offset+=BUFFER_SIZE){
|
||||
InvokeCallback(((unsigned char *) nativeBuffer)+offset*2, BUFFER_SIZE*sizeof(int16_t));
|
||||
}
|
||||
}
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTOPENSLES_H
|
||||
#define LIBTGVOIP_AUDIOINPUTOPENSLES_H
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputOpenSLES : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputOpenSLES();
|
||||
virtual ~AudioInputOpenSLES();
|
||||
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
|
||||
static unsigned int nativeBufferSize;
|
||||
|
||||
private:
|
||||
static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
|
||||
void HandleSLCallback();
|
||||
SLEngineItf slEngine;
|
||||
SLObjectItf slRecorderObj;
|
||||
SLRecordItf slRecorder;
|
||||
SLAndroidSimpleBufferQueueItf slBufferQueue;
|
||||
int16_t* buffer;
|
||||
int16_t* nativeBuffer;
|
||||
size_t positionInBuffer;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTOPENSLES_H
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "AudioOutputAndroid.h"
|
||||
#include <stdio.h>
|
||||
#include "../../logging.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
jmethodID AudioOutputAndroid::initMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::releaseMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::startMethod=NULL;
|
||||
jmethodID AudioOutputAndroid::stopMethod=NULL;
|
||||
jclass AudioOutputAndroid::jniClass=NULL;
|
||||
|
||||
AudioOutputAndroid::AudioOutputAndroid(){
|
||||
JNIEnv* env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
|
||||
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
|
||||
DEBUG_REF("AudioOutputAndroid");
|
||||
javaObject=env->NewGlobalRef(obj);
|
||||
|
||||
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
running=false;
|
||||
}
|
||||
|
||||
AudioOutputAndroid::~AudioOutputAndroid(){
|
||||
JNIEnv* env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(javaObject, releaseMethod);
|
||||
DEBUG_DELREF("AudioOutputAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
javaObject=NULL;
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAndroid::Start(){
|
||||
JNIEnv* env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(javaObject, startMethod);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
running=true;
|
||||
}
|
||||
|
||||
void AudioOutputAndroid::Stop(){
|
||||
running=false;
|
||||
JNIEnv* env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(javaObject, stopMethod);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAndroid::HandleCallback(JNIEnv* env, jbyteArray buffer){
|
||||
if(!running)
|
||||
return;
|
||||
unsigned char* buf=(unsigned char*) env->GetByteArrayElements(buffer, NULL);
|
||||
size_t len=(size_t) env->GetArrayLength(buffer);
|
||||
InvokeCallback(buf, len);
|
||||
env->ReleaseByteArrayElements(buffer, (jbyte *) buf, 0);
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputAndroid::IsPlaying(){
|
||||
return running;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTANDROID_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTANDROID_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputAndroid : public AudioOutput{
|
||||
|
||||
public:
|
||||
|
||||
AudioOutputAndroid();
|
||||
virtual ~AudioOutputAndroid();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying() override;
|
||||
void HandleCallback(JNIEnv* env, jbyteArray buffer);
|
||||
static jmethodID initMethod;
|
||||
static jmethodID releaseMethod;
|
||||
static jmethodID startMethod;
|
||||
static jmethodID stopMethod;
|
||||
static jclass jniClass;
|
||||
|
||||
private:
|
||||
jobject javaObject;
|
||||
bool running;
|
||||
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "AudioOutputOpenSLES.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "OpenSLEngineWrapper.h"
|
||||
#include "AudioInputAndroid.h"
|
||||
|
||||
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); failed=true; return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
unsigned int AudioOutputOpenSLES::nativeBufferSize;
|
||||
|
||||
AudioOutputOpenSLES::AudioOutputOpenSLES(){
|
||||
SLresult result;
|
||||
slEngine=OpenSLEngineWrapper::CreateEngine();
|
||||
|
||||
const SLInterfaceID pOutputMixIDs[] = {};
|
||||
const SLboolean pOutputMixRequired[] = {};
|
||||
result = (*slEngine)->CreateOutputMix(slEngine, &slOutputMixObj, 0, pOutputMixIDs, pOutputMixRequired);
|
||||
CHECK_SL_ERROR(result, "Error creating output mix");
|
||||
|
||||
result = (*slOutputMixObj)->Realize(slOutputMixObj, SL_BOOLEAN_FALSE);
|
||||
CHECK_SL_ERROR(result, "Error realizing output mix");
|
||||
|
||||
LOGI("Native buffer size is %u samples", nativeBufferSize);
|
||||
/*if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
|
||||
LOGE("20ms is not divisible by native buffer size!!");
|
||||
nativeBufferSize=BUFFER_SIZE;
|
||||
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
|
||||
LOGE("native buffer size is not multiple of 20ms!!");
|
||||
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
|
||||
}
|
||||
LOGI("Adjusted native buffer size is %u", nativeBufferSize);*/
|
||||
|
||||
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
|
||||
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
|
||||
slPlayerObj=NULL;
|
||||
remainingDataSize=0;
|
||||
}
|
||||
|
||||
AudioOutputOpenSLES::~AudioOutputOpenSLES(){
|
||||
if(!stopped)
|
||||
Stop();
|
||||
(*slBufferQueue)->Clear(slBufferQueue);
|
||||
LOGV("destroy slPlayerObj");
|
||||
(*slPlayerObj)->Destroy(slPlayerObj);
|
||||
LOGV("destroy slOutputMixObj");
|
||||
(*slOutputMixObj)->Destroy(slOutputMixObj);
|
||||
OpenSLEngineWrapper::DestroyEngine();
|
||||
free(buffer);
|
||||
free(nativeBuffer);
|
||||
}
|
||||
|
||||
|
||||
void AudioOutputOpenSLES::SetNativeBufferSize(unsigned int size){
|
||||
AudioOutputOpenSLES::nativeBufferSize=size;
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
|
||||
((AudioOutputOpenSLES*)context)->HandleSLCallback();
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
|
||||
assert(slPlayerObj==NULL);
|
||||
SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue =
|
||||
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
|
||||
SLDataFormat_PCM formatPCM = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
|
||||
SLDataSource audioSrc = {&locatorBufferQueue, &formatPCM};
|
||||
SLDataLocator_OutputMix locatorOutMix = {SL_DATALOCATOR_OUTPUTMIX, slOutputMixObj};
|
||||
SLDataSink audioSnk = {&locatorOutMix, NULL};
|
||||
|
||||
const SLInterfaceID id[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
SLresult result = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObj, &audioSrc, &audioSnk, 2, id, req);
|
||||
CHECK_SL_ERROR(result, "Error creating player");
|
||||
|
||||
|
||||
SLAndroidConfigurationItf playerConfig;
|
||||
result = (*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
|
||||
SLint32 streamType = SL_ANDROID_STREAM_VOICE;
|
||||
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
|
||||
|
||||
|
||||
result=(*slPlayerObj)->Realize(slPlayerObj, SL_BOOLEAN_FALSE);
|
||||
CHECK_SL_ERROR(result, "Error realizing player");
|
||||
|
||||
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_PLAY, &slPlayer);
|
||||
CHECK_SL_ERROR(result, "Error getting player interface");
|
||||
|
||||
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
|
||||
CHECK_SL_ERROR(result, "Error getting buffer queue");
|
||||
|
||||
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioOutputOpenSLES::BufferCallback, this);
|
||||
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSLES::IsPhone(){
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::EnableLoudspeaker(bool enabled){
|
||||
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::Start(){
|
||||
stopped=false;
|
||||
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PLAYING);
|
||||
CHECK_SL_ERROR(result, "Error starting player");
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::Stop(){
|
||||
stopped=true;
|
||||
LOGV("Stopping OpenSL output");
|
||||
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PAUSED);
|
||||
CHECK_SL_ERROR(result, "Error starting player");
|
||||
}
|
||||
|
||||
void AudioOutputOpenSLES::HandleSLCallback(){
|
||||
/*if(stopped){
|
||||
//LOGV("left HandleSLCallback early");
|
||||
return;
|
||||
}*/
|
||||
//LOGV("before InvokeCallback");
|
||||
if(!stopped){
|
||||
while(remainingDataSize<nativeBufferSize*2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(nativeBuffer, remainingData, nativeBufferSize*2);
|
||||
remainingDataSize-=nativeBufferSize*2;
|
||||
if(remainingDataSize>0)
|
||||
memmove(remainingData, remainingData+nativeBufferSize*2, remainingDataSize);
|
||||
//InvokeCallback((unsigned char *) nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
}else{
|
||||
memset(nativeBuffer, 0, nativeBufferSize*2);
|
||||
}
|
||||
|
||||
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
|
||||
//LOGV("left HandleSLCallback");
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputOpenSLES::IsPlaying(){
|
||||
if(slPlayer){
|
||||
uint32_t state;
|
||||
(*slPlayer)->GetPlayState(slPlayer, &state);
|
||||
return state==SL_PLAYSTATE_PLAYING;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
float AudioOutputOpenSLES::GetLevel(){
|
||||
return 0; // we don't use this anyway
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputOpenSLES : public AudioOutput{
|
||||
public:
|
||||
AudioOutputOpenSLES();
|
||||
virtual ~AudioOutputOpenSLES();
|
||||
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
|
||||
virtual bool IsPhone();
|
||||
virtual void EnableLoudspeaker(bool enabled);
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual float GetLevel();
|
||||
|
||||
static void SetNativeBufferSize(unsigned int size);
|
||||
static unsigned int nativeBufferSize;
|
||||
|
||||
private:
|
||||
static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
|
||||
void HandleSLCallback();
|
||||
SLEngineItf slEngine;
|
||||
SLObjectItf slPlayerObj;
|
||||
SLObjectItf slOutputMixObj;
|
||||
SLPlayItf slPlayer;
|
||||
SLAndroidSimpleBufferQueueItf slBufferQueue;
|
||||
int16_t* buffer;
|
||||
int16_t* nativeBuffer;
|
||||
bool stopped;
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H
|
||||
70
TMessagesProj/jni/voip/libtgvoip/os/android/JNIUtilities.h
Normal file
70
TMessagesProj/jni/voip/libtgvoip/os/android/JNIUtilities.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Created by Grishka on 15.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_JNIUTILITIES_H
|
||||
#define LIBTGVOIP_JNIUTILITIES_H
|
||||
|
||||
#include <functional>
|
||||
#include <jni.h>
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include "../../Buffers.h"
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
namespace tgvoip{
|
||||
namespace jni{
|
||||
|
||||
inline JNIEnv *GetEnv() {
|
||||
JNIEnv *env = nullptr;
|
||||
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
|
||||
return env;
|
||||
}
|
||||
|
||||
inline void DoWithJNI(std::function<void(JNIEnv*)> f){
|
||||
JNIEnv *env=GetEnv();
|
||||
bool didAttach=false;
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
f(env);
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
inline void AttachAndCallVoidMethod(jmethodID method, jobject obj, ...){
|
||||
if(!method || !obj)
|
||||
return;
|
||||
va_list va;
|
||||
va_start(va, obj);
|
||||
DoWithJNI([&va, method, obj](JNIEnv* env){
|
||||
env->CallVoidMethodV(obj, method, va);
|
||||
});
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
inline std::string JavaStringToStdString(JNIEnv* env, jstring jstr){
|
||||
if(!jstr)
|
||||
return "";
|
||||
const char* jchars=env->GetStringUTFChars(jstr, NULL);
|
||||
std::string str(jchars);
|
||||
env->ReleaseStringUTFChars(jstr, jchars);
|
||||
return str;
|
||||
}
|
||||
|
||||
inline jbyteArray BufferToByteArray(JNIEnv* env, Buffer& buf){
|
||||
jbyteArray arr=env->NewByteArray((jsize)buf.Length());
|
||||
jbyte* elements=env->GetByteArrayElements(arr, NULL);
|
||||
memcpy(elements, *buf, buf.Length());
|
||||
env->ReleaseByteArrayElements(arr, elements, 0);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_JNIUTILITIES_H
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <cstddef>
|
||||
#include "OpenSLEngineWrapper.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return NULL; }
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
|
||||
SLObjectItf OpenSLEngineWrapper::sharedEngineObj=NULL;
|
||||
SLEngineItf OpenSLEngineWrapper::sharedEngine=NULL;
|
||||
int OpenSLEngineWrapper::count=0;
|
||||
|
||||
void OpenSLEngineWrapper::DestroyEngine(){
|
||||
count--;
|
||||
LOGI("release: engine instance count %d", count);
|
||||
if(count==0){
|
||||
(*sharedEngineObj)->Destroy(sharedEngineObj);
|
||||
sharedEngineObj=NULL;
|
||||
sharedEngine=NULL;
|
||||
}
|
||||
LOGI("after release");
|
||||
}
|
||||
|
||||
SLEngineItf OpenSLEngineWrapper::CreateEngine(){
|
||||
count++;
|
||||
if(sharedEngine)
|
||||
return sharedEngine;
|
||||
const SLInterfaceID pIDs[1] = {SL_IID_ENGINE};
|
||||
const SLboolean pIDsRequired[1] = {SL_BOOLEAN_TRUE};
|
||||
SLresult result = slCreateEngine(&sharedEngineObj, 0, NULL, 1, pIDs, pIDsRequired);
|
||||
CHECK_SL_ERROR(result, "Error creating engine");
|
||||
|
||||
result=(*sharedEngineObj)->Realize(sharedEngineObj, SL_BOOLEAN_FALSE);
|
||||
CHECK_SL_ERROR(result, "Error realizing engine");
|
||||
|
||||
result = (*sharedEngineObj)->GetInterface(sharedEngineObj, SL_IID_ENGINE, &sharedEngine);
|
||||
CHECK_SL_ERROR(result, "Error getting engine interface");
|
||||
return sharedEngine;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_OPENSLENGINEWRAPPER_H
|
||||
#define LIBTGVOIP_OPENSLENGINEWRAPPER_H
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class OpenSLEngineWrapper{
|
||||
public:
|
||||
static SLEngineItf CreateEngine();
|
||||
static void DestroyEngine();
|
||||
|
||||
private:
|
||||
static SLObjectItf sharedEngineObj;
|
||||
static SLEngineItf sharedEngine;
|
||||
static int count;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_OPENSLENGINEWRAPPER_H
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#include "VideoRendererAndroid.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
jmethodID VideoRendererAndroid::resetMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::decodeAndDisplayMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::setStreamEnabledMethod=NULL;
|
||||
jmethodID VideoRendererAndroid::setRotationMethod=NULL;
|
||||
std::vector<uint32_t> VideoRendererAndroid::availableDecoders;
|
||||
int VideoRendererAndroid::maxResolution;
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
VideoRendererAndroid::VideoRendererAndroid(jobject jobj) : queue(50){
|
||||
this->jobj=jobj;
|
||||
}
|
||||
|
||||
VideoRendererAndroid::~VideoRendererAndroid(){
|
||||
running=false;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::Shutdown
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
thread->Join();
|
||||
delete thread;
|
||||
/*decoderThread.Post([this]{
|
||||
decoderEnv->DeleteGlobalRef(jobj);
|
||||
});*/
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer> &_csd){
|
||||
csd.clear();
|
||||
for(Buffer& b:_csd){
|
||||
csd.push_back(Buffer::CopyOf(b));
|
||||
}
|
||||
this->codec=codec;
|
||||
this->width=width;
|
||||
this->height=height;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::ResetDecoder
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
Request req2{
|
||||
Buffer(0),
|
||||
Request::Type::UpdateStreamState
|
||||
};
|
||||
queue.Put(std::move(req2));
|
||||
|
||||
if(!thread){
|
||||
thread=new Thread(std::bind(&VideoRendererAndroid::RunThread, this));
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::DecodeAndDisplay(Buffer frame, uint32_t pts){
|
||||
/*decoderThread.Post(std::bind([this](Buffer frame){
|
||||
}, std::move(frame)));*/
|
||||
//LOGV("2 before decode %u", (unsigned int)frame.Length());
|
||||
Request req{
|
||||
std::move(frame),
|
||||
Request::Type::DecodeFrame
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::RunThread(){
|
||||
JNIEnv* env;
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
|
||||
constexpr size_t bufferSize=200*1024;
|
||||
unsigned char* buf=reinterpret_cast<unsigned char*>(malloc(bufferSize));
|
||||
jobject jbuf=env->NewDirectByteBuffer(buf, bufferSize);
|
||||
uint16_t lastSetRotation=0;
|
||||
|
||||
while(running){
|
||||
//LOGV("before get from queue");
|
||||
Request request=std::move(queue.GetBlocking());
|
||||
//LOGV("1 before decode %u", (unsigned int)request.Length());
|
||||
if(request.type==Request::Type::Shutdown){
|
||||
LOGI("Shutting down video decoder thread");
|
||||
break;
|
||||
}else if(request.type==Request::Type::DecodeFrame){
|
||||
if(request.buffer.Length()>bufferSize){
|
||||
LOGE("Frame data is too long (%u, max %u)", (int) request.buffer.Length(), (int) bufferSize);
|
||||
}else{
|
||||
if(lastSetRotation!=rotation){
|
||||
lastSetRotation=rotation;
|
||||
env->CallVoidMethod(jobj, setRotationMethod, (jint)rotation);
|
||||
}
|
||||
memcpy(buf, *request.buffer, request.buffer.Length());
|
||||
env->CallVoidMethod(jobj, decodeAndDisplayMethod, jbuf, (jint) request.buffer.Length(), 0);
|
||||
}
|
||||
}else if(request.type==Request::Type::ResetDecoder){
|
||||
jobjectArray jcsd=NULL;
|
||||
if(!csd.empty()){
|
||||
jcsd=env->NewObjectArray((jsize)csd.size(), env->FindClass("[B"), NULL);
|
||||
jsize i=0;
|
||||
for(Buffer& b:csd){
|
||||
env->SetObjectArrayElement(jcsd, i, jni::BufferToByteArray(env, b));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
std::string codecStr="";
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecStr="video/avc";
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecStr="video/hevc";
|
||||
break;
|
||||
case CODEC_VP8:
|
||||
codecStr="video/x-vnd.on2.vp8";
|
||||
break;
|
||||
case CODEC_VP9:
|
||||
codecStr="video/x-vnd.on2.vp9";
|
||||
break;
|
||||
}
|
||||
env->CallVoidMethod(jobj, resetMethod, env->NewStringUTF(codecStr.c_str()), (jint)width, (jint)height, jcsd);
|
||||
}else if(request.type==Request::Type::UpdateStreamState){
|
||||
env->CallVoidMethod(jobj, setStreamEnabledMethod, streamEnabled);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
sharedJVM->DetachCurrentThread();
|
||||
LOGI("==== decoder thread exiting ====");
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::SetStreamEnabled(bool enabled){
|
||||
LOGI("Video stream state: %d", enabled);
|
||||
streamEnabled=enabled;
|
||||
Request req{
|
||||
Buffer(0),
|
||||
Request::Type::UpdateStreamState
|
||||
};
|
||||
queue.Put(std::move(req));
|
||||
}
|
||||
|
||||
void VideoRendererAndroid::SetRotation(uint16_t rotation){
|
||||
this->rotation=rotation;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
#define LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
|
||||
#include "../../video/VideoRenderer.h"
|
||||
#include "../../MessageThread.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include "../../BlockingQueue.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoRendererAndroid : public VideoRenderer{
|
||||
public:
|
||||
VideoRendererAndroid(jobject jobj);
|
||||
virtual ~VideoRendererAndroid();
|
||||
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
|
||||
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
|
||||
virtual void SetStreamEnabled(bool enabled) override;
|
||||
virtual void SetRotation(uint16_t rotation) override;
|
||||
|
||||
static jmethodID resetMethod;
|
||||
static jmethodID decodeAndDisplayMethod;
|
||||
static jmethodID setStreamEnabledMethod;
|
||||
static jmethodID setRotationMethod;
|
||||
static std::vector<uint32_t> availableDecoders;
|
||||
static int maxResolution;
|
||||
private:
|
||||
struct Request{
|
||||
enum Type{
|
||||
DecodeFrame,
|
||||
ResetDecoder,
|
||||
UpdateStreamState,
|
||||
Shutdown
|
||||
};
|
||||
|
||||
Buffer buffer;
|
||||
Type type;
|
||||
};
|
||||
void RunThread();
|
||||
Thread* thread=NULL;
|
||||
bool running=true;
|
||||
BlockingQueue<Request> queue;
|
||||
std::vector<Buffer> csd;
|
||||
int width;
|
||||
int height;
|
||||
bool streamEnabled=true;
|
||||
uint32_t codec;
|
||||
uint16_t rotation=0;
|
||||
jobject jobj;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_VIDEORENDERERANDROID_H
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#include "VideoSourceAndroid.h"
|
||||
#include "JNIUtilities.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "tgnet/FileLog.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
extern JavaVM* sharedJVM;
|
||||
|
||||
std::vector<uint32_t> VideoSourceAndroid::availableEncoders;
|
||||
|
||||
VideoSourceAndroid::VideoSourceAndroid(jobject jobj) : javaObject(jobj){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
jclass cls=env->GetObjectClass(javaObject);
|
||||
startMethod=env->GetMethodID(cls, "start", "()V");
|
||||
stopMethod=env->GetMethodID(cls, "stop", "()V");
|
||||
prepareEncoderMethod=env->GetMethodID(cls, "prepareEncoder", "(Ljava/lang/String;I)V");
|
||||
requestKeyFrameMethod=env->GetMethodID(cls, "requestKeyFrame", "()V");
|
||||
setBitrateMethod=env->GetMethodID(cls, "setBitrate", "(I)V");
|
||||
});
|
||||
}
|
||||
|
||||
VideoSourceAndroid::~VideoSourceAndroid(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
DEBUG_DELREF("VideoSourceAndroid");
|
||||
env->DeleteGlobalRef(javaObject);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Start(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, startMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Stop(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, stopMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SendFrame(Buffer frame, uint32_t flags){
|
||||
callback(frame, flags, rotation);
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height){
|
||||
LOGD("Video stream parameters: %d x %d", width, height);
|
||||
this->width=width;
|
||||
this->height=height;
|
||||
this->csd=std::move(csd);
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::Reset(uint32_t codec, int maxResolution){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
std::string codecStr="";
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecStr="video/avc";
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecStr="video/hevc";
|
||||
break;
|
||||
case CODEC_VP8:
|
||||
codecStr="video/x-vnd.on2.vp8";
|
||||
break;
|
||||
case CODEC_VP9:
|
||||
codecStr="video/x-vnd.on2.vp9";
|
||||
break;
|
||||
}
|
||||
env->CallVoidMethod(javaObject, prepareEncoderMethod, env->NewStringUTF(codecStr.c_str()), maxResolution);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::RequestKeyFrame(){
|
||||
jni::DoWithJNI([this](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, requestKeyFrameMethod);
|
||||
});
|
||||
}
|
||||
|
||||
void VideoSourceAndroid::SetBitrate(uint32_t bitrate){
|
||||
jni::DoWithJNI([&](JNIEnv* env){
|
||||
env->CallVoidMethod(javaObject, setBitrateMethod, (jint)bitrate);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Created by Grishka on 12.08.2018.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEOSOURCEANDROID_H
|
||||
#define LIBTGVOIP_VIDEOSOURCEANDROID_H
|
||||
|
||||
#include "../../video/VideoSource.h"
|
||||
#include "../../Buffers.h"
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoSourceAndroid : public VideoSource{
|
||||
public:
|
||||
VideoSourceAndroid(jobject jobj);
|
||||
virtual ~VideoSourceAndroid();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Reset(uint32_t codec, int maxResolution) override;
|
||||
void SendFrame(Buffer frame, uint32_t flags);
|
||||
void SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height);
|
||||
virtual void RequestKeyFrame() override;
|
||||
virtual void SetBitrate(uint32_t bitrate) override;
|
||||
|
||||
static std::vector<uint32_t> availableEncoders;
|
||||
private:
|
||||
jobject javaObject;
|
||||
jmethodID prepareEncoderMethod;
|
||||
jmethodID startMethod;
|
||||
jmethodID stopMethod;
|
||||
jmethodID requestKeyFrameMethod;
|
||||
jmethodID setBitrateMethod;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif //LIBTGVOIP_VIDEOSOURCEANDROID_H
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnit::AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioInputAudioUnit::~AudioInputAudioUnit(){
|
||||
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Start(){
|
||||
isRecording=true;
|
||||
io->EnableInput(true);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Stop(){
|
||||
isRecording=false;
|
||||
io->EnableInput(false);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
#if TARGET_OS_OSX
|
||||
assert(remainingDataSize+buf.mDataByteSize/2<10240);
|
||||
float* src=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* dst=reinterpret_cast<int16_t*>(remainingData+remainingDataSize);
|
||||
for(int j=0;j<buf.mDataByteSize/4;j++){
|
||||
dst[j]=(int16_t)(src[j]*INT16_MAX);
|
||||
}
|
||||
remainingDataSize+=buf.mDataByteSize/2;
|
||||
#else
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
remainingDataSize+=buf.mDataByteSize;
|
||||
#endif
|
||||
while(remainingDataSize>=BUFFER_SIZE*2){
|
||||
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
|
||||
remainingDataSize-=BUFFER_SIZE*2;
|
||||
if(remainingDataSize>0){
|
||||
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioInputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioInputAudioUnit : public AudioInput{
|
||||
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioInputAudioUnit);
|
||||
AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioInputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "AudioInputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../audio/Resampler.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("input: " msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnitLegacy::AudioInputAudioUnitLegacy(std::string deviceID) : AudioInput(deviceID){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
|
||||
inBufferList.mBuffers[0].mData=malloc(10240);
|
||||
inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioInputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
status=AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing unit");
|
||||
}
|
||||
|
||||
AudioInputAudioUnitLegacy::~AudioInputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Start(){
|
||||
isRecording=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Stop(){
|
||||
isRecording=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioInputAudioUnitLegacy* input=(AudioInputAudioUnitLegacy*) inRefCon;
|
||||
input->inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
AudioUnitRender(input->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &input->inBufferList);
|
||||
input->HandleBufferCallback(&input->inBufferList);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
size_t len=buf.mDataByteSize;
|
||||
if(hardwareSampleRate!=48000){
|
||||
len=tgvoip::audio::Resampler::Convert((int16_t*)buf.mData, (int16_t*)(remainingData+remainingDataSize), buf.mDataByteSize/2, (10240-(buf.mDataByteSize+remainingDataSize))/2, 48000, hardwareSampleRate)*2;
|
||||
}else{
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
}
|
||||
remainingDataSize+=len;
|
||||
while(remainingDataSize>=BUFFER_SIZE*2){
|
||||
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
|
||||
remainingDataSize-=BUFFER_SIZE*2;
|
||||
if(remainingDataSize>0){
|
||||
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInputAudioUnitLegacy::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioInputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID inputDevice=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &inputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
inputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!inputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kInputBus,
|
||||
&inputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kInputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=hardwareFormat.mSampleRate, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched capture device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(inputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
LOGV("System default input device changed");
|
||||
AudioInputAudioUnitLegacy* self=(AudioInputAudioUnitLegacy*)inClientData;
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnitLegacy : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioInputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnit unit;
|
||||
AudioBufferList inBufferList;
|
||||
int hardwareSampleRate;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "AudioUnitIO.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnit::AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
isPlaying=false;
|
||||
remainingDataSize=0;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioOutputAudioUnit::~AudioOutputAudioUnit(){
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Start(){
|
||||
isPlaying=true;
|
||||
io->EnableOutput(true);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Stop(){
|
||||
isPlaying=false;
|
||||
io->EnableOutput(false);
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnit::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
#if TARGET_OS_OSX
|
||||
unsigned int k;
|
||||
while(remainingDataSize<buf.mDataByteSize/2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
float* dst=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* src=reinterpret_cast<int16_t*>(remainingData);
|
||||
for(k=0;k<buf.mDataByteSize/4;k++){
|
||||
dst[k]=src[k]/(float)INT16_MAX;
|
||||
}
|
||||
remainingDataSize-=buf.mDataByteSize/2;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize/2, remainingDataSize);
|
||||
#else
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioOutputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioOutputAudioUnit : public AudioOutput{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioOutputAudioUnit);
|
||||
AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioOutputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool isPlaying;
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include "AudioOutputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("output: " msg": OSStatus=%d", (int)res); return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnitLegacy::AudioOutputAudioUnitLegacy(std::string deviceID){
|
||||
remainingDataSize=0;
|
||||
isPlaying=false;
|
||||
sysDevID=0;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
char model[128];
|
||||
memset(model, 0, sizeof(model));
|
||||
size_t msize=sizeof(model);
|
||||
int mres=sysctlbyname("hw.model", model, &msize, NULL, 0);
|
||||
if(mres==0){
|
||||
LOGV("Mac model: %s", model);
|
||||
isMacBookPro=(strncmp("MacBookPro", model, 10)==0);
|
||||
}
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=/*hardwareFormat.mSampleRate*/48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioOutputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
status=AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing unit");
|
||||
}
|
||||
|
||||
AudioOutputAudioUnitLegacy::~AudioOutputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Start(){
|
||||
isPlaying=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Stop(){
|
||||
isPlaying=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioOutputAudioUnitLegacy* input=(AudioOutputAudioUnitLegacy*) inRefCon;
|
||||
input->HandleBufferCallback(ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnitLegacy::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioOutputAudioUnitLegacy::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioOutputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID outputDevice=0;
|
||||
OSStatus status;
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &outputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
outputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!outputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kOutputBus,
|
||||
&outputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting output device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched playback device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
sysDevID=outputDevice;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(outputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
|
||||
if(isMacBookPro){
|
||||
if(AudioObjectHasProperty(outputDevice, &dataSourceProp)){
|
||||
UInt32 dataSource;
|
||||
size=4;
|
||||
AudioObjectGetPropertyData(outputDevice, &dataSourceProp, 0, NULL, &size, &dataSource);
|
||||
SetPanRight(dataSource=='ispk');
|
||||
AudioObjectAddPropertyListener(outputDevice, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}else{
|
||||
SetPanRight(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetPanRight(bool panRight){
|
||||
LOGI("%sabling pan right on macbook pro", panRight ? "En" : "Dis");
|
||||
int32_t channelMap[]={panRight ? -1 : 0, 0};
|
||||
OSStatus status=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, kOutputBus, channelMap, sizeof(channelMap));
|
||||
CHECK_AU_ERROR(status, "Error setting channel map");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioOutputAudioUnitLegacy* self=(AudioOutputAudioUnitLegacy*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioDevicePropertyDataSource){
|
||||
UInt32 dataSource;
|
||||
UInt32 size=4;
|
||||
AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &size, &dataSource);
|
||||
self->SetPanRight(dataSource=='ispk');
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputAudioUnitLegacy : public AudioOutput{
|
||||
|
||||
public:
|
||||
AudioOutputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioOutputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
void SetPanRight(bool panRight);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isPlaying;
|
||||
AudioUnit unit;
|
||||
int hardwareSampleRate;
|
||||
bool isMacBookPro;
|
||||
AudioDeviceID sysDevID;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
#include <stdio.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../VoIPServerConfig.h"
|
||||
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#define INPUT_BUFFER_SIZE 20480
|
||||
#else
|
||||
#define INPUT_BUFFER_SIZE 10240
|
||||
#endif
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
extern "C" {
|
||||
OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
|
||||
Float32 inDuckedLevel,
|
||||
const AudioTimeStamp* __nullable inStartTime,
|
||||
Float32 inRampDuration) __attribute__((weak_import));
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID){
|
||||
input=NULL;
|
||||
output=NULL;
|
||||
inputEnabled=false;
|
||||
outputEnabled=false;
|
||||
failed=false;
|
||||
started=false;
|
||||
inBufferList.mBuffers[0].mData=malloc(INPUT_BUFFER_SIZE);
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
DarwinSpecific::ConfigureAudioSession();
|
||||
#endif
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent inputComponent;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
inputComponent = AudioComponentFindNext(NULL, &desc);
|
||||
status = AudioComponentInstanceNew(inputComponent, &unit);
|
||||
|
||||
UInt32 flag=1;
|
||||
#if TARGET_OS_IPHONE
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0;
|
||||
#else
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0;
|
||||
#endif
|
||||
status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error disabling AGC");
|
||||
|
||||
AudioStreamBasicDescription audioFormat;
|
||||
audioFormat.mSampleRate = 48000;
|
||||
audioFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
#if TARGET_OS_IPHONE
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 16;
|
||||
audioFormat.mBytesPerPacket = 2;
|
||||
audioFormat.mBytesPerFrame = 2;
|
||||
#else // OS X
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 32;
|
||||
audioFormat.mBytesPerPacket = 4;
|
||||
audioFormat.mBytesPerFrame = 4;
|
||||
#endif
|
||||
audioFormat.mFramesPerPacket = 1;
|
||||
audioFormat.mChannelsPerFrame = 1;
|
||||
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting output format");
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting input format");
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
|
||||
callbackStruct.inputProc = AudioUnitIO::BufferCallback;
|
||||
callbackStruct.inputProcRefCon = this;
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting output buffer callback");
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
input=new AudioInputAudioUnit(inputDeviceID, this);
|
||||
output=new AudioOutputAudioUnit(outputDeviceID, this);
|
||||
}
|
||||
|
||||
AudioUnitIO::~AudioUnitIO(){
|
||||
#if TARGET_OS_OSX
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
#endif
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
delete input;
|
||||
delete output;
|
||||
}
|
||||
|
||||
OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){
|
||||
if(bus==kOutputBus){
|
||||
if(output && outputEnabled){
|
||||
output->HandleBufferCallback(ioData);
|
||||
}else{
|
||||
memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
|
||||
}
|
||||
}else if(bus==kInputBus){
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
|
||||
if(input && inputEnabled){
|
||||
input->HandleBufferCallback(&inBufferList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableInput(bool enabled){
|
||||
inputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableOutput(bool enabled){
|
||||
outputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
if(actualDuckingEnabled!=duckingEnabled){
|
||||
actualDuckingEnabled=duckingEnabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioUnitIO::StartIfNeeded(){
|
||||
if(started)
|
||||
return;
|
||||
started=true;
|
||||
OSStatus status = AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing AudioUnit");
|
||||
status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
AudioInput* AudioUnitIO::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
AudioOutput* AudioUnitIO::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioUnitIO* self=(AudioUnitIO*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default output device changed");
|
||||
if(self->currentOutputDevice=="default"){
|
||||
self->SetCurrentDevice(false, self->currentOutputDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultInputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentInputDevice=="default"){
|
||||
self->SetCurrentDevice(true, self->currentInputDevice);
|
||||
}
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID){
|
||||
LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str());
|
||||
if(started){
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
}
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID device=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &device);
|
||||
CHECK_AU_ERROR(status, "Error getting default device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
device=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!device){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice(input, "default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status=AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
input ? kInputBus : kOutputBus,
|
||||
&device,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
if(input)
|
||||
currentInputDevice=deviceID;
|
||||
else
|
||||
currentOutputDevice=deviceID;
|
||||
|
||||
/*AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(device, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}*/
|
||||
if(started){
|
||||
started=false;
|
||||
StartIfNeeded();
|
||||
}
|
||||
if(!input){
|
||||
currentOutputDeviceID=device;
|
||||
}
|
||||
LOGV("Set current %sput device done", input ? "in" : "out");
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetDuckingEnabled(bool enabled){
|
||||
duckingEnabled=enabled;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
if(outputEnabled && duckingEnabled!=actualDuckingEnabled){
|
||||
actualDuckingEnabled=enabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOUNITIO_H
|
||||
#define LIBTGVOIP_AUDIOUNITIO_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include "../../threading.h"
|
||||
#include <string>
|
||||
#include "../../audio/AudioIO.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnit;
|
||||
class AudioOutputAudioUnit;
|
||||
|
||||
class AudioUnitIO : public AudioIO{
|
||||
public:
|
||||
AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID);
|
||||
~AudioUnitIO();
|
||||
void EnableInput(bool enabled);
|
||||
void EnableOutput(bool enabled);
|
||||
virtual AudioInput* GetInput();
|
||||
virtual AudioOutput* GetOutput();
|
||||
#if TARGET_OS_OSX
|
||||
void SetCurrentDevice(bool input, std::string deviceID);
|
||||
void SetDuckingEnabled(bool enabled);
|
||||
#endif
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
void BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList* ioData);
|
||||
void StartIfNeeded();
|
||||
#if TARGET_OS_OSX
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
std::string currentInputDevice;
|
||||
std::string currentOutputDevice;
|
||||
bool duckingEnabled=true;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
bool actualDuckingEnabled=true;
|
||||
#endif // TGVOIP_NO_OSX_PRIVATE_API
|
||||
AudioDeviceID currentOutputDeviceID;
|
||||
#endif
|
||||
AudioComponentInstance unit;
|
||||
AudioInputAudioUnit* input;
|
||||
AudioOutputAudioUnit* output;
|
||||
AudioBufferList inBufferList;
|
||||
bool inputEnabled;
|
||||
bool outputEnabled;
|
||||
bool started;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif /* LIBTGVOIP_AUDIOUNITIO_H */
|
||||
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_DARWINSPECIFIC_H
|
||||
#define TGVOIP_DARWINSPECIFIC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
struct CellularCarrierInfo;
|
||||
|
||||
class DarwinSpecific{
|
||||
public:
|
||||
enum{
|
||||
THREAD_PRIO_USER_INTERACTIVE,
|
||||
THREAD_PRIO_USER_INITIATED,
|
||||
THREAD_PRIO_UTILITY,
|
||||
THREAD_PRIO_BACKGROUND,
|
||||
THREAD_PRIO_DEFAULT
|
||||
};
|
||||
static void GetSystemName(char* buf, size_t len);
|
||||
static void SetCurrentThreadPriority(int priority);
|
||||
static CellularCarrierInfo GetCarrierInfo();
|
||||
static void ConfigureAudioSession();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TGVOIP_DARWINSPECIFIC_H
|
||||
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "DarwinSpecific.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IOS
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
#import <CoreTelephony/CTCarrier.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
void DarwinSpecific::GetSystemName(char* buf, size_t len){
|
||||
NSString* v=[[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
strcpy(buf, [v UTF8String]);
|
||||
//[v getCString:buf maxLength:sizeof(buf) encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
void DarwinSpecific::SetCurrentThreadPriority(int priority){
|
||||
NSThread* thread=[NSThread currentThread];
|
||||
if([thread respondsToSelector:@selector(setQualityOfService:)]){
|
||||
NSQualityOfService qos;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
qos=NSQualityOfServiceUserInteractive;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
qos=NSQualityOfServiceUserInitiated;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
qos=NSQualityOfServiceUtility;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
qos=NSQualityOfServiceBackground;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
qos=NSQualityOfServiceDefault;
|
||||
break;
|
||||
}
|
||||
[thread setQualityOfService:qos];
|
||||
}else{
|
||||
double p;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
p=1.0;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
p=0.8;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
p=0.4;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
p=0.2;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
p=0.5;
|
||||
break;
|
||||
}
|
||||
[NSThread setThreadPriority:p];
|
||||
}
|
||||
}
|
||||
|
||||
CellularCarrierInfo DarwinSpecific::GetCarrierInfo(){
|
||||
CellularCarrierInfo info;
|
||||
#if TARGET_OS_IOS
|
||||
CTTelephonyNetworkInfo* netinfo=[CTTelephonyNetworkInfo new];
|
||||
CTCarrier* carrier=[netinfo subscriberCellularProvider];
|
||||
if(carrier){
|
||||
NSString* name=[carrier carrierName];
|
||||
NSString* mcc=[carrier mobileCountryCode];
|
||||
NSString* mnc=[carrier mobileNetworkCode];
|
||||
NSString* countryCode=[carrier isoCountryCode];
|
||||
if(name && mcc && mnc && countryCode){
|
||||
info.name=[name cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mcc=[mcc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mnc=[mnc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.countryCode=[[countryCode uppercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return info;
|
||||
}
|
||||
|
||||
void DarwinSpecific::ConfigureAudioSession(){
|
||||
#if TARGET_OS_IOS
|
||||
AVAudioSession* session=[AVAudioSession sharedInstance];
|
||||
NSError* error=nil;
|
||||
[session setPreferredSampleRate:48000.0 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred sample rate on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
[session setPreferredIOBufferDuration:0.020 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred IO buffer duration on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
LOGI("Configured AVAudioSession: sampleRate=%f, IOBufferDuration=%f", session.sampleRate, session.IOBufferDuration);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
#define TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
|
||||
#include "../../video/VideoRenderer.h"
|
||||
#include <vector>
|
||||
#include <objc/objc.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class TGVVideoRenderer;
|
||||
#else
|
||||
typedef struct objc_object TGVVideoRenderer;
|
||||
#endif
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class SampleBufferDisplayLayerRenderer : public VideoRenderer{
|
||||
public:
|
||||
SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer);
|
||||
virtual ~SampleBufferDisplayLayerRenderer();
|
||||
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
|
||||
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
|
||||
virtual void SetStreamEnabled(bool enabled) override;
|
||||
static int GetMaximumResolution();
|
||||
static std::vector<uint32_t> GetAvailableDecoders();
|
||||
private:
|
||||
TGVVideoRenderer* renderer;
|
||||
CMFormatDescriptionRef formatDesc=NULL;
|
||||
bool needReset=false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER */
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#include <UIKit/UIKit.h>
|
||||
#endif
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
#include "TGVVideoRenderer.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
SampleBufferDisplayLayerRenderer::SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer) : renderer(renderer){
|
||||
|
||||
}
|
||||
|
||||
SampleBufferDisplayLayerRenderer::~SampleBufferDisplayLayerRenderer(){
|
||||
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd){
|
||||
LOGI("video renderer reset: %d x %d", width, height);
|
||||
if(formatDesc){
|
||||
CFRelease(formatDesc);
|
||||
}
|
||||
if(codec==CODEC_AVC){
|
||||
if(csd.size()!=2){
|
||||
LOGE("H264 requires exactly 2 CSD buffers");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*csd[0]+4, *csd[1]+4};
|
||||
size_t paramSizes[]={csd[0].Length()-4, csd[1].Length()-4};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, params, paramSizes, 4, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromH264ParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else if(codec==CODEC_HEVC){
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(csd.size()!=1){
|
||||
LOGE("HEVC requires exactly 1 CSD buffer");
|
||||
return;
|
||||
}
|
||||
int offsets[]={0, 0, 0};
|
||||
Buffer& buf=csd[0];
|
||||
int currentNAL=0;
|
||||
for(int i=0;i<buf.Length()-4;i++){
|
||||
if(buf[i]==0 && buf[i+1]==0 && buf[i+2]==0 && buf[i+3]==1){
|
||||
offsets[currentNAL]=i+4;
|
||||
currentNAL++;
|
||||
}
|
||||
}
|
||||
LOGV("CSD NAL offsets: %d %d %d", offsets[0], offsets[1], offsets[2]);
|
||||
if(offsets[0]==0 || offsets[1]==0 || offsets[2]==0){
|
||||
LOGE("Error splitting CSD buffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*buf+offsets[0], *buf+offsets[1], *buf+offsets[2]};
|
||||
size_t paramSizes[]={(size_t)((offsets[1]-4)-offsets[0]), (size_t)((offsets[2]-4)-offsets[1]), (size_t)(buf.Length()-offsets[2])};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromHEVCParameterSets(NULL, 3, params, paramSizes, 4, NULL, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromHEVCParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else{
|
||||
LOGE("HEVC not available on this OS");
|
||||
}
|
||||
}
|
||||
needReset=true;
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::DecodeAndDisplay(Buffer frame, uint32_t pts){
|
||||
CMBlockBufferRef blockBuffer;
|
||||
|
||||
OSStatus status=CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, *frame, frame.Length(), kCFAllocatorNull, NULL, 0, frame.Length(), 0, &blockBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferCreateWithMemoryBlock failed: %d", status);
|
||||
return;
|
||||
}
|
||||
uint32_t _len=(uint32_t)(frame.Length()-4);
|
||||
uint8_t lenBytes[]={(uint8_t)(_len >> 24), (uint8_t)(_len >> 16), (uint8_t)(_len >> 8), (uint8_t)_len};
|
||||
status=CMBlockBufferReplaceDataBytes(lenBytes, blockBuffer, 0, 4);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferReplaceDataBytes failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
status=CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, formatDesc, 1, 0, NULL, 0, NULL, &sampleBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMSampleBufferCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
|
||||
CFRelease(blockBuffer);
|
||||
CFArrayRef attachments=CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
|
||||
CFMutableDictionaryRef dict=(CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||
|
||||
[renderer _enqueueBuffer:sampleBuffer reset:needReset];
|
||||
needReset=false;
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::SetStreamEnabled(bool enabled){
|
||||
|
||||
}
|
||||
|
||||
int SampleBufferDisplayLayerRenderer::GetMaximumResolution(){
|
||||
#if TARGET_OS_IPHONE
|
||||
CGRect screenSize=[UIScreen mainScreen].nativeBounds;
|
||||
CGFloat minSize=std::min(screenSize.size.width, screenSize.size.height);
|
||||
if(minSize>720.f){
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}else if(minSize>480.f){
|
||||
return INIT_VIDEO_RES_720;
|
||||
}else{
|
||||
return INIT_VIDEO_RES_480;
|
||||
}
|
||||
#else // OS X
|
||||
// TODO support OS X
|
||||
#endif
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> SampleBufferDisplayLayerRenderer::GetAvailableDecoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)){
|
||||
res.push_back(CODEC_HEVC);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern void (*TGVoipLoggingFunction)(NSString *);
|
||||
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_TGLOGWRAPPER_H
|
||||
#define TGVOIP_TGLOGWRAPPER_H
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void __tgvoip_call_tglog(const char* format, ...);
|
||||
|
||||
#if defined __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif //TGVOIP_TGLOGWRAPPER_H
|
||||
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
void (*TGVoipLoggingFunction)(NSString *) = NULL;
|
||||
|
||||
void __tgvoip_call_tglog(const char* format, ...){
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *string = [[NSString alloc] initWithFormat:[[NSString alloc]initWithUTF8String:format] arguments:args];
|
||||
va_end(args);
|
||||
if (TGVoipLoggingFunction) {
|
||||
TGVoipLoggingFunction(string);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamPauseReason){
|
||||
TGVStreamPauseReasonBackground,
|
||||
TGVStreamPauseReasonPoorConnection
|
||||
};
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamStopReason){
|
||||
TGVStreamStopReasonUser,
|
||||
TGVStreamStopReasonPoorConnection
|
||||
};
|
||||
|
||||
@protocol TGVVideoRendererDelegate <NSObject>
|
||||
|
||||
- (void)incomingVideoRotationDidChange: (int)rotation;
|
||||
- (void)incomingVideoStreamWillStartWithFrameSize: (CGSize)size;
|
||||
- (void)incomingVideoStreamDidStopWithReason: (TGVStreamStopReason)reason;
|
||||
- (void)incomingVideoStreamDidPauseWithReason: (TGVStreamPauseReason)reason;
|
||||
- (void)incomingVideoStreamWillResume;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoRenderer : NSObject
|
||||
|
||||
- (instancetype)initWithDisplayLayer: (AVSampleBufferDisplayLayer *)layer delegate: (id<TGVVideoRendererDelegate>)delegate;
|
||||
- (tgvoip::video::VideoRenderer*)nativeVideoRenderer;
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import "TGVVideoRenderer.h"
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
@implementation TGVVideoRenderer{
|
||||
AVSampleBufferDisplayLayer* layer;
|
||||
id<TGVVideoRendererDelegate> delegate;
|
||||
tgvoip::video::SampleBufferDisplayLayerRenderer* nativeRenderer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDisplayLayer:(AVSampleBufferDisplayLayer *)layer delegate:(nonnull id<TGVVideoRendererDelegate>)delegate{
|
||||
self=[super init];
|
||||
self->layer=layer;
|
||||
self->delegate=delegate;
|
||||
nativeRenderer=new tgvoip::video::SampleBufferDisplayLayerRenderer(self);
|
||||
layer.videoGravity=AVLayerVideoGravityResizeAspect;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeRenderer;
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoRenderer *)nativeVideoRenderer{
|
||||
return nativeRenderer;
|
||||
}
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset{
|
||||
if(reset){
|
||||
LOGV("Resetting layer");
|
||||
[layer flush];
|
||||
}
|
||||
LOGV("Enqueue buffer");
|
||||
[layer enqueueSampleBuffer:buffer];
|
||||
NSError* error=[layer error];
|
||||
if(error){
|
||||
LOGE("enqueueSampleBuffer failed: %s", [error.description cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoSource;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVVideoResolution){
|
||||
TGVVideoResolution1080,
|
||||
TGVVideoResolution720,
|
||||
TGVVideoResolution480,
|
||||
TGVVideoResolution360
|
||||
};
|
||||
|
||||
@protocol TGVVideoSourceDelegate <NSObject>
|
||||
|
||||
- (void)setFrameRate: (unsigned int)frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoSource : NSObject
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate;
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer;
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution;
|
||||
- (void)setVideoRotation: (int)rotation;
|
||||
- (void)pauseStream;
|
||||
- (void)resumeStream;
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource;
|
||||
|
||||
@end
|
||||
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import "TGVVideoSource.h"
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
|
||||
@implementation TGVVideoSource{
|
||||
tgvoip::video::VideoToolboxEncoderSource* nativeSource;
|
||||
id<TGVVideoSourceDelegate> delegate;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate{
|
||||
self=[super init];
|
||||
nativeSource=new tgvoip::video::VideoToolboxEncoderSource();
|
||||
self->delegate=delegate;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeSource;
|
||||
}
|
||||
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer{
|
||||
nativeSource->EncodeFrame(buffer);
|
||||
}
|
||||
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution{
|
||||
return TGVVideoResolution1080;
|
||||
}
|
||||
|
||||
- (void)setVideoRotation: (int)rotation{
|
||||
|
||||
}
|
||||
|
||||
- (void)pauseStream{
|
||||
|
||||
}
|
||||
|
||||
- (void)resumeStream{
|
||||
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource{
|
||||
return nativeSource;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
|
||||
#define LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
|
||||
|
||||
#include "../../video/VideoSource.h"
|
||||
#include <CoreMedia/CoreMedia.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoToolboxEncoderSource : public VideoSource{
|
||||
public:
|
||||
VideoToolboxEncoderSource();
|
||||
virtual ~VideoToolboxEncoderSource();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Reset(uint32_t codec, int maxResolution) override;
|
||||
virtual void RequestKeyFrame() override;
|
||||
virtual void SetBitrate(uint32_t bitrate) override;
|
||||
void EncodeFrame(CMSampleBufferRef frame);
|
||||
static std::vector<uint32_t> GetAvailableEncoders();
|
||||
private:
|
||||
void EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags);
|
||||
void SetEncoderBitrateAndLimit(uint32_t bitrate);
|
||||
bool needUpdateStreamParams=true;
|
||||
uint32_t codec=0;
|
||||
VTCompressionSessionRef session=NULL;
|
||||
bool keyframeRequested=false;
|
||||
uint32_t bitrateChangeRequested=0;
|
||||
uint32_t lastBitrate=512*1024;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE */
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
#define CHECK_ERR(err, msg) if(err!=noErr){LOGE("VideoToolboxEncoder: " msg " failed: %d", err); return;}
|
||||
|
||||
VideoToolboxEncoderSource::VideoToolboxEncoderSource(){
|
||||
|
||||
}
|
||||
|
||||
VideoToolboxEncoderSource::~VideoToolboxEncoderSource(){
|
||||
if(session){
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Start(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Stop(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Reset(uint32_t codec, int maxResolution){
|
||||
if(session){
|
||||
LOGV("Releasing old compression session");
|
||||
//VTCompressionSessionCompleteFrames(session, kCMTimeInvalid);
|
||||
VTCompressionSessionInvalidate(session);
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
LOGV("Released compression session");
|
||||
}
|
||||
CMVideoCodecType codecType;
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecType=kCMVideoCodecType_H264;
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecType=kCMVideoCodecType_HEVC;
|
||||
break;
|
||||
default:
|
||||
LOGE("VideoToolboxEncoder: Unsupported codec");
|
||||
return;
|
||||
}
|
||||
needUpdateStreamParams=true;
|
||||
this->codec=codec;
|
||||
// typedef void (*VTCompressionOutputCallback)(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer);
|
||||
uint32_t width, height;
|
||||
switch(maxResolution){
|
||||
case INIT_VIDEO_RES_1080:
|
||||
width=1920;
|
||||
height=1080;
|
||||
break;
|
||||
case INIT_VIDEO_RES_720:
|
||||
width=1280;
|
||||
height=720;
|
||||
break;
|
||||
case INIT_VIDEO_RES_480:
|
||||
width=854;
|
||||
height=480;
|
||||
break;
|
||||
case INIT_VIDEO_RES_360:
|
||||
default:
|
||||
width=640;
|
||||
height=360;
|
||||
break;
|
||||
}
|
||||
OSStatus status=VTCompressionSessionCreate(NULL, width, height, codecType, NULL, NULL, NULL, [](void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer){
|
||||
reinterpret_cast<VideoToolboxEncoderSource*>(outputCallbackRefCon)->EncoderCallback(status, sampleBuffer, infoFlags);
|
||||
}, this, &session);
|
||||
if(status!=noErr){
|
||||
LOGE("VTCompressionSessionCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
LOGD("Created VTCompressionSession");
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AllowFrameReordering)");
|
||||
int64_t interval=15;
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(interval));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(MaxKeyFrameIntervalDuration)");
|
||||
SetEncoderBitrateAndLimit(lastBitrate);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(RealTime)");
|
||||
LOGD("VTCompressionSession initialized");
|
||||
|
||||
// TODO change camera frame rate dynamically based on resolution + codec
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::RequestKeyFrame(){
|
||||
keyframeRequested=true;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncodeFrame(CMSampleBufferRef frame){
|
||||
if(!session)
|
||||
return;
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(frame);
|
||||
CMMediaType type=CMFormatDescriptionGetMediaType(format);
|
||||
if(type!=kCMMediaType_Video){
|
||||
//LOGW("Received non-video CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
if(bitrateChangeRequested){
|
||||
LOGI("VideoToolboxEocnder: setting bitrate to %u", bitrateChangeRequested);
|
||||
SetEncoderBitrateAndLimit(bitrateChangeRequested);
|
||||
lastBitrate=bitrateChangeRequested;
|
||||
bitrateChangeRequested=0;
|
||||
}
|
||||
CFDictionaryRef frameProps=NULL;
|
||||
if(keyframeRequested){
|
||||
LOGI("VideoToolboxEncoder: requesting keyframe");
|
||||
const void* keys[]={kVTEncodeFrameOptionKey_ForceKeyFrame};
|
||||
const void* values[]={kCFBooleanTrue};
|
||||
frameProps=CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
|
||||
keyframeRequested=false;
|
||||
}
|
||||
|
||||
//CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
//LOGD("EncodeFrame %d x %d", size.width, size.height);
|
||||
CVImageBufferRef imgBuffer=CMSampleBufferGetImageBuffer(frame);
|
||||
//OSType pixFmt=CVPixelBufferGetPixelFormatType(imgBuffer);
|
||||
//LOGV("pixel format: %c%c%c%c", PRINT_FOURCC(pixFmt));
|
||||
OSStatus status=VTCompressionSessionEncodeFrame(session, imgBuffer, CMSampleBufferGetPresentationTimeStamp(frame), CMSampleBufferGetDuration(frame), frameProps, NULL, NULL);
|
||||
CHECK_ERR(status, "VTCompressionSessionEncodeFrame");
|
||||
if(frameProps)
|
||||
CFRelease(frameProps);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetBitrate(uint32_t bitrate){
|
||||
bitrateChangeRequested=bitrate;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags){
|
||||
if(status!=noErr){
|
||||
LOGE("EncoderCallback error: %d", status);
|
||||
return;
|
||||
}
|
||||
if(flags & kVTEncodeInfo_FrameDropped){
|
||||
LOGW("VideoToolboxEncoder: Frame dropped");
|
||||
}
|
||||
if(!CMSampleBufferGetNumSamples(buffer)){
|
||||
LOGW("Empty CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t startCode[]={0, 0, 0, 1};
|
||||
if(needUpdateStreamParams){
|
||||
LOGI("VideoToolboxEncoder: Updating stream params");
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(buffer);
|
||||
CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
width=size.width;
|
||||
height=size.height;
|
||||
csd.clear();
|
||||
if(codec==CODEC_AVC){
|
||||
for(size_t i=0;i<2;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetH264ParameterSetAtIndex");
|
||||
Buffer b(pl+4);
|
||||
b.CopyFrom(ps, 4, pl);
|
||||
b.CopyFrom(startCode, 0, 4);
|
||||
csd.push_back(std::move(b));
|
||||
}
|
||||
}else if(codec==CODEC_HEVC){
|
||||
LOGD("here1");
|
||||
BufferOutputStream csdBuf(1024);
|
||||
for(size_t i=0;i<3;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetHEVCParameterSetAtIndex");
|
||||
csdBuf.WriteBytes(startCode, 4);
|
||||
csdBuf.WriteBytes(ps, pl);
|
||||
}
|
||||
csd.push_back(std::move(csdBuf));
|
||||
}
|
||||
needUpdateStreamParams=false;
|
||||
}
|
||||
CMBlockBufferRef blockBuffer=CMSampleBufferGetDataBuffer(buffer);
|
||||
size_t len=CMBlockBufferGetDataLength(blockBuffer);
|
||||
|
||||
int frameFlags=0;
|
||||
CFArrayRef attachmentsArray=CMSampleBufferGetSampleAttachmentsArray(buffer, 0);
|
||||
if(attachmentsArray && CFArrayGetCount(attachmentsArray)){
|
||||
CFBooleanRef notSync;
|
||||
CFDictionaryRef dict=(CFDictionaryRef)CFArrayGetValueAtIndex(attachmentsArray, 0);
|
||||
BOOL keyExists=CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, (const void **)¬Sync);
|
||||
if(!keyExists || !CFBooleanGetValue(notSync)){
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
}else{
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
|
||||
Buffer frame(len);
|
||||
CMBlockBufferCopyDataBytes(blockBuffer, 0, len, *frame);
|
||||
uint32_t offset=0;
|
||||
while(offset<len){
|
||||
uint32_t nalLen=CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(*frame+offset));
|
||||
//LOGV("NAL length %u", nalLen);
|
||||
frame.CopyFrom(startCode, offset, 4);
|
||||
offset+=nalLen+4;
|
||||
}
|
||||
callback(std::move(frame), frameFlags);
|
||||
|
||||
//LOGV("EncoderCallback: %u bytes total", (unsigned int)len);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetEncoderBitrateAndLimit(uint32_t bitrate){
|
||||
OSStatus status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bitrate));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AverageBitRate)");
|
||||
|
||||
int64_t dataLimitValue=(int64_t)(bitrate/8);
|
||||
CFNumberRef bytesPerSecond=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitValue);
|
||||
int64_t oneValue=1;
|
||||
CFNumberRef one=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneValue);
|
||||
const void* numbers[]={bytesPerSecond, one};
|
||||
CFArrayRef limits=CFArrayCreate(NULL, numbers, 2, &kCFTypeArrayCallBacks);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, limits);
|
||||
CFRelease(bytesPerSecond);
|
||||
CFRelease(one);
|
||||
CFRelease(limits);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(DataRateLimits");
|
||||
}
|
||||
|
||||
std::vector<uint32_t> VideoToolboxEncoderSource::GetAvailableEncoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
CFArrayRef encoders;
|
||||
OSStatus status=VTCopyVideoEncoderList(NULL, &encoders);
|
||||
for(CFIndex i=0;i<CFArrayGetCount(encoders);i++){
|
||||
CFDictionaryRef v=(CFDictionaryRef)CFArrayGetValueAtIndex(encoders, i);
|
||||
NSDictionary* encoder=(__bridge NSDictionary*)v;
|
||||
//NSString* name=(NSString*)CFDictionaryGetValue(v, kVTVideoEncoderList_EncoderName);
|
||||
uint32_t codecType=[(NSNumber*)encoder[(NSString*)kVTVideoEncoderList_CodecType] intValue];
|
||||
//LOGV("Encoders[%u]: %s, %c%c%c%c", i, [(NSString*)encoder[(NSString*)kVTVideoEncoderList_EncoderName] cStringUsingEncoding:NSUTF8StringEncoding], PRINT_FOURCC(codecType));
|
||||
if(codecType==kCMVideoCodecType_HEVC){
|
||||
res.push_back(CODEC_HEVC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(encoders);
|
||||
return res;
|
||||
}
|
||||
175
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.cpp
Normal file
175
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include "AudioInputALSA.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
|
||||
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
|
||||
|
||||
AudioInputALSA::AudioInputALSA(std::string devID){
|
||||
isRecording=false;
|
||||
handle=NULL;
|
||||
|
||||
lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libasound: %s", dlerror());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_readi", _snd_pcm_readi);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
|
||||
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioInputALSA::~AudioInputALSA(){
|
||||
if(handle)
|
||||
_snd_pcm_close(handle);
|
||||
if(lib)
|
||||
dlclose(lib);
|
||||
}
|
||||
|
||||
void AudioInputALSA::Start(){
|
||||
if(failed || isRecording)
|
||||
return;
|
||||
|
||||
isRecording=true;
|
||||
thread=new Thread(std::bind(&AudioInputALSA::RunThread, this));
|
||||
thread->SetName("AudioInputALSA");
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void AudioInputALSA::Stop(){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
isRecording=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
thread=NULL;
|
||||
}
|
||||
|
||||
void AudioInputALSA::RunThread(){
|
||||
unsigned char buffer[BUFFER_SIZE*2];
|
||||
snd_pcm_sframes_t frames;
|
||||
while(isRecording){
|
||||
frames=_snd_pcm_readi(handle, buffer, BUFFER_SIZE);
|
||||
if (frames < 0){
|
||||
frames = _snd_pcm_recover(handle, frames, 0);
|
||||
}
|
||||
if (frames < 0) {
|
||||
LOGE("snd_pcm_readi failed: %s\n", _snd_strerror(frames));
|
||||
break;
|
||||
}
|
||||
InvokeCallback(buffer, sizeof(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputALSA::SetCurrentDevice(std::string devID){
|
||||
bool wasRecording=isRecording;
|
||||
isRecording=false;
|
||||
if(handle){
|
||||
thread->Join();
|
||||
_snd_pcm_close(handle);
|
||||
}
|
||||
currentDevice=devID;
|
||||
|
||||
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_CAPTURE, 0);
|
||||
if(res<0)
|
||||
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
|
||||
CHECK_ERROR(res, "snd_pcm_open failed");
|
||||
|
||||
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
|
||||
CHECK_ERROR(res, "snd_pcm_set_params failed");
|
||||
|
||||
if(wasRecording){
|
||||
isRecording=true;
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputALSA::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
|
||||
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
|
||||
int (*_snd_device_name_free_hint)(void** hinst);
|
||||
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib)
|
||||
return;
|
||||
|
||||
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
|
||||
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
|
||||
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
|
||||
|
||||
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** hints;
|
||||
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
if(err!=0){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** n=hints;
|
||||
while(*n){
|
||||
char* name=_snd_device_name_get_hint(*n, "NAME");
|
||||
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
|
||||
free(name);
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
char* desc=_snd_device_name_get_hint(*n, "DESC");
|
||||
char* ioid=_snd_device_name_get_hint(*n, "IOID");
|
||||
if(!ioid || strcmp(ioid, "Input")==0){
|
||||
char* l1=strtok(desc, "\n");
|
||||
char* l2=strtok(NULL, "\n");
|
||||
char* tmp=strtok(l1, ",");
|
||||
char* actualName=tmp;
|
||||
while((tmp=strtok(NULL, ","))){
|
||||
actualName=tmp;
|
||||
}
|
||||
if(actualName[0]==' ')
|
||||
actualName++;
|
||||
AudioInputDevice dev;
|
||||
dev.id=std::string(name);
|
||||
if(l2){
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
|
||||
dev.displayName=std::string(buf);
|
||||
}else{
|
||||
dev.displayName=std::string(actualName);
|
||||
}
|
||||
devs.push_back(dev);
|
||||
}
|
||||
free(name);
|
||||
free(desc);
|
||||
free(ioid);
|
||||
n++;
|
||||
}
|
||||
|
||||
dlclose(lib);
|
||||
}
|
||||
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.h
Normal file
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputALSA.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTALSA_H
|
||||
#define LIBTGVOIP_AUDIOINPUTALSA_H
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputALSA : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputALSA(std::string devID);
|
||||
virtual ~AudioInputALSA();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
|
||||
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
|
||||
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
|
||||
int (*_snd_pcm_close)(snd_pcm_t* pcm);
|
||||
snd_pcm_sframes_t (*_snd_pcm_readi)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
|
||||
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
|
||||
const char* (*_snd_strerror)(int errnum);
|
||||
void* lib;
|
||||
|
||||
snd_pcm_t* handle;
|
||||
Thread* thread;
|
||||
bool isRecording;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTALSA_H
|
||||
204
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.cpp
Normal file
204
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include "AudioInputPulse.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "AudioPulse.h"
|
||||
#include "PulseFunctions.h"
|
||||
#if !defined(__GLIBC__)
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputPulse::AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
|
||||
isRecording=false;
|
||||
isConnected=false;
|
||||
didStart=false;
|
||||
|
||||
this->mainloop=mainloop;
|
||||
this->context=context;
|
||||
stream=NULL;
|
||||
remainingDataSize=0;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
stream=CreateAndInitStream();
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
isLocked=false;
|
||||
if(!stream){
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioInputPulse::~AudioInputPulse(){
|
||||
if(stream){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream* AudioInputPulse::CreateAndInitStream(){
|
||||
pa_sample_spec sampleSpec{
|
||||
.format=PA_SAMPLE_S16LE,
|
||||
.rate=48000,
|
||||
.channels=1
|
||||
};
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
|
||||
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip capture", &sampleSpec, NULL, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!stream){
|
||||
LOGE("Error initializing PulseAudio (pa_stream_new)");
|
||||
failed=true;
|
||||
return NULL;
|
||||
}
|
||||
pa_stream_set_state_callback(stream, AudioInputPulse::StreamStateCallback, this);
|
||||
pa_stream_set_read_callback(stream, AudioInputPulse::StreamReadCallback, this);
|
||||
return stream;
|
||||
}
|
||||
|
||||
void AudioInputPulse::Start(){
|
||||
if(failed || isRecording)
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
isRecording=true;
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
void AudioInputPulse::Stop(){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
isRecording=false;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioInputPulse::IsRecording(){
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
void AudioInputPulse::SetCurrentDevice(std::string devID){
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
currentDevice=devID;
|
||||
if(isRecording && isConnected){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
isConnected=false;
|
||||
stream=CreateAndInitStream();
|
||||
}
|
||||
|
||||
pa_buffer_attr bufferAttr={
|
||||
.maxlength=(uint32_t)-1,
|
||||
.tlength=(uint32_t)-1,
|
||||
.prebuf=(uint32_t)-1,
|
||||
.minreq=(uint32_t)-1,
|
||||
.fragsize=960*2
|
||||
};
|
||||
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
int err=pa_stream_connect_record(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags);
|
||||
if(err!=0){
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
/*if(devID!="default"){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
CHECK_ERROR(err, "pa_stream_connect_record");
|
||||
|
||||
while(true){
|
||||
pa_stream_state_t streamState=pa_stream_get_state(stream);
|
||||
if(!PA_STREAM_IS_GOOD(streamState)){
|
||||
LOGE("Error connecting to audio device '%s'", devID.c_str());
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(streamState==PA_STREAM_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
isConnected=true;
|
||||
|
||||
if(isRecording){
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioInputPulse::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
return AudioPulse::DoOneOperation([&](pa_context* ctx){
|
||||
return pa_context_get_source_info_list(ctx, [](pa_context* ctx, const pa_source_info* info, int eol, void* userdata){
|
||||
if(eol>0)
|
||||
return;
|
||||
std::vector<AudioInputDevice>* devs=(std::vector<AudioInputDevice>*)userdata;
|
||||
AudioInputDevice dev;
|
||||
dev.id=std::string(info->name);
|
||||
dev.displayName=std::string(info->description);
|
||||
devs->push_back(dev);
|
||||
}, &devs);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamStateCallback(pa_stream *s, void* arg) {
|
||||
AudioInputPulse* self=(AudioInputPulse*) arg;
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
|
||||
((AudioInputPulse*)userdata)->StreamReadCallback(stream, requestedBytes);
|
||||
}
|
||||
|
||||
void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes) {
|
||||
size_t bytesRemaining = requestedBytes;
|
||||
uint8_t *buffer = NULL;
|
||||
pa_usec_t latency;
|
||||
if(pa_stream_get_latency(stream, &latency, NULL)==0){
|
||||
estimatedDelay=(int32_t)(latency/100);
|
||||
}
|
||||
while (bytesRemaining > 0) {
|
||||
size_t bytesToFill = 102400;
|
||||
|
||||
if (bytesToFill > bytesRemaining) bytesToFill = bytesRemaining;
|
||||
|
||||
int err=pa_stream_peek(stream, (const void**) &buffer, &bytesToFill);
|
||||
CHECK_ERROR(err, "pa_stream_peek");
|
||||
|
||||
if(isRecording){
|
||||
if(remainingDataSize+bytesToFill>sizeof(remainingData)){
|
||||
LOGE("Capture buffer is too big (%d)", (int)bytesToFill);
|
||||
}
|
||||
memcpy(remainingData+remainingDataSize, buffer, bytesToFill);
|
||||
remainingDataSize+=bytesToFill;
|
||||
while(remainingDataSize>=960*2){
|
||||
InvokeCallback(remainingData, 960*2);
|
||||
memmove(remainingData, remainingData+960*2, remainingDataSize-960*2);
|
||||
remainingDataSize-=960*2;
|
||||
}
|
||||
}
|
||||
|
||||
err=pa_stream_drop(stream);
|
||||
CHECK_ERROR(err, "pa_stream_drop");
|
||||
|
||||
bytesRemaining -= bytesToFill;
|
||||
}
|
||||
}
|
||||
52
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.h
Normal file
52
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioInputPulse.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTPULSE_H
|
||||
#define LIBTGVOIP_AUDIOINPUTPULSE_H
|
||||
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../threading.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) typeof(name)* _import_##name
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputPulse : public AudioInput{
|
||||
public:
|
||||
AudioInputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
|
||||
virtual ~AudioInputPulse();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsRecording();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static bool EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void StreamStateCallback(pa_stream* s, void* arg);
|
||||
static void StreamReadCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
|
||||
void StreamReadCallback(pa_stream* stream, size_t requestedBytes);
|
||||
pa_stream* CreateAndInitStream();
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_context* context;
|
||||
pa_stream* stream;
|
||||
|
||||
bool isRecording;
|
||||
bool isConnected;
|
||||
bool didStart;
|
||||
bool isLocked;
|
||||
unsigned char remainingData[960*8*2];
|
||||
size_t remainingDataSize;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECLARE_DL_FUNCTION
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTPULSE_H
|
||||
177
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.cpp
Normal file
177
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include "AudioOutputALSA.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
|
||||
#define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputALSA::AudioOutputALSA(std::string devID){
|
||||
isPlaying=false;
|
||||
handle=NULL;
|
||||
|
||||
lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libasound: %s", dlerror());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_writei", _snd_pcm_writei);
|
||||
LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
|
||||
LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioOutputALSA::~AudioOutputALSA(){
|
||||
if(handle)
|
||||
_snd_pcm_close(handle);
|
||||
if(lib)
|
||||
dlclose(lib);
|
||||
}
|
||||
|
||||
void AudioOutputALSA::Start(){
|
||||
if(failed || isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=true;
|
||||
thread=new Thread(std::bind(&AudioOutputALSA::RunThread, this));
|
||||
thread->SetName("AudioOutputALSA");
|
||||
thread->Start();
|
||||
}
|
||||
|
||||
void AudioOutputALSA::Stop(){
|
||||
if(!isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=false;
|
||||
thread->Join();
|
||||
delete thread;
|
||||
thread=NULL;
|
||||
}
|
||||
|
||||
bool AudioOutputALSA::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
void AudioOutputALSA::RunThread(){
|
||||
unsigned char buffer[BUFFER_SIZE*2];
|
||||
snd_pcm_sframes_t frames;
|
||||
while(isPlaying){
|
||||
InvokeCallback(buffer, sizeof(buffer));
|
||||
frames=_snd_pcm_writei(handle, buffer, BUFFER_SIZE);
|
||||
if (frames < 0){
|
||||
frames = _snd_pcm_recover(handle, frames, 0);
|
||||
}
|
||||
if (frames < 0) {
|
||||
LOGE("snd_pcm_writei failed: %s\n", _snd_strerror(frames));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputALSA::SetCurrentDevice(std::string devID){
|
||||
bool wasPlaying=isPlaying;
|
||||
isPlaying=false;
|
||||
if(handle){
|
||||
thread->Join();
|
||||
_snd_pcm_close(handle);
|
||||
}
|
||||
currentDevice=devID;
|
||||
|
||||
int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if(res<0)
|
||||
res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
|
||||
CHECK_ERROR(res, "snd_pcm_open failed");
|
||||
|
||||
res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
|
||||
CHECK_ERROR(res, "snd_pcm_set_params failed");
|
||||
|
||||
if(wasPlaying){
|
||||
isPlaying=true;
|
||||
thread->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputALSA::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
|
||||
char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
|
||||
int (*_snd_device_name_free_hint)(void** hinst);
|
||||
void* lib=dlopen("libasound.so.2", RTLD_LAZY);
|
||||
if(!lib)
|
||||
dlopen("libasound.so", RTLD_LAZY);
|
||||
if(!lib)
|
||||
return;
|
||||
|
||||
_snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
|
||||
_snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
|
||||
_snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
|
||||
|
||||
if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** hints;
|
||||
int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
if(err!=0){
|
||||
dlclose(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
char** n=hints;
|
||||
while(*n){
|
||||
char* name=_snd_device_name_get_hint(*n, "NAME");
|
||||
if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
|
||||
free(name);
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
char* desc=_snd_device_name_get_hint(*n, "DESC");
|
||||
char* ioid=_snd_device_name_get_hint(*n, "IOID");
|
||||
if(!ioid || strcmp(ioid, "Output")==0){
|
||||
char* l1=strtok(desc, "\n");
|
||||
char* l2=strtok(NULL, "\n");
|
||||
char* tmp=strtok(l1, ",");
|
||||
char* actualName=tmp;
|
||||
while((tmp=strtok(NULL, ","))){
|
||||
actualName=tmp;
|
||||
}
|
||||
if(actualName[0]==' ')
|
||||
actualName++;
|
||||
AudioOutputDevice dev;
|
||||
dev.id=std::string(name);
|
||||
if(l2){
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
|
||||
dev.displayName=std::string(buf);
|
||||
}else{
|
||||
dev.displayName=std::string(actualName);
|
||||
}
|
||||
devs.push_back(dev);
|
||||
}
|
||||
free(name);
|
||||
free(desc);
|
||||
free(ioid);
|
||||
n++;
|
||||
}
|
||||
|
||||
dlclose(lib);
|
||||
}
|
||||
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.h
Normal file
46
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputALSA.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTALSA_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTALSA_H
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../threading.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioOutputALSA : public AudioOutput{
|
||||
public:
|
||||
AudioOutputALSA(std::string devID);
|
||||
virtual ~AudioOutputALSA();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
|
||||
int (*_snd_pcm_open)(snd_pcm_t** pcm, const char* name, snd_pcm_stream_t stream, int mode);
|
||||
int (*_snd_pcm_set_params)(snd_pcm_t* pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
|
||||
int (*_snd_pcm_close)(snd_pcm_t* pcm);
|
||||
snd_pcm_sframes_t (*_snd_pcm_writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
|
||||
int (*_snd_pcm_recover)(snd_pcm_t* pcm, int err, int silent);
|
||||
const char* (*_snd_strerror)(int errnum);
|
||||
void* lib;
|
||||
|
||||
snd_pcm_t* handle;
|
||||
Thread* thread;
|
||||
bool isPlaying;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTALSA_H
|
||||
187
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.cpp
Normal file
187
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include "AudioOutputPulse.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "AudioPulse.h"
|
||||
#include "PulseFunctions.h"
|
||||
#if !defined(__GLIBC__)
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputPulse::AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID){
|
||||
isPlaying=false;
|
||||
isConnected=false;
|
||||
didStart=false;
|
||||
isLocked=false;
|
||||
|
||||
this->mainloop=mainloop;
|
||||
this->context=context;
|
||||
stream=NULL;
|
||||
remainingDataSize=0;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
stream=CreateAndInitStream();
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
SetCurrentDevice(devID);
|
||||
}
|
||||
|
||||
AudioOutputPulse::~AudioOutputPulse(){
|
||||
if(stream){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream* AudioOutputPulse::CreateAndInitStream(){
|
||||
pa_sample_spec sampleSpec{
|
||||
.format=PA_SAMPLE_S16LE,
|
||||
.rate=48000,
|
||||
.channels=1
|
||||
};
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
|
||||
pa_stream* stream=pa_stream_new_with_proplist(context, "libtgvoip playback", &sampleSpec, NULL, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!stream){
|
||||
LOGE("Error initializing PulseAudio (pa_stream_new)");
|
||||
failed=true;
|
||||
return NULL;
|
||||
}
|
||||
pa_stream_set_state_callback(stream, AudioOutputPulse::StreamStateCallback, this);
|
||||
pa_stream_set_write_callback(stream, AudioOutputPulse::StreamWriteCallback, this);
|
||||
return stream;
|
||||
}
|
||||
|
||||
void AudioOutputPulse::Start(){
|
||||
if(failed || isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=true;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::Stop(){
|
||||
if(!isPlaying)
|
||||
return;
|
||||
|
||||
isPlaying=false;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_operation_unref(pa_stream_cork(stream, 1, NULL, NULL));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputPulse::SetCurrentDevice(std::string devID){
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
currentDevice=devID;
|
||||
if(isPlaying && isConnected){
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
isConnected=false;
|
||||
stream=CreateAndInitStream();
|
||||
}
|
||||
|
||||
pa_buffer_attr bufferAttr={
|
||||
.maxlength=(uint32_t)-1,
|
||||
.tlength=960*2,
|
||||
.prebuf=(uint32_t)-1,
|
||||
.minreq=(uint32_t)-1,
|
||||
.fragsize=(uint32_t)-1
|
||||
};
|
||||
int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
int err=pa_stream_connect_playback(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags, NULL, NULL);
|
||||
if(err!=0 && devID!="default"){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
CHECK_ERROR(err, "pa_stream_connect_playback");
|
||||
|
||||
while(true){
|
||||
pa_stream_state_t streamState=pa_stream_get_state(stream);
|
||||
if(!PA_STREAM_IS_GOOD(streamState)){
|
||||
LOGE("Error connecting to audio device '%s'", devID.c_str());
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(streamState==PA_STREAM_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
isConnected=true;
|
||||
|
||||
if(isPlaying){
|
||||
pa_operation_unref(pa_stream_cork(stream, 0, NULL, NULL));
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
return AudioPulse::DoOneOperation([&](pa_context* ctx){
|
||||
return pa_context_get_sink_info_list(ctx, [](pa_context* ctx, const pa_sink_info* info, int eol, void* userdata){
|
||||
if(eol>0)
|
||||
return;
|
||||
std::vector<AudioOutputDevice>* devs=(std::vector<AudioOutputDevice>*)userdata;
|
||||
AudioOutputDevice dev;
|
||||
dev.id=std::string(info->name);
|
||||
dev.displayName=std::string(info->description);
|
||||
devs->push_back(dev);
|
||||
}, &devs);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamStateCallback(pa_stream *s, void* arg) {
|
||||
AudioOutputPulse* self=(AudioOutputPulse*) arg;
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes, void *userdata){
|
||||
((AudioOutputPulse*)userdata)->StreamWriteCallback(stream, requestedBytes);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes) {
|
||||
//assert(requestedBytes<=sizeof(remainingData));
|
||||
if(requestedBytes>sizeof(remainingData)){
|
||||
requestedBytes=960*2; // force buffer size to 20ms. This probably wrecks the jitter buffer, but still better than crashing
|
||||
}
|
||||
pa_usec_t latency;
|
||||
if(pa_stream_get_latency(stream, &latency, NULL)==0){
|
||||
estimatedDelay=(int32_t)(latency/100);
|
||||
}
|
||||
while(requestedBytes>remainingDataSize){
|
||||
if(isPlaying){
|
||||
InvokeCallback(remainingData+remainingDataSize, 960*2);
|
||||
remainingDataSize+=960*2;
|
||||
}else{
|
||||
memset(remainingData+remainingDataSize, 0, requestedBytes-remainingDataSize);
|
||||
remainingDataSize=requestedBytes;
|
||||
}
|
||||
}
|
||||
int err=pa_stream_write(stream, remainingData, requestedBytes, NULL, 0, PA_SEEK_RELATIVE);
|
||||
CHECK_ERROR(err, "pa_stream_write");
|
||||
remainingDataSize-=requestedBytes;
|
||||
if(remainingDataSize>0)
|
||||
memmove(remainingData, remainingData+requestedBytes, remainingDataSize);
|
||||
}
|
||||
48
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.h
Normal file
48
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioOutputPulse.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../threading.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioOutputPulse : public AudioOutput{
|
||||
public:
|
||||
AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID);
|
||||
virtual ~AudioOutputPulse();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string devID);
|
||||
static bool EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void StreamStateCallback(pa_stream* s, void* arg);
|
||||
static void StreamWriteCallback(pa_stream* stream, size_t requested_bytes, void* userdata);
|
||||
void StreamWriteCallback(pa_stream* stream, size_t requestedBytes);
|
||||
pa_stream* CreateAndInitStream();
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_context* context;
|
||||
pa_stream* stream;
|
||||
|
||||
bool isPlaying;
|
||||
bool isConnected;
|
||||
bool didStart;
|
||||
bool isLocked;
|
||||
unsigned char remainingData[960*8*2];
|
||||
size_t remainingDataSize;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTPULSE_H
|
||||
288
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.cpp
Normal file
288
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "AudioPulse.h"
|
||||
#include <dlfcn.h>
|
||||
#include "../../logging.h"
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) typeof(name)* AudioPulse::_import_##name=NULL
|
||||
#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); return false;}
|
||||
#define LOAD_DL_FUNCTION(name) {_import_##name=(typeof(_import_##name))dlsym(lib, #name); CHECK_DL_ERROR(_import_##name, "Error getting entry point for " #name);}
|
||||
#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg " failed: %s", pa_strerror(res)); failed=true; return;}
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
bool AudioPulse::loaded=false;
|
||||
void* AudioPulse::lib=NULL;
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_context_new);
|
||||
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
DECLARE_DL_FUNCTION(pa_context_connect);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
|
||||
DECLARE_DL_FUNCTION(pa_operation_unref);
|
||||
DECLARE_DL_FUNCTION(pa_stream_cork);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
DECLARE_DL_FUNCTION(pa_stream_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_stream_unref);
|
||||
DECLARE_DL_FUNCTION(pa_context_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_context_unref);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
DECLARE_DL_FUNCTION(pa_stream_begin_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_strerror);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_record);
|
||||
DECLARE_DL_FUNCTION(pa_stream_peek);
|
||||
DECLARE_DL_FUNCTION(pa_stream_drop);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_operation_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_new);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_sets);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_free);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
#include "PulseFunctions.h"
|
||||
|
||||
bool AudioPulse::Load(){
|
||||
if(loaded)
|
||||
return true;
|
||||
|
||||
lib=dlopen("libpulse.so.0", RTLD_LAZY);
|
||||
if(!lib)
|
||||
lib=dlopen("libpulse.so", RTLD_LAZY);
|
||||
if(!lib){
|
||||
LOGE("Error loading libpulse: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
LOAD_DL_FUNCTION(pa_context_new);
|
||||
LOAD_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
LOAD_DL_FUNCTION(pa_context_set_state_callback);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
LOAD_DL_FUNCTION(pa_context_connect);
|
||||
LOAD_DL_FUNCTION(pa_context_get_state);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
LOAD_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_connect_playback);
|
||||
LOAD_DL_FUNCTION(pa_operation_unref);
|
||||
LOAD_DL_FUNCTION(pa_stream_cork);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
LOAD_DL_FUNCTION(pa_stream_disconnect);
|
||||
LOAD_DL_FUNCTION(pa_stream_unref);
|
||||
LOAD_DL_FUNCTION(pa_context_disconnect);
|
||||
LOAD_DL_FUNCTION(pa_context_unref);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
LOAD_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
LOAD_DL_FUNCTION(pa_stream_begin_write);
|
||||
LOAD_DL_FUNCTION(pa_stream_write);
|
||||
LOAD_DL_FUNCTION(pa_stream_get_state);
|
||||
LOAD_DL_FUNCTION(pa_strerror);
|
||||
LOAD_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
LOAD_DL_FUNCTION(pa_stream_connect_record);
|
||||
LOAD_DL_FUNCTION(pa_stream_peek);
|
||||
LOAD_DL_FUNCTION(pa_stream_drop);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_new);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_get_api);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_iterate);
|
||||
LOAD_DL_FUNCTION(pa_mainloop_free);
|
||||
LOAD_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
LOAD_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
LOAD_DL_FUNCTION(pa_operation_get_state);
|
||||
LOAD_DL_FUNCTION(pa_proplist_new);
|
||||
LOAD_DL_FUNCTION(pa_proplist_sets);
|
||||
LOAD_DL_FUNCTION(pa_proplist_free);
|
||||
LOAD_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
loaded=true;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioPulse::AudioPulse(std::string inputDevice, std::string outputDevice){
|
||||
if(!Load()){
|
||||
failed=true;
|
||||
LOGE("Failed to load libpulse");
|
||||
return;
|
||||
}
|
||||
|
||||
mainloop=pa_threaded_mainloop_new();
|
||||
if(!mainloop){
|
||||
LOGE("Error initializing PulseAudio (pa_threaded_mainloop_new)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
mainloopApi=pa_threaded_mainloop_get_api(mainloop);
|
||||
#ifndef MAXPATHLEN
|
||||
char exeName[20];
|
||||
#else
|
||||
char exePath[MAXPATHLEN];
|
||||
char exeName[MAXPATHLEN];
|
||||
ssize_t lres=readlink("/proc/self/exe", exePath, sizeof(exePath));
|
||||
if(lres==-1)
|
||||
lres=readlink("/proc/curproc/file", exePath, sizeof(exePath));
|
||||
if(lres==-1)
|
||||
lres=readlink("/proc/curproc/exe", exePath, sizeof(exePath));
|
||||
if(lres>0){
|
||||
strcpy(exeName, basename(exePath));
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
snprintf(exeName, sizeof(exeName), "Process %d", getpid());
|
||||
}
|
||||
pa_proplist* proplist=pa_proplist_new();
|
||||
pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "phone");
|
||||
context=pa_context_new_with_proplist(mainloopApi, exeName, proplist);
|
||||
pa_proplist_free(proplist);
|
||||
if(!context){
|
||||
LOGE("Error initializing PulseAudio (pa_context_new)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
pa_context_set_state_callback(context, [](pa_context* context, void* arg){
|
||||
AudioPulse* self=reinterpret_cast<AudioPulse*>(arg);
|
||||
pa_threaded_mainloop_signal(self->mainloop, 0);
|
||||
}, this);
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
isLocked=true;
|
||||
int err=pa_threaded_mainloop_start(mainloop);
|
||||
CHECK_ERROR(err, "pa_threaded_mainloop_start");
|
||||
didStart=true;
|
||||
|
||||
err=pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
|
||||
CHECK_ERROR(err, "pa_context_connect");
|
||||
|
||||
while(true){
|
||||
pa_context_state_t contextState=pa_context_get_state(context);
|
||||
if(!PA_CONTEXT_IS_GOOD(contextState)){
|
||||
LOGE("Error initializing PulseAudio (PA_CONTEXT_IS_GOOD)");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
if(contextState==PA_CONTEXT_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
isLocked=false;
|
||||
|
||||
output=new AudioOutputPulse(context, mainloop, outputDevice);
|
||||
input=new AudioInputPulse(context, mainloop, inputDevice);
|
||||
}
|
||||
|
||||
AudioPulse::~AudioPulse(){
|
||||
if(mainloop && didStart){
|
||||
if(isLocked)
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
}
|
||||
|
||||
if(input)
|
||||
delete input;
|
||||
if(output)
|
||||
delete output;
|
||||
|
||||
if(context){
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
}
|
||||
if(mainloop)
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
}
|
||||
|
||||
AudioOutput* AudioPulse::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
AudioInput* AudioPulse::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
bool AudioPulse::DoOneOperation(std::function<pa_operation*(pa_context*)> f){
|
||||
if(!Load())
|
||||
return false;
|
||||
|
||||
pa_mainloop* ml;
|
||||
pa_mainloop_api* mlAPI;
|
||||
pa_context* ctx;
|
||||
pa_operation* op=NULL;
|
||||
int paReady=0;
|
||||
|
||||
ml=pa_mainloop_new();
|
||||
mlAPI=pa_mainloop_get_api(ml);
|
||||
ctx=pa_context_new(mlAPI, "libtgvoip");
|
||||
|
||||
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
||||
pa_context_set_state_callback(ctx, [](pa_context* context, void* arg){
|
||||
pa_context_state_t state;
|
||||
int* pa_ready=(int*)arg;
|
||||
|
||||
state=pa_context_get_state(context);
|
||||
switch(state){
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
default:
|
||||
break;
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
*pa_ready=2;
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
*pa_ready=1;
|
||||
break;
|
||||
}
|
||||
}, &paReady);
|
||||
|
||||
while(true){
|
||||
if(paReady==0){
|
||||
pa_mainloop_iterate(ml, 1, NULL);
|
||||
continue;
|
||||
}
|
||||
if(paReady==2){
|
||||
pa_context_disconnect(ctx);
|
||||
pa_context_unref(ctx);
|
||||
pa_mainloop_free(ml);
|
||||
return false;
|
||||
}
|
||||
if(!op){
|
||||
op=f(ctx);
|
||||
continue;
|
||||
}
|
||||
if(pa_operation_get_state(op)==PA_OPERATION_DONE){
|
||||
pa_operation_unref(op);
|
||||
pa_context_disconnect(ctx);
|
||||
pa_context_unref(ctx);
|
||||
pa_mainloop_free(ml);
|
||||
return true;
|
||||
}
|
||||
pa_mainloop_iterate(ml, 1, NULL);
|
||||
}
|
||||
}
|
||||
95
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.h
Normal file
95
TMessagesProj/jni/voip/libtgvoip/os/linux/AudioPulse.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_PULSEAUDIOLOADER_H
|
||||
#define LIBTGVOIP_PULSEAUDIOLOADER_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include "../../audio/AudioIO.h"
|
||||
#include "AudioInputPulse.h"
|
||||
#include "AudioOutputPulse.h"
|
||||
|
||||
#define DECLARE_DL_FUNCTION(name) static typeof(name)* _import_##name
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
class AudioPulse : public AudioIO{
|
||||
public:
|
||||
AudioPulse(std::string inputDevice, std::string outputDevice);
|
||||
virtual ~AudioPulse();
|
||||
virtual AudioInput* GetInput();
|
||||
virtual AudioOutput* GetOutput();
|
||||
|
||||
static bool Load();
|
||||
static bool DoOneOperation(std::function<pa_operation*(pa_context*)> f);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_context_new);
|
||||
DECLARE_DL_FUNCTION(pa_context_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_context_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_start);
|
||||
DECLARE_DL_FUNCTION(pa_context_connect);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait);
|
||||
DECLARE_DL_FUNCTION(pa_stream_new_with_proplist);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_state_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_write_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_playback);
|
||||
DECLARE_DL_FUNCTION(pa_operation_unref);
|
||||
DECLARE_DL_FUNCTION(pa_stream_cork);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop);
|
||||
DECLARE_DL_FUNCTION(pa_stream_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_stream_unref);
|
||||
DECLARE_DL_FUNCTION(pa_context_disconnect);
|
||||
DECLARE_DL_FUNCTION(pa_context_unref);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal);
|
||||
DECLARE_DL_FUNCTION(pa_stream_begin_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_write);
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_state);
|
||||
DECLARE_DL_FUNCTION(pa_strerror);
|
||||
DECLARE_DL_FUNCTION(pa_stream_set_read_callback);
|
||||
DECLARE_DL_FUNCTION(pa_stream_connect_record);
|
||||
DECLARE_DL_FUNCTION(pa_stream_peek);
|
||||
DECLARE_DL_FUNCTION(pa_stream_drop);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_new);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_get_api);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_iterate);
|
||||
DECLARE_DL_FUNCTION(pa_mainloop_free);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_sink_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_context_get_source_info_list);
|
||||
DECLARE_DL_FUNCTION(pa_operation_get_state);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_proplist_new);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_sets);
|
||||
DECLARE_DL_FUNCTION(pa_proplist_free);
|
||||
|
||||
DECLARE_DL_FUNCTION(pa_stream_get_latency);
|
||||
|
||||
private:
|
||||
static void* lib;
|
||||
static bool loaded;
|
||||
AudioInputPulse* input=NULL;
|
||||
AudioOutputPulse* output=NULL;
|
||||
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_mainloop_api* mainloopApi;
|
||||
pa_context* context;
|
||||
bool isLocked=false;
|
||||
bool didStart=false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECLARE_DL_FUNCTION
|
||||
|
||||
#endif // LIBTGVOIP_PULSEAUDIOLOADER_H
|
||||
48
TMessagesProj/jni/voip/libtgvoip/os/linux/PulseFunctions.h
Normal file
48
TMessagesProj/jni/voip/libtgvoip/os/linux/PulseFunctions.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
#define LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
|
||||
#define pa_threaded_mainloop_new AudioPulse::_import_pa_threaded_mainloop_new
|
||||
#define pa_threaded_mainloop_get_api AudioPulse::_import_pa_threaded_mainloop_get_api
|
||||
#define pa_context_new AudioPulse::_import_pa_context_new
|
||||
#define pa_context_new_with_proplist AudioPulse::_import_pa_context_new_with_proplist
|
||||
#define pa_context_set_state_callback AudioPulse::_import_pa_context_set_state_callback
|
||||
#define pa_threaded_mainloop_lock AudioPulse::_import_pa_threaded_mainloop_lock
|
||||
#define pa_threaded_mainloop_unlock AudioPulse::_import_pa_threaded_mainloop_unlock
|
||||
#define pa_threaded_mainloop_start AudioPulse::_import_pa_threaded_mainloop_start
|
||||
#define pa_context_connect AudioPulse::_import_pa_context_connect
|
||||
#define pa_context_get_state AudioPulse::_import_pa_context_get_state
|
||||
#define pa_threaded_mainloop_wait AudioPulse::_import_pa_threaded_mainloop_wait
|
||||
#define pa_stream_new_with_proplist AudioPulse::_import_pa_stream_new_with_proplist
|
||||
#define pa_stream_set_state_callback AudioPulse::_import_pa_stream_set_state_callback
|
||||
#define pa_stream_set_write_callback AudioPulse::_import_pa_stream_set_write_callback
|
||||
#define pa_stream_connect_playback AudioPulse::_import_pa_stream_connect_playback
|
||||
#define pa_operation_unref AudioPulse::_import_pa_operation_unref
|
||||
#define pa_stream_cork AudioPulse::_import_pa_stream_cork
|
||||
#define pa_threaded_mainloop_stop AudioPulse::_import_pa_threaded_mainloop_stop
|
||||
#define pa_stream_disconnect AudioPulse::_import_pa_stream_disconnect
|
||||
#define pa_stream_unref AudioPulse::_import_pa_stream_unref
|
||||
#define pa_context_disconnect AudioPulse::_import_pa_context_disconnect
|
||||
#define pa_context_unref AudioPulse::_import_pa_context_unref
|
||||
#define pa_threaded_mainloop_free AudioPulse::_import_pa_threaded_mainloop_free
|
||||
#define pa_threaded_mainloop_signal AudioPulse::_import_pa_threaded_mainloop_signal
|
||||
#define pa_stream_begin_write AudioPulse::_import_pa_stream_begin_write
|
||||
#define pa_stream_write AudioPulse::_import_pa_stream_write
|
||||
#define pa_strerror AudioPulse::_import_pa_strerror
|
||||
#define pa_stream_get_state AudioPulse::_import_pa_stream_get_state
|
||||
#define pa_stream_set_read_callback AudioPulse::_import_pa_stream_set_read_callback
|
||||
#define pa_stream_connect_record AudioPulse::_import_pa_stream_connect_record
|
||||
#define pa_stream_peek AudioPulse::_import_pa_stream_peek
|
||||
#define pa_stream_drop AudioPulse::_import_pa_stream_drop
|
||||
#define pa_mainloop_new AudioPulse::_import_pa_mainloop_new
|
||||
#define pa_mainloop_get_api AudioPulse::_import_pa_mainloop_get_api
|
||||
#define pa_mainloop_iterate AudioPulse::_import_pa_mainloop_iterate
|
||||
#define pa_mainloop_free AudioPulse::_import_pa_mainloop_free
|
||||
#define pa_context_get_sink_info_list AudioPulse::_import_pa_context_get_sink_info_list
|
||||
#define pa_context_get_source_info_list AudioPulse::_import_pa_context_get_source_info_list
|
||||
#define pa_operation_get_state AudioPulse::_import_pa_operation_get_state
|
||||
#define pa_proplist_new AudioPulse::_import_pa_proplist_new
|
||||
#define pa_proplist_sets AudioPulse::_import_pa_proplist_sets
|
||||
#define pa_proplist_free AudioPulse::_import_pa_proplist_free
|
||||
#define pa_stream_get_latency AudioPulse::_import_pa_stream_get_latency
|
||||
|
||||
#endif //LIBTGVOIP_PULSE_FUNCTIONS_H
|
||||
644
TMessagesProj/jni/voip/libtgvoip/os/posix/NetworkSocketPosix.cpp
Normal file
644
TMessagesProj/jni/voip/libtgvoip/os/posix/NetworkSocketPosix.cpp
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "NetworkSocketPosix.h"
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <netdb.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../Buffers.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#include <sys/system_properties.h>
|
||||
extern JavaVM* sharedJVM;
|
||||
extern jclass jniUtilitiesClass;
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
|
||||
NetworkSocketPosix::NetworkSocketPosix(NetworkProtocol protocol) : NetworkSocket(protocol), lastRecvdV4(0), lastRecvdV6("::0"){
|
||||
needUpdateNat64Prefix=true;
|
||||
nat64Present=false;
|
||||
switchToV6at=0;
|
||||
isV4Available=false;
|
||||
fd=-1;
|
||||
useTCP=false;
|
||||
closing=false;
|
||||
|
||||
tcpConnectedAddress=NULL;
|
||||
tcpConnectedPort=0;
|
||||
|
||||
if(protocol==PROTO_TCP)
|
||||
timeout=10.0;
|
||||
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
|
||||
}
|
||||
|
||||
NetworkSocketPosix::~NetworkSocketPosix(){
|
||||
if(fd>=0){
|
||||
Close();
|
||||
}
|
||||
if(tcpConnectedAddress)
|
||||
delete tcpConnectedAddress;
|
||||
if(pendingOutgoingPacket)
|
||||
delete pendingOutgoingPacket;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::SetMaxPriority(){
|
||||
#ifdef __APPLE__
|
||||
int prio=NET_SERVICE_TYPE_VO;
|
||||
int res=setsockopt(fd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting darwin-specific net priority: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
int prio=6;
|
||||
int res=setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting priority: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
prio=46 << 2;
|
||||
res=setsockopt(fd, SOL_IP, IP_TOS, &prio, sizeof(prio));
|
||||
if(res<0){
|
||||
LOGE("error setting ip tos: %d / %s", errno, strerror(errno));
|
||||
}
|
||||
#else
|
||||
LOGI("cannot set socket priority");
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Send(NetworkPacket *packet){
|
||||
if(!packet || (protocol==PROTO_UDP && !packet->address)){
|
||||
LOGW("tried to send null packet");
|
||||
return;
|
||||
}
|
||||
int res;
|
||||
if(protocol==PROTO_UDP){
|
||||
sockaddr_in6 addr;
|
||||
IPv4Address *v4addr=dynamic_cast<IPv4Address *>(packet->address);
|
||||
if(v4addr){
|
||||
if(needUpdateNat64Prefix && !isV4Available && VoIPController::GetCurrentTime()>switchToV6at && switchToV6at!=0){
|
||||
LOGV("Updating NAT64 prefix");
|
||||
nat64Present=false;
|
||||
addrinfo *addr0;
|
||||
int res=getaddrinfo("ipv4only.arpa", NULL, NULL, &addr0);
|
||||
if(res!=0){
|
||||
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
|
||||
}else{
|
||||
addrinfo *addrPtr;
|
||||
unsigned char *addr170=NULL;
|
||||
unsigned char *addr171=NULL;
|
||||
for(addrPtr=addr0; addrPtr; addrPtr=addrPtr->ai_next){
|
||||
if(addrPtr->ai_family==AF_INET6){
|
||||
sockaddr_in6 *translatedAddr=(sockaddr_in6 *) addrPtr->ai_addr;
|
||||
uint32_t v4part=*((uint32_t *) &translatedAddr->sin6_addr.s6_addr[12]);
|
||||
if(v4part==0xAA0000C0 && !addr170){
|
||||
addr170=translatedAddr->sin6_addr.s6_addr;
|
||||
}
|
||||
if(v4part==0xAB0000C0 && !addr171){
|
||||
addr171=translatedAddr->sin6_addr.s6_addr;
|
||||
}
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
LOGV("Got translated address: %s", inet_ntop(AF_INET6, &translatedAddr->sin6_addr, buf, sizeof(buf)));
|
||||
}
|
||||
}
|
||||
if(addr170 && addr171 && memcmp(addr170, addr171, 12)==0){
|
||||
nat64Present=true;
|
||||
memcpy(nat64Prefix, addr170, 12);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
LOGV("Found nat64 prefix from %s", inet_ntop(AF_INET6, addr170, buf, sizeof(buf)));
|
||||
}else{
|
||||
LOGV("Didn't find nat64");
|
||||
}
|
||||
freeaddrinfo(addr0);
|
||||
}
|
||||
needUpdateNat64Prefix=false;
|
||||
}
|
||||
memset(&addr, 0, sizeof(sockaddr_in6));
|
||||
addr.sin6_family=AF_INET6;
|
||||
*((uint32_t *) &addr.sin6_addr.s6_addr[12])=v4addr->GetAddress();
|
||||
if(nat64Present)
|
||||
memcpy(addr.sin6_addr.s6_addr, nat64Prefix, 12);
|
||||
else
|
||||
addr.sin6_addr.s6_addr[11]=addr.sin6_addr.s6_addr[10]=0xFF;
|
||||
|
||||
}else{
|
||||
IPv6Address *v6addr=dynamic_cast<IPv6Address *>(packet->address);
|
||||
assert(v6addr!=NULL);
|
||||
memcpy(addr.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
|
||||
addr.sin6_family=AF_INET6;
|
||||
}
|
||||
addr.sin6_port=htons(packet->port);
|
||||
res=(int)sendto(fd, packet->data, packet->length, 0, (const sockaddr *) &addr, sizeof(addr));
|
||||
}else{
|
||||
res=(int)send(fd, packet->data, packet->length, 0);
|
||||
}
|
||||
if(res<=0){
|
||||
if(errno==EAGAIN || errno==EWOULDBLOCK){
|
||||
if(pendingOutgoingPacket){
|
||||
LOGE("Got EAGAIN but there's already a pending packet");
|
||||
failed=true;
|
||||
}else{
|
||||
LOGV("Socket %d not ready to send", fd);
|
||||
pendingOutgoingPacket=new Buffer(packet->length);
|
||||
pendingOutgoingPacket->CopyFrom(packet->data, 0, packet->length);
|
||||
readyToSend=false;
|
||||
}
|
||||
}else{
|
||||
LOGE("error sending: %d / %s", errno, strerror(errno));
|
||||
if(errno==ENETUNREACH && !isV4Available && VoIPController::GetCurrentTime()<switchToV6at){
|
||||
switchToV6at=VoIPController::GetCurrentTime();
|
||||
LOGI("Network unreachable, trying NAT64");
|
||||
}
|
||||
}
|
||||
}else if((size_t)res!=packet->length && packet->protocol==PROTO_TCP){
|
||||
if(pendingOutgoingPacket){
|
||||
LOGE("send returned less than packet length but there's already a pending packet");
|
||||
failed=true;
|
||||
}else{
|
||||
LOGV("Socket %d not ready to send", fd);
|
||||
pendingOutgoingPacket=new Buffer(packet->length-res);
|
||||
pendingOutgoingPacket->CopyFrom(packet->data+res, 0, packet->length-res);
|
||||
readyToSend=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkSocketPosix::OnReadyToSend(){
|
||||
if(pendingOutgoingPacket){
|
||||
NetworkPacket pkt={0};
|
||||
pkt.data=**pendingOutgoingPacket;
|
||||
pkt.length=pendingOutgoingPacket->Length();
|
||||
Send(&pkt);
|
||||
delete pendingOutgoingPacket;
|
||||
pendingOutgoingPacket=NULL;
|
||||
return false;
|
||||
}
|
||||
readyToSend=true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Receive(NetworkPacket *packet){
|
||||
if(failed){
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
if(protocol==PROTO_UDP){
|
||||
int addrLen=sizeof(sockaddr_in6);
|
||||
sockaddr_in6 srcAddr;
|
||||
ssize_t len=recvfrom(fd, packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
|
||||
if(len>0)
|
||||
packet->length=(size_t) len;
|
||||
else{
|
||||
LOGE("error receiving %d / %s", errno, strerror(errno));
|
||||
packet->length=0;
|
||||
return;
|
||||
}
|
||||
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
|
||||
if(!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)){
|
||||
isV4Available=true;
|
||||
LOGI("Detected IPv4 connectivity, will not try IPv6");
|
||||
}
|
||||
if(IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12)==0)){
|
||||
in_addr v4addr=*((in_addr *) &srcAddr.sin6_addr.s6_addr[12]);
|
||||
lastRecvdV4=IPv4Address(v4addr.s_addr);
|
||||
packet->address=&lastRecvdV4;
|
||||
}else{
|
||||
lastRecvdV6=IPv6Address(srcAddr.sin6_addr.s6_addr);
|
||||
packet->address=&lastRecvdV6;
|
||||
}
|
||||
packet->protocol=PROTO_UDP;
|
||||
packet->port=ntohs(srcAddr.sin6_port);
|
||||
}else if(protocol==PROTO_TCP){
|
||||
int res=(int)recv(fd, packet->data, packet->length, 0);
|
||||
if(res<=0){
|
||||
LOGE("Error receiving from TCP socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
packet->length=0;
|
||||
}else{
|
||||
packet->length=(size_t)res;
|
||||
packet->address=tcpConnectedAddress;
|
||||
packet->port=tcpConnectedPort;
|
||||
packet->protocol=PROTO_TCP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Open(){
|
||||
if(protocol!=PROTO_UDP)
|
||||
return;
|
||||
fd=socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(fd<0){
|
||||
LOGE("error creating socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
int flag=0;
|
||||
int res=setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag));
|
||||
if(res<0){
|
||||
LOGE("error enabling dual stack socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
SetMaxPriority();
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
int tries=0;
|
||||
sockaddr_in6 addr;
|
||||
//addr.sin6_addr.s_addr=0;
|
||||
memset(&addr, 0, sizeof(sockaddr_in6));
|
||||
//addr.sin6_len=sizeof(sa_family_t);
|
||||
addr.sin6_family=AF_INET6;
|
||||
for(tries=0;tries<10;tries++){
|
||||
addr.sin6_port=htons(GenerateLocalPort());
|
||||
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
|
||||
LOGV("trying bind to port %u", ntohs(addr.sin6_port));
|
||||
if(res<0){
|
||||
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(tries==10){
|
||||
addr.sin6_port=0;
|
||||
res=::bind(fd, (sockaddr *) &addr, sizeof(sockaddr_in6));
|
||||
if(res<0){
|
||||
LOGE("error binding to port %u: %d / %s", ntohs(addr.sin6_port), errno, strerror(errno));
|
||||
//SetState(STATE_FAILED);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
size_t addrLen=sizeof(sockaddr_in6);
|
||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
|
||||
LOGD("Bound to local UDP port %u", ntohs(addr.sin6_port));
|
||||
|
||||
needUpdateNat64Prefix=true;
|
||||
isV4Available=false;
|
||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Close(){
|
||||
closing=true;
|
||||
failed=true;
|
||||
|
||||
if (fd>=0) {
|
||||
shutdown(fd, SHUT_RDWR);
|
||||
close(fd);
|
||||
fd=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::Connect(const NetworkAddress *address, uint16_t port){
|
||||
const IPv4Address* v4addr=dynamic_cast<const IPv4Address*>(address);
|
||||
const IPv6Address* v6addr=dynamic_cast<const IPv6Address*>(address);
|
||||
struct sockaddr_in v4={0};
|
||||
struct sockaddr_in6 v6={0};
|
||||
struct sockaddr* addr=NULL;
|
||||
size_t addrLen=0;
|
||||
if(v4addr){
|
||||
v4.sin_family=AF_INET;
|
||||
v4.sin_addr.s_addr=v4addr->GetAddress();
|
||||
v4.sin_port=htons(port);
|
||||
addr=reinterpret_cast<sockaddr*>(&v4);
|
||||
addrLen=sizeof(v4);
|
||||
}else if(v6addr){
|
||||
v6.sin6_family=AF_INET6;
|
||||
memcpy(v6.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
|
||||
v6.sin6_flowinfo=0;
|
||||
v6.sin6_scope_id=0;
|
||||
v6.sin6_port=htons(port);
|
||||
addr=reinterpret_cast<sockaddr*>(&v6);
|
||||
addrLen=sizeof(v6);
|
||||
}else{
|
||||
LOGE("Unknown address type in TCP connect");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(fd<0){
|
||||
LOGE("Error creating TCP socket: %d / %s", errno, strerror(errno));
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
int opt=1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
|
||||
timeval timeout;
|
||||
timeout.tv_sec=5;
|
||||
timeout.tv_usec=0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
timeout.tv_sec=60;
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
int res=(int)connect(fd, (const sockaddr*) addr, (socklen_t)addrLen);
|
||||
if(res!=0 && errno!=EINVAL && errno!=EINPROGRESS){
|
||||
LOGW("error connecting TCP socket to %s:%u: %d / %s; %d / %s", address->ToString().c_str(), port, res, strerror(res), errno, strerror(errno));
|
||||
close(fd);
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
tcpConnectedAddress=v4addr ? (NetworkAddress*)new IPv4Address(*v4addr) : (NetworkAddress*)new IPv6Address(*v6addr);
|
||||
tcpConnectedPort=port;
|
||||
LOGI("successfully connected to %s:%d", tcpConnectedAddress->ToString().c_str(), tcpConnectedPort);
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::OnActiveInterfaceChanged(){
|
||||
needUpdateNat64Prefix=true;
|
||||
isV4Available=false;
|
||||
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::GetLocalInterfaceInfo(IPv4Address *v4addr, IPv6Address *v6addr){
|
||||
std::string name="";
|
||||
// Android doesn't support ifaddrs
|
||||
#ifdef __ANDROID__
|
||||
JNIEnv *env=NULL;
|
||||
bool didAttach=false;
|
||||
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
|
||||
if(!env){
|
||||
sharedJVM->AttachCurrentThread(&env, NULL);
|
||||
didAttach=true;
|
||||
}
|
||||
|
||||
jmethodID getLocalNetworkAddressesAndInterfaceNameMethod=env->GetStaticMethodID(jniUtilitiesClass, "getLocalNetworkAddressesAndInterfaceName", "()[Ljava/lang/String;");
|
||||
jobjectArray jinfo=(jobjectArray) env->CallStaticObjectMethod(jniUtilitiesClass, getLocalNetworkAddressesAndInterfaceNameMethod);
|
||||
if(jinfo){
|
||||
jstring jitfName=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 0));
|
||||
jstring jipv4=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 1));
|
||||
jstring jipv6=static_cast<jstring>(env->GetObjectArrayElement(jinfo, 2));
|
||||
if(jitfName){
|
||||
const char *itfchars=env->GetStringUTFChars(jitfName, NULL);
|
||||
name=std::string(itfchars);
|
||||
env->ReleaseStringUTFChars(jitfName, itfchars);
|
||||
}
|
||||
|
||||
if(v4addr && jipv4){
|
||||
const char* ipchars=env->GetStringUTFChars(jipv4, NULL);
|
||||
*v4addr=IPv4Address(ipchars);
|
||||
env->ReleaseStringUTFChars(jipv4, ipchars);
|
||||
}
|
||||
if(v6addr && jipv6){
|
||||
const char* ipchars=env->GetStringUTFChars(jipv6, NULL);
|
||||
*v6addr=IPv6Address(ipchars);
|
||||
env->ReleaseStringUTFChars(jipv6, ipchars);
|
||||
}
|
||||
}else{
|
||||
LOGW("Failed to get android network interface info");
|
||||
}
|
||||
|
||||
if(didAttach){
|
||||
sharedJVM->DetachCurrentThread();
|
||||
}
|
||||
#else
|
||||
struct ifaddrs* interfaces;
|
||||
if(!getifaddrs(&interfaces)){
|
||||
struct ifaddrs* interface;
|
||||
for(interface=interfaces;interface;interface=interface->ifa_next){
|
||||
if(!(interface->ifa_flags & IFF_UP) || !(interface->ifa_flags & IFF_RUNNING) || (interface->ifa_flags & IFF_LOOPBACK))
|
||||
continue;
|
||||
const struct sockaddr_in* addr=(const struct sockaddr_in*)interface->ifa_addr;
|
||||
if(addr){
|
||||
if(addr->sin_family==AF_INET){
|
||||
if((ntohl(addr->sin_addr.s_addr) & 0xFFFF0000)==0xA9FE0000)
|
||||
continue;
|
||||
if(v4addr)
|
||||
*v4addr=IPv4Address(addr->sin_addr.s_addr);
|
||||
name=interface->ifa_name;
|
||||
}else if(addr->sin_family==AF_INET6){
|
||||
const struct sockaddr_in6* addr6=(const struct sockaddr_in6*)addr;
|
||||
if((addr6->sin6_addr.s6_addr[0] & 0xF0)==0xF0)
|
||||
continue;
|
||||
if(v6addr)
|
||||
*v6addr=IPv6Address(addr6->sin6_addr.s6_addr);
|
||||
name=interface->ifa_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(interfaces);
|
||||
}
|
||||
#endif
|
||||
return name;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocketPosix::GetLocalPort(){
|
||||
sockaddr_in6 addr;
|
||||
size_t addrLen=sizeof(sockaddr_in6);
|
||||
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
|
||||
return ntohs(addr.sin6_port);
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::V4AddressToString(uint32_t address){
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
in_addr addr;
|
||||
addr.s_addr=address;
|
||||
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string NetworkSocketPosix::V6AddressToString(const unsigned char *address){
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
in6_addr addr;
|
||||
memcpy(addr.s6_addr, address, 16);
|
||||
inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
uint32_t NetworkSocketPosix::StringToV4Address(std::string address){
|
||||
in_addr addr;
|
||||
inet_pton(AF_INET, address.c_str(), &addr);
|
||||
return addr.s_addr;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::StringToV6Address(std::string address, unsigned char *out){
|
||||
in6_addr addr;
|
||||
inet_pton(AF_INET6, address.c_str(), &addr);
|
||||
memcpy(out, addr.s6_addr, 16);
|
||||
}
|
||||
|
||||
IPv4Address *NetworkSocketPosix::ResolveDomainName(std::string name){
|
||||
addrinfo* addr0;
|
||||
IPv4Address* ret=NULL;
|
||||
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
|
||||
if(res!=0){
|
||||
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
|
||||
}else{
|
||||
addrinfo* addrPtr;
|
||||
for(addrPtr=addr0;addrPtr;addrPtr=addrPtr->ai_next){
|
||||
if(addrPtr->ai_family==AF_INET){
|
||||
sockaddr_in* addr=(sockaddr_in*)addrPtr->ai_addr;
|
||||
ret=new IPv4Address(addr->sin_addr.s_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(addr0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NetworkAddress *NetworkSocketPosix::GetConnectedAddress(){
|
||||
return tcpConnectedAddress;
|
||||
}
|
||||
|
||||
uint16_t NetworkSocketPosix::GetConnectedPort(){
|
||||
return tcpConnectedPort;
|
||||
}
|
||||
|
||||
void NetworkSocketPosix::SetTimeouts(int sendTimeout, int recvTimeout){
|
||||
timeval timeout;
|
||||
timeout.tv_sec=sendTimeout;
|
||||
timeout.tv_usec=0;
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
timeout.tv_sec=recvTimeout;
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
}
|
||||
|
||||
bool NetworkSocketPosix::Select(std::vector<NetworkSocket *> &readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket *> &errorFds, SocketSelectCanceller* _canceller) {
|
||||
fd_set readSet;
|
||||
fd_set writeSet;
|
||||
fd_set errorSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
FD_ZERO(&errorSet);
|
||||
SocketSelectCancellerPosix *canceller = dynamic_cast<SocketSelectCancellerPosix *>(_canceller);
|
||||
if (canceller)
|
||||
FD_SET(canceller->pipeRead, &readSet);
|
||||
|
||||
int maxfd = canceller ? canceller->pipeRead : 0;
|
||||
|
||||
for (NetworkSocket *&s:readFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
FD_SET(sfd, &readSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
for (NetworkSocket *&s:writeFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
FD_SET(sfd, &writeSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
bool anyFailed = false;
|
||||
|
||||
for (NetworkSocket *&s:errorFds) {
|
||||
int sfd = GetDescriptorFromSocket(s);
|
||||
if (sfd <= 0) {
|
||||
LOGW("can't select on one of sockets because it's not a NetworkSocketPosix instance");
|
||||
continue;
|
||||
}
|
||||
if (s->timeout > 0 && VoIPController::GetCurrentTime() - s->lastSuccessfulOperationTime > s->timeout) {
|
||||
LOGW("Socket %d timed out", sfd);
|
||||
s->failed = true;
|
||||
}
|
||||
anyFailed |= s->IsFailed();
|
||||
FD_SET(sfd, &errorSet);
|
||||
if (maxfd < sfd)
|
||||
maxfd = sfd;
|
||||
}
|
||||
|
||||
select(maxfd + 1, &readSet, &writeSet, &errorSet, NULL);
|
||||
|
||||
if (canceller && FD_ISSET(canceller->pipeRead, &readSet) && !anyFailed) {
|
||||
char c;
|
||||
(void) read(canceller->pipeRead, &c, 1);
|
||||
return false;
|
||||
} else if (anyFailed) {
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
}
|
||||
|
||||
std::vector<NetworkSocket *>::iterator itr = readFds.begin();
|
||||
while (itr != readFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if (sfd > 0 && FD_ISSET(sfd, &readSet)) {
|
||||
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
|
||||
}
|
||||
if (sfd <= 0 || !FD_ISSET(sfd, &readSet) || !(*itr)->OnReadyToReceive()) {
|
||||
itr = readFds.erase(itr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
itr = writeFds.begin();
|
||||
while (itr != writeFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if (sfd <= 0 || !FD_ISSET(sfd, &writeSet)) {
|
||||
itr = writeFds.erase(itr);
|
||||
} else {
|
||||
LOGV("Socket %d is ready to send", sfd);
|
||||
(*itr)->lastSuccessfulOperationTime = VoIPController::GetCurrentTime();
|
||||
if ((*itr)->OnReadyToSend())
|
||||
++itr;
|
||||
else
|
||||
itr = writeFds.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
itr = errorFds.begin();
|
||||
while (itr != errorFds.end()) {
|
||||
int sfd = GetDescriptorFromSocket(*itr);
|
||||
if ((sfd <= 0 || !FD_ISSET(sfd, &errorSet)) && !(*itr)->IsFailed()) {
|
||||
itr = errorFds.erase(itr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
//LOGV("select fds left: read=%d, write=%d, error=%d", (int)readFds.size(), (int)writeFds.size(), (int)errorFds.size());
|
||||
|
||||
return readFds.size() > 0 || errorFds.size() > 0 || writeFds.size() > 0;
|
||||
}
|
||||
|
||||
SocketSelectCancellerPosix::SocketSelectCancellerPosix(){
|
||||
int p[2];
|
||||
int pipeRes=pipe(p);
|
||||
if(pipeRes!=0){
|
||||
LOGE("pipe() failed");
|
||||
abort();
|
||||
}
|
||||
pipeRead=p[0];
|
||||
pipeWrite=p[1];
|
||||
}
|
||||
|
||||
SocketSelectCancellerPosix::~SocketSelectCancellerPosix(){
|
||||
close(pipeRead);
|
||||
close(pipeWrite);
|
||||
}
|
||||
|
||||
void SocketSelectCancellerPosix::CancelSelect(){
|
||||
char c=1;
|
||||
(void) write(pipeWrite, &c, 1);
|
||||
}
|
||||
|
||||
int NetworkSocketPosix::GetDescriptorFromSocket(NetworkSocket *socket){
|
||||
NetworkSocketPosix* sp=dynamic_cast<NetworkSocketPosix*>(socket);
|
||||
if(sp)
|
||||
return sp->fd;
|
||||
NetworkSocketWrapper* sw=dynamic_cast<NetworkSocketWrapper*>(socket);
|
||||
if(sw)
|
||||
return GetDescriptorFromSocket(sw->GetWrapped());
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
||||
#define LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
||||
|
||||
#include "../../NetworkSocket.h"
|
||||
#include "../../Buffers.h"
|
||||
#include <vector>
|
||||
#include <sys/select.h>
|
||||
#include <pthread.h>
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
class SocketSelectCancellerPosix : public SocketSelectCanceller{
|
||||
friend class NetworkSocketPosix;
|
||||
public:
|
||||
SocketSelectCancellerPosix();
|
||||
virtual ~SocketSelectCancellerPosix();
|
||||
virtual void CancelSelect();
|
||||
private:
|
||||
int pipeRead;
|
||||
int pipeWrite;
|
||||
};
|
||||
|
||||
class NetworkSocketPosix : public NetworkSocket{
|
||||
public:
|
||||
NetworkSocketPosix(NetworkProtocol protocol);
|
||||
virtual ~NetworkSocketPosix();
|
||||
virtual void Send(NetworkPacket* packet) override;
|
||||
virtual void Receive(NetworkPacket* packet) override;
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
virtual void Connect(const NetworkAddress* address, uint16_t port) override;
|
||||
virtual std::string GetLocalInterfaceInfo(IPv4Address* v4addr, IPv6Address* v6addr) override;
|
||||
virtual void OnActiveInterfaceChanged() override;
|
||||
virtual uint16_t GetLocalPort() override;
|
||||
|
||||
static std::string V4AddressToString(uint32_t address);
|
||||
static std::string V6AddressToString(const unsigned char address[16]);
|
||||
static uint32_t StringToV4Address(std::string address);
|
||||
static void StringToV6Address(std::string address, unsigned char* out);
|
||||
static IPv4Address* ResolveDomainName(std::string name);
|
||||
static bool Select(std::vector<NetworkSocket*>& readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*>& errorFds, SocketSelectCanceller* canceller);
|
||||
|
||||
virtual NetworkAddress *GetConnectedAddress() override;
|
||||
|
||||
virtual uint16_t GetConnectedPort() override;
|
||||
|
||||
virtual void SetTimeouts(int sendTimeout, int recvTimeout) override;
|
||||
virtual bool OnReadyToSend() override;
|
||||
|
||||
protected:
|
||||
virtual void SetMaxPriority() override;
|
||||
|
||||
private:
|
||||
static int GetDescriptorFromSocket(NetworkSocket* socket);
|
||||
int fd;
|
||||
bool needUpdateNat64Prefix;
|
||||
bool nat64Present;
|
||||
double switchToV6at;
|
||||
bool isV4Available;
|
||||
bool useTCP;
|
||||
bool closing;
|
||||
IPv4Address lastRecvdV4;
|
||||
IPv6Address lastRecvdV6;
|
||||
NetworkAddress* tcpConnectedAddress;
|
||||
uint16_t tcpConnectedPort;
|
||||
Buffer* pendingOutgoingPacket=NULL;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_NETWORKSOCKETPOSIX_H
|
||||
459
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.cpp
Executable file
459
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.cpp
Executable file
|
|
@ -0,0 +1,459 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include "AudioInputWASAPI.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
|
||||
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if(*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputWASAPI::AudioInputWASAPI(std::string deviceID){
|
||||
isRecording=false;
|
||||
remainingDataLen=0;
|
||||
refCount=1;
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
CHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
|
||||
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
|
||||
#undef CreateEventEx
|
||||
#define CreateEventEx __CreateEventExA
|
||||
#endif
|
||||
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nChannels=1;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.nBlockAlign=2;
|
||||
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
|
||||
format.wBitsPerSample=16;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
|
||||
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
res=enumerator->RegisterEndpointNotificationCallback(this);
|
||||
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
|
||||
audioSessionControl=NULL;
|
||||
device=NULL;
|
||||
#endif
|
||||
|
||||
audioClient=NULL;
|
||||
captureClient=NULL;
|
||||
thread=NULL;
|
||||
started=false;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioInputWASAPI::~AudioInputWASAPI(){
|
||||
if(audioClient && started){
|
||||
audioClient->Stop();
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
SetEvent(shutdownEvent);
|
||||
if(thread){
|
||||
WaitForSingleObjectEx(thread, INFINITE, false);
|
||||
CloseHandle(thread);
|
||||
}
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&captureClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
#endif
|
||||
CloseHandle(shutdownEvent);
|
||||
CloseHandle(audioSamplesReadyEvent);
|
||||
CloseHandle(streamSwitchEvent);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(enumerator)
|
||||
enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
SafeRelease(&enumerator);
|
||||
#endif
|
||||
}
|
||||
void AudioInputWASAPI::Start(){
|
||||
isRecording=true;
|
||||
if(!thread){
|
||||
thread=CreateThread(NULL, 0, AudioInputWASAPI::StartThread, this, 0, NULL);
|
||||
}
|
||||
|
||||
if(audioClient && !started){
|
||||
LOGI("audioClient->Start");
|
||||
audioClient->Start();
|
||||
started=true;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::Stop(){
|
||||
isRecording=false;
|
||||
}
|
||||
|
||||
bool AudioInputWASAPI::IsRecording(){
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
SCHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator *deviceEnumerator = NULL;
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
|
||||
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
|
||||
res=deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
SCHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
SCHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
SCHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* devID;
|
||||
res=device->GetId(&devID);
|
||||
SCHECK_RES(res, "get device id");
|
||||
|
||||
IPropertyStore* propStore;
|
||||
res=device->OpenPropertyStore(STGM_READ, &propStore);
|
||||
SafeRelease(&device);
|
||||
SCHECK_RES(res, "OpenPropertyStore");
|
||||
|
||||
PROPVARIANT friendlyName;
|
||||
PropVariantInit(&friendlyName);
|
||||
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
|
||||
SafeRelease(&propStore);
|
||||
|
||||
AudioInputDevice dev;
|
||||
|
||||
wchar_t actualFriendlyName[128];
|
||||
if(friendlyName.vt==VT_LPWSTR){
|
||||
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
|
||||
}else{
|
||||
wcscpy(actualFriendlyName, L"Unknown");
|
||||
}
|
||||
PropVariantClear(&friendlyName);
|
||||
|
||||
char buf[256];
|
||||
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.id=buf;
|
||||
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.displayName=buf;
|
||||
devs.push_back(dev);
|
||||
|
||||
CoTaskMemFree(devID);
|
||||
}
|
||||
|
||||
SafeRelease(&deviceCollection);
|
||||
SafeRelease(&deviceEnumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::SetCurrentDevice(std::string deviceID){
|
||||
if(thread){
|
||||
streamChangeToDevice=deviceID;
|
||||
SetEvent(streamSwitchEvent);
|
||||
}else{
|
||||
ActuallySetCurrentDevice(deviceID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
HRESULT res;
|
||||
|
||||
if(audioClient){
|
||||
res=audioClient->Stop();
|
||||
CHECK_RES(res, "audioClient->Stop");
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
res=audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
|
||||
}
|
||||
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&captureClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
if(deviceID=="default"){
|
||||
isDefaultDevice=true;
|
||||
res=enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device);
|
||||
CHECK_RES(res, "GetDefaultAudioEndpoint");
|
||||
}else{
|
||||
isDefaultDevice=false;
|
||||
res=enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
CHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
CHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
CHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* _devID;
|
||||
res=device->GetId(&_devID);
|
||||
CHECK_RES(res, "get device id");
|
||||
|
||||
char devID[128];
|
||||
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
|
||||
|
||||
CoTaskMemFree(_devID);
|
||||
if(deviceID==devID){
|
||||
this->device=device;
|
||||
//device->AddRef();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deviceCollection)
|
||||
SafeRelease(&deviceCollection);
|
||||
|
||||
if(!device){
|
||||
LOGE("Didn't find capture device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
|
||||
CHECK_RES(res, "device->Activate");
|
||||
#else
|
||||
std::wstring devID;
|
||||
|
||||
if (deviceID=="default"){
|
||||
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId(Windows::Media::Devices::AudioDeviceRole::Communications);
|
||||
if(defaultDevID==nullptr){
|
||||
LOGE("Didn't find capture device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}else{
|
||||
isDefaultDevice=true;
|
||||
devID=defaultDevID->Data();
|
||||
}
|
||||
}else{
|
||||
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
|
||||
wchar_t* wstr=new wchar_t[wchars_num];
|
||||
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
|
||||
devID=wstr;
|
||||
}
|
||||
|
||||
HRESULT res1, res2;
|
||||
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
|
||||
CHECK_RES(res1, "activate1");
|
||||
CHECK_RES(res2, "activate2");
|
||||
|
||||
AudioClientProperties properties={};
|
||||
properties.cbSize=sizeof AudioClientProperties;
|
||||
properties.eCategory=AudioCategory_Communications;
|
||||
res = audioClient2->SetClientProperties(&properties);
|
||||
CHECK_RES(res, "audioClient2->SetClientProperties");
|
||||
|
||||
audioClient=audioClient2;
|
||||
#endif
|
||||
|
||||
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
|
||||
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
|
||||
// Use 1000ms buffer to avoid resampling glitches on Windows 8.1 and older. This should not increase latency.
|
||||
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 1000*10000, 0, &format, &guid);
|
||||
CHECK_RES(res, "audioClient->Initialize");
|
||||
|
||||
uint32_t bufSize;
|
||||
res = audioClient->GetBufferSize(&bufSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
|
||||
LOGV("buffer size: %u", bufSize);
|
||||
estimatedDelay=0;
|
||||
REFERENCE_TIME latency, devicePeriod;
|
||||
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
|
||||
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
|
||||
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
|
||||
}
|
||||
}
|
||||
|
||||
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
|
||||
CHECK_RES(res, "audioClient->SetEventHandle");
|
||||
|
||||
res = audioClient->GetService(IID_PPV_ARGS(&captureClient));
|
||||
CHECK_RES(res, "audioClient->GetService");
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
|
||||
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
|
||||
|
||||
res=audioSessionControl->RegisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
|
||||
#endif
|
||||
|
||||
if(isRecording)
|
||||
audioClient->Start();
|
||||
|
||||
LOGV("set current input device done");
|
||||
}
|
||||
|
||||
DWORD WINAPI AudioInputWASAPI::StartThread(void* arg) {
|
||||
LOGV("WASAPI capture thread starting");
|
||||
((AudioInputWASAPI*)arg)->RunThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioInputWASAPI::RunThread() {
|
||||
if(failed)
|
||||
return;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
|
||||
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
CHECK_RES(res, "CoInitializeEx in capture thread");
|
||||
|
||||
uint32_t bufferSize=0;
|
||||
uint64_t framesWritten=0;
|
||||
|
||||
bool running=true;
|
||||
//double prevCallback=VoIPController::GetCurrentTime();
|
||||
|
||||
while(running){
|
||||
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
|
||||
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
|
||||
LOGV("capture thread shutting down");
|
||||
running=false;
|
||||
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
|
||||
LOGV("stream switch");
|
||||
ActuallySetCurrentDevice(streamChangeToDevice);
|
||||
ResetEvent(streamSwitchEvent);
|
||||
bufferSize=0;
|
||||
LOGV("stream switch done");
|
||||
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
|
||||
if(!audioClient)
|
||||
continue;
|
||||
res=captureClient->GetNextPacketSize(&bufferSize);
|
||||
CHECK_RES(res, "captureClient->GetNextPacketSize");
|
||||
BYTE* data;
|
||||
uint32_t framesAvailable=0;
|
||||
DWORD flags;
|
||||
|
||||
res=captureClient->GetBuffer(&data, &framesAvailable, &flags, NULL, NULL);
|
||||
CHECK_RES(res, "captureClient->GetBuffer");
|
||||
size_t dataLen=framesAvailable*2;
|
||||
assert(remainingDataLen+dataLen<sizeof(remainingData));
|
||||
|
||||
if(flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY){
|
||||
LOGW("Audio capture data discontinuity");
|
||||
}
|
||||
|
||||
//double t=VoIPController::GetCurrentTime();
|
||||
//LOGV("audio capture: %u, time %f, flags %u", framesAvailable, t-prevCallback, flags);
|
||||
//prevCallback=t;
|
||||
|
||||
memcpy(remainingData+remainingDataLen, data, dataLen);
|
||||
remainingDataLen+=dataLen;
|
||||
while(remainingDataLen>960*2){
|
||||
if(isRecording)
|
||||
InvokeCallback(remainingData, 960*2);
|
||||
|
||||
//LOGV("remaining data len %u", remainingDataLen);
|
||||
memmove(remainingData, remainingData+(960*2), remainingDataLen-960*2);
|
||||
remainingDataLen-=960*2;
|
||||
}
|
||||
|
||||
res=captureClient->ReleaseBuffer(framesAvailable);
|
||||
CHECK_RES(res, "captureClient->ReleaseBuffer");
|
||||
//estimatedDelay=(int32_t)((devicePosition-framesWritten)/48);
|
||||
|
||||
framesWritten+=framesAvailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT AudioInputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
|
||||
if(!isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioInputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
|
||||
if(flow==eCapture && role==eCommunications && isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG AudioInputWASAPI::AddRef(){
|
||||
return InterlockedIncrement(&refCount);
|
||||
}
|
||||
|
||||
ULONG AudioInputWASAPI::Release(){
|
||||
return InterlockedDecrement(&refCount);
|
||||
}
|
||||
|
||||
HRESULT AudioInputWASAPI::QueryInterface(REFIID iid, void** obj){
|
||||
if(!obj){
|
||||
return E_POINTER;
|
||||
}
|
||||
*obj=NULL;
|
||||
|
||||
if(iid==IID_IUnknown){
|
||||
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IMMNotificationClient)){
|
||||
*obj=static_cast<IMMNotificationClient*>(this);
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IAudioSessionEvents)){
|
||||
*obj=static_cast<IAudioSessionEvents*>(this);
|
||||
AddRef();
|
||||
}else{
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
105
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.h
Executable file
105
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWASAPI.h
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTWASAPI_H
|
||||
#define LIBTGVOIP_AUDIOINPUTWASAPI_H
|
||||
|
||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
||||
#define TGVOIP_WINDOWS_PHONE
|
||||
#endif
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
|
||||
#define TGVOIP_WINDOWS_DESKTOP
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4201)
|
||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
||||
#include <mmdeviceapi.h>
|
||||
#endif
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
#include <audiopolicy.h>
|
||||
#include <functiondiscoverykeys.h>
|
||||
#else
|
||||
#include <audioclient.h>
|
||||
#include "WindowsSandboxUtils.h"
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
class AudioInputWASAPI : public AudioInput, IMMNotificationClient, IAudioSessionEvents{
|
||||
#else
|
||||
class AudioInputWASAPI : public AudioInput{
|
||||
#endif
|
||||
|
||||
public:
|
||||
AudioInputWASAPI(std::string deviceID);
|
||||
virtual ~AudioInputWASAPI();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsRecording();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD_(ULONG, AddRef)();
|
||||
STDMETHOD_(ULONG, Release)();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void ActuallySetCurrentDevice(std::string deviceID);
|
||||
static DWORD WINAPI StartThread(void* arg);
|
||||
void RunThread();
|
||||
WAVEFORMATEX format;
|
||||
bool isRecording;
|
||||
HANDLE shutdownEvent;
|
||||
HANDLE audioSamplesReadyEvent;
|
||||
HANDLE streamSwitchEvent;
|
||||
HANDLE thread;
|
||||
IAudioClient* audioClient=NULL;
|
||||
IAudioCaptureClient* captureClient=NULL;
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
IMMDeviceEnumerator* enumerator;
|
||||
IAudioSessionControl* audioSessionControl;
|
||||
IMMDevice* device;
|
||||
#endif
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataLen;
|
||||
bool isDefaultDevice;
|
||||
ULONG refCount;
|
||||
std::string streamChangeToDevice;
|
||||
bool started;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
|
||||
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
|
||||
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
|
||||
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
|
||||
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
|
||||
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
|
||||
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
|
||||
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
|
||||
|
||||
//
|
||||
// IUnknown
|
||||
//
|
||||
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTWASAPI_H
|
||||
170
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.cpp
Executable file
170
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.cpp
Executable file
|
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "AudioInputWave.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_ERROR(res, msg) if(res!=MMSYSERR_NOERROR){wchar_t _buf[1024]; waveInGetErrorTextW(res, _buf, 1024); LOGE(msg ": %ws (MMRESULT=0x%08X)", _buf, res); failed=true;}
|
||||
|
||||
AudioInputWave::AudioInputWave(std::string deviceID){
|
||||
isRecording=false;
|
||||
|
||||
for(int i=0;i<4;i++){
|
||||
ZeroMemory(&buffers[i], sizeof(WAVEHDR));
|
||||
buffers[i].dwBufferLength=960*2;
|
||||
buffers[i].lpData=(char*)malloc(960*2);
|
||||
}
|
||||
|
||||
hWaveIn=NULL;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioInputWave::~AudioInputWave(){
|
||||
for(int i=0;i<4;i++){
|
||||
free(buffers[i].lpData);
|
||||
}
|
||||
waveInClose(hWaveIn);
|
||||
}
|
||||
|
||||
void AudioInputWave::Start(){
|
||||
if(!isRecording){
|
||||
isRecording=true;
|
||||
|
||||
MMRESULT res;
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInPrepareHeader failed");
|
||||
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
res=waveInStart(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStart failed");
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::Stop(){
|
||||
if(isRecording){
|
||||
isRecording=false;
|
||||
|
||||
MMRESULT res=waveInStop(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStop failed");
|
||||
res=waveInReset(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInReset failed");
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInUnprepareHeader failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CALLBACK AudioInputWave::WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2){
|
||||
if(uMsg==WIM_DATA){
|
||||
((AudioInputWave*)dwInstance)->OnData((WAVEHDR*)dwParam1);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::OnData(WAVEHDR* hdr){
|
||||
if(!isRecording)
|
||||
return;
|
||||
|
||||
InvokeCallback((unsigned char*)hdr->lpData, hdr->dwBufferLength);
|
||||
hdr->dwFlags&= ~WHDR_DONE;
|
||||
MMRESULT res=waveInAddBuffer(hWaveIn, hdr, sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
|
||||
void AudioInputWave::EnumerateDevices(std::vector<tgvoip::AudioInputDevice>& devs){
|
||||
UINT num=waveInGetNumDevs();
|
||||
WAVEINCAPSW caps;
|
||||
char nameBuf[512];
|
||||
for(UINT i=0;i<num;i++){
|
||||
waveInGetDevCapsW(i, &caps, sizeof(caps));
|
||||
AudioInputDevice dev;
|
||||
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
|
||||
dev.displayName=std::string(nameBuf);
|
||||
dev.id=std::string(nameBuf);
|
||||
devs.push_back(dev);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInputWave::SetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
|
||||
bool wasRecording=isRecording;
|
||||
isRecording=false;
|
||||
if(hWaveIn){
|
||||
MMRESULT res;
|
||||
if(isRecording){
|
||||
res=waveInStop(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStop failed");
|
||||
res=waveInReset(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInReset failed");
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInUnprepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInUnprepareHeader failed");
|
||||
}
|
||||
}
|
||||
res=waveInClose(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInClose failed");
|
||||
}
|
||||
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.cbSize=0;
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.wBitsPerSample=16;
|
||||
format.nChannels=1;
|
||||
format.nBlockAlign=2;
|
||||
|
||||
LOGV("before open device %s", deviceID.c_str());
|
||||
|
||||
if(deviceID=="default"){
|
||||
MMRESULT res=waveInOpen(&hWaveIn, WAVE_MAPPER, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
||||
CHECK_ERROR(res, "waveInOpen failed");
|
||||
}else{
|
||||
UINT num=waveInGetNumDevs();
|
||||
WAVEINCAPSW caps;
|
||||
char nameBuf[512];
|
||||
hWaveIn=NULL;
|
||||
for(UINT i=0;i<num;i++){
|
||||
waveInGetDevCapsW(i, &caps, sizeof(caps));
|
||||
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
|
||||
std::string name=std::string(nameBuf);
|
||||
if(name==deviceID){
|
||||
MMRESULT res=waveInOpen(&hWaveIn, i, &format, (DWORD_PTR)AudioInputWave::WaveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION | WAVE_MAPPED);
|
||||
CHECK_ERROR(res, "waveInOpen failed");
|
||||
LOGD("Opened device %s", nameBuf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!hWaveIn){
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isRecording=wasRecording;
|
||||
|
||||
if(isRecording){
|
||||
MMRESULT res;
|
||||
for(int i=0;i<4;i++){
|
||||
res=waveInPrepareHeader(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInPrepareHeader failed");
|
||||
res=waveInAddBuffer(hWaveIn, &buffers[i], sizeof(WAVEHDR));
|
||||
CHECK_ERROR(res, "waveInAddBuffer failed");
|
||||
}
|
||||
res=waveInStart(hWaveIn);
|
||||
CHECK_ERROR(res, "waveInStart failed");
|
||||
}
|
||||
}
|
||||
40
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.h
Normal file
40
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioInputWave.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTWAVE_H
|
||||
#define LIBTGVOIP_AUDIOINPUTWAVE_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
class AudioInputWave : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputWave(std::string deviceID);
|
||||
virtual ~AudioInputWave();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
|
||||
private:
|
||||
static void CALLBACK WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
||||
void OnData(WAVEHDR* hdr);
|
||||
HWAVEIN hWaveIn;
|
||||
WAVEFORMATEX format;
|
||||
WAVEHDR buffers[4];
|
||||
bool isRecording;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTWAVE_H
|
||||
455
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.cpp
Executable file
455
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.cpp
Executable file
|
|
@ -0,0 +1,455 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include "AudioOutputWASAPI.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
|
||||
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if(*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
|
||||
#endif
|
||||
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputWASAPI::AudioOutputWASAPI(std::string deviceID){
|
||||
isPlaying=false;
|
||||
remainingDataLen=0;
|
||||
refCount=1;
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
CHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
#ifdef TGVOIP_WINXP_COMPAT
|
||||
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
|
||||
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
|
||||
#undef CreateEventEx
|
||||
#define CreateEventEx __CreateEventExA
|
||||
#endif
|
||||
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
ZeroMemory(&format, sizeof(format));
|
||||
format.wFormatTag=WAVE_FORMAT_PCM;
|
||||
format.nChannels=1;
|
||||
format.nSamplesPerSec=48000;
|
||||
format.nBlockAlign=2;
|
||||
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
|
||||
format.wBitsPerSample=16;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
|
||||
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
res=enumerator->RegisterEndpointNotificationCallback(this);
|
||||
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
|
||||
audioSessionControl=NULL;
|
||||
device=NULL;
|
||||
#endif
|
||||
|
||||
audioClient=NULL;
|
||||
renderClient=NULL;
|
||||
thread=NULL;
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
}
|
||||
|
||||
AudioOutputWASAPI::~AudioOutputWASAPI(){
|
||||
if(audioClient){
|
||||
audioClient->Stop();
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
SetEvent(shutdownEvent);
|
||||
if(thread){
|
||||
WaitForSingleObjectEx(thread, INFINITE, false);
|
||||
CloseHandle(thread);
|
||||
}
|
||||
SafeRelease(&renderClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
CloseHandle(shutdownEvent);
|
||||
CloseHandle(audioSamplesReadyEvent);
|
||||
CloseHandle(streamSwitchEvent);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(enumerator)
|
||||
enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
SafeRelease(&enumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::Start(){
|
||||
isPlaying=true;
|
||||
if(!thread){
|
||||
thread=CreateThread(NULL, 0, AudioOutputWASAPI::StartThread, this, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::Stop(){
|
||||
isPlaying=false;
|
||||
}
|
||||
|
||||
bool AudioOutputWASAPI::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT res;
|
||||
res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if(FAILED(res) && res!=RPC_E_CHANGED_MODE){
|
||||
SCHECK_RES(res, "CoInitializeEx");
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator *deviceEnumerator = NULL;
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
|
||||
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
|
||||
|
||||
res=deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
SCHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
SCHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
SCHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* devID;
|
||||
res=device->GetId(&devID);
|
||||
SCHECK_RES(res, "get device id");
|
||||
|
||||
IPropertyStore* propStore;
|
||||
res=device->OpenPropertyStore(STGM_READ, &propStore);
|
||||
SafeRelease(&device);
|
||||
SCHECK_RES(res, "OpenPropertyStore");
|
||||
|
||||
PROPVARIANT friendlyName;
|
||||
PropVariantInit(&friendlyName);
|
||||
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
|
||||
SafeRelease(&propStore);
|
||||
|
||||
AudioOutputDevice dev;
|
||||
|
||||
wchar_t actualFriendlyName[128];
|
||||
if(friendlyName.vt==VT_LPWSTR){
|
||||
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
|
||||
}else{
|
||||
wcscpy(actualFriendlyName, L"Unknown");
|
||||
}
|
||||
PropVariantClear(&friendlyName);
|
||||
|
||||
char buf[256];
|
||||
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.id=buf;
|
||||
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
|
||||
dev.displayName=buf;
|
||||
devs.push_back(dev);
|
||||
|
||||
CoTaskMemFree(devID);
|
||||
}
|
||||
|
||||
SafeRelease(&deviceCollection);
|
||||
SafeRelease(&deviceEnumerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::SetCurrentDevice(std::string deviceID){
|
||||
if(thread){
|
||||
streamChangeToDevice=deviceID;
|
||||
SetEvent(streamSwitchEvent);
|
||||
}else{
|
||||
ActuallySetCurrentDevice(deviceID);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
|
||||
currentDevice=deviceID;
|
||||
HRESULT res;
|
||||
|
||||
if(audioClient){
|
||||
res=audioClient->Stop();
|
||||
CHECK_RES(res, "audioClient->Stop");
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
if(audioSessionControl){
|
||||
res=audioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
|
||||
}
|
||||
|
||||
SafeRelease(&audioSessionControl);
|
||||
#endif
|
||||
SafeRelease(&renderClient);
|
||||
SafeRelease(&audioClient);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
SafeRelease(&device);
|
||||
|
||||
|
||||
IMMDeviceCollection *deviceCollection = NULL;
|
||||
|
||||
if(deviceID=="default"){
|
||||
isDefaultDevice=true;
|
||||
res=enumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &device);
|
||||
CHECK_RES(res, "GetDefaultAudioEndpoint");
|
||||
}else{
|
||||
isDefaultDevice=false;
|
||||
res=enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
|
||||
CHECK_RES(res, "EnumAudioEndpoints");
|
||||
|
||||
UINT devCount;
|
||||
res=deviceCollection->GetCount(&devCount);
|
||||
CHECK_RES(res, "GetCount");
|
||||
|
||||
for(UINT i=0;i<devCount;i++){
|
||||
IMMDevice* device;
|
||||
res=deviceCollection->Item(i, &device);
|
||||
CHECK_RES(res, "GetDeviceItem");
|
||||
wchar_t* _devID;
|
||||
res=device->GetId(&_devID);
|
||||
CHECK_RES(res, "get device id");
|
||||
|
||||
char devID[128];
|
||||
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
|
||||
|
||||
CoTaskMemFree(_devID);
|
||||
if(deviceID==devID){
|
||||
this->device=device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deviceCollection)
|
||||
SafeRelease(&deviceCollection);
|
||||
|
||||
if(!device){
|
||||
LOGE("Didn't find playback device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}
|
||||
|
||||
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
|
||||
CHECK_RES(res, "device->Activate");
|
||||
#else
|
||||
std::wstring devID;
|
||||
|
||||
if (deviceID=="default"){
|
||||
Platform::String^ defaultDevID=Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId(Windows::Media::Devices::AudioDeviceRole::Communications);
|
||||
if(defaultDevID==nullptr){
|
||||
LOGE("Didn't find playback device; failing");
|
||||
failed=true;
|
||||
return;
|
||||
}else{
|
||||
isDefaultDevice=true;
|
||||
devID=defaultDevID->Data();
|
||||
}
|
||||
}else{
|
||||
int wchars_num=MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, NULL, 0);
|
||||
wchar_t* wstr=new wchar_t[wchars_num];
|
||||
MultiByteToWideChar(CP_UTF8, 0, deviceID.c_str(), -1, wstr, wchars_num);
|
||||
devID=wstr;
|
||||
}
|
||||
|
||||
HRESULT res1, res2;
|
||||
IAudioClient2* audioClient2=WindowsSandboxUtils::ActivateAudioDevice(devID.c_str(), &res1, &res2);
|
||||
CHECK_RES(res1, "activate1");
|
||||
CHECK_RES(res2, "activate2");
|
||||
|
||||
AudioClientProperties properties={};
|
||||
properties.cbSize=sizeof AudioClientProperties;
|
||||
properties.eCategory=AudioCategory_Communications;
|
||||
res = audioClient2->SetClientProperties(&properties);
|
||||
CHECK_RES(res, "audioClient2->SetClientProperties");
|
||||
|
||||
audioClient = audioClient2;
|
||||
#endif
|
||||
|
||||
// {2C693079-3F59-49FD-964F-61C005EAA5D3}
|
||||
const GUID guid = { 0x2c693079, 0x3f59, 0x49fd, { 0x96, 0x4f, 0x61, 0xc0, 0x5, 0xea, 0xa5, 0xd3 } };
|
||||
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 60 * 10000, 0, &format, &guid);
|
||||
CHECK_RES(res, "audioClient->Initialize");
|
||||
|
||||
uint32_t bufSize;
|
||||
res = audioClient->GetBufferSize(&bufSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
|
||||
LOGV("buffer size: %u", bufSize);
|
||||
estimatedDelay=0;
|
||||
REFERENCE_TIME latency, devicePeriod;
|
||||
if(SUCCEEDED(audioClient->GetStreamLatency(&latency))){
|
||||
if(SUCCEEDED(audioClient->GetDevicePeriod(&devicePeriod, NULL))){
|
||||
estimatedDelay=(int32_t)(latency/10000+devicePeriod/10000);
|
||||
}
|
||||
}
|
||||
|
||||
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
|
||||
CHECK_RES(res, "audioClient->SetEventHandle");
|
||||
|
||||
res = audioClient->GetService(IID_PPV_ARGS(&renderClient));
|
||||
CHECK_RES(res, "audioClient->GetService");
|
||||
|
||||
BYTE* data;
|
||||
res = renderClient->GetBuffer(bufSize, &data);
|
||||
CHECK_RES(res, "renderClient->GetBuffer");
|
||||
|
||||
res = renderClient->ReleaseBuffer(bufSize, AUDCLNT_BUFFERFLAGS_SILENT);
|
||||
CHECK_RES(res, "renderClient->ReleaseBuffer");
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
|
||||
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
|
||||
|
||||
res=audioSessionControl->RegisterAudioSessionNotification(this);
|
||||
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
|
||||
#endif
|
||||
|
||||
audioClient->Start();
|
||||
|
||||
LOGV("set current output device done");
|
||||
}
|
||||
|
||||
DWORD WINAPI AudioOutputWASAPI::StartThread(void* arg) {
|
||||
((AudioOutputWASAPI*)arg)->RunThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioOutputWASAPI::RunThread() {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
|
||||
HRESULT res=CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
CHECK_RES(res, "CoInitializeEx in render thread");
|
||||
|
||||
uint32_t bufferSize;
|
||||
res=audioClient->GetBufferSize(&bufferSize);
|
||||
CHECK_RES(res, "audioClient->GetBufferSize");
|
||||
uint64_t framesWritten=0;
|
||||
|
||||
bool running=true;
|
||||
//double prevCallback=VoIPController::GetCurrentTime();
|
||||
|
||||
while(running){
|
||||
DWORD waitResult=WaitForMultipleObjectsEx(3, waitArray, false, INFINITE, false);
|
||||
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
|
||||
LOGV("render thread shutting down");
|
||||
running=false;
|
||||
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
|
||||
LOGV("stream switch");
|
||||
ActuallySetCurrentDevice(streamChangeToDevice);
|
||||
ResetEvent(streamSwitchEvent);
|
||||
LOGV("stream switch done");
|
||||
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
|
||||
if(!audioClient)
|
||||
continue;
|
||||
|
||||
BYTE* data;
|
||||
uint32_t padding;
|
||||
uint32_t framesAvailable;
|
||||
res=audioClient->GetCurrentPadding(&padding);
|
||||
CHECK_RES(res, "audioClient->GetCurrentPadding");
|
||||
framesAvailable=bufferSize-padding;
|
||||
res=renderClient->GetBuffer(framesAvailable, &data);
|
||||
CHECK_RES(res, "renderClient->GetBuffer");
|
||||
|
||||
//double t=VoIPController::GetCurrentTime();
|
||||
//LOGV("framesAvail: %u, time: %f, isPlaying: %d", framesAvailable, t-prevCallback, isPlaying);
|
||||
//prevCallback=t;
|
||||
|
||||
size_t bytesAvailable=framesAvailable*2;
|
||||
while(bytesAvailable>remainingDataLen){
|
||||
if(isPlaying){
|
||||
InvokeCallback(remainingData+remainingDataLen, 960*2);
|
||||
}else{
|
||||
memset(remainingData+remainingDataLen, 0, 960*2);
|
||||
}
|
||||
remainingDataLen+=960*2;
|
||||
}
|
||||
memcpy(data, remainingData, bytesAvailable);
|
||||
if(remainingDataLen>bytesAvailable){
|
||||
memmove(remainingData, remainingData+bytesAvailable, remainingDataLen-bytesAvailable);
|
||||
}
|
||||
remainingDataLen-=bytesAvailable;
|
||||
|
||||
res=renderClient->ReleaseBuffer(framesAvailable, 0);
|
||||
CHECK_RES(res, "renderClient->ReleaseBuffer");
|
||||
framesWritten+=framesAvailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
HRESULT AudioOutputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
|
||||
if(!isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioOutputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
|
||||
if(flow==eRender && role==eCommunications && isDefaultDevice){
|
||||
streamChangeToDevice="default";
|
||||
SetEvent(streamSwitchEvent);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG AudioOutputWASAPI::AddRef(){
|
||||
return InterlockedIncrement(&refCount);
|
||||
}
|
||||
|
||||
ULONG AudioOutputWASAPI::Release(){
|
||||
return InterlockedDecrement(&refCount);
|
||||
}
|
||||
|
||||
HRESULT AudioOutputWASAPI::QueryInterface(REFIID iid, void** obj){
|
||||
if(!obj){
|
||||
return E_POINTER;
|
||||
}
|
||||
*obj=NULL;
|
||||
|
||||
if(iid==IID_IUnknown){
|
||||
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IMMNotificationClient)){
|
||||
*obj=static_cast<IMMNotificationClient*>(this);
|
||||
AddRef();
|
||||
}else if(iid==__uuidof(IAudioSessionEvents)){
|
||||
*obj=static_cast<IAudioSessionEvents*>(this);
|
||||
AddRef();
|
||||
}else{
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
103
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.h
Executable file
103
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWASAPI.h
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTWASAPI_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTWASAPI_H
|
||||
|
||||
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
|
||||
#define TGVOIP_WINDOWS_PHONE
|
||||
#endif
|
||||
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP
|
||||
#define TGVOIP_WINDOWS_DESKTOP
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4201)
|
||||
#ifndef TGVOIP_WP_SILVERLIGHT
|
||||
#include <mmdeviceapi.h>
|
||||
#endif
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
#include <audiopolicy.h>
|
||||
#include <functiondiscoverykeys.h>
|
||||
#else
|
||||
#include <audioclient.h>
|
||||
#include "WindowsSandboxUtils.h"
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{
|
||||
namespace audio{
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
class AudioOutputWASAPI : public AudioOutput, IMMNotificationClient, IAudioSessionEvents{
|
||||
#else
|
||||
class AudioOutputWASAPI : public AudioOutput{
|
||||
#endif
|
||||
public:
|
||||
AudioOutputWASAPI(std::string deviceID);
|
||||
virtual ~AudioOutputWASAPI();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD_(ULONG, AddRef)();
|
||||
STDMETHOD_(ULONG, Release)();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void ActuallySetCurrentDevice(std::string deviceID);
|
||||
static DWORD WINAPI StartThread(void* arg);
|
||||
void RunThread();
|
||||
WAVEFORMATEX format;
|
||||
bool isPlaying;
|
||||
HANDLE shutdownEvent;
|
||||
HANDLE audioSamplesReadyEvent;
|
||||
HANDLE streamSwitchEvent;
|
||||
HANDLE thread;
|
||||
IAudioClient* audioClient=NULL;
|
||||
IAudioRenderClient* renderClient=NULL;
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
IMMDeviceEnumerator* enumerator;
|
||||
IAudioSessionControl* audioSessionControl;
|
||||
IMMDevice* device;
|
||||
#endif
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataLen;
|
||||
bool isDefaultDevice;
|
||||
ULONG refCount;
|
||||
std::string streamChangeToDevice;
|
||||
|
||||
#ifdef TGVOIP_WINDOWS_DESKTOP
|
||||
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }
|
||||
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
|
||||
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; };
|
||||
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
|
||||
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; }
|
||||
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; };
|
||||
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; };
|
||||
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
|
||||
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; };
|
||||
|
||||
//
|
||||
// IUnknown
|
||||
//
|
||||
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTWASAPI_H
|
||||
165
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWave.cpp
Executable file
165
TMessagesProj/jni/voip/libtgvoip/os/windows/AudioOutputWave.cpp
Executable 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
469
TMessagesProj/jni/voip/libtgvoip/os/windows/CXWrapper.cpp
Executable file
469
TMessagesProj/jni/voip/libtgvoip/os/windows/CXWrapper.cpp
Executable 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;
|
||||
}*/
|
||||
273
TMessagesProj/jni/voip/libtgvoip/os/windows/CXWrapper.h
Normal file
273
TMessagesProj/jni/voip/libtgvoip/os/windows/CXWrapper.h
Normal 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;
|
||||
};
|
||||
}
|
||||
719
TMessagesProj/jni/voip/libtgvoip/os/windows/NetworkSocketWinsock.cpp
Executable file
719
TMessagesProj/jni/voip/libtgvoip/os/windows/NetworkSocketWinsock.cpp
Executable 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;
|
||||
}
|
||||
72
TMessagesProj/jni/voip/libtgvoip/os/windows/NetworkSocketWinsock.h
Executable file
72
TMessagesProj/jni/voip/libtgvoip/os/windows/NetworkSocketWinsock.h
Executable 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
9
TMessagesProj/jni/voip/libtgvoip/os/windows/WindowsSpecific.cpp
Executable file
9
TMessagesProj/jni/voip/libtgvoip/os/windows/WindowsSpecific.cpp
Executable 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);
|
||||
}
|
||||
16
TMessagesProj/jni/voip/libtgvoip/os/windows/WindowsSpecific.h
Executable file
16
TMessagesProj/jni/voip/libtgvoip/os/windows/WindowsSpecific.h
Executable 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue