274 lines
9.5 KiB
C++
274 lines
9.5 KiB
C++
//
|
|
// 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);
|
|
}
|
|
|
|
inline std::string sample_rates(struct ::SoundIoDevice *dev) {
|
|
std::string result{};
|
|
for(size_t index = 0; index < dev->sample_rate_count; index++)
|
|
result += (index > 0 ? ", [" : "[") + std::to_string(dev->sample_rates[index].min) + ";" + std::to_string(dev->sample_rates[index].max) + "]";
|
|
return dev->sample_rate_count ? result : "none";
|
|
}
|
|
|
|
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 {} ({}). Raw: {}. Rates: {}"), dev->id, dev->name, dev->is_raw, sample_rates(dev));
|
|
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 {} ({}). Raw: {}. Rates: {}."), dev->id, dev->name, dev->is_raw, sample_rates(dev));
|
|
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);
|
|
|
|
if(this->device_handle->is_raw) {
|
|
this->_device_id = std::string{dev->id} + "_raw";
|
|
this->driver_name += " Raw";
|
|
} else {
|
|
this->_device_id = dev->id;
|
|
}
|
|
}
|
|
|
|
SoundIODevice::~SoundIODevice() {
|
|
soundio_device_unref(this->device_handle);
|
|
}
|
|
|
|
std::string SoundIODevice::id() const {
|
|
return this->_device_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;
|
|
} |