From 6dcf281d2477c8f2a22378e0d8c1c93f2428390b Mon Sep 17 00:00:00 2001 From: Grishka Date: Fri, 12 May 2017 19:29:35 +0300 Subject: [PATCH] Added PulseAudio on Linux Fixes on WinRT --- audio/AudioInput.cpp | 13 +- audio/AudioOutput.cpp | 13 +- libtgvoip.gyp | 4 + os/linux/AudioInputPulse.cpp | 309 +++++++++++++++++++++++++ os/linux/AudioInputPulse.h | 83 +++++++ os/linux/AudioOutputPulse.cpp | 313 ++++++++++++++++++++++++++ os/linux/AudioOutputPulse.h | 83 +++++++ os/windows/AudioInputWASAPI.cpp | 2 + os/windows/AudioOutputWASAPI.cpp | 2 + os/windows/CXWrapper.cpp | 2 +- webrtc_dsp/webrtc/base/safe_compare.h | 2 +- 11 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 os/linux/AudioInputPulse.cpp create mode 100644 os/linux/AudioInputPulse.h create mode 100644 os/linux/AudioOutputPulse.cpp create mode 100644 os/linux/AudioOutputPulse.h diff --git a/audio/AudioInput.cpp b/audio/AudioInput.cpp index 6ed1ebd..712b00a 100644 --- a/audio/AudioInput.cpp +++ b/audio/AudioInput.cpp @@ -21,6 +21,7 @@ #include "../os/windows/AudioInputWASAPI.h" #elif defined(__linux__) #include "../os/linux/AudioInputALSA.h" +#include "../os/linux/AudioInputPulse.h" #else #error "Unsupported operating system" #endif @@ -52,6 +53,13 @@ AudioInput *AudioInput::Create(std::string deviceID){ #endif return new AudioInputWASAPI(deviceID); #elif defined(__linux__) + if(AudioInputPulse::IsAvailable()){ + AudioInputPulse* aip=new AudioInputPulse(deviceID); + if(!aip->IsInitialized()) + delete aip; + else + return aip; + } return new AudioInputALSA(deviceID); #endif } @@ -77,7 +85,10 @@ void AudioInput::EnumerateDevices(std::vector& devs){ #endif AudioInputWASAPI::EnumerateDevices(devs); #elif defined(__linux__) && !defined(__ANDROID__) - AudioInputALSA::EnumerateDevices(devs); + if(AudioInputPulse::IsAvailable()) + AudioInputPulse::EnumerateDevices(devs); + else + AudioInputALSA::EnumerateDevices(devs); #endif } diff --git a/audio/AudioOutput.cpp b/audio/AudioOutput.cpp index 4d4a05d..36f463f 100644 --- a/audio/AudioOutput.cpp +++ b/audio/AudioOutput.cpp @@ -22,6 +22,7 @@ #include "../os/windows/AudioOutputWASAPI.h" #elif defined(__linux__) #include "../os/linux/AudioOutputALSA.h" +#include "../os/linux/AudioOutputPulse.h" #else #error "Unsupported operating system" #endif @@ -52,6 +53,13 @@ AudioOutput *AudioOutput::Create(std::string deviceID){ #endif return new AudioOutputWASAPI(deviceID); #elif defined(__linux__) + if(AudioOutputPulse::IsAvailable()){ + AudioOutputPulse* aop=new AudioOutputPulse(deviceID); + if(!aop->IsInitialized()) + delete aop; + else + return aop; + } return new AudioOutputALSA(deviceID); #endif } @@ -93,7 +101,10 @@ void AudioOutput::EnumerateDevices(std::vector& devs){ #endif AudioOutputWASAPI::EnumerateDevices(devs); #elif defined(__linux__) && !defined(__ANDROID__) - AudioOutputALSA::EnumerateDevices(devs); + if(AudioOutputPulse::IsAvailable()) + AudioOutputPulse::EnumerateDevices(devs); + else + AudioOutputALSA::EnumerateDevices(devs); #endif } diff --git a/libtgvoip.gyp b/libtgvoip.gyp index 62df7fd..58dc6bf 100644 --- a/libtgvoip.gyp +++ b/libtgvoip.gyp @@ -87,6 +87,10 @@ '<(tgvoip_src_loc)/os/linux/AudioInputALSA.h', '<(tgvoip_src_loc)/os/linux/AudioOutputALSA.cpp', '<(tgvoip_src_loc)/os/linux/AudioOutputALSA.h', + '<(tgvoip_src_loc)/os/linux/AudioOutputPulse.cpp', + '<(tgvoip_src_loc)/os/linux/AudioOutputPulse.h', + '<(tgvoip_src_loc)/os/linux/AudioInputPulse.cpp', + '<(tgvoip_src_loc)/os/linux/AudioInputPulse.h', # POSIX '<(tgvoip_src_loc)/os/posix/NetworkSocketPosix.cpp', diff --git a/os/linux/AudioInputPulse.cpp b/os/linux/AudioInputPulse.cpp new file mode 100644 index 0000000..1473a89 --- /dev/null +++ b/os/linux/AudioInputPulse.cpp @@ -0,0 +1,309 @@ +// +// libtgvoip is free and 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 +#include +#include +#include "AudioInputPulse.h" +#include "../../logging.h" +#include "../../VoIPController.h" + +#define BUFFER_SIZE 960 +#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg "failed: %s", pa_strerror(res)); failed=true; return;} +#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;} +#define LOAD_DL_FUNCTION(name) {_import_##name=(typeof(_import_##name))dlsym(lib, #name); CHECK_DL_ERROR(_import_##name, "Error getting entry point for " #name);} + +#define pa_threaded_mainloop_new _import_pa_threaded_mainloop_new +#define pa_threaded_mainloop_get_api _import_pa_threaded_mainloop_get_api +#define pa_context_new _import_pa_context_new +#define pa_context_set_state_callback _import_pa_context_set_state_callback +#define pa_threaded_mainloop_lock _import_pa_threaded_mainloop_lock +#define pa_threaded_mainloop_unlock _import_pa_threaded_mainloop_unlock +#define pa_threaded_mainloop_start _import_pa_threaded_mainloop_start +#define pa_context_connect _import_pa_context_connect +#define pa_context_get_state _import_pa_context_get_state +#define pa_threaded_mainloop_wait _import_pa_threaded_mainloop_wait +#define pa_stream_new _import_pa_stream_new +#define pa_stream_set_state_callback _import_pa_stream_set_state_callback +#define pa_stream_set_read_callback _import_pa_stream_set_read_callback +#define pa_stream_connect_record _import_pa_stream_connect_record +#define pa_operation_unref _import_pa_operation_unref +#define pa_stream_cork _import_pa_stream_cork +#define pa_threaded_mainloop_stop _import_pa_threaded_mainloop_stop +#define pa_stream_disconnect _import_pa_stream_disconnect +#define pa_stream_unref _import_pa_stream_unref +#define pa_context_disconnect _import_pa_context_disconnect +#define pa_context_unref _import_pa_context_unref +#define pa_threaded_mainloop_free _import_pa_threaded_mainloop_free +#define pa_threaded_mainloop_signal _import_pa_threaded_mainloop_signal +#define pa_stream_peek _import_pa_stream_peek +#define pa_stream_drop _import_pa_stream_drop +#define pa_strerror _import_pa_strerror +#define pa_stream_get_state _import_pa_stream_get_state + +using namespace tgvoip::audio; + +AudioInputPulse::AudioInputPulse(std::string devID){ + isRecording=false; + isConnected=false; + + mainloop=NULL; + mainloopApi=NULL; + context=NULL; + stream=NULL; + remainingDataSize=0; + + lib=dlopen("libpulse.so.0", RTLD_LAZY); + if(!lib) + lib=dlopen("libpulse.so", RTLD_LAZY); + if(!lib){ + LOGE("Error loading libpulse: %s", dlerror()); + failed=true; + return; + } + + LOAD_DL_FUNCTION(pa_threaded_mainloop_new); + LOAD_DL_FUNCTION(pa_threaded_mainloop_get_api); + LOAD_DL_FUNCTION(pa_context_new); + LOAD_DL_FUNCTION(pa_context_set_state_callback); + LOAD_DL_FUNCTION(pa_threaded_mainloop_lock); + LOAD_DL_FUNCTION(pa_threaded_mainloop_unlock); + LOAD_DL_FUNCTION(pa_threaded_mainloop_start); + LOAD_DL_FUNCTION(pa_context_connect); + LOAD_DL_FUNCTION(pa_context_get_state); + LOAD_DL_FUNCTION(pa_threaded_mainloop_wait); + LOAD_DL_FUNCTION(pa_stream_new); + LOAD_DL_FUNCTION(pa_stream_set_state_callback); + LOAD_DL_FUNCTION(pa_stream_set_read_callback); + LOAD_DL_FUNCTION(pa_stream_connect_record); + LOAD_DL_FUNCTION(pa_operation_unref); + LOAD_DL_FUNCTION(pa_stream_cork); + LOAD_DL_FUNCTION(pa_threaded_mainloop_stop); + LOAD_DL_FUNCTION(pa_stream_disconnect); + LOAD_DL_FUNCTION(pa_stream_unref); + LOAD_DL_FUNCTION(pa_context_disconnect); + LOAD_DL_FUNCTION(pa_context_unref); + LOAD_DL_FUNCTION(pa_threaded_mainloop_free); + LOAD_DL_FUNCTION(pa_threaded_mainloop_signal); + LOAD_DL_FUNCTION(pa_stream_peek); + LOAD_DL_FUNCTION(pa_stream_drop); + LOAD_DL_FUNCTION(pa_stream_get_state); + LOAD_DL_FUNCTION(pa_strerror); + + mainloop=pa_threaded_mainloop_new(); + if(!mainloop){ + LOGE("Error initializing PulseAudio (pa_threaded_mainloop_new)"); + failed=true; + return; + } + mainloopApi=pa_threaded_mainloop_get_api(mainloop); + char exePath[MAXPATHLEN]; + char exeName[MAXPATHLEN]; + ssize_t lres=readlink("/proc/self/exe", exePath, sizeof(exePath)); + if(lres==-1) + lres=readlink("/proc/curproc/file", exePath, sizeof(exePath)); + if(lres==-1) + lres=readlink("/proc/curproc/exe", exePath, sizeof(exePath)); + if(lres>0){ + strcpy(exeName, basename(exePath)); + }else{ + snprintf(exeName, sizeof(exeName), "Process %d", getpid()); + } + context=pa_context_new(mainloopApi, exeName); + if(!context){ + LOGE("Error initializing PulseAudio (pa_context_new)"); + failed=true; + return; + } + pa_context_set_state_callback(context, AudioInputPulse::ContextStateCallback, this); + pa_threaded_mainloop_lock(mainloop); + int err=pa_threaded_mainloop_start(mainloop); + CHECK_ERROR(err, "pa_threaded_mainloop_start"); + + err=pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); + CHECK_ERROR(err, "pa_context_connect"); + + while(true){ + pa_context_state_t contextState=pa_context_get_state(context); + if(!PA_CONTEXT_IS_GOOD(contextState)){ + LOGE("Error initializing PulseAudio (PA_CONTEXT_IS_GOOD)"); + failed=true; + return; + } + if(contextState==PA_CONTEXT_READY) + break; + pa_threaded_mainloop_wait(mainloop); + } + + pa_sample_spec sample_specifications{ + .format=PA_SAMPLE_S16LE, + .rate=48000, + .channels=1 + }; + + stream=pa_stream_new(context, "libtgvoip capture", &sample_specifications, NULL); + if(!stream){ + LOGE("Error initializing PulseAudio (pa_stream_new)"); + failed=true; + return; + } + pa_stream_set_state_callback(stream, AudioInputPulse::StreamStateCallback, this); + pa_stream_set_read_callback(stream, AudioInputPulse::StreamReadCallback, this); + + SetCurrentDevice(devID); +} + +AudioInputPulse::~AudioInputPulse(){ + if(mainloop) + pa_threaded_mainloop_stop(mainloop); + if(stream){ + pa_stream_disconnect(stream); + pa_stream_unref(stream); + } + if(context){ + pa_context_disconnect(context); + pa_context_unref(context); + } + if(mainloop) + pa_threaded_mainloop_free(mainloop); + + if(lib) + dlclose(lib); +} + +bool AudioInputPulse::IsAvailable(){ + void* lib=dlopen("libpulse.so.0", RTLD_LAZY); + if(!lib) + lib=dlopen("libpulse.so", RTLD_LAZY); + if(lib){ + dlclose(lib); + return true; + } + return false; +} + +void AudioInputPulse::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + +} + +void AudioInputPulse::Start(){ + if(failed || isRecording) + return; + + isRecording=true; + pa_operation_unref(pa_stream_cork(stream, 0, AudioInputPulse::StreamSuccessCallback, mainloop)); +} + +void AudioInputPulse::Stop(){ + if(!isRecording) + return; + + isRecording=false; + pa_operation_unref(pa_stream_cork(stream, 1, AudioInputPulse::StreamSuccessCallback, mainloop)); +} + +bool AudioInputPulse::IsRecording(){ + return isRecording; +} + +void AudioInputPulse::SetCurrentDevice(std::string devID){ + currentDevice=devID; + if(isRecording && isConnected){ + pa_threaded_mainloop_lock(mainloop); + pa_stream_disconnect(stream); + isConnected=false; + } + + pa_buffer_attr bufferAttr={ + .maxlength=960*6, + .tlength=960*2, + .prebuf=0, + .minreq=960*2 + }; + int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_NOT_MONOTONIC | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; + + int err=pa_stream_connect_record(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags); + if(err!=0 && devID!="default"){ + SetCurrentDevice("default"); + return; + } + CHECK_ERROR(err, "pa_stream_connect_record"); + + while(true){ + pa_stream_state_t streamState=pa_stream_get_state(stream); + if(!PA_STREAM_IS_GOOD(streamState)){ + LOGE("Error connecting to audio device '%s'", devID.c_str()); + failed=true; + return; + } + if(streamState==PA_STREAM_READY) + break; + pa_threaded_mainloop_wait(mainloop); + } + + isConnected=true; + pa_threaded_mainloop_unlock(mainloop); + + if(isRecording){ + pa_operation_unref(pa_stream_cork(stream, 0, AudioInputPulse::StreamSuccessCallback, mainloop)); + } +} + +void AudioInputPulse::EnumerateDevices(std::vector& devs){ + +} + +void AudioInputPulse::ContextStateCallback(pa_context* context, void* arg) { + AudioInputPulse* self=(AudioInputPulse*) arg; + self->pa_threaded_mainloop_signal(self->mainloop, 0); +} + +void AudioInputPulse::StreamStateCallback(pa_stream *s, void* arg) { + AudioInputPulse* self=(AudioInputPulse*) arg; + self->pa_threaded_mainloop_signal(self->mainloop, 0); +} + +void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes, void *userdata){ + ((AudioInputPulse*)userdata)->StreamReadCallback(stream, requestedBytes); +} + +void AudioInputPulse::StreamReadCallback(pa_stream *stream, size_t requestedBytes) { + int bytesRemaining = requestedBytes; + uint8_t *buffer = NULL; + while (bytesRemaining > 0) { + size_t bytesToFill = 102400; + size_t i; + + if (bytesToFill > bytesRemaining) bytesToFill = bytesRemaining; + + int err=pa_stream_peek(stream, (const void**) &buffer, &bytesToFill); + CHECK_ERROR(err, "pa_stream_peek"); + + if(isRecording){ + if(remainingDataSize+bytesToFill>sizeof(remainingData)){ + LOGE("Capture buffer is too big (%d)", (int)bytesToFill); + } + memcpy(remainingData+remainingDataSize, buffer, bytesToFill); + remainingDataSize+=bytesToFill; + while(remainingDataSize>=960*2){ + InvokeCallback(remainingData, 960*2); + memmove(remainingData, remainingData+960*2, remainingDataSize-960*2); + remainingDataSize-=960*2; + } + } + + err=pa_stream_drop(stream); + CHECK_ERROR(err, "pa_stream_drop"); + + bytesRemaining -= bytesToFill; + } +} + +void AudioInputPulse::StreamSuccessCallback(pa_stream *stream, int success, void *userdata) { + return; +} \ No newline at end of file diff --git a/os/linux/AudioInputPulse.h b/os/linux/AudioInputPulse.h new file mode 100644 index 0000000..24905de --- /dev/null +++ b/os/linux/AudioInputPulse.h @@ -0,0 +1,83 @@ +// +// libtgvoip is free and unencumbered public domain software. +// For more information, see http://unlicense.org or the UNLICENSE file +// you should have received with this source code distribution. +// + +#ifndef LIBTGVOIP_AUDIOINPUTPULSE_H +#define LIBTGVOIP_AUDIOINPUTPULSE_H + +#include "../../audio/AudioInput.h" +#include "../../threading.h" +#include + +#define DECLARE_DL_FUNCTION(name) typeof(name)* _import_##name + +namespace tgvoip{ +namespace audio{ + +class AudioInputPulse : public AudioInput{ +public: + AudioInputPulse(std::string devID); + virtual ~AudioInputPulse(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + virtual bool IsRecording(); + virtual void SetCurrentDevice(std::string devID); + static void EnumerateDevices(std::vector& devs); + static bool IsAvailable(); + +private: + static void ContextStateCallback(pa_context* context, void* arg); + static void StreamStateCallback(pa_stream* s, void* arg); + static void StreamSuccessCallback(pa_stream* stream, int success, void* userdata); + static void StreamReadCallback(pa_stream* stream, size_t requested_bytes, void* userdata); + void StreamReadCallback(pa_stream* stream, size_t requestedBytes); + + pa_threaded_mainloop* mainloop; + pa_mainloop_api* mainloopApi; + pa_context* context; + pa_stream* stream; + void* lib; + + DECLARE_DL_FUNCTION(pa_threaded_mainloop_new); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api); + DECLARE_DL_FUNCTION(pa_context_new); + DECLARE_DL_FUNCTION(pa_context_set_state_callback); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_start); + DECLARE_DL_FUNCTION(pa_context_connect); + DECLARE_DL_FUNCTION(pa_context_get_state); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait); + DECLARE_DL_FUNCTION(pa_stream_new); + DECLARE_DL_FUNCTION(pa_stream_set_state_callback); + DECLARE_DL_FUNCTION(pa_stream_set_read_callback); + DECLARE_DL_FUNCTION(pa_stream_connect_record); + DECLARE_DL_FUNCTION(pa_operation_unref); + DECLARE_DL_FUNCTION(pa_stream_cork); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop); + DECLARE_DL_FUNCTION(pa_stream_disconnect); + DECLARE_DL_FUNCTION(pa_stream_unref); + DECLARE_DL_FUNCTION(pa_context_disconnect); + DECLARE_DL_FUNCTION(pa_context_unref); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_free); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal); + DECLARE_DL_FUNCTION(pa_stream_peek); + DECLARE_DL_FUNCTION(pa_stream_drop); + DECLARE_DL_FUNCTION(pa_stream_get_state); + DECLARE_DL_FUNCTION(pa_strerror); + + bool isRecording; + bool isConnected; + unsigned char remainingData[10240]; + size_t remainingDataSize; +}; + +} +} + +#undef DECLARE_DL_FUNCTION + +#endif //LIBTGVOIP_AUDIOINPUTPULSE_H diff --git a/os/linux/AudioOutputPulse.cpp b/os/linux/AudioOutputPulse.cpp new file mode 100644 index 0000000..18fd40c --- /dev/null +++ b/os/linux/AudioOutputPulse.cpp @@ -0,0 +1,313 @@ +// +// libtgvoip is free and 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 +#include +#include +#include "AudioOutputPulse.h" +#include "../../logging.h" +#include "../../VoIPController.h" + +#define BUFFER_SIZE 960 +#define CHECK_ERROR(res, msg) if(res!=0){LOGE(msg "failed: %s", pa_strerror(res)); failed=true; return;} +#define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;} +#define LOAD_DL_FUNCTION(name) {_import_##name=(typeof(_import_##name))dlsym(lib, #name); CHECK_DL_ERROR(_import_##name, "Error getting entry point for " #name);} + +#define pa_threaded_mainloop_new _import_pa_threaded_mainloop_new +#define pa_threaded_mainloop_get_api _import_pa_threaded_mainloop_get_api +#define pa_context_new _import_pa_context_new +#define pa_context_set_state_callback _import_pa_context_set_state_callback +#define pa_threaded_mainloop_lock _import_pa_threaded_mainloop_lock +#define pa_threaded_mainloop_unlock _import_pa_threaded_mainloop_unlock +#define pa_threaded_mainloop_start _import_pa_threaded_mainloop_start +#define pa_context_connect _import_pa_context_connect +#define pa_context_get_state _import_pa_context_get_state +#define pa_threaded_mainloop_wait _import_pa_threaded_mainloop_wait +#define pa_stream_new _import_pa_stream_new +#define pa_stream_set_state_callback _import_pa_stream_set_state_callback +#define pa_stream_set_write_callback _import_pa_stream_set_write_callback +#define pa_stream_connect_playback _import_pa_stream_connect_playback +#define pa_operation_unref _import_pa_operation_unref +#define pa_stream_cork _import_pa_stream_cork +#define pa_threaded_mainloop_stop _import_pa_threaded_mainloop_stop +#define pa_stream_disconnect _import_pa_stream_disconnect +#define pa_stream_unref _import_pa_stream_unref +#define pa_context_disconnect _import_pa_context_disconnect +#define pa_context_unref _import_pa_context_unref +#define pa_threaded_mainloop_free _import_pa_threaded_mainloop_free +#define pa_threaded_mainloop_signal _import_pa_threaded_mainloop_signal +#define pa_stream_begin_write _import_pa_stream_begin_write +#define pa_stream_write _import_pa_stream_write +#define pa_strerror _import_pa_strerror +#define pa_stream_get_state _import_pa_stream_get_state + +using namespace tgvoip::audio; + +AudioOutputPulse::AudioOutputPulse(std::string devID){ + isPlaying=false; + isConnected=false; + + mainloop=NULL; + mainloopApi=NULL; + context=NULL; + stream=NULL; + remainingDataSize=0; + + lib=dlopen("libpulse.so.0", RTLD_LAZY); + if(!lib) + lib=dlopen("libpulse.so", RTLD_LAZY); + if(!lib){ + LOGE("Error loading libpulse: %s", dlerror()); + failed=true; + return; + } + + LOAD_DL_FUNCTION(pa_threaded_mainloop_new); + LOAD_DL_FUNCTION(pa_threaded_mainloop_get_api); + LOAD_DL_FUNCTION(pa_context_new); + LOAD_DL_FUNCTION(pa_context_set_state_callback); + LOAD_DL_FUNCTION(pa_threaded_mainloop_lock); + LOAD_DL_FUNCTION(pa_threaded_mainloop_unlock); + LOAD_DL_FUNCTION(pa_threaded_mainloop_start); + LOAD_DL_FUNCTION(pa_context_connect); + LOAD_DL_FUNCTION(pa_context_get_state); + LOAD_DL_FUNCTION(pa_threaded_mainloop_wait); + LOAD_DL_FUNCTION(pa_stream_new); + LOAD_DL_FUNCTION(pa_stream_set_state_callback); + LOAD_DL_FUNCTION(pa_stream_set_write_callback); + LOAD_DL_FUNCTION(pa_stream_connect_playback); + LOAD_DL_FUNCTION(pa_operation_unref); + LOAD_DL_FUNCTION(pa_stream_cork); + LOAD_DL_FUNCTION(pa_threaded_mainloop_stop); + LOAD_DL_FUNCTION(pa_stream_disconnect); + LOAD_DL_FUNCTION(pa_stream_unref); + LOAD_DL_FUNCTION(pa_context_disconnect); + LOAD_DL_FUNCTION(pa_context_unref); + LOAD_DL_FUNCTION(pa_threaded_mainloop_free); + LOAD_DL_FUNCTION(pa_threaded_mainloop_signal); + LOAD_DL_FUNCTION(pa_stream_begin_write); + LOAD_DL_FUNCTION(pa_stream_write); + LOAD_DL_FUNCTION(pa_stream_get_state); + LOAD_DL_FUNCTION(pa_strerror); + + mainloop=pa_threaded_mainloop_new(); + if(!mainloop){ + LOGE("Error initializing PulseAudio (pa_threaded_mainloop_new)"); + failed=true; + return; + } + mainloopApi=pa_threaded_mainloop_get_api(mainloop); + char exePath[MAXPATHLEN]; + char exeName[MAXPATHLEN]; + ssize_t lres=readlink("/proc/self/exe", exePath, sizeof(exePath)); + if(lres==-1) + lres=readlink("/proc/curproc/file", exePath, sizeof(exePath)); + if(lres==-1) + lres=readlink("/proc/curproc/exe", exePath, sizeof(exePath)); + if(lres>0){ + strcpy(exeName, basename(exePath)); + }else{ + snprintf(exeName, sizeof(exeName), "Process %d", getpid()); + } + context=pa_context_new(mainloopApi, exeName); + if(!context){ + LOGE("Error initializing PulseAudio (pa_context_new)"); + failed=true; + return; + } + pa_context_set_state_callback(context, AudioOutputPulse::ContextStateCallback, this); + pa_threaded_mainloop_lock(mainloop); + int err=pa_threaded_mainloop_start(mainloop); + CHECK_ERROR(err, "pa_threaded_mainloop_start"); + + err=pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); + CHECK_ERROR(err, "pa_context_connect"); + + while(true){ + pa_context_state_t contextState=pa_context_get_state(context); + if(!PA_CONTEXT_IS_GOOD(contextState)){ + LOGE("Error initializing PulseAudio (PA_CONTEXT_IS_GOOD)"); + failed=true; + return; + } + if(contextState==PA_CONTEXT_READY) + break; + pa_threaded_mainloop_wait(mainloop); + } + + pa_sample_spec sample_specifications{ + .format=PA_SAMPLE_S16LE, + .rate=48000, + .channels=1 + }; + + stream=pa_stream_new(context, "libtgvoip playback", &sample_specifications, NULL); + if(!stream){ + LOGE("Error initializing PulseAudio (pa_stream_new)"); + failed=true; + return; + } + pa_stream_set_state_callback(stream, AudioOutputPulse::StreamStateCallback, this); + pa_stream_set_write_callback(stream, AudioOutputPulse::StreamWriteCallback, this); + + SetCurrentDevice(devID); +} + +AudioOutputPulse::~AudioOutputPulse(){ + if(mainloop) + pa_threaded_mainloop_stop(mainloop); + if(stream){ + pa_stream_disconnect(stream); + pa_stream_unref(stream); + } + if(context){ + pa_context_disconnect(context); + pa_context_unref(context); + } + if(mainloop) + pa_threaded_mainloop_free(mainloop); + + if(lib) + dlclose(lib); +} + +bool AudioOutputPulse::IsAvailable(){ + void* lib=dlopen("libpulse.so.0", RTLD_LAZY); + if(!lib) + lib=dlopen("libpulse.so", RTLD_LAZY); + if(lib){ + dlclose(lib); + return true; + } + return false; +} + +void AudioOutputPulse::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ + +} + +void AudioOutputPulse::Start(){ + if(failed || isPlaying) + return; + + isPlaying=true; + pa_operation_unref(pa_stream_cork(stream, 0, AudioOutputPulse::StreamSuccessCallback, mainloop)); +} + +void AudioOutputPulse::Stop(){ + if(!isPlaying) + return; + + isPlaying=false; + pa_operation_unref(pa_stream_cork(stream, 1, AudioOutputPulse::StreamSuccessCallback, mainloop)); +} + +bool AudioOutputPulse::IsPlaying(){ + return isPlaying; +} + +void AudioOutputPulse::SetCurrentDevice(std::string devID){ + currentDevice=devID; + if(isPlaying && isConnected){ + pa_threaded_mainloop_lock(mainloop); + pa_stream_disconnect(stream); + isConnected=false; + } + + pa_buffer_attr bufferAttr={ + .maxlength=960*6, + .tlength=960*2, + .prebuf=0, + .minreq=960*2 + }; + int streamFlags=PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_NOT_MONOTONIC | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; + + int err=pa_stream_connect_playback(stream, devID=="default" ? NULL : devID.c_str(), &bufferAttr, (pa_stream_flags_t)streamFlags, NULL, NULL); + if(err!=0 && devID!="default"){ + SetCurrentDevice("default"); + return; + } + CHECK_ERROR(err, "pa_stream_connect_playback"); + + while(true){ + pa_stream_state_t streamState=pa_stream_get_state(stream); + if(!PA_STREAM_IS_GOOD(streamState)){ + LOGE("Error connecting to audio device '%s'", devID.c_str()); + failed=true; + return; + } + if(streamState==PA_STREAM_READY) + break; + pa_threaded_mainloop_wait(mainloop); + } + + isConnected=true; + pa_threaded_mainloop_unlock(mainloop); + + if(isPlaying){ + pa_operation_unref(pa_stream_cork(stream, 0, AudioOutputPulse::StreamSuccessCallback, mainloop)); + } +} + +void AudioOutputPulse::EnumerateDevices(std::vector& devs){ + +} + +void AudioOutputPulse::ContextStateCallback(pa_context* context, void* arg) { + AudioOutputPulse* self=(AudioOutputPulse*) arg; + self->pa_threaded_mainloop_signal(self->mainloop, 0); +} + +void AudioOutputPulse::StreamStateCallback(pa_stream *s, void* arg) { + AudioOutputPulse* self=(AudioOutputPulse*) arg; + self->pa_threaded_mainloop_signal(self->mainloop, 0); +} + +void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes, void *userdata){ + ((AudioOutputPulse*)userdata)->StreamWriteCallback(stream, requestedBytes); +} + +void AudioOutputPulse::StreamWriteCallback(pa_stream *stream, size_t requestedBytes) { + int bytesRemaining = requestedBytes; + uint8_t *buffer = NULL; + while (bytesRemaining > 0) { + size_t bytesToFill = 102400; + size_t i; + + if (bytesToFill > bytesRemaining) bytesToFill = bytesRemaining; + + int err=pa_stream_begin_write(stream, (void**) &buffer, &bytesToFill); + CHECK_ERROR(err, "pa_stream_begin_write"); + + if(isPlaying){ + while(remainingDataSize=sizeof(remainingData)){ + LOGE("Can't provide %d bytes of audio data at a time", (int)bytesToFill); + failed=true; + return; + } + InvokeCallback(remainingData+remainingDataSize, 960*2); + remainingDataSize+=960*2; + } + memcpy(buffer, remainingData, bytesToFill); + memmove(remainingData, remainingData+bytesToFill, remainingDataSize-bytesToFill); + remainingDataSize-=bytesToFill; + }else{ + memset(buffer, 0, bytesToFill); + } + + err=pa_stream_write(stream, buffer, bytesToFill, NULL, 0LL, PA_SEEK_RELATIVE); + CHECK_ERROR(err, "pa_stream_write"); + + bytesRemaining -= bytesToFill; + } +} + +void AudioOutputPulse::StreamSuccessCallback(pa_stream *stream, int success, void *userdata) { + return; +} \ No newline at end of file diff --git a/os/linux/AudioOutputPulse.h b/os/linux/AudioOutputPulse.h new file mode 100644 index 0000000..b1ee470 --- /dev/null +++ b/os/linux/AudioOutputPulse.h @@ -0,0 +1,83 @@ +// +// libtgvoip is free and unencumbered public domain software. +// For more information, see http://unlicense.org or the UNLICENSE file +// you should have received with this source code distribution. +// + +#ifndef LIBTGVOIP_AUDIOOUTPUTPULSE_H +#define LIBTGVOIP_AUDIOOUTPUTPULSE_H + +#include "../../audio/AudioOutput.h" +#include "../../threading.h" +#include + +#define DECLARE_DL_FUNCTION(name) typeof(name)* _import_##name + +namespace tgvoip{ +namespace audio{ + +class AudioOutputPulse : public AudioOutput{ +public: + AudioOutputPulse(std::string devID); + virtual ~AudioOutputPulse(); + virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels); + virtual void Start(); + virtual void Stop(); + virtual bool IsPlaying(); + virtual void SetCurrentDevice(std::string devID); + static void EnumerateDevices(std::vector& devs); + static bool IsAvailable(); + +private: + static void ContextStateCallback(pa_context* context, void* arg); + static void StreamStateCallback(pa_stream* s, void* arg); + static void StreamSuccessCallback(pa_stream* stream, int success, void* userdata); + static void StreamWriteCallback(pa_stream* stream, size_t requested_bytes, void* userdata); + void StreamWriteCallback(pa_stream* stream, size_t requestedBytes); + + pa_threaded_mainloop* mainloop; + pa_mainloop_api* mainloopApi; + pa_context* context; + pa_stream* stream; + void* lib; + + DECLARE_DL_FUNCTION(pa_threaded_mainloop_new); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_get_api); + DECLARE_DL_FUNCTION(pa_context_new); + DECLARE_DL_FUNCTION(pa_context_set_state_callback); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_lock); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_unlock); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_start); + DECLARE_DL_FUNCTION(pa_context_connect); + DECLARE_DL_FUNCTION(pa_context_get_state); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_wait); + DECLARE_DL_FUNCTION(pa_stream_new); + DECLARE_DL_FUNCTION(pa_stream_set_state_callback); + DECLARE_DL_FUNCTION(pa_stream_set_write_callback); + DECLARE_DL_FUNCTION(pa_stream_connect_playback); + DECLARE_DL_FUNCTION(pa_operation_unref); + DECLARE_DL_FUNCTION(pa_stream_cork); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_stop); + DECLARE_DL_FUNCTION(pa_stream_disconnect); + DECLARE_DL_FUNCTION(pa_stream_unref); + DECLARE_DL_FUNCTION(pa_context_disconnect); + DECLARE_DL_FUNCTION(pa_context_unref); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_free); + DECLARE_DL_FUNCTION(pa_threaded_mainloop_signal); + DECLARE_DL_FUNCTION(pa_stream_begin_write); + DECLARE_DL_FUNCTION(pa_stream_write); + DECLARE_DL_FUNCTION(pa_stream_get_state); + DECLARE_DL_FUNCTION(pa_strerror); + + bool isPlaying; + bool isConnected; + unsigned char remainingData[10240]; + size_t remainingDataSize; +}; + +} +} + +#undef DECLARE_DL_FUNCTION + +#endif //LIBTGVOIP_AUDIOOUTPUTPULSE_H diff --git a/os/windows/AudioInputWASAPI.cpp b/os/windows/AudioInputWASAPI.cpp index 7c665bd..fd779f0 100644 --- a/os/windows/AudioInputWASAPI.cpp +++ b/os/windows/AudioInputWASAPI.cpp @@ -329,6 +329,8 @@ void AudioInputWASAPI::RunThread() { bufferSize=0; LOGV("stream switch done"); }else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent + if(!audioClient) + continue; if(bufferSize==0){ res=audioClient->GetBufferSize(&bufferSize); CHECK_RES(res, "audioClient->GetBufferSize"); diff --git a/os/windows/AudioOutputWASAPI.cpp b/os/windows/AudioOutputWASAPI.cpp index 3f86415..e60d97a 100644 --- a/os/windows/AudioOutputWASAPI.cpp +++ b/os/windows/AudioOutputWASAPI.cpp @@ -339,6 +339,8 @@ void AudioOutputWASAPI::RunThread() { 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; diff --git a/os/windows/CXWrapper.cpp b/os/windows/CXWrapper.cpp index cbe361f..fd7a264 100644 --- a/os/windows/CXWrapper.cpp +++ b/os/windows/CXWrapper.cpp @@ -158,7 +158,7 @@ void VoIPControllerWrapper::UpdateServerConfig(Platform::String^ json){ char _value[256]; WideCharToMultiByte(CP_UTF8, 0, item->Key->Data(), -1, _key, sizeof(_key), NULL, NULL); if(item->Value->ValueType==Windows::Data::Json::JsonValueType::String) - WideCharToMultiByte(CP_UTF8, 0, ((Platform::String^)item->Value)->Data(), -1, _value, sizeof(_value), NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, item->Value->GetString()->Data(), -1, _value, sizeof(_value), NULL, NULL); else WideCharToMultiByte(CP_UTF8, 0, item->Value->ToString()->Data(), -1, _value, sizeof(_value), NULL, NULL); std::string key(_key); diff --git a/webrtc_dsp/webrtc/base/safe_compare.h b/webrtc_dsp/webrtc/base/safe_compare.h index a49a2a1..0995424 100644 --- a/webrtc_dsp/webrtc/base/safe_compare.h +++ b/webrtc_dsp/webrtc/base/safe_compare.h @@ -31,7 +31,7 @@ #ifndef WEBRTC_BASE_SAFE_COMPARE_H_ #define WEBRTC_BASE_SAFE_COMPARE_H_ -#if _MSC_VER<=1800 +#if defined(_MSC_VER) && _MSC_VER<=1800 #define constexpr const // Older MSVC used for WP app doesn't support this thing but we can ignore it #endif