260 lines
8.9 KiB
C++
260 lines
8.9 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);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|