From 5d96ee5492c29bf969743990c5397266b9f4b5ec Mon Sep 17 00:00:00 2001 From: "jonas@jkvinge.net" Date: Tue, 11 May 2021 19:14:00 +0200 Subject: [PATCH] Add ALSA PCM devices and option to set channels Fixes #262 --- src/CMakeLists.txt | 1 + src/engine/alsapcmdevicefinder.cpp | 85 +++++++++ src/engine/alsapcmdevicefinder.h | 37 ++++ src/engine/devicefinders.cpp | 2 + src/engine/enginebase.cpp | 6 + src/engine/enginebase.h | 4 + src/engine/gstengine.cpp | 1 + src/engine/gstenginepipeline.cpp | 11 ++ src/engine/gstenginepipeline.h | 5 + src/settings/backendsettingspage.cpp | 237 ++++++++++++++++-------- src/settings/backendsettingspage.h | 12 +- src/settings/backendsettingspage.ui | 258 ++++++++++++++------------- 12 files changed, 459 insertions(+), 200 deletions(-) create mode 100644 src/engine/alsapcmdevicefinder.cpp create mode 100644 src/engine/alsapcmdevicefinder.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad00b112..f31f3a94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -574,6 +574,7 @@ endif(HAVE_GLOBALSHORTCUTS) optional_source(HAVE_ALSA SOURCES engine/alsadevicefinder.cpp + engine/alsapcmdevicefinder.cpp ) # DBUS diff --git a/src/engine/alsapcmdevicefinder.cpp b/src/engine/alsapcmdevicefinder.cpp new file mode 100644 index 00000000..5e234f70 --- /dev/null +++ b/src/engine/alsapcmdevicefinder.cpp @@ -0,0 +1,85 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "devicefinder.h" +#include "alsapcmdevicefinder.h" + +AlsaPCMDeviceFinder::AlsaPCMDeviceFinder() + : DeviceFinder("alsa", {"alsa","alsasink"}) {} + +QList AlsaPCMDeviceFinder::ListDevices() { + + QList ret; + + void **hints = nullptr; + if (snd_device_name_hint(-1, "pcm", &hints) < 0) { + return ret; + } + + for (void **n = hints ; *n ; ++n) { + char *io = snd_device_name_get_hint(*n, "IOID"); + char *name = snd_device_name_get_hint(*n, "NAME"); + char *desc = snd_device_name_get_hint(*n, "DESC"); + if (io && name && desc && strcmp(io, "Output") == 0) { + + char *desc_last = desc; + QString description; + for (char *desc_i = desc ; desc_i && *desc_i != '\0' ; ++desc_i) { + if (*desc_i == '\n') { + *desc_i = '\0'; + if (!description.isEmpty()) description.append(' '); + description.append(desc_last); + desc_last = desc_i + 1; + } + } + + if (desc_last) { + if (!description.isEmpty()) description.append(' '); + description.append(desc_last); + } + + Device device; + device.value = name; + device.description = description; + device.iconname = GuessIconName(device.description); + ret << device; + } + if (io) free(io); + if (name) free(name); + if (desc) free(desc); + } + + snd_device_name_free_hint(hints); + + return ret; + +} diff --git a/src/engine/alsapcmdevicefinder.h b/src/engine/alsapcmdevicefinder.h new file mode 100644 index 00000000..5d569602 --- /dev/null +++ b/src/engine/alsapcmdevicefinder.h @@ -0,0 +1,37 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALSAPCMDEVICEFINDER_H +#define ALSAPCMDEVICEFINDER_H + +#include "config.h" + +#include + +#include "devicefinder.h" + +class AlsaPCMDeviceFinder : public DeviceFinder { + public: + explicit AlsaPCMDeviceFinder(); + + bool Initialize() override { return true; } + QList ListDevices() override; +}; + +#endif // ALSAPCMDEVICEFINDER_H diff --git a/src/engine/devicefinders.cpp b/src/engine/devicefinders.cpp index 68272e1b..39382d70 100644 --- a/src/engine/devicefinders.cpp +++ b/src/engine/devicefinders.cpp @@ -30,6 +30,7 @@ #ifdef HAVE_ALSA # include "alsadevicefinder.h" +# include "alsapcmdevicefinder.h" #endif #ifdef HAVE_LIBPULSE @@ -57,6 +58,7 @@ void DeviceFinders::Init() { #ifdef HAVE_ALSA device_finders.append(new AlsaDeviceFinder); + device_finders.append(new AlsaPCMDeviceFinder); #endif #ifdef HAVE_LIBPULSE device_finders.append(new PulseDeviceFinder); diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp index 8d128e27..d130b2b7 100644 --- a/src/engine/enginebase.cpp +++ b/src/engine/enginebase.cpp @@ -60,6 +60,8 @@ Engine::Base::Base() fadeout_duration_(2), fadeout_duration_nanosec_(2 * kNsecPerSec), proxy_authentication_(false), + channels_enabled_(false), + channels_(0), about_to_end_emitted_(false) {} Engine::Base::~Base() {} @@ -110,6 +112,9 @@ void Engine::Base::ReloadSettings() { volume_control_ = s.value("volume_control", true).toBool(); + channels_enabled_ = s.value("channels_enabled", false).toBool(); + channels_ = s.value("channels", 0).toInt(); + buffer_duration_nanosec_ = s.value("bufferduration", BackendSettingsPage::kDefaultBufferDuration).toLongLong() * kNsecPerMsec; buffer_low_watermark_ = s.value("bufferlowwatermark", BackendSettingsPage::kDefaultBufferLowWatermark).toDouble(); buffer_high_watermark_ = s.value("bufferhighwatermark", BackendSettingsPage::kDefaultBufferHighWatermark).toDouble(); @@ -156,6 +161,7 @@ void Engine::Base::ReloadSettings() { proxy_user_.clear(); proxy_pass_.clear(); } + s.endGroup(); } diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 421d067b..50538e60 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -206,6 +206,10 @@ class Base : public QObject { QString proxy_user_; QString proxy_pass_; + // Channels + bool channels_enabled_; + int channels_; + private: bool about_to_end_emitted_; Q_DISABLE_COPY(Base) diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index d696f7ce..3595cc24 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -817,6 +817,7 @@ std::shared_ptr GstEngine::CreatePipeline() { ret->set_buffer_low_watermark(buffer_low_watermark_); ret->set_buffer_high_watermark(buffer_high_watermark_); ret->set_proxy_settings(proxy_address_, proxy_authentication_, proxy_user_, proxy_pass_); + ret->set_channels(channels_enabled_, channels_); ret->AddBufferConsumer(this); for (GstBufferConsumer *consumer : buffer_consumers_) { diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index c56f5a40..90f9d371 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -86,6 +86,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark), buffering_(false), proxy_authentication_(false), + channels_enabled_(false), + channels_(0), segment_start_(0), segment_start_received_(false), end_offset_nanosec_(-1), @@ -206,6 +208,11 @@ void GstEnginePipeline::set_proxy_settings(const QString &address, const bool au proxy_pass_ = pass; } +void GstEnginePipeline::set_channels(const bool enabled, const int channels) { + channels_enabled_ = enabled; + channels_ = channels; +} + bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec) { stream_url_ = stream_url; @@ -440,6 +447,10 @@ bool GstEnginePipeline::InitAudioBin() { gst_element_link(next, audioconverter); GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); + if (channels_enabled_ && channels_ > 0) { + qLog(Debug) << "Setting channels to" << channels_; + gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr); + } gst_element_link_filtered(audioconverter, audiosink, caps); gst_caps_unref(caps); diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index bc1ef045..4e54e8f9 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -74,6 +74,7 @@ class GstEnginePipeline : public QObject { void set_buffer_low_watermark(const double value); void set_buffer_high_watermark(const double value); void set_proxy_settings(const QString &address, const bool authentication, const QString &user, const QString &pass); + void set_channels(const bool enabled, const int channels); // Creates the pipeline, returns false on error bool InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec); @@ -221,6 +222,10 @@ class GstEnginePipeline : public QObject { QString proxy_user_; QString proxy_pass_; + // Channels + bool channels_enabled_; + int channels_; + // These get called when there is a new audio buffer available QList buffer_consumers_; QMutex buffer_consumers_mutex_; diff --git a/src/settings/backendsettingspage.cpp b/src/settings/backendsettingspage.cpp index f3f269a3..cab26f8a 100644 --- a/src/settings/backendsettingspage.cpp +++ b/src/settings/backendsettingspage.cpp @@ -58,11 +58,17 @@ #include "ui_backendsettingspage.h" const char *BackendSettingsPage::kSettingsGroup = "Backend"; +const char *BackendSettingsPage::kOutputAutomaticallySelect = "Automatically select"; +const char *BackendSettingsPage::kOutputCustom = "Custom"; const qint64 BackendSettingsPage::kDefaultBufferDuration = 4000; const double BackendSettingsPage::kDefaultBufferLowWatermark = 0.33; const double BackendSettingsPage::kDefaultBufferHighWatermark = 0.99; -BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BackendSettingsPage) { +BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : + SettingsPage(dialog), + ui_(new Ui_BackendSettingsPage), + configloaded_(false), + engineloaded_(false) { ui_->setupUi(this); setWindowIcon(IconLoader::Load("soundcard")); @@ -75,6 +81,24 @@ BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage( ui_->label_replaygainfallbackgain->setMinimumWidth(QFontMetrics(ui_->label_replaygainfallbackgain->font()).width("-WW.W dB")); #endif + QObject::connect(ui_->combobox_engine, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::EngineChanged); + QObject::connect(ui_->combobox_output, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::OutputChanged); + QObject::connect(ui_->combobox_device, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::DeviceSelectionChanged); + QObject::connect(ui_->lineedit_device, &QLineEdit::textChanged, this, &BackendSettingsPage::DeviceStringChanged); +#ifdef HAVE_ALSA + QObject::connect(ui_->radiobutton_alsa_hw, &QRadioButton::clicked, this, &BackendSettingsPage::radiobutton_alsa_hw_clicked); + QObject::connect(ui_->radiobutton_alsa_plughw, &QRadioButton::clicked, this, &BackendSettingsPage::radiobutton_alsa_plughw_clicked); + QObject::connect(ui_->radiobutton_alsa_pcm, &QRadioButton::clicked, this, &BackendSettingsPage::radiobutton_alsa_pcm_clicked); +#endif + QObject::connect(ui_->stickyslider_replaygainpreamp, &StickySlider::valueChanged, this, &BackendSettingsPage::RgPreampChanged); + QObject::connect(ui_->stickyslider_replaygainfallbackgain, &StickySlider::valueChanged, this, &BackendSettingsPage::RgFallbackGainChanged); + QObject::connect(ui_->checkbox_fadeout_stop, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); + QObject::connect(ui_->checkbox_fadeout_cross, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); + QObject::connect(ui_->checkbox_fadeout_auto, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); + QObject::connect(ui_->checkbox_volume_control, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); + QObject::connect(ui_->checkbox_channels, &QCheckBox::toggled, ui_->widget_channels, &QSpinBox::setEnabled); + QObject::connect(ui_->button_buffer_defaults, &QPushButton::clicked, this, &BackendSettingsPage::BufferDefaults); + } BackendSettingsPage::~BackendSettingsPage() { @@ -111,8 +135,11 @@ void BackendSettingsPage::Load() { ui_->checkbox_volume_control->setChecked(s.value("volume_control", true).toBool()); - ui_->spinbox_bufferduration->setValue(s.value("bufferduration", kDefaultBufferDuration).toInt()); + ui_->checkbox_channels->setChecked(s.value("channels_enabled", false).toBool()); + ui_->spinbox_channels->setValue(s.value("channels", 2).toInt()); + ui_->widget_channels->setEnabled(ui_->checkbox_channels->isChecked()); + ui_->spinbox_bufferduration->setValue(s.value("bufferduration", kDefaultBufferDuration).toInt()); ui_->spinbox_low_watermark->setValue(s.value("bufferlowwatermark", kDefaultBufferLowWatermark).toDouble()); ui_->spinbox_high_watermark->setValue(s.value("bufferhighwatermark", kDefaultBufferHighWatermark).toDouble()); @@ -122,7 +149,7 @@ void BackendSettingsPage::Load() { ui_->checkbox_replaygaincompression->setChecked(s.value("rgcompression", true).toBool()); ui_->stickyslider_replaygainfallbackgain->setValue(static_cast(s.value("rgfallbackgain", 0.0).toDouble() * 10 + 600)); -#if defined(HAVE_ALSA) +#ifdef HAVE_ALSA bool fade_default = false; #else bool fade_default = true; @@ -136,7 +163,7 @@ void BackendSettingsPage::Load() { ui_->spinbox_fadeduration->setValue(s.value("FadeoutDuration", 2000).toInt()); ui_->spinbox_fadeduration_pauseresume->setValue(s.value("FadeoutPauseDuration", 250).toInt()); -#if defined(HAVE_ALSA) +#ifdef HAVE_ALSA ui_->lineedit_device->show(); ui_->widget_alsa_plugin->show(); int alsaplug_int = alsa_plugin(s.value("alsaplugin", 0).toInt()); @@ -149,6 +176,9 @@ void BackendSettingsPage::Load() { case alsa_plugin::alsa_plughw: ui_->radiobutton_alsa_plughw->setChecked(true); break; + case alsa_plugin::alsa_pcm: + ui_->radiobutton_alsa_pcm->setChecked(true); + break; } } #else @@ -168,22 +198,6 @@ void BackendSettingsPage::Load() { configloaded_ = true; - QObject::connect(ui_->combobox_engine, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::EngineChanged); - QObject::connect(ui_->combobox_output, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::OutputChanged); - QObject::connect(ui_->combobox_device, QOverload::of(&QComboBox::currentIndexChanged), this, &BackendSettingsPage::DeviceSelectionChanged); - QObject::connect(ui_->lineedit_device, &QLineEdit::textChanged, this, &BackendSettingsPage::DeviceStringChanged); -#if defined(HAVE_ALSA) - QObject::connect(ui_->radiobutton_alsa_hw, &QRadioButton::clicked, this, &BackendSettingsPage::radiobutton_alsa_hw_clicked); - QObject::connect(ui_->radiobutton_alsa_plughw, &QRadioButton::clicked, this, &BackendSettingsPage::radiobutton_alsa_plughw_clicked); -#endif - QObject::connect(ui_->stickyslider_replaygainpreamp, &StickySlider::valueChanged, this, &BackendSettingsPage::RgPreampChanged); - QObject::connect(ui_->stickyslider_replaygainfallbackgain, &StickySlider::valueChanged, this, &BackendSettingsPage::RgFallbackGainChanged); - QObject::connect(ui_->checkbox_fadeout_stop, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); - QObject::connect(ui_->checkbox_fadeout_cross, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); - QObject::connect(ui_->checkbox_fadeout_auto, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); - QObject::connect(ui_->checkbox_volume_control, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); - QObject::connect(ui_->button_buffer_defaults, &QPushButton::clicked, this, &BackendSettingsPage::BufferDefaults); - FadingOptionsChanged(); RgPreampChanged(ui_->stickyslider_replaygainpreamp->value()); RgFallbackGainChanged(ui_->stickyslider_replaygainfallbackgain->value()); @@ -202,7 +216,7 @@ void BackendSettingsPage::Load() { } QVariant device_value; if (ui_->combobox_device->currentText().isEmpty()) device_value = QVariant(); - else if (ui_->combobox_device->currentText() == "Custom") device_value = ui_->lineedit_device->text(); + else if (ui_->combobox_device->currentText() == kOutputCustom) device_value = ui_->lineedit_device->text(); else device_value = ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value(); if (enginetype_current_ != enginetype || output_name != output_current_ || device_value != device_current_) { @@ -318,7 +332,7 @@ void BackendSettingsPage::Load_Device(const QString &output, const QVariant &dev #ifdef Q_OS_WIN if (engine()->type() != Engine::GStreamer) #endif - ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", QVariant()); + ui_->combobox_device->addItem(IconLoader::Load("soundcard"), kOutputAutomaticallySelect, QVariant()); for (DeviceFinder *f : dialog()->app()->device_finders()->ListFinders()) { if (!f->outputs().contains(output)) continue; @@ -330,7 +344,7 @@ void BackendSettingsPage::Load_Device(const QString &output, const QVariant &dev } if (engine()->CustomDeviceSupport(output)) { - ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", QVariant()); + ui_->combobox_device->addItem(IconLoader::Load("soundcard"), kOutputCustom, QVariant()); ui_->lineedit_device->setEnabled(true); } else { @@ -339,28 +353,36 @@ void BackendSettingsPage::Load_Device(const QString &output, const QVariant &dev #ifdef HAVE_ALSA if (engine()->ALSADeviceSupport(output)) { + ui_->widget_alsa_plugin->setEnabled(true); ui_->radiobutton_alsa_hw->setEnabled(true); ui_->radiobutton_alsa_plughw->setEnabled(true); - if (device.toString().contains(QRegularExpression("^plughw:.*"))) { - ui_->radiobutton_alsa_hw->setChecked(false); + ui_->radiobutton_alsa_pcm->setEnabled(true); + if (device.toString().contains(QRegularExpression("^hw:.*"))) { + ui_->radiobutton_alsa_hw->setChecked(true); + SwitchALSADevices(alsa_plugin::alsa_hw); + } + else if (device.toString().contains(QRegularExpression("^plughw:.*"))) { ui_->radiobutton_alsa_plughw->setChecked(true); SwitchALSADevices(alsa_plugin::alsa_plughw); } + else if (device.toString().contains(QRegularExpression("^.*:CARD=.*DEV=.*"))) { + ui_->radiobutton_alsa_pcm->setChecked(true); + SwitchALSADevices(alsa_plugin::alsa_pcm); + } else { - ui_->radiobutton_alsa_plughw->setChecked(false); ui_->radiobutton_alsa_hw->setChecked(true); SwitchALSADevices(alsa_plugin::alsa_hw); } } else { - ui_->radiobutton_alsa_hw->setEnabled(false); + ui_->widget_alsa_plugin->setDisabled(true); ui_->radiobutton_alsa_hw->setChecked(false); - ui_->radiobutton_alsa_plughw->setEnabled(false); ui_->radiobutton_alsa_plughw->setChecked(false); + ui_->radiobutton_alsa_pcm->setChecked(false); } #endif - bool found(false); + bool found = false; for (int i = 0; i < ui_->combobox_device->count(); ++i) { QVariant d = ui_->combobox_device->itemData(i).value(); if (df_device.value.isValid() && df_device.value == d) { @@ -381,8 +403,8 @@ void BackendSettingsPage::Load_Device(const QString &output, const QVariant &dev ui_->lineedit_device->setText(device.toString()); if (!found) { for (int i = 0; i < ui_->combobox_device->count(); ++i) { - if (ui_->combobox_device->itemText(i) == "Custom") { - if (ui_->combobox_device->currentText() != "Custom") ui_->combobox_device->setCurrentIndex(i); + if (ui_->combobox_device->itemText(i) == kOutputCustom) { + if (ui_->combobox_device->currentText() != kOutputCustom) ui_->combobox_device->setCurrentIndex(i); break; } } @@ -411,7 +433,7 @@ void BackendSettingsPage::Save() { } if (ui_->combobox_device->currentText().isEmpty()) device_value = QVariant(); - else if (ui_->combobox_device->currentText() == "Custom") device_value = ui_->lineedit_device->text(); + else if (ui_->combobox_device->currentText() == kOutputCustom) device_value = ui_->lineedit_device->text(); else device_value = ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value(); QSettings s; @@ -442,20 +464,26 @@ void BackendSettingsPage::Save() { #ifdef HAVE_ALSA if (ui_->radiobutton_alsa_hw->isChecked()) s.setValue("alsaplugin", static_cast(alsa_plugin::alsa_hw)); else if (ui_->radiobutton_alsa_plughw->isChecked()) s.setValue("alsaplugin", static_cast(alsa_plugin::alsa_plughw)); + else if (ui_->radiobutton_alsa_pcm->isChecked()) s.setValue("alsaplugin", static_cast(alsa_plugin::alsa_pcm)); else s.remove("alsaplugin"); #endif s.setValue("volume_control", ui_->checkbox_volume_control->isChecked()); + s.setValue("channels_enabled", ui_->checkbox_channels->isChecked()); + s.setValue("channels", ui_->spinbox_channels->value()); + s.endGroup(); } void BackendSettingsPage::Cancel() { + if (engine() && engine()->type() != enginetype_current_) { // Reset engine back to the original because user cancelled. dialog()->app()->player()->CreateEngine(enginetype_current_); dialog()->app()->player()->Init(); } + } void BackendSettingsPage::EngineChanged(const int index) { @@ -496,7 +524,7 @@ void BackendSettingsPage::DeviceSelectionChanged(int index) { if (engine()->CustomDeviceSupport(output.name)) { ui_->lineedit_device->setEnabled(true); - if (ui_->combobox_device->currentText() != "Custom") { + if (ui_->combobox_device->currentText() != kOutputCustom) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (device.metaType().id() == QMetaType::QString) #else @@ -520,7 +548,7 @@ void BackendSettingsPage::DeviceStringChanged() { if (!configloaded_ || !EngineInitialized()) return; EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); - bool found(false); + bool found = false; #ifdef HAVE_ALSA if (engine()->ALSADeviceSupport(output.name)) { @@ -532,6 +560,10 @@ void BackendSettingsPage::DeviceStringChanged() { ui_->radiobutton_alsa_plughw->setChecked(true); SwitchALSADevices(alsa_plugin::alsa_plughw); } + else if (ui_->lineedit_device->text().contains(QRegularExpression("^.*:CARD=.*DEV=.*")) && !ui_->radiobutton_alsa_pcm->isChecked()) { + ui_->radiobutton_alsa_pcm->setChecked(true); + SwitchALSADevices(alsa_plugin::alsa_plughw); + } } #endif @@ -542,9 +574,10 @@ void BackendSettingsPage::DeviceStringChanged() { #else if (device.type() != QVariant::String) continue; #endif - if (device.toString().isEmpty()) continue; - if (ui_->combobox_device->itemText(i) == "Custom") continue; - if (device.toString() == ui_->lineedit_device->text()) { + QString device_str = device.toString(); + if (device_str.isEmpty()) continue; + if (ui_->combobox_device->itemText(i) == kOutputCustom) continue; + if (device_str == ui_->lineedit_device->text()) { if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i); found = true; } @@ -552,15 +585,15 @@ void BackendSettingsPage::DeviceStringChanged() { if (engine()->CustomDeviceSupport(output.name)) { ui_->lineedit_device->setEnabled(true); - if ((!found) && (ui_->combobox_device->currentText() != "Custom")) { + if ((!found) && (ui_->combobox_device->currentText() != kOutputCustom)) { for (int i = 0; i < ui_->combobox_device->count(); ++i) { - if (ui_->combobox_device->itemText(i) == "Custom") { + if (ui_->combobox_device->itemText(i) == kOutputCustom) { ui_->combobox_device->setCurrentIndex(i); break; } } } - if (ui_->combobox_device->currentText() == "Custom") { + if (ui_->combobox_device->currentText() == kOutputCustom) { if ((ui_->lineedit_device->text().isEmpty()) && (ui_->combobox_device->count() > 0) && (ui_->combobox_device->currentIndex() != 0)) ui_->combobox_device->setCurrentIndex(0); } } @@ -596,12 +629,14 @@ void BackendSettingsPage::SwitchALSADevices(const alsa_plugin alsaplugin) { // All ALSA devices are listed twice, one for "hw" and one for "plughw" // Only show one of them by making the other ones invisible based on the alsa plugin radiobuttons for (int i = 0; i < ui_->combobox_device->count(); ++i) { - QListView *view = qobject_cast(ui_->combobox_device->view()); + QListView *view = qobject_cast(ui_->combobox_device->view()); if (!view) continue; - if (alsaplugin == alsa_plugin::alsa_hw && ui_->combobox_device->itemData(i).toString().contains(QRegularExpression("^plughw:.*"))) { - view->setRowHidden(i, true); - } - else if (alsaplugin == alsa_plugin::alsa_plughw && ui_->combobox_device->itemData(i).toString().contains(QRegularExpression("^hw:.*"))) { + if ((ui_->combobox_device->itemData(i).toString().contains(QRegularExpression("^hw:.*")) && alsaplugin != alsa_plugin::alsa_hw) + || + (ui_->combobox_device->itemData(i).toString().contains(QRegularExpression("^plughw:.*")) && alsaplugin != alsa_plugin::alsa_plughw) + || + (ui_->combobox_device->itemData(i).toString().contains(QRegularExpression("^.*:CARD=.*DEV=.*")) && alsaplugin != alsa_plugin::alsa_pcm) + ) { view->setRowHidden(i, true); } else { @@ -614,7 +649,7 @@ void BackendSettingsPage::SwitchALSADevices(const alsa_plugin alsaplugin) { void BackendSettingsPage::radiobutton_alsa_hw_clicked(const bool checked) { - Q_UNUSED(checked); + if (!checked) return; #ifdef HAVE_ALSA @@ -623,33 +658,27 @@ void BackendSettingsPage::radiobutton_alsa_hw_clicked(const bool checked) { EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); if (!engine()->ALSADeviceSupport(output.name)) return; - if (ui_->lineedit_device->text().contains(QRegularExpression("^plughw:.*"))) { - SwitchALSADevices(alsa_plugin::alsa_hw); - QString device_new = ui_->lineedit_device->text().replace(QRegularExpression("^plughw:"), "hw:"); - bool found(false); - for (int i = 0; i < ui_->combobox_device->count(); ++i) { - QVariant device = ui_->combobox_device->itemData(i).value(); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - if (device.metaType().id() != QMetaType::QString) continue; -#else - if (device.type() != QVariant::String) continue; -#endif - if (device.toString().isEmpty()) continue; - if (device.toString() == device_new) { - if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i); - found = true; - } - } - if (!found) ui_->lineedit_device->setText(device_new); + SwitchALSADevices(alsa_plugin::alsa_hw); + + QString device_new = ui_->lineedit_device->text(); + + if (device_new.contains(QRegularExpression("^plughw:.*"))) { + device_new = device_new.replace(QRegularExpression("^plughw:"), "hw:"); } + if (!device_new.contains(QRegularExpression("^hw:.*"))) { + device_new.clear(); + } + + SelectDevice(device_new); + #endif } void BackendSettingsPage::radiobutton_alsa_plughw_clicked(const bool checked) { - Q_UNUSED(checked); + if (!checked) return; #ifdef HAVE_ALSA @@ -658,28 +687,88 @@ void BackendSettingsPage::radiobutton_alsa_plughw_clicked(const bool checked) { EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); if (!engine()->ALSADeviceSupport(output.name)) return; - if (ui_->lineedit_device->text().contains(QRegularExpression("^hw:.*"))) { - SwitchALSADevices(alsa_plugin::alsa_plughw); - QString device_new = ui_->lineedit_device->text().replace(QRegularExpression("^hw:"), "plughw:"); - bool found(false); + SwitchALSADevices(alsa_plugin::alsa_plughw); + + QString device_new = ui_->lineedit_device->text(); + + if (device_new.contains(QRegularExpression("^hw:.*"))) { + device_new = device_new.replace(QRegularExpression("^hw:"), "plughw:"); + } + + if (!device_new.contains(QRegularExpression("^plughw:.*"))) { + device_new.clear(); + } + + SelectDevice(device_new); + +#endif + +} + +void BackendSettingsPage::radiobutton_alsa_pcm_clicked(const bool checked) { + + if (!checked) return; + +#ifdef HAVE_ALSA + + if (!configloaded_ || !EngineInitialized()) return; + + EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); + if (!engine()->ALSADeviceSupport(output.name)) return; + + SwitchALSADevices(alsa_plugin::alsa_pcm); + + QString device_new = ui_->lineedit_device->text(); + + if (!device_new.contains(QRegularExpression("^.*:CARD=.*DEV=.*"))) { + device_new.clear(); + } + + SelectDevice(device_new); + +#endif + +} + +void BackendSettingsPage::SelectDevice(const QString &device_new) { + + if (device_new.isEmpty()) { for (int i = 0; i < ui_->combobox_device->count(); ++i) { + if (ui_->combobox_device->itemText(i) == kOutputAutomaticallySelect && ui_->combobox_device->currentIndex() != i) { + ui_->combobox_device->setCurrentIndex(i); + break; + } + } + } + else { + bool found = false; + for (int i = 0; i < ui_->combobox_device->count(); ++i) { + QListView *view = qobject_cast(ui_->combobox_device->view()); + if (view && view->isRowHidden(i)) continue; QVariant device = ui_->combobox_device->itemData(i).value(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (device.metaType().id() != QMetaType::QString) continue; #else if (device.type() != QVariant::String) continue; #endif - if (device.toString().isEmpty()) continue; - if (device.toString() == device_new) { + QString device_str = device.toString(); + if (device_str.isEmpty()) continue; + if (device_str == device_new) { if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i); found = true; } } - if (!found) ui_->lineedit_device->setText(device_new); + if (!found) { + ui_->lineedit_device->setText(device_new); + for (int i = 0; i < ui_->combobox_device->count(); ++i) { + if (ui_->combobox_device->itemText(i) == kOutputCustom && ui_->combobox_device->currentIndex() != i) { + ui_->combobox_device->setCurrentIndex(i); + break; + } + } + } } -#endif - } void BackendSettingsPage::FadingOptionsChanged() { diff --git a/src/settings/backendsettingspage.h b/src/settings/backendsettingspage.h index 42633d61..eb217e37 100644 --- a/src/settings/backendsettingspage.h +++ b/src/settings/backendsettingspage.h @@ -64,6 +64,7 @@ public: void RgFallbackGainChanged(const int value); void radiobutton_alsa_hw_clicked(const bool checked); void radiobutton_alsa_plughw_clicked(const bool checked); + void radiobutton_alsa_pcm_clicked(const bool checked); void FadingOptionsChanged(); void BufferDefaults(); @@ -71,12 +72,11 @@ public: #ifdef HAVE_ALSA enum alsa_plugin { alsa_hw = 1, - alsa_plughw = 2 + alsa_plughw = 2, + alsa_pcm = 3 }; #endif - Ui_BackendSettingsPage *ui_; - bool EngineInitialized(); void Load_Engine(Engine::EngineType enginetype); @@ -85,7 +85,13 @@ public: #ifdef HAVE_ALSA void SwitchALSADevices(const alsa_plugin alsaplugin); #endif + void SelectDevice(const QString &device_new); + private: + static const char *kOutputAutomaticallySelect; + static const char *kOutputCustom; + + Ui_BackendSettingsPage *ui_; bool configloaded_; bool engineloaded_; ErrorDialog errordialog_; diff --git a/src/settings/backendsettingspage.ui b/src/settings/backendsettingspage.ui index 1250b049..a7cf5c42 100644 --- a/src/settings/backendsettingspage.ui +++ b/src/settings/backendsettingspage.ui @@ -21,125 +21,23 @@ - - - 10 - - - - - Engine - - - - - - - false - - - - 400 - 0 - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - 10 - - - - - Output - - - - - - - false - - - - 400 - 0 - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - 10 - - + + Device - - - - false - - - - 240 - 0 - - - - - + - false + true - 90 - 30 - - - - - 160 - 16777215 + 0 + 20 @@ -147,23 +45,48 @@ - - - - Qt::Horizontal + + + + false - - - 0 - 0 - + + + + + + Output - + + + + + + false + + + + + + + false + + + + + + + Engine + + + + false + 0 @@ -180,7 +103,7 @@ - ALSA plugin + ALSA plugin: @@ -204,6 +127,16 @@ + + + + false + + + pcm + + + @@ -227,6 +160,82 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Upmix / downmix to + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 20 + + + 2 + + + + + + + channels + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + @@ -689,7 +698,10 @@ combobox_device radiobutton_alsa_hw radiobutton_alsa_plughw + radiobutton_alsa_pcm checkbox_volume_control + checkbox_channels + spinbox_channels spinbox_bufferduration spinbox_low_watermark spinbox_high_watermark @@ -716,12 +728,12 @@ setEnabled(bool) - 89 - 259 + 110 + 465 - 143 - 285 + 164 + 573