Repo created

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,165 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include "AudioOutputWave.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) if(res!=MMSYSERR_NOERROR){wchar_t _buf[1024]; waveOutGetErrorTextW(res, _buf, 1024); LOGE(msg ": %ws (MMRESULT=0x%08X)", _buf, res); failed=true;}
using namespace tgvoip::audio;
AudioOutputWave::AudioOutputWave(std::string deviceID){
isPlaying=false;
hWaveOut=NULL;
for(int i=0;i<4;i++){
ZeroMemory(&buffers[i], sizeof(WAVEHDR));
buffers[i].dwBufferLength=960*2;
buffers[i].lpData=(char*)malloc(960*2);
}
SetCurrentDevice(deviceID);
}
AudioOutputWave::~AudioOutputWave(){
for(int i=0;i<4;i++){
free(buffers[i].lpData);
}
waveOutClose(hWaveOut);
}
void AudioOutputWave::Start(){
if(!isPlaying){
isPlaying=true;
for(int i=0;i<4;i++){
MMRESULT res=waveOutPrepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutPrepareHeader failed");
//InvokeCallback((unsigned char*)buffers[i].lpData, buffers[i].dwBufferLength);
ZeroMemory(buffers[i].lpData, buffers[i].dwBufferLength);
res=waveOutWrite(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutWrite failed");
}
}
}
void AudioOutputWave::Stop(){
if(isPlaying){
isPlaying=false;
MMRESULT res=waveOutReset(hWaveOut);
CHECK_ERROR(res, "waveOutReset failed");
for(int i=0;i<4;i++){
res=waveOutUnprepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutUnprepareHeader failed");
}
}
}
bool AudioOutputWave::IsPlaying(){
return isPlaying;
}
void AudioOutputWave::WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if(uMsg==WOM_DONE){
((AudioOutputWave*)dwInstance)->OnBufferDone((WAVEHDR*)dwParam1);
}
}
void AudioOutputWave::OnBufferDone(WAVEHDR* hdr){
if(!isPlaying)
return;
InvokeCallback((unsigned char*)hdr->lpData, hdr->dwBufferLength);
hdr->dwFlags&= ~WHDR_DONE;
MMRESULT res=waveOutWrite(hWaveOut, hdr, sizeof(WAVEHDR));
}
void AudioOutputWave::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
UINT num=waveOutGetNumDevs();
WAVEOUTCAPSW caps;
char nameBuf[512];
for(UINT i=0;i<num;i++){
waveOutGetDevCapsW(i, &caps, sizeof(caps));
AudioOutputDevice dev;
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
dev.displayName=std::string(nameBuf);
dev.id=std::string(nameBuf);
devs.push_back(dev);
}
}
void AudioOutputWave::SetCurrentDevice(std::string deviceID){
currentDevice=deviceID;
bool wasPlaying=isPlaying;
isPlaying=false;
LOGV("closing, hWaveOut=%d", (int)hWaveOut);
if(hWaveOut){
MMRESULT res;
if(isPlaying){
res=waveOutReset(hWaveOut);
CHECK_ERROR(res, "waveOutReset failed");
for(int i=0;i<4;i++){
res=waveOutUnprepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutUnprepareHeader failed");
}
}
res=waveOutClose(hWaveOut);
CHECK_ERROR(res, "waveOutClose failed");
}
ZeroMemory(&format, sizeof(format));
format.cbSize=0;
format.wFormatTag=WAVE_FORMAT_PCM;
format.nSamplesPerSec=48000;
format.wBitsPerSample=16;
format.nChannels=1;
format.nBlockAlign=2;
LOGV("before open device %s", deviceID.c_str());
if(deviceID=="default"){
MMRESULT res=waveOutOpen(&hWaveOut, WAVE_MAPPER, &format, (DWORD_PTR)AudioOutputWave::WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
CHECK_ERROR(res, "waveOutOpen failed");
}else{
UINT num=waveOutGetNumDevs();
WAVEOUTCAPSW caps;
char nameBuf[512];
hWaveOut=NULL;
for(UINT i=0;i<num;i++){
waveOutGetDevCapsW(i, &caps, sizeof(caps));
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nameBuf, sizeof(nameBuf), NULL, NULL);
std::string name=std::string(nameBuf);
if(name==deviceID){
MMRESULT res=waveOutOpen(&hWaveOut, i, &format, (DWORD_PTR)AudioOutputWave::WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION | WAVE_MAPPED);
CHECK_ERROR(res, "waveOutOpen failed");
LOGD("Opened device %s", nameBuf);
break;
}
}
if(!hWaveOut){
SetCurrentDevice("default");
return;
}
}
isPlaying=wasPlaying;
if(isPlaying){
MMRESULT res;
for(int i=0;i<4;i++){
res=waveOutPrepareHeader(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutPrepareHeader failed");
res=waveOutWrite(hWaveOut, &buffers[i], sizeof(WAVEHDR));
CHECK_ERROR(res, "waveOutWrite failed");
}
}
}

View file

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

View file

@ -0,0 +1,469 @@
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <vector>
#include <string>
#include <collection.h>
#include "CXWrapper.h"
#include <wrl.h>
#include <robuffer.h>
using namespace Windows::Storage::Streams;
using namespace Microsoft::WRL;
using namespace libtgvoip;
using namespace Platform;
using namespace tgvoip;
using namespace Windows::Security::Cryptography;
using namespace Windows::Security::Cryptography::Core;
using namespace Windows::Storage::Streams;
using namespace Windows::Data::Json;
using namespace Windows::Phone::Media::Devices;
//CryptographicHash^ MicrosoftCryptoImpl::sha1Hash;
//CryptographicHash^ MicrosoftCryptoImpl::sha256Hash;
HashAlgorithmProvider^ MicrosoftCryptoImpl::sha1Provider;
HashAlgorithmProvider^ MicrosoftCryptoImpl::sha256Provider;
SymmetricKeyAlgorithmProvider^ MicrosoftCryptoImpl::aesKeyProvider;
/*struct tgvoip_cx_data{
VoIPControllerWrapper^ self;
};*/
VoIPControllerWrapper::VoIPControllerWrapper(){
VoIPController::crypto.aes_ige_decrypt=MicrosoftCryptoImpl::AesIgeDecrypt;
VoIPController::crypto.aes_ige_encrypt=MicrosoftCryptoImpl::AesIgeEncrypt;
VoIPController::crypto.aes_ctr_encrypt = MicrosoftCryptoImpl::AesCtrEncrypt;
VoIPController::crypto.sha1=MicrosoftCryptoImpl::SHA1;
VoIPController::crypto.sha256=MicrosoftCryptoImpl::SHA256;
VoIPController::crypto.rand_bytes=MicrosoftCryptoImpl::RandBytes;
MicrosoftCryptoImpl::Init();
controller=new VoIPController();
controller->implData=(void*)this;
VoIPController::Callbacks callbacks={0};
callbacks.connectionStateChanged=VoIPControllerWrapper::OnStateChanged;
callbacks.signalBarCountChanged=VoIPControllerWrapper::OnSignalBarsChanged;
controller->SetCallbacks(callbacks);
}
VoIPControllerWrapper::~VoIPControllerWrapper(){
controller->Stop();
delete controller;
}
void VoIPControllerWrapper::Start(){
controller->Start();
}
void VoIPControllerWrapper::Connect(){
controller->Connect();
}
void VoIPControllerWrapper::SetPublicEndpoints(const Platform::Array<libtgvoip::Endpoint^>^ endpoints, bool allowP2P, int32_t connectionMaxLayer){
std::vector<tgvoip::Endpoint> eps;
for (unsigned int i = 0; i < endpoints->Length; i++)
{
libtgvoip::Endpoint^ _ep = endpoints[i];
tgvoip::Endpoint ep;
ep.id = _ep->id;
ep.type = tgvoip::Endpoint::Type::UDP_RELAY;
char buf[128];
if (_ep->ipv4){
WideCharToMultiByte(CP_UTF8, 0, _ep->ipv4->Data(), -1, buf, sizeof(buf), NULL, NULL);
ep.address = IPv4Address(buf);
}
if (_ep->ipv6){
WideCharToMultiByte(CP_UTF8, 0, _ep->ipv6->Data(), -1, buf, sizeof(buf), NULL, NULL);
ep.v6address = IPv6Address(buf);
}
ep.port = _ep->port;
if (_ep->peerTag->Length != 16)
throw ref new Platform::InvalidArgumentException("Peer tag must be exactly 16 bytes long");
memcpy(ep.peerTag, _ep->peerTag->Data, 16);
eps.push_back(ep);
}
controller->SetRemoteEndpoints(eps, allowP2P, connectionMaxLayer);
}
void VoIPControllerWrapper::SetNetworkType(NetworkType type){
controller->SetNetworkType((int)type);
}
void VoIPControllerWrapper::SetMicMute(bool mute){
controller->SetMicMute(mute);
}
int64 VoIPControllerWrapper::GetPreferredRelayID(){
return controller->GetPreferredRelayID();
}
int32_t VoIPControllerWrapper::GetConnectionMaxLayer(){
return tgvoip::VoIPController::GetConnectionMaxLayer();
}
void VoIPControllerWrapper::SetEncryptionKey(const Platform::Array<uint8>^ key, bool isOutgoing){
if(key->Length!=256)
throw ref new Platform::InvalidArgumentException("Encryption key must be exactly 256 bytes long");
controller->SetEncryptionKey((char*)key->Data, isOutgoing);
}
int VoIPControllerWrapper::GetSignalBarsCount(){
return controller->GetSignalBarsCount();
}
CallState VoIPControllerWrapper::GetConnectionState(){
return (CallState)controller->GetConnectionState();
}
TrafficStats^ VoIPControllerWrapper::GetStats(){
tgvoip::VoIPController::TrafficStats _stats;
controller->GetStats(&_stats);
TrafficStats^ stats = ref new TrafficStats();
stats->bytesSentWifi = _stats.bytesSentWifi;
stats->bytesSentMobile = _stats.bytesSentMobile;
stats->bytesRecvdWifi = _stats.bytesRecvdWifi;
stats->bytesRecvdMobile = _stats.bytesRecvdMobile;
return stats;
}
Platform::String^ VoIPControllerWrapper::GetDebugString(){
std::string log = controller->GetDebugString();
size_t len = sizeof(wchar_t)*(log.length() + 1);
wchar_t* wlog = (wchar_t*)malloc(len);
MultiByteToWideChar(CP_UTF8, 0, log.c_str(), -1, wlog, len / sizeof(wchar_t));
Platform::String^ res = ref new Platform::String(wlog);
free(wlog);
return res;
}
Platform::String^ VoIPControllerWrapper::GetDebugLog(){
std::string log=controller->GetDebugLog();
size_t len=sizeof(wchar_t)*(log.length()+1);
wchar_t* wlog=(wchar_t*)malloc(len);
MultiByteToWideChar(CP_UTF8, 0, log.c_str(), -1, wlog, len/sizeof(wchar_t));
Platform::String^ res=ref new Platform::String(wlog);
free(wlog);
return res;
}
Error VoIPControllerWrapper::GetLastError(){
return (Error)controller->GetLastError();
}
Platform::String^ VoIPControllerWrapper::GetVersion(){
const char* v=VoIPController::GetVersion();
wchar_t buf[32];
MultiByteToWideChar(CP_UTF8, 0, v, -1, buf, sizeof(buf));
return ref new Platform::String(buf);
}
void VoIPControllerWrapper::OnStateChanged(VoIPController* c, int state){
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnStateChangedInternal(state);
}
void VoIPControllerWrapper::OnSignalBarsChanged(VoIPController* c, int count){
reinterpret_cast<VoIPControllerWrapper^>(c->implData)->OnSignalBarsChangedInternal(count);
}
void VoIPControllerWrapper::OnStateChangedInternal(int state){
CallStateChanged(this, (CallState)state);
}
void VoIPControllerWrapper::OnSignalBarsChangedInternal(int count){
SignalBarsChanged(this, count);
}
void VoIPControllerWrapper::SetConfig(VoIPConfig^ wrapper){
VoIPController::Config config{0};
config.initTimeout=wrapper->initTimeout;
config.recvTimeout=wrapper->recvTimeout;
config.dataSaving=(int)wrapper->dataSaving;
config.logFilePath;
config.statsDumpFilePath;
config.enableAEC=wrapper->enableAEC;
config.enableNS=wrapper->enableNS;
config.enableAGC=wrapper->enableAGC;
config.enableCallUpgrade=wrapper->enableCallUpgrade;
config.logPacketStats=wrapper->logPacketStats;
config.enableVolumeControl=wrapper->enableVolumeControl;
config.enableVideoSend=wrapper->enableVideoSend;
config.enableVideoReceive=wrapper->enableVideoReceive;
if(wrapper->logFilePath!=nullptr&&!wrapper->logFilePath->IsEmpty()){
config.logFilePath = wstring(wrapper->logFilePath->Data());
}
if (wrapper->statsDumpFilePath != nullptr&&!wrapper->statsDumpFilePath->IsEmpty()){
config.statsDumpFilePath = wstring(wrapper->statsDumpFilePath->Data());
}
controller->SetConfig(config);
}
void VoIPControllerWrapper::SetProxy(ProxyProtocol protocol, Platform::String^ address, uint16_t port, Platform::String^ username, Platform::String^ password){
char _address[2000];
char _username[256];
char _password[256];
WideCharToMultiByte(CP_UTF8, 0, address->Data(), -1, _address, sizeof(_address), NULL, NULL);
WideCharToMultiByte(CP_UTF8, 0, username->Data(), -1, _username, sizeof(_username), NULL, NULL);
WideCharToMultiByte(CP_UTF8, 0, password->Data(), -1, _password, sizeof(_password), NULL, NULL);
controller->SetProxy((int)protocol, _address, port, _username, _password);
}
void VoIPControllerWrapper::SetAudioOutputGainControlEnabled(bool enabled){
controller->SetAudioOutputGainControlEnabled(enabled);
}
void VoIPControllerWrapper::SetInputVolume(float level){
controller->SetInputVolume(level);
}
void VoIPControllerWrapper::SetOutputVolume(float level){
controller->SetOutputVolume(level);
}
void VoIPControllerWrapper::UpdateServerConfig(Platform::String^ json){
std::string config=ToUtf8(json->Data(), json->Length());
ServerConfig::GetSharedInstance()->Update(config);
}
void VoIPControllerWrapper::SwitchSpeaker(bool external){
auto routingManager = AudioRoutingManager::GetDefault();
if (external){
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Speakerphone);
}
else{
if ((routingManager->AvailableAudioEndpoints & AvailableAudioRoutingEndpoints::Bluetooth) == AvailableAudioRoutingEndpoints::Bluetooth){
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Bluetooth);
}
else if ((routingManager->AvailableAudioEndpoints & AvailableAudioRoutingEndpoints::Earpiece) == AvailableAudioRoutingEndpoints::Earpiece){
routingManager->SetAudioEndpoint(AudioRoutingEndpoint::Earpiece);
}
}
}
void MicrosoftCryptoImpl::AesIgeEncrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv){
IBuffer^ keybuf=IBufferFromPtr(key, 32);
CryptographicKey^ _key=aesKeyProvider->CreateSymmetricKey(keybuf);
uint8_t tmpOut[16];
uint8_t* xPrev=iv+16;
uint8_t* yPrev=iv;
uint8_t x[16];
uint8_t y[16];
for(size_t offset=0;offset<len;offset+=16){
for (size_t i=0;i<16;i++){
if (offset+i < len){
x[i] = in[offset+i];
}
else{
x[i]=0;
}
}
XorInt128(x, yPrev, y);
IBuffer^ inbuf=IBufferFromPtr(y, 16);
IBuffer^ outbuf=CryptographicEngine::Encrypt(_key, inbuf, nullptr);
IBufferToPtr(outbuf, 16, tmpOut);
XorInt128(tmpOut, xPrev, y);
memcpy(xPrev, x, 16);
memcpy(yPrev, y, 16);
memcpy(out+offset, y, 16);
}
}
void MicrosoftCryptoImpl::AesIgeDecrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv){
IBuffer^ keybuf=IBufferFromPtr(key, 32);
CryptographicKey^ _key=aesKeyProvider->CreateSymmetricKey(keybuf);
uint8_t tmpOut[16];
uint8_t* xPrev=iv;
uint8_t* yPrev=iv+16;
uint8_t x[16];
uint8_t y[16];
for(size_t offset=0;offset<len;offset+=16){
for (size_t i=0;i<16;i++){
if (offset+i < len){
x[i] = in[offset+i];
}
else{
x[i]=0;
}
}
XorInt128(x, yPrev, y);
IBuffer^ inbuf=IBufferFromPtr(y, 16);
IBuffer^ outbuf=CryptographicEngine::Decrypt(_key, inbuf, nullptr);
IBufferToPtr(outbuf, 16, tmpOut);
XorInt128(tmpOut, xPrev, y);
memcpy(xPrev, x, 16);
memcpy(yPrev, y, 16);
memcpy(out+offset, y, 16);
}
}
#define GETU32(pt) (((uint32_t)(pt)[0] << 24) ^ ((uint32_t)(pt)[1] << 16) ^ ((uint32_t)(pt)[2] << 8) ^ ((uint32_t)(pt)[3]))
#define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
typedef uint8_t u8;
#define L_ENDIAN
/* increment counter (128-bit int) by 2^64 */
static void AES_ctr128_inc(unsigned char *counter) {
unsigned long c;
/* Grab 3rd dword of counter and increment */
#ifdef L_ENDIAN
c = GETU32(counter + 8);
c++;
PUTU32(counter + 8, c);
#else
c = GETU32(counter + 4);
c++;
PUTU32(counter + 4, c);
#endif
/* if no overflow, we're done */
if (c)
return;
/* Grab top dword of counter and increment */
#ifdef L_ENDIAN
c = GETU32(counter + 12);
c++;
PUTU32(counter + 12, c);
#else
c = GETU32(counter + 0);
c++;
PUTU32(counter + 0, c);
#endif
}
void MicrosoftCryptoImpl::AesCtrEncrypt(uint8_t* inout, size_t len, uint8_t* key, uint8_t* counter, uint8_t* ecount_buf, uint32_t* num){
unsigned int n;
unsigned long l = len;
//assert(in && out && key && counter && num);
//assert(*num < AES_BLOCK_SIZE);
IBuffer^ keybuf = IBufferFromPtr(key, 32);
CryptographicKey^ _key = aesKeyProvider->CreateSymmetricKey(keybuf);
n = *num;
while (l--) {
if (n == 0) {
IBuffer^ inbuf = IBufferFromPtr(counter, 16);
IBuffer^ outbuf = CryptographicEngine::Encrypt(_key, inbuf, nullptr);
IBufferToPtr(outbuf, 16, ecount_buf);
//AES_encrypt(counter, ecount_buf, key);
AES_ctr128_inc(counter);
}
*inout = *(inout++) ^ ecount_buf[n];
n = (n + 1) % 16;
}
*num = n;
}
void MicrosoftCryptoImpl::SHA1(uint8_t* msg, size_t len, uint8_t* out){
//EnterCriticalSection(&hashMutex);
IBuffer^ arr=IBufferFromPtr(msg, len);
CryptographicHash^ hash=sha1Provider->CreateHash();
hash->Append(arr);
IBuffer^ res=hash->GetValueAndReset();
IBufferToPtr(res, 20, out);
//LeaveCriticalSection(&hashMutex);
}
void MicrosoftCryptoImpl::SHA256(uint8_t* msg, size_t len, uint8_t* out){
//EnterCriticalSection(&hashMutex);
IBuffer^ arr=IBufferFromPtr(msg, len);
CryptographicHash^ hash=sha256Provider->CreateHash();
hash->Append(arr);
IBuffer^ res=hash->GetValueAndReset();
IBufferToPtr(res, 32, out);
//LeaveCriticalSection(&hashMutex);
}
void MicrosoftCryptoImpl::RandBytes(uint8_t* buffer, size_t len){
IBuffer^ res=CryptographicBuffer::GenerateRandom(len);
IBufferToPtr(res, len, buffer);
}
void MicrosoftCryptoImpl::Init(){
/*sha1Hash=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1)->CreateHash();
sha256Hash=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256)->CreateHash();*/
sha1Provider=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1);
sha256Provider=HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256);
aesKeyProvider=SymmetricKeyAlgorithmProvider::OpenAlgorithm(SymmetricAlgorithmNames::AesEcb);
}
void MicrosoftCryptoImpl::XorInt128(uint8_t* a, uint8_t* b, uint8_t* out){
uint64_t* _a=reinterpret_cast<uint64_t*>(a);
uint64_t* _b=reinterpret_cast<uint64_t*>(b);
uint64_t* _out=reinterpret_cast<uint64_t*>(out);
_out[0]=_a[0]^_b[0];
_out[1]=_a[1]^_b[1];
}
void MicrosoftCryptoImpl::IBufferToPtr(IBuffer^ buffer, size_t len, uint8_t* out)
{
ComPtr<IBufferByteAccess> bufferByteAccess;
reinterpret_cast<IInspectable*>(buffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess));
byte* hashBuffer;
bufferByteAccess->Buffer(&hashBuffer);
CopyMemory(out, hashBuffer, len);
}
IBuffer^ MicrosoftCryptoImpl::IBufferFromPtr(uint8_t* msg, size_t len)
{
ComPtr<NativeBuffer> nativeBuffer=Make<NativeBuffer>((byte *)msg, len);
return reinterpret_cast<IBuffer^>(nativeBuffer.Get());
}
/*Platform::String^ VoIPControllerWrapper::TestAesIge(){
MicrosoftCryptoImpl::Init();
Platform::String^ res="";
Platform::Array<uint8>^ data=ref new Platform::Array<uint8>(32);
Platform::Array<uint8>^ out=ref new Platform::Array<uint8>(32);
Platform::Array<uint8>^ key=ref new Platform::Array<uint8>(16);
Platform::Array<uint8>^ iv=ref new Platform::Array<uint8>(32);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("0000000000000000000000000000000000000000000000000000000000000000"), &data);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), &iv);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f"), &key);
MicrosoftCryptoImpl::AesIgeEncrypt(data->Data, out->Data, 32, key->Data, iv->Data);
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
res+="\n";
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("1A8519A6557BE652E9DA8E43DA4EF4453CF456B4CA488AA383C79C98B34797CB"), &data);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), &iv);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("000102030405060708090a0b0c0d0e0f"), &key);
MicrosoftCryptoImpl::AesIgeDecrypt(data->Data, out->Data, 32, key->Data, iv->Data);
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
res+="\n";
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("99706487A1CDE613BC6DE0B6F24B1C7AA448C8B9C3403E3467A8CAD89340F53B"), &data);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353"), &iv);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("5468697320697320616E20696D706C65"), &key);
MicrosoftCryptoImpl::AesIgeEncrypt(data->Data, out->Data, 32, key->Data, iv->Data);
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
res+="\n";
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("4C2E204C6574277320686F70652042656E20676F74206974207269676874210A"), &data);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353"), &iv);
CryptographicBuffer::CopyToByteArray(CryptographicBuffer::DecodeFromHexString("5468697320697320616E20696D706C65"), &key);
MicrosoftCryptoImpl::AesIgeDecrypt(data->Data, out->Data, 32, key->Data, iv->Data);
res+=CryptographicBuffer::EncodeToHexString(CryptographicBuffer::CreateFromByteArray(out));
return res;
}*/

View file

@ -0,0 +1,273 @@
#pragma once
#include <wrl.h>
#include <wrl/implements.h>
#include <windows.storage.streams.h>
#include <robuffer.h>
#include <vector>
#include "../../VoIPController.h"
#include "../../VoIPServerConfig.h"
using namespace Platform;
#define STACK_ARRAY(TYPE, LEN) \
static_cast<TYPE*>(::alloca((LEN) * sizeof(TYPE)))
inline std::wstring ToUtf16(const char* utf8, size_t len) {
int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len),
nullptr, 0);
wchar_t* ws = STACK_ARRAY(wchar_t, len16);
::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len), ws, len16);
return std::wstring(ws, len16);
}
inline std::wstring ToUtf16(const std::string& str) {
return ToUtf16(str.data(), str.length());
}
inline std::string ToUtf8(const wchar_t* wide, size_t len) {
int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len),
nullptr, 0, nullptr, nullptr);
char* ns = STACK_ARRAY(char, len8);
::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len), ns, len8,
nullptr, nullptr);
return std::string(ns, len8);
}
inline std::string ToUtf8(const wchar_t* wide) {
return ToUtf8(wide, wcslen(wide));
}
inline std::string ToUtf8(const std::wstring& wstr) {
return ToUtf8(wstr.data(), wstr.length());
}
namespace libtgvoip{
public ref class Endpoint sealed{
public:
property int64 id;
property uint16 port;
property Platform::String^ ipv4;
property Platform::String^ ipv6;
property Platform::Array<uint8>^ peerTag;
};
public ref class TrafficStats sealed{
public:
property uint64_t bytesSentWifi;
property uint64_t bytesRecvdWifi;
property uint64_t bytesSentMobile;
property uint64_t bytesRecvdMobile;
};
public enum class CallState : int{
WaitInit=1,
WaitInitAck,
Established,
Failed
};
public enum class Error : int{
Unknown=0,
Incompatible,
Timeout,
AudioIO
};
public enum class NetworkType : int{
Unknown=0,
GPRS,
EDGE,
UMTS,
HSPA,
LTE,
WiFi,
Ethernet,
OtherHighSpeed,
OtherLowSpeed,
Dialup,
OtherMobile
};
public enum class DataSavingMode{
Never=0,
MobileOnly,
Always
};
public enum class ProxyProtocol{
None=0,
SOCKS5
};
public ref class VoIPConfig sealed {
public:
VoIPConfig() {
logPacketStats = false;
enableVolumeControl = false;
enableVideoSend = false;
enableVideoReceive = false;
}
property double initTimeout;
property double recvTimeout;
property DataSavingMode dataSaving;
property String^ logFilePath;
property String^ statsDumpFilePath;
property bool enableAEC;
property bool enableNS;
property bool enableAGC;
property bool enableCallUpgrade;
property bool logPacketStats;
property bool enableVolumeControl;
property bool enableVideoSend;
property bool enableVideoReceive;
};
ref class VoIPControllerWrapper;
public delegate void CallStateChangedEventHandler(VoIPControllerWrapper^ sender, CallState newState);
ref class VoIPControllerWrapper;
public delegate void SignalBarsChangedEventHandler(VoIPControllerWrapper^ sender, int newCount);
public ref class VoIPControllerWrapper sealed{
public:
VoIPControllerWrapper();
virtual ~VoIPControllerWrapper();
void Start();
void Connect();
void SetPublicEndpoints(const Platform::Array<Endpoint^>^ endpoints, bool allowP2P, int32_t connectionMaxLayer);
void SetNetworkType(NetworkType type);
void SetMicMute(bool mute);
void SetEncryptionKey(const Platform::Array<uint8>^ key, bool isOutgoing);
void SetConfig(VoIPConfig^ config);
void SetProxy(ProxyProtocol protocol, Platform::String^ address, uint16_t port, Platform::String^ username, Platform::String^ password);
int GetSignalBarsCount();
CallState GetConnectionState();
TrafficStats^ GetStats();
Platform::String^ GetDebugString();
Platform::String^ GetDebugLog();
Error GetLastError();
static Platform::String^ GetVersion();
int64 GetPreferredRelayID();
void SetAudioOutputGainControlEnabled(bool enabled);
void SetInputVolume(float level);
void SetOutputVolume(float level);
property String^ CurrentAudioInput
{
String^ get()
{
return ref new String(ToUtf16(controller->GetCurrentAudioInputID()).data());
}
void set(String^ value)
{
controller->SetCurrentAudioInput(ToUtf8(value->Data()));
}
}
property String^ CurrentAudioOutput
{
String^ get()
{
return ref new String(ToUtf16(controller->GetCurrentAudioOutputID()).data());
}
void set(String^ value)
{
controller->SetCurrentAudioOutput(ToUtf8(value->Data()));
}
}
static int32_t GetConnectionMaxLayer();
static void UpdateServerConfig(Platform::String^ json);
static void SwitchSpeaker(bool external);
//static Platform::String^ TestAesIge();
event CallStateChangedEventHandler^ CallStateChanged;
event SignalBarsChangedEventHandler^ SignalBarsChanged;
private:
static void OnStateChanged(tgvoip::VoIPController* c, int state);
static void OnSignalBarsChanged(tgvoip::VoIPController* c, int count);
void OnStateChangedInternal(int state);
void OnSignalBarsChangedInternal(int count);
tgvoip::VoIPController* controller;
};
ref class MicrosoftCryptoImpl{
public:
static void AesIgeEncrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv);
static void AesIgeDecrypt(uint8_t* in, uint8_t* out, size_t len, uint8_t* key, uint8_t* iv);
static void AesCtrEncrypt(uint8_t* inout, size_t len, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num);
static void SHA1(uint8_t* msg, size_t len, uint8_t* out);
static void SHA256(uint8_t* msg, size_t len, uint8_t* out);
static void RandBytes(uint8_t* buffer, size_t len);
static void Init();
private:
static inline void XorInt128(uint8_t* a, uint8_t* b, uint8_t* out);
static void IBufferToPtr(Windows::Storage::Streams::IBuffer^ buffer, size_t len, uint8_t* out);
static Windows::Storage::Streams::IBuffer^ IBufferFromPtr(uint8_t* msg, size_t len);
/*static Windows::Security::Cryptography::Core::CryptographicHash^ sha1Hash;
static Windows::Security::Cryptography::Core::CryptographicHash^ sha256Hash;*/
static Windows::Security::Cryptography::Core::HashAlgorithmProvider^ sha1Provider;
static Windows::Security::Cryptography::Core::HashAlgorithmProvider^ sha256Provider;
static Windows::Security::Cryptography::Core::SymmetricKeyAlgorithmProvider^ aesKeyProvider;
};
class NativeBuffer :
public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::WinRtClassicComMix>,
ABI::Windows::Storage::Streams::IBuffer,
Windows::Storage::Streams::IBufferByteAccess>
{
public:
NativeBuffer(byte *buffer, UINT totalSize)
{
m_length=totalSize;
m_buffer=buffer;
}
virtual ~NativeBuffer()
{
}
STDMETHODIMP RuntimeClassInitialize(byte *buffer, UINT totalSize)
{
m_length=totalSize;
m_buffer=buffer;
return S_OK;
}
STDMETHODIMP Buffer(byte **value)
{
*value=m_buffer;
return S_OK;
}
STDMETHODIMP get_Capacity(UINT32 *value)
{
*value=m_length;
return S_OK;
}
STDMETHODIMP get_Length(UINT32 *value)
{
*value=m_length;
return S_OK;
}
STDMETHODIMP put_Length(UINT32 value)
{
m_length=value;
return S_OK;
}
private:
UINT32 m_length;
byte *m_buffer;
};
}

View file

@ -0,0 +1,719 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <winsock2.h>
#include <ws2tcpip.h>
#include "NetworkSocketWinsock.h"
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
#else
#include <IPHlpApi.h>
#endif
#include <assert.h>
#include "../../logging.h"
#include "../../VoIPController.h"
#include "WindowsSpecific.h"
#include "../../Buffers.h"
using namespace tgvoip;
NetworkSocketWinsock::NetworkSocketWinsock(NetworkProtocol protocol) : NetworkSocket(protocol), lastRecvdV4(0), lastRecvdV6("::0"){
needUpdateNat64Prefix=true;
nat64Present=false;
switchToV6at=0;
isV4Available=false;
closing=false;
fd=INVALID_SOCKET;
#ifdef TGVOIP_WINXP_COMPAT
DWORD version=GetVersion();
isAtLeastVista=LOBYTE(LOWORD(version))>=6; // Vista is 6.0, XP is 5.1 and 5.2
#else
isAtLeastVista=true;
#endif
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
LOGD("Initialized winsock, version %d.%d", wsaData.wHighVersion, wsaData.wVersion);
tcpConnectedAddress=NULL;
if(protocol==PROTO_TCP)
timeout=10.0;
lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
}
NetworkSocketWinsock::~NetworkSocketWinsock(){
if(tcpConnectedAddress)
delete tcpConnectedAddress;
if(pendingOutgoingPacket)
delete pendingOutgoingPacket;
}
void NetworkSocketWinsock::SetMaxPriority(){
}
void NetworkSocketWinsock::Send(NetworkPacket *packet){
if(!packet || (protocol==PROTO_UDP && !packet->address)){
LOGW("tried to send null packet");
return;
}
int res;
if(protocol==PROTO_UDP){
IPv4Address *v4addr=dynamic_cast<IPv4Address *>(packet->address);
if(isAtLeastVista){
sockaddr_in6 addr;
if(v4addr){
if(needUpdateNat64Prefix && !isV4Available && VoIPController::GetCurrentTime()>switchToV6at && switchToV6at!=0){
LOGV("Updating NAT64 prefix");
nat64Present=false;
addrinfo *addr0;
int res=getaddrinfo("ipv4only.arpa", NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo *addrPtr;
unsigned char *addr170=NULL;
unsigned char *addr171=NULL;
for(addrPtr=addr0; addrPtr; addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET6){
sockaddr_in6 *translatedAddr=(sockaddr_in6 *) addrPtr->ai_addr;
uint32_t v4part=*((uint32_t *) &translatedAddr->sin6_addr.s6_addr[12]);
if(v4part==0xAA0000C0 && !addr170){
addr170=translatedAddr->sin6_addr.s6_addr;
}
if(v4part==0xAB0000C0 && !addr171){
addr171=translatedAddr->sin6_addr.s6_addr;
}
char buf[INET6_ADDRSTRLEN];
//LOGV("Got translated address: %s", inet_ntop(AF_INET6, &translatedAddr->sin6_addr, buf, sizeof(buf)));
}
}
if(addr170 && addr171 && memcmp(addr170, addr171, 12)==0){
nat64Present=true;
memcpy(nat64Prefix, addr170, 12);
char buf[INET6_ADDRSTRLEN];
//LOGV("Found nat64 prefix from %s", inet_ntop(AF_INET6, addr170, buf, sizeof(buf)));
}else{
LOGV("Didn't find nat64");
}
freeaddrinfo(addr0);
}
needUpdateNat64Prefix=false;
}
memset(&addr, 0, sizeof(sockaddr_in6));
addr.sin6_family=AF_INET6;
*((uint32_t *) &addr.sin6_addr.s6_addr[12])=v4addr->GetAddress();
if(nat64Present)
memcpy(addr.sin6_addr.s6_addr, nat64Prefix, 12);
else
addr.sin6_addr.s6_addr[11]=addr.sin6_addr.s6_addr[10]=0xFF;
}else{
IPv6Address *v6addr=dynamic_cast<IPv6Address *>(packet->address);
assert(v6addr!=NULL);
memcpy(addr.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
}
addr.sin6_port=htons(packet->port);
res=sendto(fd, (const char*)packet->data, packet->length, 0, (const sockaddr *) &addr, sizeof(addr));
}else if(v4addr){
sockaddr_in addr;
addr.sin_addr.s_addr=v4addr->GetAddress();
addr.sin_port=htons(packet->port);
addr.sin_family=AF_INET;
res=sendto(fd, (const char*)packet->data, packet->length, 0, (const sockaddr*)&addr, sizeof(addr));
}
}else{
res=send(fd, (const char*)packet->data, packet->length, 0);
}
if(res==SOCKET_ERROR){
int error=WSAGetLastError();
if(error==WSAEWOULDBLOCK){
if(pendingOutgoingPacket){
LOGE("Got EAGAIN but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length);
pendingOutgoingPacket->CopyFrom(packet->data, 0, packet->length);
readyToSend=false;
}
}else{
LOGE("error sending: %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
if(error==WSAENETUNREACH && !isV4Available && VoIPController::GetCurrentTime()<switchToV6at){
switchToV6at=VoIPController::GetCurrentTime();
LOGI("Network unreachable, trying NAT64");
}
}
}else if(res<packet->length && protocol==PROTO_TCP){
if(pendingOutgoingPacket){
LOGE("send returned less than packet length but there's already a pending packet");
failed=true;
}else{
LOGV("Socket %d not ready to send", fd);
pendingOutgoingPacket=new Buffer(packet->length-res);
pendingOutgoingPacket->CopyFrom(packet->data+res, 0, packet->length-res);
readyToSend=false;
}
}
}
bool NetworkSocketWinsock::OnReadyToSend(){
if(pendingOutgoingPacket){
NetworkPacket pkt={0};
pkt.data=**pendingOutgoingPacket;
pkt.length=pendingOutgoingPacket->Length();
Send(&pkt);
delete pendingOutgoingPacket;
pendingOutgoingPacket=NULL;
return false;
}
readyToSend=true;
return true;
}
void NetworkSocketWinsock::Receive(NetworkPacket *packet){
if(protocol==PROTO_UDP){
if(isAtLeastVista){
int addrLen=sizeof(sockaddr_in6);
sockaddr_in6 srcAddr;
int res=recvfrom(fd, (char*)packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
if(res!=SOCKET_ERROR)
packet->length=(size_t) res;
else{
int error=WSAGetLastError();
LOGE("error receiving %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
packet->length=0;
return;
}
//LOGV("Received %d bytes from %s:%d at %.5lf", len, inet_ntoa(srcAddr.sin_addr), ntohs(srcAddr.sin_port), GetCurrentTime());
if(!isV4Available && IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr)){
isV4Available=true;
LOGI("Detected IPv4 connectivity, will not try IPv6");
}
if(IN6_IS_ADDR_V4MAPPED(&srcAddr.sin6_addr) || (nat64Present && memcmp(nat64Prefix, srcAddr.sin6_addr.s6_addr, 12)==0)){
in_addr v4addr=*((in_addr *) &srcAddr.sin6_addr.s6_addr[12]);
lastRecvdV4=IPv4Address(v4addr.s_addr);
packet->address=&lastRecvdV4;
}else{
lastRecvdV6=IPv6Address(srcAddr.sin6_addr.s6_addr);
packet->address=&lastRecvdV6;
}
packet->port=ntohs(srcAddr.sin6_port);
}else{
int addrLen=sizeof(sockaddr_in);
sockaddr_in srcAddr;
int res=recvfrom(fd, (char*)packet->data, packet->length, 0, (sockaddr *) &srcAddr, (socklen_t *) &addrLen);
if(res!=SOCKET_ERROR)
packet->length=(size_t) res;
else{
LOGE("error receiving %d", WSAGetLastError());
packet->length=0;
return;
}
lastRecvdV4=IPv4Address(srcAddr.sin_addr.s_addr);
packet->address=&lastRecvdV4;
packet->port=ntohs(srcAddr.sin_port);
}
packet->protocol=PROTO_UDP;
}else if(protocol==PROTO_TCP){
int res=recv(fd, (char*)packet->data, packet->length, 0);
if(res==SOCKET_ERROR){
int error=WSAGetLastError();
LOGE("Error receiving from TCP socket: %d / %s", error, WindowsSpecific::GetErrorMessage(error).c_str());
failed=true;
}else{
packet->length=(size_t)res;
packet->address=tcpConnectedAddress;
packet->port=tcpConnectedPort;
packet->protocol=PROTO_TCP;
}
}
}
void NetworkSocketWinsock::Open(){
if(protocol==PROTO_UDP){
fd=socket(isAtLeastVista ? AF_INET6 : AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(fd==INVALID_SOCKET){
int error=WSAGetLastError();
LOGE("error creating socket: %d", error);
failed=true;
return;
}
int res;
if(isAtLeastVista){
DWORD flag=0;
res=setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&flag, sizeof(flag));
if(res==SOCKET_ERROR){
LOGE("error enabling dual stack socket: %d", WSAGetLastError());
failed=true;
return;
}
}
u_long one=1;
ioctlsocket(fd, FIONBIO, &one);
SetMaxPriority();
int tries=0;
sockaddr* addr;
sockaddr_in addr4;
sockaddr_in6 addr6;
int addrLen;
if(isAtLeastVista){
//addr.sin6_addr.s_addr=0;
memset(&addr6, 0, sizeof(sockaddr_in6));
//addr.sin6_len=sizeof(sa_family_t);
addr6.sin6_family=AF_INET6;
addr=(sockaddr*)&addr6;
addrLen=sizeof(addr6);
}else{
sockaddr_in addr4;
addr4.sin_addr.s_addr=0;
addr4.sin_family=AF_INET;
addr=(sockaddr*)&addr4;
addrLen=sizeof(addr4);
}
for(tries=0;tries<10;tries++){
uint16_t port=htons(GenerateLocalPort());
if(isAtLeastVista)
((sockaddr_in6*)addr)->sin6_port=port;
else
((sockaddr_in*)addr)->sin_port=port;
res=::bind(fd, addr, addrLen);
LOGV("trying bind to port %u", ntohs(port));
if(res<0){
LOGE("error binding to port %u: %d / %s", ntohs(port), errno, strerror(errno));
}else{
break;
}
}
if(tries==10){
if(isAtLeastVista)
((sockaddr_in6*)addr)->sin6_port=0;
else
((sockaddr_in*)addr)->sin_port=0;
res=::bind(fd, addr, addrLen);
if(res<0){
LOGE("error binding to port %u: %d / %s", 0, errno, strerror(errno));
//SetState(STATE_FAILED);
return;
}
}
getsockname(fd, addr, (socklen_t*) &addrLen);
uint16_t localUdpPort;
if(isAtLeastVista)
localUdpPort=ntohs(((sockaddr_in6*)addr)->sin6_port);
else
localUdpPort=ntohs(((sockaddr_in*)addr)->sin_port);
LOGD("Bound to local UDP port %u", localUdpPort);
needUpdateNat64Prefix=true;
isV4Available=false;
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
}
}
void NetworkSocketWinsock::Close(){
closing=true;
failed=true;
if(fd!=INVALID_SOCKET)
closesocket(fd);
}
void NetworkSocketWinsock::OnActiveInterfaceChanged(){
needUpdateNat64Prefix=true;
isV4Available=false;
switchToV6at=VoIPController::GetCurrentTime()+ipv6Timeout;
}
std::string NetworkSocketWinsock::GetLocalInterfaceInfo(IPv4Address *v4addr, IPv6Address *v6addr){
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
Windows::Networking::Connectivity::ConnectionProfile^ profile=Windows::Networking::Connectivity::NetworkInformation::GetInternetConnectionProfile();
if(profile){
Windows::Foundation::Collections::IVectorView<Windows::Networking::HostName^>^ hostnames=Windows::Networking::Connectivity::NetworkInformation::GetHostNames();
for(unsigned int i=0;i<hostnames->Size;i++){
Windows::Networking::HostName^ n = hostnames->GetAt(i);
if(n->Type!=Windows::Networking::HostNameType::Ipv4 && n->Type!=Windows::Networking::HostNameType::Ipv6)
continue;
if(n->IPInformation->NetworkAdapter->Equals(profile->NetworkAdapter)){
if(v4addr && n->Type==Windows::Networking::HostNameType::Ipv4){
char buf[INET_ADDRSTRLEN];
WideCharToMultiByte(CP_UTF8, 0, n->RawName->Data(), -1, buf, sizeof(buf), NULL, NULL);
*v4addr=IPv4Address(buf);
}else if(v6addr && n->Type==Windows::Networking::HostNameType::Ipv6){
char buf[INET6_ADDRSTRLEN];
WideCharToMultiByte(CP_UTF8, 0, n->RawName->Data(), -1, buf, sizeof(buf), NULL, NULL);
*v6addr=IPv6Address(buf);
}
}
}
char buf[128];
WideCharToMultiByte(CP_UTF8, 0, profile->NetworkAdapter->NetworkAdapterId.ToString()->Data(), -1, buf, sizeof(buf), NULL, NULL);
return std::string(buf);
}
return "";
#else
IP_ADAPTER_ADDRESSES* addrs=(IP_ADAPTER_ADDRESSES*)malloc(15*1024);
ULONG size=15*1024;
ULONG flags=GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
ULONG res=GetAdaptersAddresses(AF_UNSPEC, flags, NULL, addrs, &size);
if(res==ERROR_BUFFER_OVERFLOW){
addrs=(IP_ADAPTER_ADDRESSES*)realloc(addrs, size);
res=GetAdaptersAddresses(AF_UNSPEC, flags, NULL, addrs, &size);
}
ULONG bestMetric=0;
std::string bestName("");
if(res==ERROR_SUCCESS){
IP_ADAPTER_ADDRESSES* current=addrs;
while(current){
char* name=current->AdapterName;
LOGV("Adapter '%s':", name);
IP_ADAPTER_UNICAST_ADDRESS* curAddr=current->FirstUnicastAddress;
if(current->OperStatus!=IfOperStatusUp){
LOGV("-> (down)");
current=current->Next;
continue;
}
if(current->IfType==IF_TYPE_SOFTWARE_LOOPBACK){
LOGV("-> (loopback)");
current=current->Next;
continue;
}
if(isAtLeastVista)
LOGV("v4 metric: %u, v6 metric: %u", current->Ipv4Metric, current->Ipv6Metric);
while(curAddr){
sockaddr* addr=curAddr->Address.lpSockaddr;
if(addr->sa_family==AF_INET && v4addr){
sockaddr_in* ipv4=(sockaddr_in*)addr;
LOGV("-> V4: %s", V4AddressToString(ipv4->sin_addr.s_addr).c_str());
uint32_t ip=ntohl(ipv4->sin_addr.s_addr);
if((ip & 0xFFFF0000)!=0xA9FE0000){
if(isAtLeastVista){
if(current->Ipv4Metric>bestMetric){
bestMetric=current->Ipv4Metric;
bestName=std::string(current->AdapterName);
*v4addr=IPv4Address(ipv4->sin_addr.s_addr);
}
}else{
bestName=std::string(current->AdapterName);
*v4addr=IPv4Address(ipv4->sin_addr.s_addr);
}
}
}else if(addr->sa_family==AF_INET6 && v6addr){
sockaddr_in6* ipv6=(sockaddr_in6*)addr;
LOGV("-> V6: %s", V6AddressToString(ipv6->sin6_addr.s6_addr).c_str());
if(!IN6_IS_ADDR_LINKLOCAL(&ipv6->sin6_addr)){
*v6addr=IPv6Address(ipv6->sin6_addr.s6_addr);
}
}
curAddr=curAddr->Next;
}
current=current->Next;
}
}
free(addrs);
return bestName;
#endif
}
uint16_t NetworkSocketWinsock::GetLocalPort(){
if(!isAtLeastVista){
sockaddr_in addr;
size_t addrLen=sizeof(sockaddr_in);
getsockname(fd, (sockaddr*)&addr, (socklen_t*)&addrLen);
return ntohs(addr.sin_port);
}
sockaddr_in6 addr;
size_t addrLen=sizeof(sockaddr_in6);
getsockname(fd, (sockaddr*)&addr, (socklen_t*) &addrLen);
return ntohs(addr.sin6_port);
}
std::string NetworkSocketWinsock::V4AddressToString(uint32_t address){
char buf[INET_ADDRSTRLEN];
sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=address;
DWORD len=sizeof(buf);
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
wchar_t wbuf[INET_ADDRSTRLEN];
ZeroMemory(wbuf, sizeof(wbuf));
WSAAddressToStringW((sockaddr*)&addr, sizeof(addr), NULL, wbuf, &len);
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf), NULL, NULL);
#else
WSAAddressToStringA((sockaddr*)&addr, sizeof(addr), NULL, buf, &len);
#endif
return std::string(buf);
}
std::string NetworkSocketWinsock::V6AddressToString(const unsigned char *address){
char buf[INET6_ADDRSTRLEN];
sockaddr_in6 addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin6_family=AF_INET6;
memcpy(addr.sin6_addr.s6_addr, address, 16);
DWORD len=sizeof(buf);
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
wchar_t wbuf[INET6_ADDRSTRLEN];
ZeroMemory(wbuf, sizeof(wbuf));
WSAAddressToStringW((sockaddr*)&addr, sizeof(addr), NULL, wbuf, &len);
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf), NULL, NULL);
#else
WSAAddressToStringA((sockaddr*)&addr, sizeof(addr), NULL, buf, &len);
#endif
return std::string(buf);
}
uint32_t NetworkSocketWinsock::StringToV4Address(std::string address){
sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family=AF_INET;
int size=sizeof(addr);
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
wchar_t buf[INET_ADDRSTRLEN];
MultiByteToWideChar(CP_UTF8, 0, address.c_str(), -1, buf, INET_ADDRSTRLEN);
WSAStringToAddressW(buf, AF_INET, NULL, (sockaddr*)&addr, &size);
#else
WSAStringToAddressA((char*)address.c_str(), AF_INET, NULL, (sockaddr*)&addr, &size);
#endif
return addr.sin_addr.s_addr;
}
void NetworkSocketWinsock::StringToV6Address(std::string address, unsigned char *out){
sockaddr_in6 addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin6_family=AF_INET6;
int size=sizeof(addr);
#if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
wchar_t buf[INET6_ADDRSTRLEN];
MultiByteToWideChar(CP_UTF8, 0, address.c_str(), -1, buf, INET6_ADDRSTRLEN);
WSAStringToAddressW(buf, AF_INET, NULL, (sockaddr*)&addr, &size);
#else
WSAStringToAddressA((char*)address.c_str(), AF_INET, NULL, (sockaddr*)&addr, &size);
#endif
memcpy(out, addr.sin6_addr.s6_addr, 16);
}
void NetworkSocketWinsock::Connect(const NetworkAddress *address, uint16_t port){
const IPv4Address* v4addr=dynamic_cast<const IPv4Address*>(address);
const IPv6Address* v6addr=dynamic_cast<const IPv6Address*>(address);
sockaddr_in v4;
sockaddr_in6 v6;
sockaddr* addr=NULL;
size_t addrLen=0;
if(v4addr){
v4.sin_family=AF_INET;
v4.sin_addr.s_addr=v4addr->GetAddress();
v4.sin_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v4);
addrLen=sizeof(v4);
}else if(v6addr){
v6.sin6_family=AF_INET6;
memcpy(v6.sin6_addr.s6_addr, v6addr->GetAddress(), 16);
v6.sin6_flowinfo=0;
v6.sin6_scope_id=0;
v6.sin6_port=htons(port);
addr=reinterpret_cast<sockaddr*>(&v6);
addrLen=sizeof(v6);
}else{
LOGE("Unknown address type in TCP connect");
failed=true;
return;
}
fd=socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
if(fd==INVALID_SOCKET){
LOGE("Error creating TCP socket: %d", WSAGetLastError());
failed=true;
return;
}
u_long one=1;
ioctlsocket(fd, FIONBIO, &one);
int opt=1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&opt, sizeof(opt));
DWORD timeout=5000;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
timeout=60000;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
int res=connect(fd, (const sockaddr*) addr, addrLen);
if(res!=0){
int error=WSAGetLastError();
if(error!=WSAEINPROGRESS && error!=WSAEWOULDBLOCK){
LOGW("error connecting TCP socket to %s:%u: %d / %s", address->ToString().c_str(), port, error, WindowsSpecific::GetErrorMessage(error).c_str());
closesocket(fd);
failed=true;
return;
}
}
tcpConnectedAddress=v4addr ? (NetworkAddress*)new IPv4Address(*v4addr) : (NetworkAddress*)new IPv6Address(*v6addr);
tcpConnectedPort=port;
LOGI("successfully connected to %s:%d", tcpConnectedAddress->ToString().c_str(), tcpConnectedPort);
}
IPv4Address *NetworkSocketWinsock::ResolveDomainName(std::string name){
addrinfo* addr0;
IPv4Address* ret=NULL;
int res=getaddrinfo(name.c_str(), NULL, NULL, &addr0);
if(res!=0){
LOGW("Error updating NAT64 prefix: %d / %s", res, gai_strerror(res));
}else{
addrinfo* addrPtr;
for(addrPtr=addr0;addrPtr;addrPtr=addrPtr->ai_next){
if(addrPtr->ai_family==AF_INET){
sockaddr_in* addr=(sockaddr_in*)addrPtr->ai_addr;
ret=new IPv4Address(addr->sin_addr.s_addr);
break;
}
}
freeaddrinfo(addr0);
}
return ret;
}
NetworkAddress *NetworkSocketWinsock::GetConnectedAddress(){
return tcpConnectedAddress;
}
uint16_t NetworkSocketWinsock::GetConnectedPort(){
return tcpConnectedPort;
}
void NetworkSocketWinsock::SetTimeouts(int sendTimeout, int recvTimeout){
DWORD timeout=sendTimeout*1000;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
timeout=recvTimeout*1000;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
}
bool NetworkSocketWinsock::Select(std::vector<NetworkSocket*> &readFds, std::vector<NetworkSocket*>& writeFds, std::vector<NetworkSocket*> &errorFds, SocketSelectCanceller* _canceller){
fd_set readSet;
fd_set errorSet;
fd_set writeSet;
SocketSelectCancellerWin32* canceller=dynamic_cast<SocketSelectCancellerWin32*>(_canceller);
timeval timeout={0, 10000};
bool anyFailed=false;
int res=0;
do{
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_ZERO(&errorSet);
for(std::vector<NetworkSocket*>::iterator itr=readFds.begin();itr!=readFds.end();++itr){
int sfd=GetDescriptorFromSocket(*itr);
if(sfd==0){
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
continue;
}
FD_SET(sfd, &readSet);
}
for(NetworkSocket*& s:writeFds){
int sfd=GetDescriptorFromSocket(s);
if(sfd==0){
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
continue;
}
FD_SET(sfd, &writeSet);
}
for(std::vector<NetworkSocket*>::iterator itr=errorFds.begin();itr!=errorFds.end();++itr){
int sfd=GetDescriptorFromSocket(*itr);
if(sfd==0){
LOGW("can't select on one of sockets because it's not a NetworkSocketWinsock instance");
continue;
}
if((*itr)->timeout>0 && VoIPController::GetCurrentTime()-(*itr)->lastSuccessfulOperationTime>(*itr)->timeout){
LOGW("Socket %d timed out", sfd);
(*itr)->failed=true;
}
anyFailed |= (*itr)->IsFailed();
FD_SET(sfd, &errorSet);
}
if(canceller && canceller->canceled)
break;
res=select(0, &readSet, &writeSet, &errorSet, &timeout);
//LOGV("select result %d", res);
if(res==SOCKET_ERROR)
LOGE("SELECT ERROR %d", WSAGetLastError());
}while(res==0);
if(canceller && canceller->canceled && !anyFailed){
canceller->canceled=false;
return false;
}else if(anyFailed){
FD_ZERO(&readSet);
FD_ZERO(&errorSet);
}
std::vector<NetworkSocket*>::iterator itr=readFds.begin();
while(itr!=readFds.end()){
int sfd=GetDescriptorFromSocket(*itr);
if(FD_ISSET(sfd, &readSet))
(*itr)->lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
if(sfd==0 || !FD_ISSET(sfd, &readSet) || !(*itr)->OnReadyToReceive()){
itr=readFds.erase(itr);
}else{
++itr;
}
}
itr=writeFds.begin();
while(itr!=writeFds.end()){
int sfd=GetDescriptorFromSocket(*itr);
if(FD_ISSET(sfd, &writeSet)){
(*itr)->lastSuccessfulOperationTime=VoIPController::GetCurrentTime();
LOGI("Socket %d is ready to send", sfd);
}
if(sfd==0 || !FD_ISSET(sfd, &writeSet) || !(*itr)->OnReadyToSend()){
itr=writeFds.erase(itr);
}else{
++itr;
}
}
itr=errorFds.begin();
while(itr!=errorFds.end()){
int sfd=GetDescriptorFromSocket(*itr);
if((sfd==0 || !FD_ISSET(sfd, &errorSet)) && !(*itr)->IsFailed()){
itr=errorFds.erase(itr);
}else{
++itr;
}
}
//LOGV("select fds left: read=%d, error=%d", readFds.size(), errorFds.size());
return readFds.size()>0 || errorFds.size()>0;
}
SocketSelectCancellerWin32::SocketSelectCancellerWin32(){
canceled=false;
}
SocketSelectCancellerWin32::~SocketSelectCancellerWin32(){
}
void SocketSelectCancellerWin32::CancelSelect(){
canceled=true;
}
int NetworkSocketWinsock::GetDescriptorFromSocket(NetworkSocket *socket){
NetworkSocketWinsock* sp=dynamic_cast<NetworkSocketWinsock*>(socket);
if(sp)
return sp->fd;
NetworkSocketWrapper* sw=dynamic_cast<NetworkSocketWrapper*>(socket);
if(sw)
return GetDescriptorFromSocket(sw->GetWrapped());
return 0;
}

View file

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

View file

@ -0,0 +1,68 @@
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "WindowsSandboxUtils.h"
#include <audioclient.h>
#include <windows.h>
#ifdef TGVOIP_WP_SILVERLIGHT
#include <phoneaudioclient.h>
#endif
using namespace tgvoip;
using namespace Microsoft::WRL;
IAudioClient2* WindowsSandboxUtils::ActivateAudioDevice(const wchar_t* devID, HRESULT* callRes, HRESULT* actRes) {
#ifndef TGVOIP_WP_SILVERLIGHT
// Did I say that I hate pointlessly asynchronous things?
HANDLE event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
ActivationHandler activationHandler(event);
IActivateAudioInterfaceAsyncOperation* actHandler;
HRESULT cr = ActivateAudioInterfaceAsync(devID, __uuidof(IAudioClient2), NULL, (IActivateAudioInterfaceCompletionHandler*)&activationHandler, &actHandler);
if (callRes)
*callRes = cr;
DWORD resulttt = WaitForSingleObjectEx(event, INFINITE, false);
DWORD last = GetLastError();
CloseHandle(event);
if (actRes)
*actRes = activationHandler.actResult;
return activationHandler.client;
#else
IAudioClient2* client;
HRESULT res=ActivateAudioInterface(devID, __uuidof(IAudioClient2), (void**)&client);
if(callRes)
*callRes=S_OK;
if(actRes)
*actRes=res;
return client;
#endif
}
#ifndef TGVOIP_WP_SILVERLIGHT
ActivationHandler::ActivationHandler(HANDLE _event) : event(_event)
{
}
STDMETHODIMP ActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation * operation)
{
HRESULT hr = S_OK;
HRESULT hrActivateResult = S_OK;
IUnknown *punkAudioInterface = nullptr;
hr = operation->GetActivateResult(&hrActivateResult, &punkAudioInterface);
if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult))
{
punkAudioInterface->QueryInterface(IID_PPV_ARGS(&client));
}
SetEvent(event);
return hr;
}
#endif

View file

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

View file

@ -0,0 +1,9 @@
#include "WindowsSpecific.h"
using namespace tgvoip;
std::string WindowsSpecific::GetErrorMessage(DWORD code){
char buf[1024]={0};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buf, sizeof(buf), NULL);
return std::string(buf);
}

View file

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