Revered the audio system back to PortAudio
This commit is contained in:
@@ -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) {
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user