Switched to libsoundio

This commit is contained in:
WolverinDEV 2020-02-08 16:50:48 +01:00
parent 7007e8f6aa
commit 411778f932
31 changed files with 1725 additions and 738 deletions

View File

@ -3,6 +3,8 @@ window["require_setup"](module);
import {audio as naudio} from "teaclient_connection";
namespace audio.player {
//FIXME: Native audio initialize handle!
export interface Device {
device_id: string;
name: string;
@ -15,6 +17,7 @@ namespace audio.player {
let _initialized_callbacks: (() => any)[] = [];
export let _initialized = false;
export let _initializing = false;
export let _audioContext: AudioContext;
export let _processor: ScriptProcessorNode;
export let _output_stream: naudio.playback.OwnedAudioOutputStream;
@ -44,59 +47,65 @@ namespace audio.player {
}
export function initialize() {
_output_stream = naudio.playback.create_stream();
_output_stream.set_buffer_max_latency(0.4);
_output_stream.set_buffer_latency(0.02);
if(_initializing) return;
_initializing = true;
_output_stream.callback_overflow = () => {
console.warn("Main audio overflow");
_output_stream.clear();
};
_output_stream.callback_underflow = () => {
console.warn("Main audio underflow");
};
naudio.initialize(() => {
_output_stream = naudio.playback.create_stream();
_output_stream.set_buffer_max_latency(0.4);
_output_stream.set_buffer_latency(0.02);
_audioContext = new AudioContext();
_processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels);
_output_stream.callback_overflow = () => {
console.warn("Main audio overflow");
_output_stream.clear();
};
_output_stream.callback_underflow = () => {
console.warn("Main audio underflow");
};
_processor.onaudioprocess = function(event) {
const buffer = event.inputBuffer;
//console.log("Received %d channels of %d with a rate of %d", buffer.numberOfChannels, buffer.length, buffer.sampleRate);
const target_buffer = new Float32Array(buffer.numberOfChannels * buffer.length);
_audioContext = new AudioContext();
_processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels);
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channel_data = buffer.getChannelData(channel);
target_buffer.set(channel_data, channel * buffer.length);
}
_output_stream.write_data_rated(target_buffer.buffer, false, buffer.sampleRate);
};
_processor.connect(_audioContext.destination);
_processor.onaudioprocess = function(event) {
const buffer = event.inputBuffer;
//console.log("Received %d channels of %d with a rate of %d", buffer.numberOfChannels, buffer.length, buffer.sampleRate);
const target_buffer = new Float32Array(buffer.numberOfChannels * buffer.length);
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channel_data = buffer.getChannelData(channel);
target_buffer.set(channel_data, channel * buffer.length);
}
_output_stream.write_data_rated(target_buffer.buffer, false, buffer.sampleRate);
};
_processor.connect(_audioContext.destination);
_initialized = true;
for(const callback of _initialized_callbacks)
callback();
_initialized_callbacks = [];
_initialized = true;
for(const callback of _initialized_callbacks)
callback();
_initialized_callbacks = [];
});
return true;
}
export async function available_devices() : Promise<Device[]> {
return naudio.available_devices().filter(e => e.output_supported || e.output_default).map(e => {
return {
device_id: e.device_id,
name: e.name
}
});
return naudio.available_devices().filter(e => e.output_supported || e.output_default);
}
export async function set_device(device_id?: string) : Promise<void> {
const dev = naudio.available_devices().filter(e => e.device_id == device_id);
if(dev.length == 0) {
console.warn("Missing audio device with is %s", device_id)
console.warn("Missing audio device with is %s", device_id);
throw "invalid device id";
}
await naudio.playback.set_device(dev[0].device_index);
try {
naudio.playback.set_device(dev[0].device_id);
} catch(error) {
if(error instanceof Error)
throw error.message;
throw error;
}
_current_device = dev[0];
}

View File

@ -1,8 +1,8 @@
window["require_setup"](module);
import {audio as naudio} from "teaclient_connection";
//import {audio, tr} from "../imports/imports_shared";
/// <reference types="./imports/import_shared.d.ts" />
// <reference types="../imports/import_shared.d.ts" />
/// <reference types="../../modules/renderer/imports/imports_shared.d.ts" />
export namespace _audio.recorder {
import InputDevice = audio.recorder.InputDevice;
@ -14,6 +14,9 @@ export namespace _audio.recorder {
let _device_cache: NativeDevice[] = undefined;
export function devices() : InputDevice[] {
//TODO: Handle device updates!
if(!naudio.initialized()) return [];
return _device_cache || (_device_cache = naudio.available_devices().filter(e => e.input_supported || e.input_default).map(e => {
return {
unique_id: e.device_id,
@ -22,8 +25,7 @@ export namespace _audio.recorder {
supported: e.input_supported,
name: e.name,
driver: e.driver,
sample_rate: 44100, /* TODO! */
device_index: e.device_index,
sample_rate: 48000, /* TODO! */
} as NativeDevice
}));
}
@ -293,22 +295,13 @@ export namespace _audio.recorder {
const device = _device as NativeDevice; /* TODO: test for? */
this._current_device = _device;
try {
await new Promise((resolve, reject) => {
this.handle.set_device(device ? device.device_index : -1, flag => {
if(typeof(flag) === "boolean" && flag)
resolve();
else
reject("failed to set device" + (typeof(flag) === "string" ? (": " + flag) : ""));
});
});
if(!device) return;
await new Promise(resolve => this.handle.set_device(this._current_device.unique_id, resolve));
await new Promise((resolve, reject) => {
this.handle.start(flag => {
if(flag)
if(typeof flag === "boolean" && flag)
resolve();
else
reject("start failed");
reject(typeof flag === "string" ? flag : "failed to start");
});
});
} catch(error) {
@ -453,20 +446,13 @@ export namespace _audio.recorder {
this._filter.set_attack_smooth(.75);
this._filter.set_release_smooth(.75);
await new Promise((resolve, reject) => {
this._recorder.set_device(this._device.device_index, flag => {
if(typeof(flag) === "boolean" && flag)
resolve();
else
reject("initialize failed" + (typeof(flag) === "string" ? (": " + flag) : ""));
});
});
await new Promise(resolve => this._recorder.set_device(this._device.unique_id, resolve));
await new Promise((resolve, reject) => {
this._recorder.start(flag => {
if(flag)
if(typeof flag === "boolean" && flag)
resolve();
else
reject("start failed");
reject(typeof flag === "string" ? flag : "failed to start");
});
});
} catch(error) {
@ -490,7 +476,7 @@ export namespace _audio.recorder {
if(this._consumer)
this._recorder.delete_consumer(this._consumer);
this._recorder.stop();
this._recorder.set_device(-1, () => {}); /* -1 := No device */
this._recorder.set_device(undefined, () => {}); /* -1 := No device */
this._recorder = undefined;
this._consumer = undefined;
this._filter = undefined;
@ -506,5 +492,4 @@ export namespace _audio.recorder {
}
}
Object.assign(window["audio"] || (window["audio"] = {} as any), _audio);
_audio.recorder.devices(); /* query devices */
Object.assign(window["audio"] || (window["audio"] = {} as any), _audio);

View File

@ -84,7 +84,7 @@ namespace Nan {
struct callback_wrap {
std::shared_ptr<callback_scoped<Args...>> handle;
void call_cpy(Args... args, bool no_throw = false) {
void call_cpy(Args... args, bool no_throw = false) const {
if(!this->handle) {
if(no_throw)
return;
@ -93,7 +93,7 @@ namespace Nan {
handle->callback(std::forward<Args>(args)...);
}
void call(Args&&... args, bool no_throw = false) {
void call(Args&&... args, bool no_throw = false) const {
if(!this->handle) {
if(no_throw)
return;
@ -102,11 +102,6 @@ namespace Nan {
handle->callback(std::forward<Args>(args)...);
}
void operator()(Args&&... args) {
if(!this->handle)
throw std::bad_function_call();
handle->callback(std::forward<Args>(args)...);
}
void operator()(Args&&... args) const {
if(!this->handle)
throw std::bad_function_call();

View File

@ -5,12 +5,12 @@ set(SOURCE_FILES
src/logger.cpp
src/EventLoop.cpp
src/hwuid.cpp
src/ring_buffer.cpp
src/connection/ft/FileTransferManager.cpp
src/connection/ft/FileTransferObject.cpp
src/audio/AudioSamples.cpp
src/audio/AudioDevice.cpp
src/audio/AudioMerger.cpp
src/audio/AudioOutput.cpp
src/audio/AudioInput.cpp
@ -23,6 +23,11 @@ set(SOURCE_FILES
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
@ -83,6 +88,8 @@ include_directories(${StringVariable_INCLUDE_DIR})
find_package(Ed25519 REQUIRED)
include_directories(${ed25519_INCLUDE_DIR})
find_package(soundio REQUIRED)
find_package(ThreadPool REQUIRED)
include_directories(${ThreadPool_INCLUDE_DIR})
if (WIN32)
@ -95,9 +102,6 @@ endif ()
find_package(Soxr REQUIRED)
include_directories(${soxr_INCLUDE_DIR})
find_package(PortAudio REQUIRED)
include_directories(${PortAudio_INCLUDE_DIR})
find_package(fvad REQUIRED)
include_directories(${fvad_INCLUDE_DIR})
@ -118,11 +122,11 @@ set(REQUIRED_LIBRARIES
${DataPipes_LIBRARIES_STATIC} #Needs to be static because something causes ca bad function call when loaded in electron
${ThreadPool_LIBRARIES_STATIC}
${soxr_LIBRARIES_STATIC}
${PortAudio_LIBRARIES_STATIC}
${fvad_LIBRARIES_STATIC}
${opus_LIBRARIES_STATIC}
${ed25519_LIBRARIES_STATIC}
soundio::static
spdlog::spdlog_header_only
Nan::Helpers
@ -144,10 +148,10 @@ target_link_libraries(${MODULE_NAME} ${REQUIRED_LIBRARIES})
target_compile_definitions(${MODULE_NAME} PUBLIC -DNODEJS_API)
add_executable(Audio-Test ${SOURCE_FILES} test/audio/main.cpp)
target_link_libraries(Audio-Test ${REQUIRED_LIBRARIES})
target_link_libraries(Audio-Test ${REQUIRED_LIBRARIES} soundio.a)
add_executable(Audio-Test-2 ${SOURCE_FILES} test/audio/sio.cpp)
target_link_libraries(Audio-Test-2 ${REQUIRED_LIBRARIES} soundio.a pulse)
target_link_libraries(Audio-Test-2 ${REQUIRED_LIBRARIES} soundio.a)
add_executable(HW-UID-Test src/hwuid.cpp)
target_link_libraries(HW-UID-Test

View File

@ -144,8 +144,6 @@ declare module "teaclient_connection" {
input_default: boolean;
output_default: boolean;
device_index: number;
}
export namespace playback {
@ -174,8 +172,8 @@ declare module "teaclient_connection" {
delete();
}
export function set_device(device: number);
export function current_device() : number;
export function set_device(device_id: string);
export function current_device() : string;
export function create_stream() : OwnedAudioOutputStream;
@ -234,10 +232,10 @@ declare module "teaclient_connection" {
}
export interface AudioRecorder {
get_device() : number;
set_device(device: number, callback: (flag: boolean | string) => void); /* Recorder needs to be started afterwards */
get_device() : string;
set_device(device_id: string, callback: () => void); /* Recorder needs to be started afterwards */
start(callback: (flag: boolean) => void);
start(callback: (result: boolean | string) => void);
started() : boolean;
stop();
@ -252,7 +250,8 @@ declare module "teaclient_connection" {
export function create_recorder() : AudioRecorder;
}
export function initialize();
export function initialize(callback: () => any);
export function initialized() : boolean;
export function available_devices() : AudioDevice[];
}
}

View File

@ -1,102 +0,0 @@
#include "AudioDevice.h"
#include "../logger.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */
extern std::deque<std::shared_ptr<AudioDevice>> devices();
bool _devices_cached = false;
std::mutex _audio_devices_lock;
std::deque<std::shared_ptr<AudioDevice>> _audio_devices{};
bool audio::devices_cached() {
return _devices_cached;
}
void audio::clear_device_cache() {
std::lock_guard lock(_audio_devices_lock);
_audio_devices.clear();
_devices_cached = false;
}
deque<shared_ptr<AudioDevice>> audio::devices() {
std::lock_guard lock(_audio_devices_lock);
if(_devices_cached)
return _audio_devices;
/* query devices */
auto device_count = Pa_GetDeviceCount();
if(device_count < 0) {
log_error(category::audio, tr("Pa_GetDeviceCount() returned {}"), device_count);
return {};
}
auto default_input_device = Pa_GetDefaultInputDevice();
auto default_output_device = Pa_GetDefaultOutputDevice();
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;
}
auto info = make_shared<AudioDevice>();
info->device_id = device_index;
info->name = device_info->name;
info->max_inputs = device_info->maxInputChannels;
info->input_supported = device_info->maxInputChannels > 0;
info->max_outputs = device_info->maxOutputChannels;
info->output_supported = device_info->maxOutputChannels > 0;
info->is_default_input = device_index == default_input_device;
info->is_default_output = device_index == default_output_device;
info->driver = device_host_info->name;
info->is_default_driver_input = device_index == device_host_info->defaultInputDevice;
info->is_default_driver_output = device_index == device_host_info->defaultOutputDevice;
PaStreamParameters test_parameters{};
test_parameters.device = device_index;
test_parameters.sampleFormat = paFloat32;
test_parameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
test_parameters.hostApiSpecificStreamInfo = nullptr;
/*
if(info->input_supported) {
test_parameters.channelCount = device_info->maxInputChannels;
for(size_t index = 0; standard_sample_rates[index] > 0; index++) {
auto rate = standard_sample_rates[index];
auto err = Pa_IsFormatSupported(&test_parameters, nullptr, rate);
if(err == paFormatIsSupported)
info->supported_input_rates.push_back(rate);
}
}
if(info->output_supported) {
test_parameters.channelCount = device_info->maxOutputChannels;
for(size_t index = 0; standard_sample_rates[index] > 0; index++) {
auto rate = standard_sample_rates[index];
auto err = Pa_IsFormatSupported(nullptr, &test_parameters, rate);
if(err == paFormatIsSupported)
info->supported_output_rates.push_back(rate);
}
}
*/
_audio_devices.push_back(info);
}
_devices_cached = true;
return _audio_devices;
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <mutex>
#include <deque>
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
namespace tc {
namespace audio {
static constexpr double standard_sample_rates[] = {
8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0,
44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */
};
struct AudioDevice {
PaDeviceIndex device_id;
bool is_default_output;
bool is_default_input;
bool is_default_driver_output;
bool is_default_driver_input;
bool input_supported;
bool output_supported;
std::string name;
std::string driver;
/*
std::vector<double> supported_input_rates;
std::vector<double> supported_output_rates;
*/
int max_inputs;
int max_outputs;
};
extern void clear_device_cache();
extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */
extern std::deque<std::shared_ptr<AudioDevice>> devices();
}
}

View File

@ -9,6 +9,7 @@ using namespace std;
using namespace tc;
using namespace tc::audio;
#if false
class AudioInputSource {
public:
constexpr static auto kChannelCount{2};
@ -121,7 +122,7 @@ class AudioInputSource {
std::lock_guard lock{input_source->registered_inputs_lock};
for(auto& client : input_source->registered_inputs)
client->audio_callback(input, frameCount, timeInfo, statusFlags);
client->consume(input, frameCount, 2);
return 0;
}
@ -152,6 +153,7 @@ std::shared_ptr<AudioInputSource> get_input_source(PaDeviceIndex device_index, b
input_sources.push_back(std::make_shared<AudioInputSource>(device_index));
return input;
}
#endif
AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) :
handle(handle),
@ -189,50 +191,57 @@ AudioInput::~AudioInput() {
consumer->handle = nullptr;
}
PaDeviceIndex AudioInput::current_device() {
void AudioInput::set_device(const std::shared_ptr<AudioDevice> &device) {
lock_guard lock(this->input_source_lock);
return this->input_source ? this->input_source->device_index : paNoDevice;
}
if(device == this->input_device) return;
bool AudioInput::open_device(std::string& error, PaDeviceIndex index) {
lock_guard lock(this->input_source_lock);
if(index == (this->input_source ? this->input_source->device_index : paNoDevice))
return true;
this->close_device();
if(index == paNoDevice)
return true;
this->input_source = get_input_source(index, true);
this->input_source->register_consumer(this);
return this->input_source->begin_recording(error);
this->close_device();
this->input_device = device;
}
void AudioInput::close_device() {
lock_guard lock(this->input_source_lock);
if(this->input_source) {
this->input_source->remove_consumer(this);
this->input_source->stop_recording_if_possible();
this->input_source.reset();
if(this->input_recorder) {
this->input_recorder->remove_consumer(this);
this->input_recorder->stop_if_possible();
this->input_recorder.reset();
}
this->input_recording = false;
this->input_device = nullptr;
}
bool AudioInput::record() {
bool AudioInput::record(std::string& error) {
lock_guard lock(this->input_source_lock);
if(!this->input_source) return false;
if(!this->input_device) {
error = "no device";
return false;
}
if(this->input_recorder) return true;
this->input_recording = true;
this->input_recorder = this->input_device->record();
if(!this->input_recorder) {
error = "failed to get recorder";
return false;
}
this->input_recorder->register_consumer(this);
if(!this->input_recorder->start(error)) {
this->input_recorder->remove_consumer(this);
this->input_recorder.reset();
return false;
}
return true;
}
bool AudioInput::recording() {
return this->input_recording;
return !!this->input_recorder;
}
void AudioInput::stop() {
this->input_recording = false;
if(!this->input_recorder) return;
this->input_recorder->remove_consumer(this);
this->input_recorder->stop_if_possible();
this->input_recorder.reset();
}
std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) {
@ -255,9 +264,7 @@ void AudioInput::delete_consumer(const std::shared_ptr<AudioConsumer> &source) {
source->handle = nullptr;
}
void AudioInput::audio_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
if(!this->input_recording) return;
void AudioInput::consume(const void *input, unsigned long frameCount, size_t /* channels */) {
if(this->_volume != 1 && false) {
auto ptr = (float*) input;
auto left = frameCount * this->_channel_count;

View File

@ -5,9 +5,9 @@
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
#include <misc/spin_lock.h>
#include "AudioSamples.h"
#include "driver/AudioDriver.h"
class AudioInputSource;
namespace tc {
@ -31,25 +31,22 @@ namespace tc {
AudioConsumer(AudioInput* handle, size_t channel_count, size_t sample_rate, size_t frame_size);
std::unique_ptr<Reframer> reframer;
std::mutex buffer_lock;
size_t buffered_samples = 0;
std::deque<std::shared_ptr<SampleBuffer>> sample_buffers;
void process_data(const void* /* buffer */, size_t /* samples */);
void handle_framed_data(const void* /* buffer */, size_t /* samples */);
};
class AudioInput {
class AudioInput : public AudioDeviceRecord::Consumer {
friend class ::AudioInputSource;
public:
AudioInput(size_t /* channels */, size_t /* rate */);
virtual ~AudioInput();
[[nodiscard]] bool open_device(std::string& /* error */, PaDeviceIndex);
[[nodiscard]] PaDeviceIndex current_device();
void set_device(const std::shared_ptr<AudioDevice>& /* device */);
[[nodiscard]] std::shared_ptr<AudioDevice> current_device() const { return this->input_device; }
void close_device();
[[nodiscard]] bool record();
[[nodiscard]] bool record(std::string& /* error */);
[[nodiscard]] bool recording();
void stop();
@ -67,7 +64,7 @@ namespace tc {
inline float volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; }
private:
void audio_callback(const void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags);
void consume(const void *, unsigned long, size_t) override;
size_t const _channel_count;
size_t const _sample_rate;
@ -76,10 +73,10 @@ namespace tc {
std::deque<std::shared_ptr<AudioConsumer>> _consumers;
std::recursive_mutex input_source_lock;
bool input_recording{false};
std::shared_ptr<::AudioInputSource> input_source{};
std::shared_ptr<AudioDevice> input_device{};
float _volume = 1.f;
};
std::shared_ptr<AudioDeviceRecord> input_recorder{};
};
}
}

View File

@ -10,110 +10,127 @@ using namespace tc;
using namespace tc::audio;
void AudioOutputSource::clear() {
lock_guard lock(this->buffer_lock);
this->sample_buffers.clear();
this->buffered_samples = 0;
this->buffer.clear();
this->buffering = true;
}
ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) {
auto sample_count = samples;
size_t written{0}, written_bytes{0};
_retest:
{
lock_guard lock(this->buffer_lock);
if(this->buffering) {
if(this->buffered_samples > this->min_buffer) {
this->buffering = false;
} else {
return 0;
}
}
while(sample_count > 0 && !this->sample_buffers.empty()) {
auto buf = this->sample_buffers[0];
auto sc = min((size_t) (buf->sample_size - buf->sample_index), (size_t) sample_count);
if(sc > 0 && buffer) { /* just to ensure */
memcpy(buffer, (char *) buf->sample_data + this->channel_count * buf->sample_index * 4, sc * this->channel_count * 4);
} else {
#ifndef WIN32
/* for my debugger */
__asm__("nop");
#endif
}
load_buffer:
auto available_bytes = this->buffer.fill_count();
if(available_bytes < sizeof(float) * this->channel_count) return written;
sample_count -= sc;
buf->sample_index += (uint16_t) sc;
if(buf->sample_index == buf->sample_size)
this->sample_buffers.pop_front();
auto available_samples = available_bytes / sizeof(float) / this->channel_count;
//log_trace(category::audio, tr("Min: {}, Max: {}, Current: {}, Buffering: {}"), this->min_buffered_samples, this->max_buffered_samples, available_samples, this->buffering);
if(this->buffering && available_samples < this->min_buffered_samples) return -2;
if(buffer)
buffer = (char*) buffer + sc * this->channel_count * 4;
}
this->buffered_samples -= samples - sample_count;
}
this->buffering = false;
if(available_samples >= samples - written) {
const auto byte_length = (samples - written) * sizeof(float) * this->channel_count;
memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
this->buffer.advance_read_ptr(byte_length);
return samples;
} else {
const auto byte_length = available_samples * sizeof(float) * this->channel_count;
memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
this->buffer.advance_read_ptr(byte_length);
written += available_samples;
written_bytes += byte_length;
}
if(sample_count > 0) {
if(this->on_underflow) {
if(this->on_underflow()) {
goto _retest;
}
}
this->buffering = true;
}
if(auto fn = this->on_underflow; fn)
if(fn())
goto load_buffer;
this->buffering = true;
if(this->on_read)
this->on_read();
return samples - sample_count; /* return the written samples */
return written; /* return the written samples */
}
ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) {
auto buf = SampleBuffer::allocate((uint8_t) this->channel_count, (uint16_t) samples);
if(!buf)
return -1;
size_t enqueued{0};
buf->sample_index = 0;
memcpy(buf->sample_data, buffer, this->channel_count * samples * 4);
auto free_bytes = this->buffer.free_count();
auto free_samples = free_bytes / sizeof(float) / this->channel_count;
if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
return this->enqueue_samples(buf);
if(free_samples >= samples) {
const auto byte_length = samples * sizeof(float) * this->channel_count;
memcpy(this->buffer.write_ptr(), buffer, byte_length);
this->buffer.advance_write_ptr(byte_length);
return samples;
} else {
const auto byte_length = free_samples * sizeof(float) * this->channel_count;
memcpy(this->buffer.write_ptr(), buffer, byte_length);
this->buffer.advance_write_ptr(byte_length);
enqueued += free_samples;
}
if(auto fn = this->on_overflow; fn)
fn(samples - enqueued);
switch (this->overflow_strategy) {
case overflow_strategy::discard_input:
return -2;
case overflow_strategy::discard_buffer_all:
this->buffer.clear();
break;
case overflow_strategy::discard_buffer_half:
this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
break;
case overflow_strategy::ignore:
break;
}
return enqueued;
}
ssize_t AudioOutputSource::enqueue_samples(const std::shared_ptr<tc::audio::SampleBuffer> &buf) {
if(!buf) return 0;
ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *buffer, size_t samples) {
auto free_bytes = this->buffer.free_count();
auto free_samples = free_bytes / sizeof(float) / this->channel_count;
if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
{
unique_lock lock(this->buffer_lock);
if(this->max_latency > 0 && this->buffered_samples + buf->sample_size > this->max_latency) {
/* overflow! */
auto overflow_length = this->buffered_samples + buf->sample_size - this->max_latency;
if(this->on_overflow) {
lock.unlock();
this->on_overflow(overflow_length);
lock.lock();
}
auto samples_to_write{samples};
if(samples_to_write > free_samples) samples_to_write = free_samples;
const auto enqueued{samples_to_write};
{
auto src_buffer = (const float*) buffer;
auto target_buffer = (float*) this->buffer.write_ptr();
switch (this->overflow_strategy) {
case overflow_strategy::discard_input:
return -2;
case overflow_strategy::discard_buffer_all:
this->sample_buffers.clear();
break;
case overflow_strategy::discard_buffer_half:
this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + (int) ceil(this->sample_buffers.size() / 2));
break;
case overflow_strategy::ignore:
break;
}
}
while (samples_to_write-- > 0) {
*target_buffer = *src_buffer;
*(target_buffer + 1) = *(src_buffer + samples);
this->sample_buffers.push_back(buf);
this->buffered_samples += buf->sample_size;
}
target_buffer += 2;
src_buffer++;
}
}
return buf->sample_size;
if(auto fn = this->on_overflow; fn)
fn(samples - enqueued);
switch (this->overflow_strategy) {
case overflow_strategy::discard_input:
return -2;
case overflow_strategy::discard_buffer_all:
this->buffer.clear();
break;
case overflow_strategy::discard_buffer_half:
this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
break;
case overflow_strategy::ignore:
break;
}
return enqueued;
}
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) {}
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { }
AudioOutput::~AudioOutput() {
this->close_device();
this->cleanup_buffers();
@ -152,136 +169,115 @@ void AudioOutput::cleanup_buffers() {
this->source_buffer_length = 0;
}
int AudioOutput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) {
return reinterpret_cast<AudioOutput*>(_ptr_audio_output)->audio_callback(a, b, c, d, e);
}
int AudioOutput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
if(!output) /* hmmm.. suspicious */
return 0;
void AudioOutput::fill_buffer(void *output, unsigned long frameCount, size_t channels) {
lock_guard buffer_lock(this->buffer_lock);
if(this->_volume <= 0) {
for(auto& source : this->_sources)
source->pop_samples(nullptr, frameCount);
memset(output, 0, sizeof(frameCount) * channels * sizeof(float));
return;
}
size_t buffer_length = frameCount * 4 * this->_channel_count;
size_t sources = 0;
size_t actual_sources = 0;
auto volume = this->_volume;
{
lock_guard lock(this->sources_lock);
sources = this->_sources.size();
actual_sources = sources;
if(sources > 0) {
if(volume > 0) { /* allocate the required space */
auto source_buffer_length = buffer_length * sources;
auto source_merge_buffer_length = sizeof(void*) * sources;
/* allocate the required space */
auto source_buffer_length = buffer_length * sources;
auto source_merge_buffer_length = sizeof(void*) * sources;
if(this->source_buffer_length < source_buffer_length || !this->source_buffer) {
if(this->source_buffer)
free(this->source_buffer);
this->source_buffer = malloc(source_buffer_length);
this->source_buffer_length = source_buffer_length;
}
if(this->source_merge_buffer_length < source_merge_buffer_length || !this->source_merge_buffer) {
if(this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = (void**) malloc(source_merge_buffer_length);
this->source_merge_buffer_length = source_merge_buffer_length;
}
}
//TODO: Move this out of the loop?
{
if(this->source_buffer_length < source_buffer_length || !this->source_buffer) {
if(this->source_buffer)
free(this->source_buffer);
this->source_buffer = malloc(source_buffer_length);
this->source_buffer_length = source_buffer_length;
}
if(this->source_merge_buffer_length < source_merge_buffer_length || !this->source_merge_buffer) {
if (this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = (void **) malloc(source_merge_buffer_length);
this->source_merge_buffer_length = source_merge_buffer_length;
}
}
for(size_t index = 0; index < sources; index++) {
auto& source = this->_sources[index];
if(volume > 0) {
this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index);
auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount);
if(written_frames != frameCount) {
if(written_frames == 0) {
this->source_merge_buffer[index] = nullptr;
actual_sources--;
} else {
/* fill up the rest with silence (0) */
auto written = written_frames * this->_channel_count * 4;
memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4);
}
}
} else {
this->_sources[index]->pop_samples(nullptr, frameCount);
}
this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index);
auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount);
if(written_frames != frameCount) {
if(written_frames <= 0) {
this->source_merge_buffer[index] = nullptr;
actual_sources--;
} else {
/* fill up the rest with silence (0) */
auto written = written_frames * this->_channel_count * 4;
memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4);
}
}
}
}
}
if(actual_sources > 0 && volume > 0) {
if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount)) {
if(actual_sources > 0) {
if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount))
log_warn(category::audio, tr("failed to merge buffers!"));
auto volume = this->_volume;
if(volume != 1) {
auto float_length = this->_channel_count * frameCount;
auto data = (float*) output;
while(float_length-- > 0)
*data++ *= volume;
}
auto float_length = this->_channel_count * frameCount;
auto data = (float*) output;
while(float_length-- > 0)
*data++ *= volume;
} else {
memset(output, 0, this->_channel_count * 4 * frameCount);
memset(output, 0, this->_channel_count * sizeof(float) * frameCount);
}
return 0;
}
bool AudioOutput::open_device(std::string& error, PaDeviceIndex index) {
lock_guard lock(this->output_stream_lock);
void AudioOutput::set_device(const std::shared_ptr<AudioDevice> &device) {
lock_guard lock(this->device_lock);
if(this->device == device) return;
if(index == this->_current_device_index)
return true;
this->close_device();
this->_current_device_index = index;
this->_current_device = Pa_GetDeviceInfo(index);
if(!this->_current_device) {
this->_current_device_index = paNoDevice;
error = "failed to get device info";
return false;
}
PaStreamParameters output_parameters{};
memset(&output_parameters, 0, sizeof(output_parameters));
output_parameters.channelCount = (int) this->_channel_count;
output_parameters.device = this->_current_device_index;
output_parameters.sampleFormat = paFloat32;
output_parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency;
auto err = Pa_OpenStream(&output_stream, nullptr, &output_parameters, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioOutput::_audio_callback, this);
if(err != paNoError) {
error = to_string(err) + "/" + Pa_GetErrorText(err);
return false;
}
return true;
}
bool AudioOutput::playback() {
lock_guard lock(this->output_stream_lock);
if(!this->output_stream)
return false;
auto err = Pa_StartStream(this->output_stream);
if(err != paNoError && err != paStreamIsNotStopped) {
log_error(category::audio, tr("Pa_StartStream returned {}"), err);
return false;
}
return true;
this->close_device();
this->device = device;
}
void AudioOutput::close_device() {
lock_guard lock(this->output_stream_lock);
if(this->output_stream) {
/* TODO: Test for errors */
Pa_StopStream(this->output_stream);
Pa_CloseStream(this->output_stream);
lock_guard lock(this->device_lock);
if(this->_playback) {
this->_playback->remove_source(this);
this->_playback->stop_if_possible();
this->_playback.reset();
}
this->output_stream = nullptr;
this->device = nullptr;
}
bool AudioOutput::playback(std::string& error) {
lock_guard lock(this->device_lock);
if(!this->device) {
error = "invalid device handle";
return false;
}
if(this->_playback) return true;
this->_playback = this->device->playback();
if(!this->_playback) {
error = "failed to allocate memory";
return false;
}
this->_playback->register_source(this);
return this->_playback->start(error);
}

View File

@ -5,15 +5,15 @@
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
#include "AudioSamples.h"
#include "./AudioSamples.h"
#include "./driver/AudioDriver.h"
#include "../ring_buffer.h"
#ifdef WIN32
#define ssize_t int64_t
#endif
namespace tc {
namespace audio {
namespace tc::audio {
class AudioOutput;
namespace overflow_strategy {
@ -32,13 +32,21 @@ namespace tc {
size_t const channel_count = 0;
size_t const sample_rate = 0;
bool buffering = true;
size_t min_buffer = 0;
[[nodiscard]] inline size_t max_supported_latency() const {
return this->buffer.capacity() / this->channel_count / sizeof(float);
}
[[nodiscard]] inline size_t max_latency() const {
const auto max_samples = this->max_supported_latency();
if(this->max_buffered_samples && this->max_buffered_samples <= max_samples) return this->max_buffered_samples;
return max_samples;
}
bool buffering{true};
size_t min_buffered_samples{0};
size_t max_buffered_samples{0};
/* For stream set it to the max latency in samples you want.
* Zero means unlimited
*/
size_t max_latency = 0;
overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half;
/* if it returns true then the it means that the buffer has been refilled, we have to test again */
@ -49,24 +57,24 @@ namespace tc {
void clear();
ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */);
ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */);
ssize_t enqueue_samples(const std::shared_ptr<SampleBuffer>& /* buffer */);
ssize_t enqueue_samples_no_interleave(const void * /* input buffer */, size_t /* sample count */);
private:
AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate) : handle(handle), channel_count(channel_count), sample_rate(sample_rate) {}
AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate) :
handle(handle), channel_count(channel_count), sample_rate(sample_rate), buffer{channel_count * sample_rate * sizeof(float)} {
}
std::mutex buffer_lock;
size_t buffered_samples = 0;
std::deque<std::shared_ptr<SampleBuffer>> sample_buffers;
tc::ring_buffer buffer;
};
class AudioOutput {
class AudioOutput : public AudioDevicePlayback::Source {
public:
AudioOutput(size_t /* channels */, size_t /* rate */);
virtual ~AudioOutput();
bool open_device(std::string& /* error */, PaDeviceIndex);
bool playback();
void set_device(const std::shared_ptr<AudioDevice>& /* device */);
bool playback(std::string& /* error */);
void close_device();
PaDeviceIndex current_device() { return this->_current_device_index; }
std::shared_ptr<AudioDevice> current_device() { return this->device; }
std::deque<std::shared_ptr<AudioOutputSource>> sources() {
std::lock_guard lock(this->sources_lock);
@ -82,8 +90,7 @@ namespace tc {
inline float volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; }
private:
static int _audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*);
int audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags);
void fill_buffer(void *, size_t frames, size_t channels) override ;
size_t const _channel_count;
size_t const _sample_rate;
@ -91,10 +98,9 @@ namespace tc {
std::mutex sources_lock;
std::deque<std::shared_ptr<AudioOutputSource>> _sources;
std::recursive_mutex output_stream_lock;
const PaDeviceInfo* _current_device = nullptr;
PaDeviceIndex _current_device_index = paNoDevice;
PaStream* output_stream = nullptr;
std::recursive_mutex device_lock;
std::shared_ptr<AudioDevice> device{nullptr};
std::shared_ptr<AudioDevicePlayback> _playback{nullptr};
std::mutex buffer_lock; /* not required, but why not. Usually only used within audio_callback! */
void* source_buffer = nullptr;
@ -106,5 +112,4 @@ namespace tc {
float _volume = 1.f;
};
}
}
}

View File

@ -0,0 +1,219 @@
//
// Created by wolverindev on 07.02.20.
//
#include <thread>
#include <condition_variable>
#include <ThreadPool/ThreadHelper.h>
#include "AudioDriver.h"
#include "SoundIO.h"
#include "../../logger.h"
#include "../AudioMerger.h"
using namespace tc::audio;
namespace tc::audio {
std::deque<std::shared_ptr<AudioDevice>> devices() {
std::deque<std::shared_ptr<AudioDevice>> result{};
for(auto& backend : SoundIOBackendHandler::all_backends()) {
auto input_devices = backend->input_devices();
auto output_devices = backend->output_devices();
result.insert(result.end(), input_devices.begin(), input_devices.end());
result.insert(result.end(), output_devices.begin(), output_devices.end());
}
return result;
}
std::shared_ptr<AudioDevice> find_device_by_id(const std::string_view& id, bool input) {
for(auto& backend : SoundIOBackendHandler::all_backends()) {
for(auto& dev : input ? backend->input_devices() : backend->output_devices())
if(dev->id() == id)
return dev;
}
return nullptr;
}
std::mutex initialize_lock{};
std::deque<initialize_callback_t> initialize_callbacks{};
int initialize_state{0}; /* 0 := not initialized | 1 := initialized | 2 := initializing */
void _initialize() {
SoundIOBackendHandler::initialize_all();
SoundIOBackendHandler::connect_all();
}
void _finalize() {
SoundIOBackendHandler::shutdown_all();
}
void initialize(const initialize_callback_t& callback) {
{
std::unique_lock init_lock{initialize_lock};
if(initialize_state == 2) {
if(callback)
initialize_callbacks.push_back(callback);
return;
} else if(initialize_state == 1) {
init_lock.unlock();
callback();
return;
} else if(initialize_state != 0) {
init_lock.unlock();
callback();
log_warn(category::audio, tr("Invalid initialize state ({})"), initialize_state);
return;
}
initialize_state = 2;
}
std::thread init_thread([]{
_initialize();
std::unique_lock lock{initialize_lock};
auto callbacks = std::move(initialize_callbacks);
initialize_state = 1;
lock.unlock();
for(auto& callback : callbacks)
callback();
});
threads::name(init_thread, tr("audio init"));
init_thread.detach();
}
void await_initialized() {
std::condition_variable cv{};
std::mutex m{};
std::unique_lock init_lock{initialize_lock};
if(initialize_state != 2) return;
initialize_callbacks.emplace_back([&]{ cv.notify_all(); });
init_lock.unlock();
std::unique_lock m_lock{m};
cv.wait(m_lock);
}
bool initialized() {
std::unique_lock init_lock{initialize_lock};
return initialize_state == 1;
}
void finalize() {
await_initialized();
_finalize();
}
bool AudioDevicePlayback::start(std::string &error) {
std::lock_guard lock{this->state_lock};
if(this->running) return true;
if(!this->impl_start(error)) {
log_error(category::audio, tr("Failed to start playback: {}"), error);
return false;
}
this->running = true;
return true;
}
void AudioDevicePlayback::stop_if_possible() {
std::lock_guard lock{this->state_lock};
{
std::lock_guard s_lock{this->source_lock};
if(!this->_sources.empty()) return;
}
this->impl_stop();
this->running = false;
}
void AudioDevicePlayback::stop() {
std::lock_guard lock{this->state_lock};
if(this->running) return;
this->impl_stop();
this->running = false;
}
void AudioDevicePlayback::register_source(Source* source) {
std::lock_guard s_lock{this->source_lock};
this->_sources.push_back(source);
}
void AudioDevicePlayback::remove_source(Source* source) {
std::lock_guard s_lock{this->source_lock};
auto index = find(this->_sources.begin(), this->_sources.end(), source);
if(index == this->_sources.end()) return;
this->_sources.erase(index);
}
#define TMP_BUFFER_SIZE 8096
void AudioDevicePlayback::fill_buffer(void *buffer, size_t samples, size_t channels) {
std::lock_guard lock{this->source_lock};
const auto size = this->_sources.size();
if(size == 1) {
this->_sources.front()->fill_buffer(buffer, samples, channels);
} else if(size > 1) {
this->_sources.front()->fill_buffer(buffer, samples, channels);
uint8_t tmp_buffer[TMP_BUFFER_SIZE];
if(sizeof(float) * samples * channels > TMP_BUFFER_SIZE) {
log_warn(category::audio, tr("Dropping input source data because of too small merge buffer"));
return;
}
for(auto it = this->_sources.begin() + 1; it != this->_sources.end(); it++) {
(*it)->fill_buffer(tmp_buffer, samples, channels);
merge::merge_sources(buffer, buffer, tmp_buffer, channels, samples);
}
} else {
memset(buffer, 0, samples * channels * sizeof(float));
}
}
bool AudioDeviceRecord::start(std::string &error) {
std::lock_guard lock{this->state_lock};
if(this->running) return true;
if(!this->impl_start(error)) {
log_error(category::audio, tr("Failed to start record: {}"), error);
return false;
}
this->running = true;
return true;
}
void AudioDeviceRecord::stop_if_possible() {
std::lock_guard lock{this->state_lock};
{
std::lock_guard s_lock{this->consumer_lock};
if(!this->_consumers.empty()) return;
}
this->impl_stop();
this->running = false;
}
void AudioDeviceRecord::stop() {
std::lock_guard lock{this->state_lock};
if(this->running) return;
this->impl_stop();
this->running = false;
}
void AudioDeviceRecord::register_consumer(Consumer* source) {
std::lock_guard s_lock{this->consumer_lock};
this->_consumers.push_back(source);
}
void AudioDeviceRecord::remove_consumer(Consumer* source) {
std::lock_guard s_lock{this->consumer_lock};
auto index = find(this->_consumers.begin(), this->_consumers.end(), source);
if(index == this->_consumers.end()) return;
this->_consumers.erase(index);
}
}

View File

@ -0,0 +1,96 @@
#pragma once
#include <mutex>
#include <deque>
#include <memory>
#include <iostream>
#include <functional>
namespace tc::audio {
class AudioDeviceRecord {
public:
class Consumer {
public:
virtual void consume(const void* /* buffer */, size_t /* samples */, size_t /* channel count */) = 0;
};
[[nodiscard]] bool start(std::string& /* error */);
void stop_if_possible();
void stop();
[[nodiscard]] inline std::vector<Consumer*> consumer() {
std::lock_guard lock{this->consumer_lock};
return this->_consumers;
}
void register_consumer(Consumer* /* source */);
void remove_consumer(Consumer* /* source */);
protected:
virtual bool impl_start(std::string& /* error */) = 0;
virtual void impl_stop() = 0;
std::mutex state_lock{};
bool running{false};
std::mutex consumer_lock{};
std::vector<Consumer*> _consumers{};
};
class AudioDevicePlayback {
public:
class Source {
public:
virtual void fill_buffer(void* /* target */, size_t /* samples */, size_t /* channel count */) = 0;
};
[[nodiscard]] bool start(std::string& /* error */);
void stop_if_possible();
void stop();
[[nodiscard]] inline std::vector<Source*> sources() {
std::lock_guard lock{this->source_lock};
return this->_sources;
}
void register_source(Source* /* source */);
void remove_source(Source* /* source */);
protected:
virtual bool impl_start(std::string& /* error */) = 0;
virtual void impl_stop() = 0;
void fill_buffer(void* /* target */, size_t /* samples */, size_t /* channel count */);
std::mutex state_lock{};
bool running{false};
std::mutex source_lock{};
std::vector<Source*> _sources{};
};
class AudioDevice {
public:
/* information */
[[nodiscard]] virtual std::string id() const = 0;
[[nodiscard]] virtual std::string name() const = 0;
[[nodiscard]] virtual std::string driver() const = 0;
[[nodiscard]] virtual bool is_input_supported() const = 0;
[[nodiscard]] virtual bool is_output_supported() const = 0;
[[nodiscard]] virtual bool is_input_default() const = 0;
[[nodiscard]] virtual bool is_output_default() const = 0;
[[nodiscard]] virtual std::shared_ptr<AudioDevicePlayback> playback() = 0;
[[nodiscard]] virtual std::shared_ptr<AudioDeviceRecord> record() = 0;
};
typedef std::function<void()> initialize_callback_t;
extern void finalize();
extern void initialize(const initialize_callback_t& /* callback */ = []{});
extern void await_initialized();
extern bool initialized();
extern std::deque<std::shared_ptr<AudioDevice>> devices();
extern std::shared_ptr<AudioDevice> find_device_by_id(const std::string_view& /* id */, bool /* input */);
}

View File

@ -0,0 +1,260 @@
//
// Created by wolverindev on 07.02.20.
//
#include "SoundIO.h"
#include <algorithm>
#include "../../logger.h"
using namespace tc::audio;
std::mutex SoundIOBackendHandler::backend_lock{};
std::vector<std::shared_ptr<SoundIOBackendHandler>> SoundIOBackendHandler::backends{};
std::shared_ptr<SoundIOBackendHandler> SoundIOBackendHandler::get_backend(SoundIoBackend backend_type) {
std::lock_guard lock{backend_lock};
for(auto& backend : SoundIOBackendHandler::backends)
if(backend->backend == backend_type)
return backend;
return nullptr;
}
void SoundIOBackendHandler::initialize_all() {
std::lock_guard lock{backend_lock};
for(const auto& backend : {
SoundIoBackendJack,
SoundIoBackendPulseAudio,
SoundIoBackendAlsa,
SoundIoBackendCoreAudio,
SoundIoBackendWasapi,
SoundIoBackendDummy
}) {
if(!soundio_have_backend(backend)) {
log_debug(category::audio, tr("Skipping audio backend {} because its not supported on this platform."), soundio_backend_name(backend));
continue;
}
auto handler = std::make_shared<SoundIOBackendHandler>(backend);
if(std::string error{}; !handler->initialize(error)) {
log_error(category::audio, tr("Failed to initialize sound backed {}: {}"), soundio_backend_name(backend), error);
continue;
}
backends.push_back(handler);
}
std::stable_sort(backends.begin(), backends.end(), [](const auto& a, const auto& b) { return a->priority() > b->priority(); });
}
void SoundIOBackendHandler::connect_all() {
std::string error{};
std::lock_guard lock{backend_lock};
for(const auto& backend : backends)
if(!backend->connect(error))
log_error(category::audio, tr("Failed to connect to audio backend {}: {}"), backend->name(), error);
}
void SoundIOBackendHandler::shutdown_all() {
std::lock_guard lock{backend_lock};
for(auto& entry : backends)
entry->shutdown();
backends.clear();
}
SoundIOBackendHandler::SoundIOBackendHandler(SoundIoBackend backed) : backend{backed} {}
SoundIOBackendHandler::~SoundIOBackendHandler() {
this->shutdown();
}
bool SoundIOBackendHandler::initialize(std::string &error) {
assert(!this->soundio_handle);
this->soundio_handle = soundio_create();
if(!this->soundio_handle) {
error = "out of memory";
return false;
}
this->soundio_handle->userdata = this;
this->soundio_handle->on_devices_change = [](auto handle){
reinterpret_cast<SoundIOBackendHandler*>(handle->userdata)->handle_device_change();
};
this->soundio_handle->on_backend_disconnect = [](auto handle, auto err){
reinterpret_cast<SoundIOBackendHandler*>(handle->userdata)->handle_backend_disconnect(err);
};
return true;
}
void SoundIOBackendHandler::shutdown() {
if(!this->soundio_handle) return;
soundio_destroy(this->soundio_handle);
this->soundio_handle = nullptr;
}
bool SoundIOBackendHandler::connect(std::string &error, bool enforce) {
if(!this->soundio_handle) {
error = "invalid handle";
return false;
}
if(this->_connected && !enforce) {
error = "already connected";
return false;
}
auto err = soundio_connect_backend(this->soundio_handle, this->backend);
if(err) {
error = soundio_strerror(err);
return false;
}
this->soundio_handle->app_name = "TeaClient";
this->_connected = true;
{
auto begin = std::chrono::system_clock::now();
soundio_flush_events(this->soundio_handle);
auto end = std::chrono::system_clock::now();
log_debug(category::audio, tr("Flushed connect events within {}ms for backend {}"),
std::chrono::ceil<std::chrono::milliseconds>(end - begin).count(),
this->name());
}
return true;
}
void SoundIOBackendHandler::disconnect() {
if(!this->soundio_handle || !this->_connected) return;
soundio_disconnect(this->soundio_handle);
}
void SoundIOBackendHandler::handle_device_change() {
log_debug(category::audio, tr("Device list changed for backend {}. Reindexing devices."), this->name());
std::lock_guard lock{this->device_lock};
this->_default_output_device.reset();
this->_default_input_device.reset();
this->cached_input_devices.clear();
this->cached_output_devices.clear();
if(!this->_connected || !this->soundio_handle) return;
size_t input_devices{0}, output_devices{0};
auto default_input_device{soundio_default_input_device_index(this->soundio_handle)};
for(int i = 0; i < soundio_input_device_count(this->soundio_handle); i++) {
auto dev = soundio_get_input_device(this->soundio_handle, i);
if(!dev) {
log_warn(category::audio, tr("Failed to get input device at index {} for backend {}."), i, this->name());
continue;
}
if(dev->probe_error) {
log_trace(category::audio, tr("Skipping input device {} ({}) for backend {} because of probe error: {}"), dev->id, dev->name, this->name(), soundio_strerror(dev->probe_error));
soundio_device_unref(dev);
continue;
}
auto device = std::make_shared<SoundIODevice>(dev, this->name(), i == default_input_device, true);
log_trace(category::audio, tr("Found input device {} ({})."), dev->id, dev->name);
this->cached_input_devices.push_back(device);
if(i == default_input_device)
this->_default_input_device = device;
input_devices++;
}
auto default_output_device{soundio_default_output_device_index(this->soundio_handle)};
for(int i = 0; i < soundio_output_device_count(this->soundio_handle); i++) {
auto dev = soundio_get_output_device(this->soundio_handle, i);
if(!dev) {
log_warn(category::audio, tr("Failed to get output device at index {} for backend {}."), i, this->name());
continue;
}
if(dev->probe_error) {
log_trace(category::audio, tr("Skipping output device {} ({}) for backend {} because of probe error: {}"), dev->id, dev->name, this->name(), soundio_strerror(dev->probe_error));
soundio_device_unref(dev);
continue;
}
auto device = std::make_shared<SoundIODevice>(dev, this->name(), i == default_output_device, true);
log_trace(category::audio, tr("Found output device {} ({})."), dev->id, dev->name);
this->cached_output_devices.push_back(device);
if(i == default_output_device)
this->_default_output_device = device;
output_devices++;
}
log_info(category::audio, tr("Queried devices for backend {}, resulting in {} input and {} output devices."),
this->name(),
input_devices,
output_devices
);
}
void SoundIOBackendHandler::handle_backend_disconnect(int error) {
log_info(category::audio, tr("Backend {} disconnected with error {}."), this->name(), soundio_strerror(error));
}
SoundIODevice::SoundIODevice(struct ::SoundIoDevice *dev, std::string driver, bool default_, bool owned) : device_handle{dev}, driver_name{std::move(driver)}, _default{default_} {
if(!owned) soundio_device_ref(dev);
}
SoundIODevice::~SoundIODevice() {
soundio_device_unref(this->device_handle);
}
std::string SoundIODevice::id() const {
return this->device_handle->id;
}
std::string SoundIODevice::name() const {
return this->device_handle->name;
}
std::string SoundIODevice::driver() const {
return this->driver_name; /* we do not use this->device_handle->soundio->current_backend because the soundio could be null */
}
bool SoundIODevice::is_input_supported() const {
return this->device_handle->aim == SoundIoDeviceAimInput;
}
bool SoundIODevice::is_output_supported() const {
return this->device_handle->aim == SoundIoDeviceAimOutput;
}
bool SoundIODevice::is_input_default() const {
return this->_default && this->is_input_supported();
}
bool SoundIODevice::is_output_default() const {
return this->_default && this->is_output_supported();
}
std::shared_ptr<AudioDevicePlayback> SoundIODevice::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<SoundIOPlayback>(this->device_handle);
return this->_playback;
}
std::shared_ptr<AudioDeviceRecord> SoundIODevice::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<SoundIORecord>(this->device_handle);
return this->_record;
}

View File

@ -0,0 +1,158 @@
#pragma once
#include <soundio/soundio.h>
#include <memory>
#include <vector>
#include <mutex>
#include "./AudioDriver.h"
namespace tc::audio {
struct BackendPriority {
static constexpr std::array<int, SoundIoBackendDummy + 1> mapping{
/* SoundIoBackendNone */ -100,
/* SoundIoBackendJack */ 100,
/* SoundIoBackendPulseAudio */ 90,
/* SoundIoBackendAlsa */ 50,
/* SoundIoBackendCoreAudio */ 100,
/* SoundIoBackendWasapi */ 100,
/* SoundIoBackendDummy */ 0
};
[[nodiscard]] static constexpr auto priority(SoundIoBackend backend) {
if(backend >= mapping.size())
return 0;
return mapping[backend];
}
};
class SoundIOPlayback : public AudioDevicePlayback {
public:
constexpr static auto kChunkSize{960};
explicit SoundIOPlayback(struct ::SoundIoDevice* /* handle */);
virtual ~SoundIOPlayback();
protected:
bool impl_start(std::string& /* error */) override;
void impl_stop() override;
private:
bool stream_invalid{false};
struct ::SoundIoDevice* device_handle{nullptr};
struct ::SoundIoOutStream* stream{nullptr};
struct ::SoundIoRingBuffer* buffer{nullptr};
void write_callback(int frame_count_min, int frame_count_max);
};
class SoundIORecord : public AudioDeviceRecord {
public:
constexpr static auto kChunkSize{960};
explicit SoundIORecord(struct ::SoundIoDevice* /* handle */);
virtual ~SoundIORecord();
protected:
bool impl_start(std::string& /* error */) override;
void impl_stop() override;
private:
bool stream_invalid{false};
struct ::SoundIoDevice* device_handle{nullptr};
struct ::SoundIoInStream* stream{nullptr};
struct ::SoundIoRingBuffer* buffer{nullptr};
void read_callback(int frame_count_min, int frame_count_max);
};
class SoundIODevice : public AudioDevice {
public:
explicit SoundIODevice(struct ::SoundIoDevice* /* handle */, std::string /* driver */, bool /* default */, bool /* owned */);
virtual ~SoundIODevice();
[[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:
std::string driver_name{};
struct ::SoundIoDevice* device_handle{nullptr};
bool _default{false};
std::mutex io_lock{};
std::shared_ptr<SoundIOPlayback> _playback;
std::shared_ptr<SoundIORecord> _record;
};
class SoundIOBackendHandler {
public:
/* its sorted by priority */
static std::vector<std::shared_ptr<SoundIOBackendHandler>> all_backends() {
std::lock_guard lock{backend_lock};
return backends;;
}
static std::shared_ptr<SoundIOBackendHandler> get_backend(SoundIoBackend backend);
static void initialize_all();
static void connect_all();
static void shutdown_all();
explicit SoundIOBackendHandler(SoundIoBackend backed);
virtual ~SoundIOBackendHandler();
bool initialize(std::string& error);
void shutdown();
bool connect(std::string& /* error */, bool /* enforce */ = false);
[[nodiscard]] inline bool connected() const { return this->_connected; }
void disconnect();
[[nodiscard]] inline int priority() const { return BackendPriority::priority(this->backend); }
[[nodiscard]] inline const char* name() const { return soundio_backend_name(this->backend); }
[[nodiscard]] inline std::vector<std::shared_ptr<SoundIODevice>> input_devices() const {
std::lock_guard lock{this->device_lock};
return this->cached_input_devices;
}
[[nodiscard]] inline std::vector<std::shared_ptr<SoundIODevice>> output_devices() const {
std::lock_guard lock{this->device_lock};
return this->cached_output_devices;
}
[[nodiscard]] inline std::shared_ptr<SoundIODevice> default_input_device() const {
return this->_default_input_device;
}
[[nodiscard]] inline std::shared_ptr<SoundIODevice> default_output_device() const {
return this->_default_output_device;
}
const SoundIoBackend backend;
private:
static std::mutex backend_lock;
static std::vector<std::shared_ptr<SoundIOBackendHandler>> backends;
void handle_backend_disconnect(int /* error */);
void handle_device_change();
bool _connected{false};
struct SoundIo* soundio_handle{nullptr};
mutable std::mutex device_lock{};
std::vector<std::shared_ptr<SoundIODevice>> cached_input_devices{};
std::vector<std::shared_ptr<SoundIODevice>> cached_output_devices{};
std::shared_ptr<SoundIODevice> _default_output_device{nullptr};
std::shared_ptr<SoundIODevice> _default_input_device{nullptr};
};
}

View File

@ -0,0 +1,145 @@
//
// Created by wolverindev on 07.02.20.
//
#include "SoundIO.h"
#include <algorithm>
#include "../../logger.h"
using namespace tc::audio;
SoundIOPlayback::SoundIOPlayback(struct ::SoundIoDevice *device) : device_handle{device} {
soundio_device_ref(device);
}
SoundIOPlayback::~SoundIOPlayback() {
soundio_device_unref(this->device_handle);
}
bool SoundIOPlayback::impl_start(std::string &error) {
assert(this->device_handle);
this->buffer = soundio_ring_buffer_create(nullptr, kChunkSize * sizeof(float) * 2); /* 2 channels */
if(!buffer) {
error = "failed to allocate the buffer";
return false;
}
soundio_ring_buffer_clear(this->buffer);
this->stream = soundio_outstream_create(this->device_handle);
if(!this->stream) {
error = "out of memory";
return false;
}
this->stream->userdata = this;
this->stream->format = SoundIoFormatFloat32LE;
this->stream->software_latency = 0.02;
this->stream->underflow_callback = [](auto str) {
auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata);
log_info(category::audio, tr("Having an underflow on {}"), handle->device_handle->id);
};
this->stream->error_callback = [](auto str, int err) {
auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata);
log_info(category::audio, tr("Having an error on {}: {}. Aborting playback."), handle->device_handle->id, soundio_strerror(err));
handle->stream_invalid = true;
};
this->stream->write_callback = [](struct SoundIoOutStream *str, int frame_count_min, int frame_count_max) {
auto handle = reinterpret_cast<SoundIOPlayback*>(str->userdata);
handle->write_callback(frame_count_min, frame_count_max);
};
if(auto err = soundio_outstream_open(this->stream); err) {
error = soundio_strerror(err) + std::string{" (open)"};
soundio_outstream_destroy(this->stream);
this->stream = nullptr;
return false;
}
if(false && this->stream->layout_error) {
error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error);
soundio_outstream_destroy(this->stream);
this->stream = nullptr;
return false;
}
if(auto err = soundio_outstream_start(this->stream); err) {
error = soundio_strerror(err) + std::string{" (start)"};
soundio_outstream_destroy(this->stream);
this->stream = nullptr;
return false;
}
//TODO: Test for interleaved channel layout!
return true;
}
void SoundIOPlayback::impl_stop() {
if(!this->stream) return;
soundio_outstream_destroy(this->stream);
this->stream = nullptr;
soundio_ring_buffer_destroy(this->buffer);
this->buffer = nullptr;
}
void SoundIOPlayback::write_callback(int frame_count_min, int frame_count_max) {
const struct SoundIoChannelLayout *layout = &this->stream->layout;
struct SoundIoChannelArea *areas;
int frames_left{frame_count_min}, err;
if(frames_left < 120)
frames_left = 120;
if(frames_left > frame_count_max)
frames_left = frame_count_max;
while(frames_left > 0) {
int frame_count{frames_left};
auto buffered = soundio_ring_buffer_fill_count(this->buffer) / (sizeof(float) * layout->channel_count);
if(frame_count > buffered) {
if(buffered == 0) {
const auto length = sizeof(float) * frame_count * layout->channel_count;
this->fill_buffer(soundio_ring_buffer_write_ptr(this->buffer), frame_count, layout->channel_count);
soundio_ring_buffer_advance_write_ptr(this->buffer, length);
} else
frame_count = buffered;
}
if((err = soundio_outstream_begin_write(this->stream, &areas, &frame_count))) {
log_warn(category::audio, tr("Failed to begin a write to the soundio buffer: {}"), err);
return;
}
/* test for interleaved */
{
char* begin = areas[0].ptr - sizeof(float);
for(size_t channel{0}; channel < layout->channel_count; channel++) {
if((begin += sizeof(float)) != areas[channel].ptr) {
log_error(category::audio, tr("Expected interleaved buffer, which it isn't"));
return;
}
if(areas[channel].step != sizeof(float) * layout->channel_count) {
log_error(category::audio, tr("Invalid step size for channel {}"), channel);
return;
}
}
}
const auto length = sizeof(float) * frame_count * layout->channel_count;
memcpy(areas[0].ptr, soundio_ring_buffer_read_ptr(this->buffer), length);
soundio_ring_buffer_advance_read_ptr(this->buffer, length);
if((err = soundio_outstream_end_write(this->stream))) {
log_warn(category::audio, tr("Failed to end a write to the soundio buffer: {}"), err);
return;
}
frames_left -= frame_count;
}
}

View File

@ -0,0 +1,145 @@
//
// Created by wolverindev on 07.02.20.
//
#include "SoundIO.h"
#include <algorithm>
#include "../../logger.h"
using namespace tc::audio;
SoundIORecord::SoundIORecord(struct ::SoundIoDevice *device) : device_handle{device} {
soundio_device_ref(device);
}
SoundIORecord::~SoundIORecord() {
soundio_device_unref(this->device_handle);
}
bool SoundIORecord::impl_start(std::string &error) {
assert(this->device_handle);
this->buffer = soundio_ring_buffer_create(nullptr, kChunkSize * sizeof(float) * 2); //2 Channels
if(!buffer) {
error = "failed to allocate the buffer";
return false;
}
soundio_ring_buffer_clear(this->buffer);
this->stream = soundio_instream_create(this->device_handle);
if(!this->stream) {
error = "out of memory";
return false;
}
this->stream->userdata = this;
this->stream->format = SoundIoFormatFloat32LE;
this->stream->software_latency = 0.02;
this->stream->overflow_callback = [](auto str) {
auto handle = reinterpret_cast<SoundIORecord*>(str->userdata);
log_info(category::audio, tr("Having an overflow on {}"), handle->device_handle->id);
};
this->stream->error_callback = [](auto str, int err) {
auto handle = reinterpret_cast<SoundIORecord*>(str->userdata);
log_info(category::audio, tr("Having an error on {}: {}. Aborting recording."), handle->device_handle->id, soundio_strerror(err));
handle->stream_invalid = true;
};
this->stream->read_callback = [](struct SoundIoInStream *str, int frame_count_min, int frame_count_max) {
auto handle = reinterpret_cast<SoundIORecord*>(str->userdata);
handle->read_callback(frame_count_min, frame_count_max);
};
if(auto err = soundio_instream_open(this->stream); err) {
error = soundio_strerror(err) + std::string{" (open)"};
soundio_instream_destroy(this->stream);
this->stream = nullptr;
return false;
}
if(false && this->stream->layout_error) {
error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error);
soundio_instream_destroy(this->stream);
this->stream = nullptr;
return false;
}
if(auto err = soundio_instream_start(this->stream); err) {
error = soundio_strerror(err) + std::string{" (start)"};
soundio_instream_destroy(this->stream);
this->stream = nullptr;
return false;
}
//TODO: Test for interleaved channel layout!
return true;
}
void SoundIORecord::impl_stop() {
if(!this->stream) return;
soundio_instream_destroy(this->stream);
this->stream = nullptr;
soundio_ring_buffer_destroy(this->buffer);
this->buffer = nullptr;
}
void SoundIORecord::read_callback(int frame_count_min, int frame_count_max) {
const struct SoundIoChannelLayout *layout = &this->stream->layout;
struct SoundIoChannelArea *areas;
int frames_left{frame_count_max};
int buffer_samples = soundio_ring_buffer_free_count(this->buffer) / (sizeof(float) * layout->channel_count);
while(frames_left > 0) {
int frame_count{frames_left};
if(frame_count > buffer_samples)
frame_count = buffer_samples;
if(auto err = soundio_instream_begin_read(this->stream, &areas, &frame_count); err) {
log_error(category::audio, tr("Failed to begin read from input stream buffer: {}"), soundio_strerror(err));
return;
}
/* test for interleaved */
{
char* begin = areas[0].ptr - sizeof(float);
for(size_t channel{0}; channel < layout->channel_count; channel++) {
if((begin += sizeof(float)) != areas[channel].ptr) {
log_error(category::audio, tr("Expected interleaved buffer, which it isn't"));
return;
}
if(areas[channel].step != sizeof(float) * layout->channel_count) {
log_error(category::audio, tr("Invalid step size for channel {}"), channel);
return;
}
}
}
const auto length = sizeof(float) * layout->channel_count * frame_count;
memcpy(soundio_ring_buffer_write_ptr(this->buffer), areas[0].ptr, length);
soundio_ring_buffer_advance_write_ptr(this->buffer, length);
buffer_samples -= frame_count;
frames_left -= frame_count;
if(buffer_samples == 0) {
std::lock_guard consumer{this->consumer_lock};
const auto byte_count = soundio_ring_buffer_fill_count(this->buffer);
const auto frame_count = byte_count / (sizeof(float) * layout->channel_count);
for(auto& consumer : this->_consumers)
consumer->consume(soundio_ring_buffer_read_ptr(this->buffer), frame_count, layout->channel_count);
soundio_ring_buffer_advance_read_ptr(this->buffer, byte_count);
buffer_samples = frame_count;
}
if(auto err = soundio_instream_end_read(this->stream); err) {
log_error(category::audio, tr("Failed to close input stream buffer: {}"), soundio_strerror(err));
return;
}
}
}

View File

@ -311,7 +311,7 @@ NAN_METHOD(AudioConsumerWrapper::_create_filter_state) {
assert(consumer); /* should never be null! */
string error;
auto filter = make_shared<filter::StateFilter>(consumer->channel_count,consumer->sample_rate,consumer->frame_size);
auto filter = make_shared<filter::StateFilter>(consumer->channel_count, consumer->sample_rate, consumer->frame_size);
if(!filter->initialize(error)) {
Nan::ThrowError(Nan::New<v8::String>("failed to initialize filter (" + error + ")").ToLocalChecked());
return;

View File

@ -121,25 +121,10 @@ NAN_METHOD(AudioOutputStreamWrapper::_delete) {
}
ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) {
ssize_t result = 0;
if(interleaved) {
result = handle->enqueue_samples(source, samples);
} else {
auto buffer = SampleBuffer::allocate(handle->channel_count, samples);
auto src_buffer = (float*) source;
auto target_buffer = (float*) buffer->sample_data;
while (samples-- > 0) {
*target_buffer = *src_buffer;
*(target_buffer + 1) = *(src_buffer + buffer->sample_size);
target_buffer += 2;
src_buffer++;
}
result = handle->enqueue_samples(buffer);
}
return result;
if(interleaved)
return handle->enqueue_samples(source, samples);
else
return handle->enqueue_samples_no_interleave(source, samples);
}
NAN_METHOD(AudioOutputStreamWrapper::_write_data) {
@ -198,6 +183,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) {
return;
}
//TODO: Use a tmp preallocated buffer here!
ssize_t target_samples = client->_resampler->estimated_output_size(samples);
auto buffer = SampleBuffer::allocate(handle->channel_count, max((size_t) samples, (size_t) target_samples));
auto source_buffer = js_buffer.Data();
@ -224,7 +210,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) {
buffer->sample_index = 0;
buffer->sample_size = target_samples;
info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer));
info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer->sample_data, target_samples));
}
}
@ -237,7 +223,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) {
return;
}
info.GetReturnValue().Set((float) handle->min_buffer / (float) handle->sample_rate);
info.GetReturnValue().Set((float) handle->min_buffered_samples / (float) handle->sample_rate);
}
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
@ -254,7 +240,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
return;
}
handle->min_buffer = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
handle->min_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
@ -266,7 +252,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
return;
}
info.GetReturnValue().Set((float) handle->max_latency / (float) handle->sample_rate);
info.GetReturnValue().Set((float) handle->max_latency() / (float) handle->sample_rate);
}
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
@ -283,7 +269,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
return;
}
handle->max_latency = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
handle->max_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) {

View File

@ -1,10 +1,9 @@
#include <misc/base64.h>
#include <misc/digest.h>
#include <include/NanStrings.h>
#include "AudioPlayer.h"
#include "../AudioOutput.h"
#include "../AudioDevice.h"
#include "AudioOutputStream.h"
#include "../../logger.h"
using namespace tc;
using namespace tc::audio;
@ -22,6 +21,11 @@ NAN_MODULE_INIT(player::init_js) {
}
NAN_METHOD(audio::available_devices) {
if(!audio::initialized()) {
Nan::ThrowError(tr("audio hasn't been initialized!"));
return;
}
auto devices = audio::devices();
auto result = Nan::New<v8::Array>(devices.size());
@ -29,17 +33,15 @@ NAN_METHOD(audio::available_devices) {
auto device_info = Nan::New<v8::Object>();
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>(base64::encode(digest::sha1(device->name + device->driver))).ToLocalChecked());
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::New<v8::String>("input_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->input_supported));
Nan::Set(device_info, Nan::New<v8::String>("output_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->output_supported));
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::New<v8::String>("input_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_input));
Nan::Set(device_info, Nan::New<v8::String>("output_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_output));
Nan::Set(device_info, Nan::New<v8::String>("device_index").ToLocalChecked(), Nan::New<v8::Number>(device->device_id));
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(result, index, device_info);
}
@ -47,13 +49,48 @@ NAN_METHOD(audio::available_devices) {
info.GetReturnValue().Set(result);
}
NAN_METHOD(audio::await_initialized_js) {
if(info.Length() != 1 || !info[0]->IsFunction()) {
Nan::ThrowError(tr("Invalid arguments"));
return;
}
if(audio::initialized()) {
(void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
} else {
auto _callback = std::make_unique<Nan::Persistent<v8::Function>>(info[0].As<v8::Function>());
auto _async_callback = Nan::async_callback([call = std::move(_callback)] {
Nan::HandleScope scope;
auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate());
(void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
call->Reset();
}).option_destroyed_execute(true);
audio::initialize([_async_callback] {
_async_callback.call();
});
}
}
NAN_METHOD(audio::initialized_js) {
info.GetReturnValue().Set(audio::initialized());
}
NAN_METHOD(player::current_playback_device) {
if(!global_audio_output) {
info.GetReturnValue().Set(paNoDevice);
info.GetReturnValue().Set(Nan::Undefined());
return;
}
info.GetReturnValue().Set(global_audio_output->current_device());
auto device = global_audio_output->current_device();
if(!device) {
info.GetReturnValue().Set(Nan::Undefined());
return;
}
info.GetReturnValue().Set(Nan::LocalString(device->id()));
}
NAN_METHOD(player::set_playback_device) {
@ -62,24 +99,33 @@ NAN_METHOD(player::set_playback_device) {
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
const auto null_device = info[0]->IsNullOrUndefined();
if(info.Length() != 1 || !(info[0]->IsString() || null_device)) {
Nan::ThrowError("invalid arguments");
return;
}
std::string error;
if(!global_audio_output->open_device(error, info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0))) {
Nan::ThrowError(Nan::New<v8::String>("failed to open device (" + error + ")").ToLocalChecked());
return;
}
auto device = null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), false);
if(!device && !null_device) {
Nan::ThrowError("invalid device id");
return;
}
if(!global_audio_output->playback()) {
Nan::ThrowError("failed to open playback stream");
std::string error;
global_audio_output->set_device(device);
if(!global_audio_output->playback(error)) {
Nan::ThrowError(error.c_str());
return;
}
}
NAN_METHOD(player::create_stream) {
if(!audio::initialized()) {
Nan::ThrowError(tr("audio hasn't been initialized yet"));
return;
}
if(!global_audio_output) {
Nan::ThrowError("Global audio output hasn't been yet initialized!");
return;

View File

@ -2,32 +2,32 @@
#include <nan.h>
namespace tc {
namespace audio {
extern NAN_METHOD(available_devices);
namespace tc::audio {
extern NAN_METHOD(available_devices);
extern NAN_METHOD(await_initialized_js);
extern NAN_METHOD(initialized_js);
namespace player {
extern NAN_MODULE_INIT(init_js);
namespace player {
extern NAN_MODULE_INIT(init_js);
extern NAN_METHOD(current_playback_device);
extern NAN_METHOD(set_playback_device);
extern NAN_METHOD(current_playback_device);
extern NAN_METHOD(set_playback_device);
extern NAN_METHOD(create_stream);
extern NAN_METHOD(create_stream);
extern NAN_METHOD(get_master_volume);
extern NAN_METHOD(set_master_volume);
/*
export function get_master_volume() : number;
export function set_master_volume(volume: number);
extern NAN_METHOD(get_master_volume);
extern NAN_METHOD(set_master_volume);
/*
export function get_master_volume() : number;
export function set_master_volume(volume: number);
export function set_device(device: AudioDevice) : Promise<void>;
export function current_device() : AudioDevice;
export function set_device(device: AudioDevice) : Promise<void>;
export function current_device() : AudioDevice;
export function available_devices() : AudioDevice[];
export function available_devices() : AudioDevice[];
export function create_stream() : OwnedAudioOutputStream;
export function delete_stream(stream: OwnedAudioOutputStream) : number;
*/
}
}
export function create_stream() : OwnedAudioOutputStream;
export function delete_stream(stream: OwnedAudioOutputStream) : number;
*/
}
}

View File

@ -1,4 +1,5 @@
#include <utility>
#include <NanStrings.h>
#include "AudioRecorder.h"
#include "AudioConsumer.h"
@ -14,6 +15,10 @@ NAN_MODULE_INIT(recorder::init_js) {
}
NAN_METHOD(recorder::create_recorder) {
if(!audio::initialized()) {
Nan::ThrowError(tr("audio hasn't been initialized yet"));
return;
}
auto input = make_shared<AudioInput>(2, 48000);
auto wrapper = new AudioRecorderWrapper(input);
auto js_object = Nan::NewInstance(Nan::New(AudioRecorderWrapper::constructor())).ToLocalChecked();
@ -122,42 +127,50 @@ NAN_METHOD(AudioRecorderWrapper::_get_device) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
info.GetReturnValue().Set(input->current_device());
auto device = input->current_device();
if(device)
info.GetReturnValue().Set(Nan::LocalString(device->id()));
else
info.GetReturnValue().Set(Nan::Undefined());
}
NAN_METHOD(AudioRecorderWrapper::_set_device) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
if(info.Length() != 2 || !info[0]->IsNumber() || !info[1]->IsFunction()) {
const auto is_null_device = info[0]->IsNullOrUndefined();
if(info.Length() != 2 || !(is_null_device || info[0]->IsString()) || !info[1]->IsFunction()) {
Nan::ThrowError("invalid arguments");
return;
}
auto device_id = info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
if(!audio::initialized()) {
Nan::ThrowError("audio hasn't been initialized yet");
return;
}
auto device = is_null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), true);
if(!device && !is_null_device) {
Nan::ThrowError("invalid device id");
return;
}
unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[1].As<v8::Function>());
unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder());
auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)](bool result, std::string error) mutable {
auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)] {
Nan::HandleScope scope;
auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate());
v8::Local<v8::Value> argv[1];
if(result)
argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), result);
else
argv[0] = Nan::NewOneByteString((uint8_t*) error.data(), error.length()).ToLocalChecked();
callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
(void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
recorder->Reset();
call->Reset();
}).option_destroyed_execute(true);
std::thread([_async_callback, input, device_id]{
string error;
auto flag = input->open_device(error, device_id);
_async_callback(std::forward<bool>(flag), std::forward<std::string>(error));
std::thread([_async_callback, input, device]{
input->set_device(device);
_async_callback();
}).detach();
}
@ -173,9 +186,13 @@ NAN_METHOD(AudioRecorderWrapper::_start) {
}
auto input = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder())->_input;
std::string error{};
v8::Local<v8::Value> argv[1];
argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), input->record());
if(input->record(error))
argv[0] = Nan::New<v8::Boolean>(true);
else
argv[0] = Nan::LocalString(error);
(void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
}

View File

@ -5,6 +5,7 @@
#include <mutex>
#include <event2/thread.h>
#include <misc/digest.h>
#include <NanStrings.h>
#include "logger.h"
#include "include/NanException.h"
@ -15,7 +16,7 @@
#include "connection/ft/FileTransferManager.h"
#include "connection/ft/FileTransferObject.h"
#include "audio/AudioOutput.h"
#include "audio/AudioDevice.h"
#include "audio/driver/AudioDriver.h"
#include "audio/js/AudioOutputStream.h"
#include "audio/js/AudioPlayer.h"
#include "audio/js/AudioRecorder.h"
@ -104,11 +105,7 @@ NAN_MODULE_INIT(init) {
}
string error;
//TODO here
//PaJack_SetClientName("TeaClient");
Pa_Initialize();
std::thread(audio::devices).detach(); /* cache the devices */
tc::audio::initialize(); //TODO: Notify JS when initialized?
logger::info(category::general, "Loading crypt modules");
std::string descriptors = "LTGE";
@ -141,20 +138,32 @@ NAN_MODULE_INIT(init) {
#endif
tc::audio::init_event_loops();
/* TODO: Test error codes and make the audi playback device configurable */
global_audio_output = new tc::audio::AudioOutput(2, 48000); //48000 44100
if(!global_audio_output->open_device(error, Pa_GetDefaultOutputDevice())) {
logger::error(category::audio, "Failed to initialize default audio playback: {}", error);
} else {
if(!global_audio_output->playback()) {
logger::error(category::audio, "Failed to start audio playback");
}
}
tc::audio::initialize([]{
std::string error{};
std::shared_ptr<tc::audio::AudioDevice> default_output{};
for(auto& device : tc::audio::devices()) {
if(device->is_output_default()) {
default_output = device;
break;
}
}
/* TODO: Test error codes and make the audi playback device configurable */
global_audio_output = new tc::audio::AudioOutput(2, 48000);
global_audio_output->set_device(default_output);
if(!global_audio_output->playback(error)) {
logger::error(category::audio, "Failed to start audio playback: {}", error);
}
});
{
auto namespace_audio = Nan::New<v8::Object>();
Nan::Set(namespace_audio, Nan::New<v8::String>("available_devices").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::available_devices)).ToLocalChecked());
{
Nan::Set(namespace_audio, Nan::LocalString("available_devices"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::available_devices)).ToLocalChecked());
Nan::Set(namespace_audio, Nan::LocalString("initialize"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::await_initialized_js)).ToLocalChecked());
Nan::Set(namespace_audio, Nan::LocalString("initialized"), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::initialized_js)).ToLocalChecked());
{
auto namespace_playback = Nan::New<v8::Object>();
audio::player::init_js(namespace_playback);
audio::AudioOutputStreamWrapper::Init(namespace_playback);

View File

@ -211,8 +211,8 @@ VoiceClientWrap::~VoiceClientWrap() {}
VoiceClient::VoiceClient(const std::shared_ptr<VoiceConnection>&, uint16_t client_id) : _client_id(client_id) {
this->output_source = global_audio_output->create_source();
this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
this->output_source->max_latency = (size_t) ceil(this->output_source->sample_rate * 0.5);
this->output_source->min_buffer = (size_t) ceil(this->output_source->sample_rate * 0.04);
this->output_source->max_buffered_samples = (size_t) ceil(this->output_source->sample_rate * 0.5);
this->output_source->min_buffered_samples = (size_t) ceil(this->output_source->sample_rate * 0.04);
this->output_source->on_underflow = [&]{
if(this->_state == state::stopping)
@ -533,8 +533,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch
if(replay_head->reset_decoder)
audio_codec.converter->reset_decoder();
//TODO: Use statically allocated buffer?
auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer);
this->output_source->enqueue_samples(decoded);
this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size);
this->set_state(state::playing);
}

View File

@ -1,6 +1,5 @@
#include "FileTransferManager.h"
#include "FileTransferObject.h"
#include <NanGet.h>
#include <misc/net.h>
#include <algorithm>
@ -593,6 +592,7 @@ void FileTransferManager::remove_transfer(tc::ft::Transfer *transfer) {
}
#ifdef NODEJS_API
#include <NanGet.h>
NAN_MODULE_INIT(JSTransfer::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance);

View File

@ -1,5 +1,4 @@
#include <iostream>
#include <NanGet.h>
#include "logger.h"
/* Basic */
@ -9,6 +8,7 @@ void force_log_raw(logger::category::value, spdlog::level::level_enum level, con
void force_log_node(logger::category::value, spdlog::level::level_enum, const std::string_view &);
#ifdef NODEJS_API
#include <NanGet.h>
#include <include/NanEventCallback.h>
#include <include/NanStrings.h>
@ -109,7 +109,6 @@ void force_log_node(logger::category::value category, spdlog::level::level_enum
}
log_messages_callback();
}
#endif
void logger::initialize_raw() {

View File

@ -0,0 +1,49 @@
//
// Created by wolverindev on 07.02.20.
//
#include "ring_buffer.h"
#include <soundio/soundio.h>
using namespace tc;
ring_buffer::ring_buffer(size_t cap) {
this->handle = soundio_ring_buffer_create(nullptr, cap);
}
ring_buffer::~ring_buffer() {
soundio_ring_buffer_destroy((SoundIoRingBuffer*) this->handle);
}
size_t ring_buffer::capacity() const {
return soundio_ring_buffer_capacity((SoundIoRingBuffer*) this->handle);
}
size_t ring_buffer::free_count() const {
return soundio_ring_buffer_free_count((SoundIoRingBuffer*) this->handle);
}
size_t ring_buffer::fill_count() const {
return soundio_ring_buffer_fill_count((SoundIoRingBuffer*) this->handle);
}
char* ring_buffer::write_ptr() {
return soundio_ring_buffer_write_ptr((SoundIoRingBuffer*) this->handle);
}
void ring_buffer::advance_write_ptr(size_t bytes) {
soundio_ring_buffer_advance_write_ptr((SoundIoRingBuffer*) this->handle, bytes);
}
const void* ring_buffer::read_ptr() const {
return soundio_ring_buffer_read_ptr((SoundIoRingBuffer*) this->handle);
}
void ring_buffer::advance_read_ptr(size_t bytes) {
soundio_ring_buffer_advance_read_ptr((SoundIoRingBuffer*) this->handle, bytes);
}
void ring_buffer::clear() {
soundio_ring_buffer_clear((SoundIoRingBuffer*) this->handle);
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <cstddef>
namespace tc {
class ring_buffer {
public:
/* Attention: Actual size may be larger than given capacity */
explicit ring_buffer(size_t /* minimum capacity */);
~ring_buffer();
[[nodiscard]] size_t capacity() const;
[[nodiscard]] size_t fill_count() const;
[[nodiscard]] size_t free_count() const;
/* do not write more than the capacity! */
char* write_ptr();
void advance_write_ptr(size_t /* count */);
/* do not read more than the capacity! */
[[nodiscard]] const void* read_ptr() const;
void advance_read_ptr(size_t /* count */);
void clear();
private:
void* handle{nullptr};
};
}

View File

@ -1,17 +1,14 @@
#include <stdio.h>
#include <math.h>
#include <cstring>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
#include <deque>
#include "../../src/audio/AudioOutput.h"
#include "../../src/audio/AudioInput.h"
#include "../../src/audio/filter/FilterVad.h"
#include "../../src/audio/filter/FilterThreshold.h"
#include "../../src/audio/Audio.h"
#include "../../src/logger.h"
#ifdef WIN32
#include <windows.h>
@ -21,61 +18,85 @@ using namespace std;
using namespace tc;
int main() {
string error;
if(!tc::audio::initialize(error)) {
cerr << "Failed to initialize audio: " << error << endl;
return 1;
}
auto playback_manager = audio::AudioOutput(2, 48000);
if(!playback_manager.open_device(error, Pa_GetDefaultOutputDevice())) {
cerr << "Failed to open output device (" << error << ")" << endl;
return 1;
}
if(!playback_manager.playback()) {
cerr << "failed to start playback" << endl;
return 1;
}
std::string error{};
auto input = audio::AudioInput(2, 48000);
if(!input.open_device(error, Pa_GetDefaultInputDevice())) {
cerr << "Failed to open input device (" << error << ")" << endl;
return 1;
}
if(!input.record()) {
cerr << "failed to start record" << endl;
return 1;
}
Pa_Initialize();
{
auto consumer = input.create_consumer(960);
auto target_stream = playback_manager.create_source();
auto vad_handler = make_shared<audio::filter::VadFilter>(2, 48000, 960);
if(!vad_handler->initialize(error, 3, 4)) {
cerr << "failed to initialize vad handler (" << error << ")";
return 1;
}
logger::initialize_raw();
tc::audio::initialize();
tc::audio::await_initialized();
auto threshold_filter = make_shared<audio::filter::ThresholdFilter>(2, 48000, 960);
if(!threshold_filter->initialize(error, .5, 5)) {
cerr << "failed to initialize threashold handler (" << error << ")";
return 1;
}
std::shared_ptr<tc::audio::AudioDevice> default_playback{nullptr}, default_record{nullptr};
for(auto& device : tc::audio::devices()) {
if(device->is_output_default())
default_playback = device;
if(device->is_input_default())
default_record = device;
}
assert(default_record);
assert(default_playback);
consumer->on_read = [target_stream, vad_handler, threshold_filter](const void* buffer, size_t samples) {
target_stream->enqueue_samples(buffer, samples);
for(auto& dev : tc::audio::devices()) {
if(!dev->is_input_supported()) continue;
cout << "T: " << threshold_filter->analyze(buffer, 0) << endl;
if(vad_handler->process(buffer)) {
cout << "Read " << samples << endl;
target_stream->enqueue_samples(buffer, samples);
} else {
cout << "Drop " << samples << endl;
}
};
cout << "Read started" << endl;
}
auto playback_manager = std::make_unique<audio::AudioOutput>(2, 48000);
if(!playback_manager->set_device(error, default_playback)) {
cerr << "Failed to open output device (" << error << ")" << endl;
return 1;
}
if(!playback_manager->playback()) {
cerr << "failed to start playback" << endl;
return 1;
}
this_thread::sleep_for(chrono::seconds(60));
auto input = std::make_unique<audio::AudioInput>(2, 48000);
if(!input->set_device(error, dev)) {
cerr << "Failed to open input device (" << error << "): " << dev->id() << " (" << dev->name() << ")" << endl;
continue;
}
if(!input->record()) {
cerr << "failed to start record for " << dev->id() << " (" << dev->name() << ")" << endl;
continue;
}
{
auto target_stream = playback_manager->create_source();
auto consumer = input->create_consumer(960);
auto vad_handler = make_shared<audio::filter::VadFilter>(2, 48000, 960);
if(!vad_handler->initialize(error, 3, 4)) {
cerr << "failed to initialize vad handler (" << error << ")";
return 1;
}
auto threshold_filter = make_shared<audio::filter::ThresholdFilter>(2, 48000, 960);
if(!threshold_filter->initialize(error, .5, 5)) {
cerr << "failed to initialize threashold handler (" << error << ")";
return 1;
}
consumer->on_read = [target_stream, vad_handler, threshold_filter](const void* buffer, size_t samples) { //Do not capture consumer!
target_stream->enqueue_samples(buffer, samples);
/*
const auto analize_value = threshold_filter->analyze(buffer, 0);
if(vad_handler->process(buffer)) {
cout << "Read " << samples << " (" << analize_value << ")" << endl;
target_stream->enqueue_samples(buffer, samples);
} else {
cout << "Drop " << samples << " (" << analize_value << ")" << endl;
}
*/
};
input.release();
cout << "Read started" << endl;
}
playback_manager.release(); //FIXME: Memory leak!
}
this_thread::sleep_for(chrono::seconds(360));
/*
while(true) {

View File

@ -1,13 +1,16 @@
#include <soundio/soundio.h>
#include <cmath>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <math.h>
#include <chrono>
#include <mutex>
#include "../../src/logger.h"
#include "../../src/audio/driver/SoundIO.h"
using namespace std;
using namespace tc::audio;
static const float PI = 3.1415926535f;
static float seconds_offset = 0.0f;
@ -63,8 +66,27 @@ static void write_callback(struct SoundIoOutStream *outstream,
}
int main(int argc, char **argv) {
int err;
logger::initialize_raw();
SoundIOBackendHandler::initialize_all();
SoundIOBackendHandler::connect_all();
const auto print_device = [](const std::shared_ptr<SoundIODevice>& device) {
std::cout << " - " << device->id() << " (" << device->name() << ")";
if(device->is_output_default() || device->is_input_default())
std::cout << " [Default]";
std::cout << "\n";
};
for(auto& backend : tc::audio::SoundIOBackendHandler::all_backends()) {
std::cout << "Backend " << backend->name() << ":\n";
std::cout << " Input devices: (" << backend->input_devices().size() << "): \n";
for(auto& device : backend->input_devices()) print_device(device);
std::cout << " Output devices: (" << backend->input_devices().size() << "): \n";
for(auto& device : backend->input_devices()) print_device(device);
}
SoundIOBackendHandler::shutdown_all();
return 0;
int err;
struct SoundIo *soundio = soundio_create();
if (!soundio) {
fprintf(stderr, "out of memory\n");
@ -85,6 +107,7 @@ int main(int argc, char **argv) {
for(int i = 0; i < soundio_input_device_count(soundio); i++) {
auto dev = soundio_get_input_device(soundio, i);
cout << dev->name << " - " << dev->id << endl;
soundio_device_unref(dev);
}

View File

@ -1,114 +1,48 @@
/// <reference path="../../exports/exports.d.ts" />
console.log("HELLO WORLD");
console.log("Starting app");
module.paths.push("../../build/linux_x64");
module.paths.push("../../build/win32_64");
//LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.5
const os = require('os');
//process.dlopen(module, '/usr/lib/x86_64-linux-gnu/libasan.so.5',
// os.constants.dlopen.RTLD_NOW);
import * as fs from "fs";
const original_require = require;
require = (module => original_require(__dirname + "/../../../build/linux_x64/" + module + ".node")) as any;
import * as handle from "teaclient_connection";
require = original_require;
const connection_list = [];
const connection = handle.spawn_server_connection();
const client_list = [];
console.dir(handle.audio);
handle.audio.initialize(() => {
console.log("Audio initialized");
console.dir(handle);
console.log("Query devices...");
console.log("Devices: %o", handle.audio.available_devices());
console.log("Current playback device: %o", handle.audio.playback.current_device());
//handle.audio.playback.set_device(14);
//console.log("Current playback device: %o", handle.audio.playback.current_device());
console.log("Query devices...");
console.log("Devices: %o", handle.audio.available_devices());
console.log("Current playback device: %o", handle.audio.playback.current_device());
const stream = handle.audio.playback.create_stream();
console.log("Own stream: %o", stream);
const stream = handle.audio.playback.create_stream();
console.log("Own stream: %o", stream);
for(let i = 0; i < 12; i++) {
const recorder = handle.audio.record.create_recorder();
for(const device of handle.audio.available_devices()) {
if(!device.input_supported)
continue;
const default_input = handle.audio.available_devices().find(e => e.input_default);
console.log(default_input);
console.log(handle.audio.available_devices().find(e => e.device_id == handle.audio.playback.current_device()));
if(device.name != "pulse")
continue;
console.log("Found pulse at %o", device.device_index);
recorder.set_device(device.device_index, () => {
recorder.start(flag => console.log("X: " + flag));
const consumer = recorder.create_consumer();
consumer.create_filter_threshold(2);
recorder.set_device(default_input.device_id, () => {
const consumer = recorder.create_consumer();
consumer.callback_data = buffer => {
stream.write_data(buffer.buffer, true);
};
recorder.start(result => {
console.log("Start result: %o", result);
});
break;
}
}
});
/* -1 => default device */
const recorder = handle.audio.record.create_recorder();
console.log("Have device: %o", recorder);
console.log("Device: %o", recorder.get_device());
if(recorder.get_device() == -1) {
console.log("Looking for devices");
for(const device of handle.audio.available_devices()) {
if(!device.input_supported)
continue;
setInterval(() => {
const elements = handle.audio.available_devices().filter(e => e.input_supported);
const dev = elements[Math.floor(Math.random() * elements.length)];
recorder.set_device(dev.device_id, () => {
console.log("Dev updated: %o", dev);
if(device.name != "pulse")
continue;
console.log("Found pulse at %o", device.device_index);
recorder.set_device(device.device_index, () => {});
}
}
console.log("Device: %o", recorder.get_device());
recorder.start(() => {});
console.log("Started: %o", recorder.started());
const consumer = recorder.create_consumer();
{
const filter = consumer.create_filter_threshold(.5);
filter.set_margin_frames(10);
/*
filter.set_analyze_filter(value => {
console.log(value);
})
*/
}
{
//const filter = consumer.create_filter_vad(3);
//console.log("Filter name: %s; Filter level: %d; Filter margin: %d", filter.get_name(), filter.get_level(), filter.get_margin_frames());
}
{
const consume = consumer.create_filter_state();
setTimeout(() => {
console.log("Silence now!");
consume.set_consuming(true);
setTimeout(() => {
console.log("Speak now!");
consume.set_consuming(false);
}, 1000);
recorder.start(() => {
console.log("Started");
});
});
}, 1000);
}
setInterval(() => {
if("gc" in global) {
console.log("GC");
global.gc();
}
}, 1000);
let a_map = [consumer, recorder];
/* keep the object alive */
setTimeout(() => {
connection.connected();
a_map = a_map.filter(e => true);
}, 1000);
});