Revered the audio system back to PortAudio
This commit is contained in:
parent
91982ecc7b
commit
19b7e46137
@ -63,7 +63,9 @@ namespace audio.player {
|
||||
console.warn("Main audio underflow");
|
||||
};
|
||||
|
||||
_audioContext = new AudioContext();
|
||||
_audioContext = new AudioContext({
|
||||
sampleRate: _output_stream.sample_rate
|
||||
});
|
||||
_processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels);
|
||||
|
||||
_processor.onaudioprocess = function(event) {
|
||||
|
@ -10,6 +10,7 @@ export namespace _audio.recorder {
|
||||
|
||||
interface NativeDevice extends InputDevice {
|
||||
device_index: number;
|
||||
native: any;
|
||||
}
|
||||
|
||||
let _device_cache: NativeDevice[] = undefined;
|
||||
@ -26,6 +27,7 @@ export namespace _audio.recorder {
|
||||
name: e.name,
|
||||
driver: e.driver,
|
||||
sample_rate: 48000, /* TODO! */
|
||||
native: e
|
||||
} as NativeDevice
|
||||
}));
|
||||
}
|
||||
|
@ -22,11 +22,10 @@ message("Module path: ${CMAKE_MODULE_PATH}")
|
||||
function(setup_nodejs)
|
||||
set(NodeJS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
|
||||
set(NODEJS_URL "https://atom.io/download/atom-shell")
|
||||
set(NODEJS_VERSION "v7.1.2")
|
||||
#set(NODEJS_VERSION "v8.0.0")
|
||||
set(NODEJS_VERSION "v8.0.0")
|
||||
|
||||
#set(NODEJS_URL "https://nodejs.org/download/release/")
|
||||
#set(NODEJS_VERSION "v12.14.1")
|
||||
#set(NODEJS_VERSION "v12.13.0")
|
||||
|
||||
find_package(NodeJS REQUIRED)
|
||||
|
||||
|
@ -2,72 +2,89 @@ set(MODULE_NAME "teaclient_connection")
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -static-libasan -lasan -lubsan")
|
||||
set(SOURCE_FILES
|
||||
src/logger.cpp
|
||||
src/EventLoop.cpp
|
||||
src/hwuid.cpp
|
||||
src/logger.cpp
|
||||
src/EventLoop.cpp
|
||||
src/hwuid.cpp
|
||||
src/ring_buffer.cpp
|
||||
|
||||
src/connection/ft/FileTransferManager.cpp
|
||||
src/connection/ft/FileTransferObject.cpp
|
||||
src/connection/ft/FileTransferManager.cpp
|
||||
src/connection/ft/FileTransferObject.cpp
|
||||
|
||||
src/audio/AudioSamples.cpp
|
||||
src/audio/AudioMerger.cpp
|
||||
src/audio/AudioOutput.cpp
|
||||
src/audio/AudioInput.cpp
|
||||
src/audio/AudioResampler.cpp
|
||||
src/audio/AudioReframer.cpp
|
||||
src/audio/AudioSamples.cpp
|
||||
src/audio/AudioMerger.cpp
|
||||
src/audio/AudioOutput.cpp
|
||||
src/audio/AudioInput.cpp
|
||||
src/audio/AudioResampler.cpp
|
||||
src/audio/AudioReframer.cpp
|
||||
|
||||
src/audio/filter/FilterVad.cpp
|
||||
src/audio/filter/FilterThreshold.cpp
|
||||
src/audio/filter/FilterState.cpp
|
||||
src/audio/filter/FilterVad.cpp
|
||||
src/audio/filter/FilterThreshold.cpp
|
||||
src/audio/filter/FilterState.cpp
|
||||
|
||||
src/audio/codec/Converter.cpp
|
||||
src/audio/codec/OpusConverter.cpp
|
||||
src/audio/codec/Converter.cpp
|
||||
src/audio/codec/OpusConverter.cpp
|
||||
|
||||
src/audio/driver/AudioDriver.cpp
|
||||
src/audio/driver/SoundIO.cpp
|
||||
src/audio/driver/SoundIOPlayback.cpp
|
||||
src/audio/driver/SoundIORecord.cpp
|
||||
)
|
||||
)
|
||||
|
||||
set(NODEJS_SOURCE_FILES
|
||||
src/bindings.cpp
|
||||
src/bindings.cpp
|
||||
|
||||
src/connection/ServerConnection.cpp
|
||||
src/connection/Socket.cpp
|
||||
src/connection/ProtocolHandler.cpp
|
||||
src/connection/ProtocolHandlerPOW.cpp
|
||||
src/connection/ProtocolHandlerCrypto.cpp
|
||||
src/connection/ProtocolHandlerPackets.cpp
|
||||
src/connection/ProtocolHandlerCommands.cpp
|
||||
src/connection/audio/AudioSender.cpp
|
||||
src/connection/audio/AudioEventLoop.cpp
|
||||
src/connection/ServerConnection.cpp
|
||||
src/connection/Socket.cpp
|
||||
src/connection/ProtocolHandler.cpp
|
||||
src/connection/ProtocolHandlerPOW.cpp
|
||||
src/connection/ProtocolHandlerCrypto.cpp
|
||||
src/connection/ProtocolHandlerPackets.cpp
|
||||
src/connection/ProtocolHandlerCommands.cpp
|
||||
src/connection/audio/AudioSender.cpp
|
||||
src/connection/audio/AudioEventLoop.cpp
|
||||
|
||||
src/connection/audio/VoiceConnection.cpp
|
||||
src/connection/audio/VoiceClient.cpp
|
||||
src/connection/audio/VoiceConnection.cpp
|
||||
src/connection/audio/VoiceClient.cpp
|
||||
|
||||
src/audio/js/AudioPlayer.cpp
|
||||
src/audio/js/AudioOutputStream.cpp
|
||||
src/audio/js/AudioPlayer.cpp
|
||||
src/audio/js/AudioOutputStream.cpp
|
||||
|
||||
src/audio/js/AudioRecorder.cpp
|
||||
src/audio/js/AudioConsumer.cpp
|
||||
src/audio/js/AudioFilter.cpp
|
||||
src/audio/js/AudioRecorder.cpp
|
||||
src/audio/js/AudioConsumer.cpp
|
||||
src/audio/js/AudioFilter.cpp
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
set(SOURCE_FILES ${SOURCE_FILES})
|
||||
add_definitions(-DUSING_UV_SHARED)
|
||||
if (SOUNDIO_BACKED)
|
||||
find_package(soundio REQUIRED)
|
||||
include_directories(${soundio_INCLUDE_DIR})
|
||||
add_compile_definitions(SOUNDIO_STATIC_LIBRARY)
|
||||
|
||||
list(APPEND SOURCE_FILES
|
||||
src/audio/driver/SoundIO.cpp
|
||||
src/audio/driver/SoundIOPlayback.cpp
|
||||
src/audio/driver/SoundIORecord.cpp
|
||||
)
|
||||
add_definitions(-DHAVE_SOUNDIO)
|
||||
else()
|
||||
set(SOURCE_FILES ${SOURCE_FILES})
|
||||
add_definitions(-DHAVE_PORTAUDIO)
|
||||
|
||||
list(APPEND SOURCE_FILES
|
||||
src/audio/driver/PortAudio.cpp
|
||||
src/audio/driver/PortAudioRecord.cpp
|
||||
src/audio/driver/PortAudioPlayback.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set(SOURCE_FILES ${SOURCE_FILES})
|
||||
add_definitions(-DUSING_UV_SHARED)
|
||||
else()
|
||||
set(SOURCE_FILES ${SOURCE_FILES})
|
||||
endif()
|
||||
|
||||
add_nodejs_module(${MODULE_NAME} ${SOURCE_FILES} ${NODEJS_SOURCE_FILES})
|
||||
target_link_libraries(${MODULE_NAME} ${NODEJS_LIBRARIES})
|
||||
#target_compile_options(${MODULE_NAME} PUBLIC "-fPIC")
|
||||
|
||||
find_package(soundio REQUIRED)
|
||||
include_directories(${soundio_INCLUDE_DIR})
|
||||
add_compile_definitions(SOUNDIO_STATIC_LIBRARY)
|
||||
find_package(PortAudio REQUIRED)
|
||||
include_directories(${PortAudio_INCLUDE_DIR})
|
||||
|
||||
find_package(TomMath REQUIRED)
|
||||
include_directories(${TomMath_INCLUDE_DIR})
|
||||
@ -95,10 +112,10 @@ include_directories(${ed25519_INCLUDE_DIR})
|
||||
find_package(ThreadPool REQUIRED)
|
||||
include_directories(${ThreadPool_INCLUDE_DIR})
|
||||
if (WIN32)
|
||||
add_compile_options(/NODEFAULTLIB:ThreadPoolStatic)
|
||||
add_definitions(-DWINDOWS) #Required by ThreadPool
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS) # Let windows allow strerror
|
||||
add_definitions(-D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING) # For the FMT library
|
||||
add_compile_options(/NODEFAULTLIB:ThreadPoolStatic)
|
||||
add_definitions(-DWINDOWS) #Required by ThreadPool
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS) # Let windows allow strerror
|
||||
add_definitions(-D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING) # For the FMT library
|
||||
endif ()
|
||||
|
||||
find_package(Soxr REQUIRED)
|
||||
@ -113,36 +130,41 @@ include_directories(${opus_INCLUDE_DIR})
|
||||
find_package(spdlog REQUIRED)
|
||||
|
||||
set(REQUIRED_LIBRARIES
|
||||
${TeaSpeak_SharedLib_LIBRARIES_STATIC}
|
||||
${TeaSpeak_SharedLib_LIBRARIES_STATIC}
|
||||
|
||||
${TomCrypt_LIBRARIES_STATIC}
|
||||
${TomMath_LIBRARIES_STATIC}
|
||||
${TomCrypt_LIBRARIES_STATIC}
|
||||
${TomMath_LIBRARIES_STATIC}
|
||||
|
||||
${LIBEVENT_STATIC_LIBRARIES}
|
||||
${LIBEVENT_STATIC_LIBRARIES}
|
||||
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${DataPipes_LIBRARIES_STATIC} #Needs to be static because something causes ca bad function call when loaded in electron
|
||||
${ThreadPool_LIBRARIES_STATIC}
|
||||
${soxr_LIBRARIES_STATIC}
|
||||
${fvad_LIBRARIES_STATIC}
|
||||
${opus_LIBRARIES_STATIC}
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${DataPipes_LIBRARIES_STATIC} #Needs to be static because something causes ca bad function call when loaded in electron
|
||||
${ThreadPool_LIBRARIES_STATIC}
|
||||
${soxr_LIBRARIES_STATIC}
|
||||
${fvad_LIBRARIES_STATIC}
|
||||
${opus_LIBRARIES_STATIC}
|
||||
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
soundio::static
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
|
||||
spdlog::spdlog_header_only
|
||||
Nan::Helpers
|
||||
)
|
||||
spdlog::spdlog_header_only
|
||||
Nan::Helpers
|
||||
)
|
||||
|
||||
if (SOUNDIO_BACKED)
|
||||
list(APPEND REQUIRED_LIBRARIES soundio::static)
|
||||
else()
|
||||
list(APPEND REQUIRED_LIBRARIES ${PortAudio_LIBRARIES_STATIC})
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
set(REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} "Ws2_32.Lib")
|
||||
set(REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} "Ws2_32.Lib")
|
||||
else()
|
||||
set(REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES}
|
||||
libstdc++fs.a
|
||||
asound
|
||||
jack.a
|
||||
pthread
|
||||
)
|
||||
set(REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES}
|
||||
libstdc++fs.a
|
||||
asound
|
||||
jack.a
|
||||
pthread
|
||||
)
|
||||
endif()
|
||||
|
||||
add_definitions(-DNO_OPEN_SSL)
|
||||
@ -157,5 +179,5 @@ target_link_libraries(Audio-Test-2 ${REQUIRED_LIBRARIES})
|
||||
|
||||
add_executable(HW-UID-Test src/hwuid.cpp)
|
||||
target_link_libraries(HW-UID-Test
|
||||
${REQUIRED_LIBRARIES}
|
||||
)
|
||||
${REQUIRED_LIBRARIES}
|
||||
)
|
@ -111,6 +111,8 @@ ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *buffer, siz
|
||||
src_buffer++;
|
||||
}
|
||||
}
|
||||
this->buffer.advance_write_ptr(enqueued * this->channel_count * sizeof(float));
|
||||
if(enqueued == samples) return enqueued;
|
||||
|
||||
if(auto fn = this->on_overflow; fn)
|
||||
fn(samples - enqueued);
|
||||
@ -173,17 +175,17 @@ void AudioOutput::cleanup_buffers() {
|
||||
this->resample_overhead_samples = 0;
|
||||
}
|
||||
|
||||
void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t channels) {
|
||||
if(channels != this->_channel_count) {
|
||||
log_critical(category::audio, tr("Channel count miss match (output)! Fixme!"));
|
||||
void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_channels) {
|
||||
if(out_channels != this->_channel_count) {
|
||||
log_critical(category::audio, tr("Channel count miss match (output)! Expected: {} Received: {}. Fixme!"), this->_channel_count, out_channels);
|
||||
return;
|
||||
}
|
||||
const auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count;
|
||||
auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count;
|
||||
void* const original_output{output};
|
||||
|
||||
if(this->resample_overhead_samples > 0) {
|
||||
const auto samples_to_write = this->resample_overhead_samples > out_frame_count ? out_frame_count : this->resample_overhead_samples;
|
||||
const auto byte_length = samples_to_write * sizeof(float) * channels;
|
||||
const auto byte_length = samples_to_write * sizeof(float) * out_channels;
|
||||
|
||||
if(output) memcpy(output, this->resample_overhead_buffer, byte_length);
|
||||
if(samples_to_write == out_frame_count) {
|
||||
@ -194,6 +196,7 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann
|
||||
this->resample_overhead_samples = 0;
|
||||
output = (char*) output + byte_length;
|
||||
out_frame_count -= samples_to_write;
|
||||
local_frame_count -= this->_resampler ? this->_resampler->input_size(samples_to_write) : samples_to_write;
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +207,7 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann
|
||||
} else if(this->_volume <= 0) {
|
||||
for(auto& source : this->_sources)
|
||||
source->pop_samples(nullptr, local_frame_count);
|
||||
memset(output, 0, local_frame_count * channels * sizeof(float));
|
||||
memset(output, 0, local_frame_count * out_channels * sizeof(float));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -272,6 +275,7 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann
|
||||
if(resampled_samples != out_frame_count) {
|
||||
if(resampled_samples > out_frame_count) {
|
||||
const auto diff_length = resampled_samples - out_frame_count;
|
||||
log_warn(category::audio, tr("enqueuing {} samples"), diff_length);
|
||||
const auto overhead_buffer_offset = this->resample_overhead_samples * sizeof(float) * this->_channel_count;
|
||||
const auto diff_byte_length = diff_length * sizeof(float) * this->_channel_count;
|
||||
|
||||
@ -290,7 +294,7 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann
|
||||
);
|
||||
this->resample_overhead_samples += diff_length;
|
||||
} else {
|
||||
log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}"), resampled_samples, out_frame_count);
|
||||
log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}. Sampled from {} to {}"), resampled_samples, out_frame_count, this->_resampler->input_rate(), this->_resampler->output_rate());
|
||||
}
|
||||
}
|
||||
memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->_channel_count);
|
||||
@ -308,6 +312,7 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann
|
||||
} else {
|
||||
clear_buffer_exit:
|
||||
memset(output, 0, this->_channel_count * sizeof(float) * out_frame_count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ namespace tc::audio {
|
||||
inline float volume() { return this->_volume; }
|
||||
inline void set_volume(float value) { this->_volume = value; }
|
||||
private:
|
||||
void fill_buffer(void *, size_t out_frame_count, size_t channels) override;
|
||||
void fill_buffer(void *, size_t out_frame_count, size_t out_channels) override;
|
||||
|
||||
size_t const _channel_count;
|
||||
size_t const _sample_rate;
|
||||
|
@ -10,8 +10,7 @@
|
||||
#define ssize_t int64_t
|
||||
#endif
|
||||
|
||||
namespace tc {
|
||||
namespace audio {
|
||||
namespace tc::audio {
|
||||
class AudioResampler {
|
||||
public:
|
||||
AudioResampler(size_t /* input rate */, size_t /* output rate */, size_t /* channels */);
|
||||
@ -23,10 +22,11 @@ namespace tc {
|
||||
|
||||
[[nodiscard]] inline long double io_ratio() { return (long double) this->_output_rate / (long double) this->_input_rate; }
|
||||
[[nodiscard]] inline size_t estimated_output_size(size_t input_length) {
|
||||
return (size_t) lroundl(this->io_ratio() * input_length) + 1;
|
||||
if(!this->soxr_handle) return input_length; /* no resembling needed */
|
||||
return (size_t) ceill(this->io_ratio() * input_length + *soxr_num_clips(this->soxr_handle)) + 1;
|
||||
}
|
||||
[[nodiscard]] inline size_t input_size(size_t output_length) {
|
||||
return (size_t) lroundl((long double) this->_input_rate / (long double) this->_output_rate * output_length);
|
||||
return (size_t) ceill((long double) this->_input_rate / (long double) this->_output_rate * output_length);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool valid() { return this->io_ratio() == 1 || this->soxr_handle != nullptr; }
|
||||
@ -39,5 +39,4 @@ namespace tc {
|
||||
|
||||
soxr_t soxr_handle = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -5,21 +5,22 @@
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include "AudioDriver.h"
|
||||
#include "SoundIO.h"
|
||||
#include "./AudioDriver.h"
|
||||
#include "../../logger.h"
|
||||
#include "../AudioMerger.h"
|
||||
|
||||
#ifdef HAVE_SOUNDIO
|
||||
#include "./SoundIO.h"
|
||||
#else
|
||||
#include "PortAudio.h"
|
||||
#endif
|
||||
|
||||
using namespace tc::audio;
|
||||
|
||||
namespace tc::audio {
|
||||
inline bool ends_with(std::string const & value, std::string const & ending) {
|
||||
if (ending.size() > value.size()) return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<AudioDevice>> devices() {
|
||||
std::deque<std::shared_ptr<AudioDevice>> result{};
|
||||
#ifdef HAVE_SOUNDIO
|
||||
for(auto& backend : SoundIOBackendHandler::all_backends()) {
|
||||
auto input_devices = backend->input_devices();
|
||||
auto output_devices = backend->output_devices();
|
||||
@ -27,16 +28,26 @@ namespace tc::audio {
|
||||
result.insert(result.end(), input_devices.begin(), input_devices.end());
|
||||
result.insert(result.end(), output_devices.begin(), output_devices.end());
|
||||
}
|
||||
#else
|
||||
auto devices = pa::devices();
|
||||
result.insert(result.end(), devices.begin(), devices.end());
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioDevice> find_device_by_id(const std::string_view& id, bool input) {
|
||||
#ifdef HAVE_SOUNDIO
|
||||
for(auto& backend : SoundIOBackendHandler::all_backends()) {
|
||||
for(auto& dev : input ? backend->input_devices() : backend->output_devices())
|
||||
if(dev->id() == id)
|
||||
return dev;
|
||||
}
|
||||
#else
|
||||
for(auto& device : devices())
|
||||
if(device->id() == id && (input ? device->is_input_supported() : device->is_output_supported()))
|
||||
return device;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -44,6 +55,7 @@ namespace tc::audio {
|
||||
std::deque<initialize_callback_t> initialize_callbacks{};
|
||||
int initialize_state{0}; /* 0 := not initialized | 1 := initialized | 2 := initializing */
|
||||
|
||||
#ifdef HAVE_SOUNDIO
|
||||
void _initialize() {
|
||||
SoundIOBackendHandler::initialize_all();
|
||||
SoundIOBackendHandler::connect_all();
|
||||
@ -52,6 +64,15 @@ namespace tc::audio {
|
||||
void _finalize() {
|
||||
SoundIOBackendHandler::shutdown_all();
|
||||
}
|
||||
#else
|
||||
void _initialize() {
|
||||
pa::initialize();
|
||||
}
|
||||
|
||||
void _finalize() {
|
||||
pa::finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
void initialize(const initialize_callback_t& callback) {
|
||||
{
|
||||
|
111
native/serverconnection/src/audio/driver/PortAudio.cpp
Normal file
111
native/serverconnection/src/audio/driver/PortAudio.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Created by WolverinDEV on 13/02/2020.
|
||||
//
|
||||
|
||||
#include "PortAudio.h"
|
||||
#include "../../logger.h"
|
||||
#include <portaudio.h>
|
||||
|
||||
namespace tc::audio::pa {
|
||||
std::mutex _audio_devices_lock;
|
||||
std::unique_ptr<std::deque<std::shared_ptr<PaAudioDevice>>> _audio_devices{};
|
||||
|
||||
void initialize_devices() {
|
||||
std::lock_guard dev_lock{_audio_devices_lock};
|
||||
_audio_devices = std::make_unique<std::deque<std::shared_ptr<PaAudioDevice>>>();
|
||||
|
||||
/* query devices */
|
||||
auto device_count = Pa_GetDeviceCount();
|
||||
if(device_count < 0) {
|
||||
log_error(category::audio, tr("Pa_GetDeviceCount() returned {}"), device_count);
|
||||
return;
|
||||
}
|
||||
|
||||
for(PaDeviceIndex device_index = 0; device_index < device_count; device_index++) {
|
||||
auto device_info = Pa_GetDeviceInfo(device_index);
|
||||
if(!device_info) {
|
||||
log_warn(category::audio, tr("Pa_GetDeviceInfo(...) failed for device {}"), device_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto device_host_info = Pa_GetHostApiInfo(device_info->hostApi);
|
||||
if(!device_host_info) {
|
||||
log_warn(category::audio, tr("Pa_GetHostApiInfo(...) failed for device {} with host api {}"), device_index, device_info->hostApi);
|
||||
continue;
|
||||
}
|
||||
|
||||
_audio_devices->push_back(std::make_shared<PaAudioDevice>(device_index, device_info, device_host_info));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
Pa_Initialize();
|
||||
initialize_devices();
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<PaAudioDevice>> devices() {
|
||||
std::lock_guard dev_lock{_audio_devices_lock};
|
||||
return *_audio_devices;
|
||||
}
|
||||
|
||||
/* device class */
|
||||
PaAudioDevice::PaAudioDevice(PaDeviceIndex index, const PaDeviceInfo* info, const PaHostApiInfo* host)
|
||||
: _index{index}, _info{info}, _host_info{host} { }
|
||||
|
||||
std::string PaAudioDevice::id() const {
|
||||
return std::string{this->_info->name} + "_" + this->_host_info->name;
|
||||
}
|
||||
|
||||
std::string PaAudioDevice::name() const {
|
||||
return this->_info->name;
|
||||
}
|
||||
|
||||
std::string PaAudioDevice::driver() const {
|
||||
return this->_host_info->name;
|
||||
}
|
||||
|
||||
bool PaAudioDevice::is_input_supported() const {
|
||||
return this->_info->maxInputChannels > 0;
|
||||
}
|
||||
|
||||
bool PaAudioDevice::is_output_supported() const {
|
||||
return this->_info->maxOutputChannels > 0;
|
||||
}
|
||||
|
||||
bool PaAudioDevice::is_input_default() const {
|
||||
return this->_index == Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
bool PaAudioDevice::is_output_default() const {
|
||||
return this->_index == Pa_GetDefaultOutputDevice();
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioDevicePlayback> PaAudioDevice::playback() {
|
||||
if(!this->is_output_supported()) {
|
||||
log_warn(category::audio, tr("Tried to create playback manager for device which does not supports it."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->io_lock};
|
||||
if(!this->_playback)
|
||||
this->_playback = std::make_shared<PortAudioPlayback>(this->_index, this->_info);
|
||||
return this->_playback;
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioDeviceRecord> PaAudioDevice::record() {
|
||||
if(!this->is_input_supported()) {
|
||||
log_warn(category::audio, tr("Tried to create record manager for device which does not supports it."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->io_lock};
|
||||
if(!this->_record)
|
||||
this->_record = std::make_shared<PortAudioRecord>(this->_index, this->_info);
|
||||
return this->_record;
|
||||
}
|
||||
}
|
87
native/serverconnection/src/audio/driver/PortAudio.h
Normal file
87
native/serverconnection/src/audio/driver/PortAudio.h
Normal file
@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <portaudio.h>
|
||||
#include "./AudioDriver.h"
|
||||
|
||||
namespace tc::audio::pa {
|
||||
class PortAudioPlayback : public AudioDevicePlayback {
|
||||
public:
|
||||
static constexpr auto kChannelCount{2};
|
||||
static constexpr auto kSampleRate{44100};
|
||||
static constexpr auto kTimeSpan{0.01};
|
||||
|
||||
explicit PortAudioPlayback(PaDeviceIndex index, const PaDeviceInfo* info);
|
||||
virtual ~PortAudioPlayback();
|
||||
|
||||
[[nodiscard]] size_t sample_rate() const override;
|
||||
protected:
|
||||
bool impl_start(std::string& /* error */) override;
|
||||
void impl_stop() override;
|
||||
|
||||
private:
|
||||
void write_callback(void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags);
|
||||
|
||||
PaDeviceIndex index;
|
||||
const PaDeviceInfo* info;
|
||||
PaStream* stream{nullptr};
|
||||
};
|
||||
|
||||
class PortAudioRecord : public AudioDeviceRecord {
|
||||
public:
|
||||
static constexpr auto kChannelCount{2};
|
||||
static constexpr auto kSampleRate{44100};
|
||||
|
||||
explicit PortAudioRecord(PaDeviceIndex index, const PaDeviceInfo* info);
|
||||
virtual ~PortAudioRecord();
|
||||
|
||||
[[nodiscard]] size_t sample_rate() const override;
|
||||
protected:
|
||||
bool impl_start(std::string& /* error */) override;
|
||||
void impl_stop() override;
|
||||
|
||||
private:
|
||||
void read_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags);
|
||||
|
||||
PaDeviceIndex index;
|
||||
const PaDeviceInfo* info;
|
||||
PaStream* stream{nullptr};
|
||||
};
|
||||
|
||||
|
||||
struct PaAudioDevice : public AudioDevice {
|
||||
public:
|
||||
explicit PaAudioDevice(PaDeviceIndex index, const PaDeviceInfo* info, const PaHostApiInfo* host);
|
||||
virtual ~PaAudioDevice() = default;
|
||||
|
||||
[[nodiscard]] std::string id() const override;
|
||||
[[nodiscard]] std::string name() const override;
|
||||
[[nodiscard]] std::string driver() const override;
|
||||
|
||||
[[nodiscard]] bool is_input_supported() const override;
|
||||
[[nodiscard]] bool is_output_supported() const override;
|
||||
|
||||
[[nodiscard]] bool is_input_default() const override;
|
||||
[[nodiscard]] bool is_output_default() const override;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<AudioDevicePlayback> playback() override;
|
||||
[[nodiscard]] std::shared_ptr<AudioDeviceRecord> record() override;
|
||||
private:
|
||||
const PaDeviceIndex _index;
|
||||
const PaDeviceInfo* _info;
|
||||
const PaHostApiInfo* _host_info;
|
||||
|
||||
std::mutex io_lock{};
|
||||
std::shared_ptr<PortAudioPlayback> _playback;
|
||||
std::shared_ptr<PortAudioRecord> _record;
|
||||
};
|
||||
|
||||
|
||||
extern void initialize();
|
||||
extern void finalize();
|
||||
|
||||
extern std::deque<std::shared_ptr<PaAudioDevice>> devices();
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
//
|
||||
// Created by WolverinDEV on 13/02/2020.
|
||||
//
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "./PortAudio.h"
|
||||
#include "../../logger.h"
|
||||
|
||||
using namespace tc::audio::pa;
|
||||
|
||||
PortAudioPlayback::PortAudioPlayback(PaDeviceIndex index, const PaDeviceInfo *info)
|
||||
: index{index}, info{info} { }
|
||||
|
||||
PortAudioPlayback::~PortAudioPlayback() {
|
||||
this->stop();
|
||||
}
|
||||
|
||||
bool PortAudioPlayback::impl_start(std::string &error) {
|
||||
//TODO: Detect correct sample rate
|
||||
{
|
||||
auto device_info = Pa_GetDeviceInfo(this->index);
|
||||
if(this->info != device_info) {
|
||||
error = "invalid info pointer";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto proxied_write_callback = [](
|
||||
const void *input, void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData) {
|
||||
assert(output);
|
||||
|
||||
auto player = reinterpret_cast<PortAudioPlayback*>(userData);
|
||||
assert(player);
|
||||
|
||||
player->write_callback(output, frameCount, timeInfo, statusFlags);
|
||||
return 0;
|
||||
};
|
||||
|
||||
PaStreamParameters parameters{};
|
||||
memset(¶meters, 0, sizeof(parameters));
|
||||
parameters.channelCount = (int) kChannelCount;
|
||||
parameters.device = this->index;
|
||||
parameters.sampleFormat = paFloat32;
|
||||
parameters.suggestedLatency = this->info->defaultLowInputLatency;
|
||||
auto err = Pa_OpenStream(
|
||||
&this->stream,
|
||||
nullptr,
|
||||
¶meters,
|
||||
(double) kSampleRate,
|
||||
kSampleRate * kTimeSpan,
|
||||
paClipOff,
|
||||
proxied_write_callback,
|
||||
this);
|
||||
|
||||
if(err != paNoError) {
|
||||
this->stream = nullptr;
|
||||
error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
err = Pa_StartStream(this->stream);
|
||||
if(err != paNoError) {
|
||||
error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")";
|
||||
err = Pa_CloseStream(this->stream);
|
||||
if(err != paNoError)
|
||||
log_critical(category::audio, tr("Failed to close opened pa stream. This will cause memory leaks. Error: {}/{}"), err, Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortAudioPlayback::impl_stop() {
|
||||
if(Pa_IsStreamActive(this->stream))
|
||||
Pa_AbortStream(this->stream);
|
||||
|
||||
auto error = Pa_CloseStream(this->stream);
|
||||
if(error != paNoError)
|
||||
log_error(category::audio, tr("Failed to close PA stream: {}"), error);
|
||||
this->stream = nullptr;
|
||||
}
|
||||
|
||||
size_t PortAudioPlayback::sample_rate() const {
|
||||
return this->info->defaultSampleRate;
|
||||
}
|
||||
|
||||
void PortAudioPlayback::write_callback(void *output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) {
|
||||
this->fill_buffer(output, frameCount, kChannelCount);
|
||||
}
|
95
native/serverconnection/src/audio/driver/PortAudioRecord.cpp
Normal file
95
native/serverconnection/src/audio/driver/PortAudioRecord.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
//
|
||||
// Created by WolverinDEV on 13/02/2020.
|
||||
//
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "./PortAudio.h"
|
||||
#include "../../logger.h"
|
||||
|
||||
using namespace tc::audio::pa;
|
||||
|
||||
PortAudioRecord::PortAudioRecord(PaDeviceIndex index, const PaDeviceInfo *info) : index{index}, info{info} {}
|
||||
PortAudioRecord::~PortAudioRecord() {
|
||||
this->stop();
|
||||
}
|
||||
|
||||
bool PortAudioRecord::impl_start(std::string &error) {
|
||||
//TODO: Detect correct sample rate
|
||||
{
|
||||
auto device_info = Pa_GetDeviceInfo(this->index);
|
||||
if(this->info != device_info) {
|
||||
error = "invalid info pointer";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto proxied_read_callback = [](
|
||||
const void *input, void *output,
|
||||
unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData) {
|
||||
assert(input);
|
||||
|
||||
auto recorder = reinterpret_cast<PortAudioRecord*>(userData);
|
||||
assert(recorder);
|
||||
|
||||
recorder->read_callback(input, frameCount, timeInfo, statusFlags);
|
||||
return 0;
|
||||
};
|
||||
|
||||
PaStreamParameters parameters{};
|
||||
memset(¶meters, 0, sizeof(parameters));
|
||||
parameters.channelCount = (int) kChannelCount;
|
||||
parameters.device = this->index;
|
||||
parameters.sampleFormat = paFloat32;
|
||||
parameters.suggestedLatency = this->info->defaultLowInputLatency;
|
||||
auto err = Pa_OpenStream(
|
||||
&this->stream,
|
||||
¶meters,
|
||||
nullptr,
|
||||
(double) kSampleRate,
|
||||
paFramesPerBufferUnspecified,
|
||||
paClipOff,
|
||||
proxied_read_callback,
|
||||
this);
|
||||
|
||||
if(err != paNoError) {
|
||||
this->stream = nullptr;
|
||||
error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
err = Pa_StartStream(this->stream);
|
||||
if(err != paNoError) {
|
||||
error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")";
|
||||
err = Pa_CloseStream(this->stream);
|
||||
if(err != paNoError)
|
||||
log_critical(category::audio, tr("Failed to close opened pa stream. This will cause memory leaks. Error: {}/{}"), err, Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortAudioRecord::impl_stop() {
|
||||
if(Pa_IsStreamActive(this->stream))
|
||||
Pa_AbortStream(this->stream);
|
||||
|
||||
auto error = Pa_CloseStream(this->stream);
|
||||
if(error != paNoError)
|
||||
log_error(category::audio, tr("Failed to close PA stream: {}"), error);
|
||||
this->stream = nullptr;
|
||||
}
|
||||
|
||||
size_t PortAudioRecord::sample_rate() const {
|
||||
return this->info->defaultSampleRate;
|
||||
}
|
||||
|
||||
void PortAudioRecord::read_callback(const void *input, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) {
|
||||
std::lock_guard consumer_lock{this->consumer_lock};
|
||||
for(auto& consumer : this->_consumers)
|
||||
consumer->consume(input, frameCount, kChannelCount);
|
||||
}
|
@ -54,6 +54,7 @@ bool SoundIOPlayback::impl_start(std::string &error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->write_exit = false;
|
||||
this->stream->userdata = this;
|
||||
this->stream->sample_rate = this->_sample_rate;
|
||||
this->stream->format = SoundIoFormatFloat32LE;
|
||||
@ -144,7 +145,7 @@ void SoundIOPlayback::write_callback(int frame_count_min, int frame_count_max) {
|
||||
this->priority_boost = true;
|
||||
|
||||
// Attempt to assign "Pro Audio" characteristic to thread
|
||||
HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" );
|
||||
HMODULE AvrtDll = LoadLibrary((LPCTSTR) "AVRT.dll");
|
||||
if ( AvrtDll ) {
|
||||
DWORD taskIndex = 0;
|
||||
|
||||
|
@ -11,13 +11,13 @@ using namespace tc::audio;
|
||||
extern tc::audio::AudioOutput* global_audio_output;
|
||||
|
||||
NAN_MODULE_INIT(player::init_js) {
|
||||
Nan::Set(target, Nan::New<v8::String>("current_device").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::current_playback_device)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New<v8::String>("set_device").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_playback_device)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::LocalString("current_device"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::current_playback_device)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::LocalString("set_device"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_playback_device)).ToLocalChecked());
|
||||
|
||||
Nan::Set(target, Nan::New<v8::String>("create_stream").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::create_stream)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::LocalString("create_stream"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::create_stream)).ToLocalChecked());
|
||||
|
||||
Nan::Set(target, Nan::New<v8::String>("get_master_volume").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::get_master_volume)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New<v8::String>("set_master_volume").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_master_volume)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::LocalString("get_master_volume"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::get_master_volume)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::LocalString("set_master_volume"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_master_volume)).ToLocalChecked());
|
||||
}
|
||||
|
||||
NAN_METHOD(audio::available_devices) {
|
||||
@ -34,14 +34,14 @@ NAN_METHOD(audio::available_devices) {
|
||||
auto device = devices[index];
|
||||
|
||||
Nan::Set(device_info, Nan::LocalString("name"), Nan::LocalStringUTF8(device->name()));
|
||||
Nan::Set(device_info, Nan::New<v8::String>("driver").ToLocalChecked(), Nan::New<v8::String>(device->driver()).ToLocalChecked());
|
||||
Nan::Set(device_info, Nan::New<v8::String>("device_id").ToLocalChecked(), Nan::New<v8::String>(device->id()).ToLocalChecked());
|
||||
Nan::Set(device_info, Nan::LocalString("driver"), Nan::LocalStringUTF8(device->driver()));
|
||||
Nan::Set(device_info, Nan::LocalString("device_id"), Nan::New<v8::String>(device->id()).ToLocalChecked());
|
||||
|
||||
Nan::Set(device_info, Nan::New<v8::String>("input_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_input_supported()));
|
||||
Nan::Set(device_info, Nan::New<v8::String>("output_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_output_supported()));
|
||||
Nan::Set(device_info, Nan::LocalString("input_supported"), Nan::New<v8::Boolean>(device->is_input_supported()));
|
||||
Nan::Set(device_info, Nan::LocalString("output_supported"), Nan::New<v8::Boolean>(device->is_output_supported()));
|
||||
|
||||
Nan::Set(device_info, Nan::New<v8::String>("input_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_input_default()));
|
||||
Nan::Set(device_info, Nan::New<v8::String>("output_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_output_default()));
|
||||
Nan::Set(device_info, Nan::LocalString("input_default"), Nan::New<v8::Boolean>(device->is_input_default()));
|
||||
Nan::Set(device_info, Nan::LocalString("output_default"), Nan::New<v8::Boolean>(device->is_output_default()));
|
||||
|
||||
Nan::Set(result, index, device_info);
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ NAN_MODULE_INIT(init) {
|
||||
}
|
||||
|
||||
/* TODO: Test error codes and make the audi playback device configurable */
|
||||
global_audio_output = new tc::audio::AudioOutput(2, 48000);
|
||||
global_audio_output = new tc::audio::AudioOutput(2, 44100);
|
||||
global_audio_output->set_device(default_output);
|
||||
if(!global_audio_output->playback(error)) {
|
||||
logger::error(category::audio, "Failed to start audio playback: {}", error);
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include "./hwuid.h"
|
||||
#include <tomcrypt.h>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <misc/strobf.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
#ifndef WIN32
|
||||
|
@ -3,13 +3,13 @@
|
||||
//
|
||||
|
||||
#include "ring_buffer.h"
|
||||
|
||||
#ifdef HAVE_SOUNDIO
|
||||
using namespace tc;
|
||||
#include <soundio/soundio.h>
|
||||
|
||||
|
||||
using namespace tc;
|
||||
|
||||
ring_buffer::ring_buffer(size_t cap) {
|
||||
this->handle = soundio_ring_buffer_create(nullptr, cap);
|
||||
this->handle = soundio_ring_buffer_create(nullptr, (int) cap);
|
||||
}
|
||||
|
||||
ring_buffer::~ring_buffer() {
|
||||
@ -33,7 +33,7 @@ char* ring_buffer::write_ptr() {
|
||||
}
|
||||
|
||||
void ring_buffer::advance_write_ptr(size_t bytes) {
|
||||
soundio_ring_buffer_advance_write_ptr((SoundIoRingBuffer*) this->handle, bytes);
|
||||
soundio_ring_buffer_advance_write_ptr((SoundIoRingBuffer*) this->handle, (int) bytes);
|
||||
}
|
||||
|
||||
const void* ring_buffer::read_ptr() const {
|
||||
@ -41,9 +41,172 @@ const void* ring_buffer::read_ptr() const {
|
||||
}
|
||||
|
||||
void ring_buffer::advance_read_ptr(size_t bytes) {
|
||||
soundio_ring_buffer_advance_read_ptr((SoundIoRingBuffer*) this->handle, bytes);
|
||||
soundio_ring_buffer_advance_read_ptr((SoundIoRingBuffer*) this->handle, (int) bytes);
|
||||
}
|
||||
|
||||
void ring_buffer::clear() {
|
||||
soundio_ring_buffer_clear((SoundIoRingBuffer*) this->handle);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace tc {
|
||||
bool sysinfo_initialized{false};
|
||||
static SYSTEM_INFO win32_system_info;
|
||||
|
||||
int soundio_os_page_size() {
|
||||
if(!sysinfo_initialized) {
|
||||
GetSystemInfo(&win32_system_info);
|
||||
sysinfo_initialized = true;
|
||||
}
|
||||
return win32_system_info.dwAllocationGranularity;
|
||||
}
|
||||
|
||||
static inline size_t ceil_dbl_to_size_t(double x) {
|
||||
const double truncation = (size_t)x;
|
||||
return truncation + (truncation < x);
|
||||
}
|
||||
|
||||
ring_buffer::ring_buffer(size_t min_capacity) {
|
||||
this->allocate_memory(min_capacity);
|
||||
}
|
||||
|
||||
ring_buffer::~ring_buffer() {
|
||||
this->free_memory();
|
||||
}
|
||||
|
||||
bool ring_buffer::allocate_memory(size_t requested_capacity) {
|
||||
size_t actual_capacity = ceil_dbl_to_size_t(requested_capacity / (double) soundio_os_page_size()) * soundio_os_page_size();
|
||||
|
||||
this->memory.address = nullptr;
|
||||
#ifdef WIN32
|
||||
BOOL ok;
|
||||
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, actual_capacity * 2, nullptr);
|
||||
if (!hMapFile) return false;
|
||||
|
||||
for (;;) {
|
||||
// find a free address space with the correct size
|
||||
char *address = (char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity * 2);
|
||||
if (!address) {
|
||||
ok = CloseHandle(hMapFile);
|
||||
assert(ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// found a big enough address space. hopefully it will remain free
|
||||
// while we map to it. if not, we'll try again.
|
||||
ok = UnmapViewOfFile(address);
|
||||
assert(ok);
|
||||
|
||||
char *addr1 = (char*)MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity, address);
|
||||
if (addr1 != address) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_INVALID_ADDRESS) {
|
||||
continue;
|
||||
} else {
|
||||
ok = CloseHandle(hMapFile);
|
||||
assert(ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char *addr2 = (char*)MapViewOfFileEx(hMapFile, FILE_MAP_WRITE, 0, 0,
|
||||
actual_capacity, address + actual_capacity);
|
||||
if (addr2 != address + actual_capacity) {
|
||||
ok = UnmapViewOfFile(addr1);
|
||||
assert(ok);
|
||||
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_INVALID_ADDRESS) {
|
||||
continue;
|
||||
} else {
|
||||
ok = CloseHandle(hMapFile);
|
||||
assert(ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->memory.priv = hMapFile;
|
||||
this->memory.address = address;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
#error "Implement me!"
|
||||
#endif
|
||||
|
||||
this->memory.capacity = actual_capacity;
|
||||
this->_capacity = actual_capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ring_buffer::free_memory() {
|
||||
if(!this->memory.address) return;
|
||||
|
||||
#ifdef WIN32
|
||||
BOOL ok;
|
||||
ok = UnmapViewOfFile(this->memory.address);
|
||||
assert(ok);
|
||||
ok = UnmapViewOfFile(this->memory.address + this->memory.capacity);
|
||||
assert(ok);
|
||||
ok = CloseHandle((HANDLE) this->memory.priv);
|
||||
assert(ok);
|
||||
#else
|
||||
#error "Implement me!"
|
||||
#endif
|
||||
|
||||
this->memory.address = nullptr;
|
||||
}
|
||||
|
||||
size_t ring_buffer::capacity() const {
|
||||
return this->_capacity;
|
||||
}
|
||||
|
||||
char* ring_buffer::write_ptr() {
|
||||
auto offset{this->write_offset.load()};
|
||||
return this->memory.address + (offset % this->memory.capacity);
|
||||
}
|
||||
|
||||
void ring_buffer::advance_write_ptr(size_t bytes) {
|
||||
this->write_offset.fetch_add(bytes);
|
||||
assert(this->fill_count() >= 0);
|
||||
}
|
||||
|
||||
const void* ring_buffer::read_ptr() const {
|
||||
auto offset{this->read_offset.load()};
|
||||
return this->memory.address + (offset % this->memory.capacity);
|
||||
}
|
||||
|
||||
void ring_buffer::advance_read_ptr(size_t bytes) {
|
||||
this->read_offset.fetch_add(bytes);
|
||||
assert(this->fill_count() >= 0);
|
||||
}
|
||||
|
||||
size_t ring_buffer::fill_count() const {
|
||||
// Whichever offset we load first might have a smaller value. So we load
|
||||
// the read_offset first.
|
||||
auto roffset{this->read_offset.load()};
|
||||
auto woffset{this->write_offset.load()};
|
||||
|
||||
int count = woffset - roffset;
|
||||
assert(count >= 0);
|
||||
assert(count <= this->memory.capacity);
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t ring_buffer::free_count() const {
|
||||
return this->memory.capacity - this->fill_count();
|
||||
}
|
||||
|
||||
void ring_buffer::clear() {
|
||||
this->write_offset.store(this->read_offset.load());
|
||||
}
|
||||
|
||||
bool ring_buffer::valid() const {
|
||||
return this->memory.address != nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <atomic>
|
||||
|
||||
namespace tc {
|
||||
class ring_buffer {
|
||||
@ -22,7 +23,27 @@ namespace tc {
|
||||
void advance_read_ptr(size_t /* count */);
|
||||
|
||||
void clear();
|
||||
[[nodiscard]] bool valid() const;
|
||||
|
||||
[[nodiscard]] inline operator bool() const { return this->valid(); }
|
||||
private:
|
||||
#ifndef HAVE_SOUNDIO
|
||||
struct MirroredMemory {
|
||||
size_t capacity;
|
||||
char *address;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
bool allocate_memory(size_t requested_capacity);
|
||||
void free_memory();
|
||||
|
||||
MirroredMemory memory{};
|
||||
|
||||
std::atomic_long write_offset;
|
||||
std::atomic_long read_offset;
|
||||
size_t _capacity{0}; /* for faster access */
|
||||
#endif
|
||||
|
||||
void* handle{nullptr};
|
||||
};
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/// <reference path="../../exports/exports.d.ts" />
|
||||
console.log("Starting app");
|
||||
module.paths.push("../../build/linux_x64");
|
||||
module.paths.push("../../build/win32_64");
|
||||
module.paths.push("../../build/win32_x64");
|
||||
|
||||
const original_require = require;
|
||||
require = (module => original_require(__dirname + "/../../../build/linux_x64/" + module + ".node")) as any;
|
||||
require = (module => original_require(__dirname + "/../../../build/win32_x64/" + module + ".node")) as any;
|
||||
import * as handle from "teaclient_connection";
|
||||
require = original_require;
|
||||
|
||||
|
87
package-lock.json
generated
87
package-lock.json
generated
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "TeaClient",
|
||||
"version": "1.4.2-5",
|
||||
"version": "1.4.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@electron/get": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.7.2.tgz",
|
||||
"integrity": "sha512-LSE4LZGMjGS9TloDx0yO44D2UTbaeKRk+QjlhWLiQlikV6J4spgDCjb6z4YIcqmPAwNzlNCnWF4dubytwI+ATA==",
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.7.6.tgz",
|
||||
"integrity": "sha512-zlNikt6ziVLNcm4lly1L4y62fJd/eYpEBjF5DiV/VAQq2vdPjH4sbUphXt9upmHz86lAhAj8g9lTnWrxJ/KBZw==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"env-paths": "^2.2.0",
|
||||
@ -108,8 +108,7 @@
|
||||
"@types/node": {
|
||||
"version": "12.12.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.6.tgz",
|
||||
"integrity": "sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA=="
|
||||
},
|
||||
"@types/request": {
|
||||
"version": "2.48.4",
|
||||
@ -153,19 +152,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/ssh2": {
|
||||
"version": "0.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.39.tgz",
|
||||
"integrity": "sha512-MtX4tr6Jtdn/JPVsQytjB/NBeOd7JfCyf/l79KOdkUYL+r4GXUXcySX1n8W4O61fnQdljTBXEPJJ2dhnGhi2xg==",
|
||||
"version": "0.5.40",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.40.tgz",
|
||||
"integrity": "sha512-Yrs2vFIirdscR+xY4leiUosHTClRbVBiFLlm5gA0JMZMjbCulHKO3qcgMqATElrquiZal5sSHoXMk4t8DzDawA==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2-streams": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
|
||||
"integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/ssh2-streams": {
|
||||
@ -174,13 +166,6 @@
|
||||
"integrity": "sha512-NB+SYftagfHTDPdgVyvSZCeg5MURyHECd/PycGIW9hwhnEbTFxIdDbFtf3jxUO3rRnfr0lfdtkZFiv28T1cAsg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
|
||||
"integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/tar-stream": {
|
||||
@ -552,9 +537,9 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"boolean": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.0.tgz",
|
||||
"integrity": "sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
|
||||
"integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==",
|
||||
"optional": true
|
||||
},
|
||||
"boxen": {
|
||||
@ -1431,20 +1416,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-7.1.2.tgz",
|
||||
"integrity": "sha512-7hjONYt2GlQfKuKgQrhhUL1P9lbGWLBfMUq+2QFU3yeLtCvM0ROfPJCRP4OF5pVp3KDyfFp4DtmhuVzAnxV3jA==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-8.0.0.tgz",
|
||||
"integrity": "sha512-vBXUKRqTUq0jv1upvISdvScDDH3uCPwXj4eA5BeR3UDbJp2hOhq7eJxwjIQbfLQql98aYz4X6pSlzBnhfyQqHA==",
|
||||
"requires": {
|
||||
"@electron/get": "^1.0.1",
|
||||
"@types/node": "^12.0.12",
|
||||
"extract-zip": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.12.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz",
|
||||
"integrity": "sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-context-menu": {
|
||||
@ -3530,9 +3508,9 @@
|
||||
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg=="
|
||||
},
|
||||
"http-cache-semantics": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz",
|
||||
"integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew=="
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
||||
"integrity": "sha512-Z2EICWNJou7Tr9Bd2M2UqDJq3A9F2ePG9w3lIpjoyuSyXFP9QbniJVu3XQYytuw5ebmG7dXSXO9PgAjJG8DDKA=="
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.3.1",
|
||||
@ -5293,9 +5271,9 @@
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
@ -5304,7 +5282,7 @@
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.0",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
@ -5314,7 +5292,7 @@
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.4.3",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
@ -5345,18 +5323,13 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"requires": {
|
||||
"psl": "^1.1.24",
|
||||
"punycode": "^1.4.1"
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5561,9 +5534,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.2.tgz",
|
||||
"integrity": "sha512-BJs9T/H8sEVHbeigqzIEo57Iu/3DG6c4QoqTfbQB3BPA4zgzAomh/Fk9E7QtjWQ8mx2dgA9YCfSF4y9k9bHNpQ==",
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz",
|
||||
"integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==",
|
||||
"optional": true
|
||||
},
|
||||
"semver-compare": {
|
||||
|
12
package.json
12
package.json
@ -38,26 +38,26 @@
|
||||
"asar": "^2.0.1",
|
||||
"cmake-js": "^4.0.1",
|
||||
"ejs": "^2.7.1",
|
||||
"electron-installer-windows": "^1.1.1",
|
||||
"electron-installer-windows": "^1.1.0",
|
||||
"electron-packager": "8.7.2",
|
||||
"electron-winstaller": "^2.7.0",
|
||||
"electron-wix-msi": "^2.2.0",
|
||||
"electron-wix-msi": "^2.1.1",
|
||||
"nodemon": "^1.19.4",
|
||||
"platform-dependent-modules": "0.0.14",
|
||||
"rc": "^1.2.8",
|
||||
"rcedit": "^1.1.2",
|
||||
"rcedit": "^1.1.1",
|
||||
"sass": "^1.23.2",
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/ssh2": "^0.5.39",
|
||||
"@types/ssh2": "^0.5.40",
|
||||
"argparse": "^1.0.10",
|
||||
"asn1": "^0.2.4",
|
||||
"assert-plus": "^1.0.0",
|
||||
"aws-sign2": "^0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"electron": "7.1.2",
|
||||
"electron": "8.0.0",
|
||||
"electron-navigation": "^1.5.8",
|
||||
"electron-rebuild": "^1.8.6",
|
||||
"extend": "^3.0.2",
|
||||
@ -73,7 +73,7 @@
|
||||
"only": "0.0.2",
|
||||
"psl": "^1.4.0",
|
||||
"pure-uuid": "^1.5.7",
|
||||
"request": "^2.47.1",
|
||||
"request": "^2.88.2",
|
||||
"request-progress": "^3.0.0",
|
||||
"request-promise": "^4.2.5",
|
||||
"safe-buffer": "^5.2.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user