From 542ffe83112c16e9a56fe946c496c8b4973683ff Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 20 Sep 2020 18:20:16 +0100 Subject: [PATCH] Improve audio device handling and error recovery where possible audio devices that disappear are not forgotten until the user selects another device, this should allow temporarily missing devices or forgetting to switch on devices before starting WSJT-X to be handled more cleanly. If all else fails, visiting the Settings dialog and clicking OK should get things going again. Note that we still do not have a reliable way of detecting failed audio out devices, in that case selecting another device and then returning to the original should work. Enumerating audio devices is expensive and on Linux may take many seconds per device. To avoid lengthy blocking behaviour until it is absolutely necessary, audio devices are not enumerated until one of the "Settings->Audio" device drop-down lists is opened. Elsewhere when devices must be discovered the enumeration stops as soon as the configured device is discovered. A status bar message is posted when audio devices are being enumerated as a reminder that the UI may block while this is happening. The message box warning about unaccounted-for input audio samples now only triggers when >5 seconds of audio appears to be missing or over provided. Hopefully this will make the warning less annoying for those that are using audio sources with high and/or variable latencies. A status bar message is still posted for any amount of audio input samples unaccounted for >1/5 second, this message appearing a lot should be considered as notification that there is a problem with the audio sub-system, system load is too high, or time synchronization is stepping the PC clock rather than adjusting the frequency to maintain monotonic clock ticks. --- Audio/AudioDevice.cpp | 1 - Audio/AudioDevice.hpp | 4 +- Audio/soundin.cpp | 32 +++--- Audio/soundin.h | 3 +- Audio/soundout.cpp | 54 +++++----- Audio/soundout.h | 9 +- CMakeLists.txt | 1 + Configuration.cpp | 185 +++++++++++++++++++++-------------- Configuration.hpp | 2 + Configuration.ui | 143 ++++++++++++++------------- Detector/Detector.cpp | 3 +- Modulator/Modulator.cpp | 2 - widgets/LazyFillComboBox.cpp | 3 + widgets/LazyFillComboBox.hpp | 40 ++++++++ widgets/mainwindow.cpp | 8 +- 15 files changed, 291 insertions(+), 199 deletions(-) create mode 100644 widgets/LazyFillComboBox.cpp create mode 100644 widgets/LazyFillComboBox.hpp diff --git a/Audio/AudioDevice.cpp b/Audio/AudioDevice.cpp index 2b8525826..16fec3148 100644 --- a/Audio/AudioDevice.cpp +++ b/Audio/AudioDevice.cpp @@ -7,4 +7,3 @@ bool AudioDevice::initialize (OpenMode mode, Channel channel) // open and ensure we are unbuffered if possible return QIODevice::open (mode | QIODevice::Unbuffered); } - diff --git a/Audio/AudioDevice.hpp b/Audio/AudioDevice.hpp index 3ab19f0f4..2d47af89b 100644 --- a/Audio/AudioDevice.hpp +++ b/Audio/AudioDevice.hpp @@ -33,8 +33,8 @@ public: Channel channel () const {return m_channel;} protected: - AudioDevice (QObject * parent = 0) - : QIODevice (parent) + AudioDevice (QObject * parent = nullptr) + : QIODevice {parent} { } diff --git a/Audio/soundin.cpp b/Audio/soundin.cpp index 7dc940406..00129311a 100644 --- a/Audio/soundin.cpp +++ b/Audio/soundin.cpp @@ -10,11 +10,9 @@ #include "moc_soundin.cpp" -bool SoundInput::audioError () const +bool SoundInput::checkStream () { - bool result (true); - - Q_ASSERT_X (m_stream, "SoundInput", "programming error"); + bool result (false); if (m_stream) { switch (m_stream->error ()) @@ -36,9 +34,13 @@ bool SoundInput::audioError () const break; case QAudio::NoError: - result = false; + result = true; break; } + if (!result) + { + stop (); + } } return result; } @@ -74,12 +76,13 @@ void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, Audi // qDebug () << "Selected audio input format:" << format; m_stream.reset (new QAudioInput {device, format}); - if (audioError ()) + if (!checkStream ()) { return; } connect (m_stream.data(), &QAudioInput::stateChanged, this, &SoundInput::handleStateChanged); + connect (m_stream.data(), &QAudioInput::notify, [this] () {checkStream ();}); //qDebug () << "SoundIn default buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize (); // the Windows MME version of QAudioInput uses 1/5 of the buffer @@ -89,10 +92,10 @@ void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, Audi #else Q_UNUSED (framesPerBuffer); #endif - if (sink->initialize (QIODevice::WriteOnly, channel)) + if (m_sink->initialize (QIODevice::WriteOnly, channel)) { m_stream->start (sink); - audioError (); + checkStream (); cummulative_lost_usec_ = -1; //qDebug () << "SoundIn selected buffer size (bytes):" << m_stream->bufferSize () << "peirod size:" << m_stream->periodSize (); } @@ -107,7 +110,7 @@ void SoundInput::suspend () if (m_stream) { m_stream->suspend (); - audioError (); + checkStream (); } } @@ -122,14 +125,12 @@ void SoundInput::resume () if (m_stream) { m_stream->resume (); - audioError (); + checkStream (); } } void SoundInput::handleStateChanged (QAudio::State newState) { - //qDebug () << "SoundInput::handleStateChanged: newState:" << newState; - switch (newState) { case QAudio::IdleState: @@ -152,7 +153,7 @@ void SoundInput::handleStateChanged (QAudio::State newState) #endif case QAudio::StoppedState: - if (audioError ()) + if (!checkStream ()) { Q_EMIT status (tr ("Error")); } @@ -193,11 +194,6 @@ void SoundInput::stop() m_stream->stop (); } m_stream.reset (); - - if (m_sink) - { - m_sink->close (); - } } SoundInput::~SoundInput () diff --git a/Audio/soundin.h b/Audio/soundin.h index a126fbbd1..c35b3d7d8 100644 --- a/Audio/soundin.h +++ b/Audio/soundin.h @@ -24,7 +24,6 @@ class SoundInput public: SoundInput (QObject * parent = nullptr) : QObject {parent} - , m_sink {nullptr} , cummulative_lost_usec_ {std::numeric_limits::min ()} { } @@ -47,7 +46,7 @@ private: // used internally Q_SLOT void handleStateChanged (QAudio::State); - bool audioError () const; + bool checkStream (); QScopedPointer m_stream; QPointer m_sink; diff --git a/Audio/soundout.cpp b/Audio/soundout.cpp index 40eff6012..5e89de147 100644 --- a/Audio/soundout.cpp +++ b/Audio/soundout.cpp @@ -7,11 +7,13 @@ #include #include +#include "Audio/AudioDevice.hpp" + #include "moc_soundout.cpp" -bool SoundOutput::audioError () const +bool SoundOutput::checkStream () const { - bool result (true); + bool result {false}; Q_ASSERT_X (m_stream, "SoundOutput", "programming error"); if (m_stream) { @@ -34,7 +36,7 @@ bool SoundOutput::audioError () const break; case QAudio::NoError: - result = false; + result = true; break; } } @@ -43,15 +45,19 @@ bool SoundOutput::audioError () const void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered) { - if (!device.isNull ()) + Q_ASSERT (0 < channels && channels < 3); + m_device = device; + m_channels = channels; + m_framesBuffered = frames_buffered; +} + +void SoundOutput::restart (AudioDevice * source) +{ + if (!m_device.isNull ()) { - Q_ASSERT (0 < channels && channels < 3); - - m_framesBuffered = frames_buffered; - - QAudioFormat format (device.preferredFormat ()); + QAudioFormat format (m_device.preferredFormat ()); // qDebug () << "Preferred audio output format:" << format; - format.setChannelCount (channels); + format.setChannelCount (m_channels); format.setCodec ("audio/pcm"); format.setSampleRate (48000); format.setSampleType (QAudioFormat::SignedInt); @@ -61,29 +67,25 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, { Q_EMIT error (tr ("Requested output audio format is not valid.")); } - else if (!device.isFormatSupported (format)) + else if (!m_device.isFormatSupported (format)) { Q_EMIT error (tr ("Requested output audio format is not supported on device.")); } else { // qDebug () << "Selected audio output format:" << format; - - m_stream.reset (new QAudioOutput (device, format)); - audioError (); + m_stream.reset (new QAudioOutput (m_device, format)); + checkStream (); m_stream->setVolume (m_volume); - m_stream->setNotifyInterval(100); + m_stream->setNotifyInterval(1000); error_ = false; connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged); + connect (m_stream.data(), &QAudioOutput::notify, [this] () {checkStream ();}); // qDebug() << "A" << m_volume << m_stream->notifyInterval(); } } -} - -void SoundOutput::restart (QIODevice * source) -{ if (!m_stream) { if (!error_) @@ -109,6 +111,7 @@ void SoundOutput::restart (QIODevice * source) #endif } m_stream->setCategory ("production"); + m_source = source; m_stream->start (source); // qDebug () << "SoundOut selected buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize (); } @@ -118,7 +121,7 @@ void SoundOutput::suspend () if (m_stream && QAudio::ActiveState == m_stream->state ()) { m_stream->suspend (); - audioError (); + checkStream (); } } @@ -127,7 +130,7 @@ void SoundOutput::resume () if (m_stream && QAudio::SuspendedState == m_stream->state ()) { m_stream->resume (); - audioError (); + checkStream (); } } @@ -136,7 +139,7 @@ void SoundOutput::reset () if (m_stream) { m_stream->reset (); - audioError (); + checkStream (); } } @@ -144,9 +147,10 @@ void SoundOutput::stop () { if (m_stream) { + m_stream->reset (); m_stream->stop (); - audioError (); } + m_stream.reset (); } qreal SoundOutput::attenuation () const @@ -176,8 +180,6 @@ void SoundOutput::resetAttenuation () void SoundOutput::handleStateChanged (QAudio::State newState) { - // qDebug () << "SoundOutput::handleStateChanged: newState:" << newState; - switch (newState) { case QAudio::IdleState: @@ -199,7 +201,7 @@ void SoundOutput::handleStateChanged (QAudio::State newState) #endif case QAudio::StoppedState: - if (audioError ()) + if (!checkStream ()) { Q_EMIT status (tr ("Error")); } diff --git a/Audio/soundout.h b/Audio/soundout.h index c46e1563f..95efaeb15 100644 --- a/Audio/soundout.h +++ b/Audio/soundout.h @@ -6,7 +6,9 @@ #include #include #include +#include +class AudioDevice; class QAudioDeviceInfo; // An instance of this sends audio data to a specified soundcard. @@ -28,7 +30,7 @@ public: public Q_SLOTS: void setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered = 0); - void restart (QIODevice *); + void restart (AudioDevice *); void suspend (); void resume (); void reset (); @@ -41,13 +43,16 @@ Q_SIGNALS: void status (QString message) const; private: - bool audioError () const; + bool checkStream () const; private Q_SLOTS: void handleStateChanged (QAudio::State); private: + QAudioDeviceInfo m_device; + unsigned m_channels; QScopedPointer m_stream; + QPointer m_source; int m_framesBuffered; qreal m_volume; bool error_; diff --git a/CMakeLists.txt b/CMakeLists.txt index a2a31b388..f24abbbeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,7 @@ set (wsjt_qt_CXXSRCS logbook/WorkedBefore.cpp logbook/Multiplier.cpp Network/NetworkAccessManager.cpp + widgets/LazyFillComboBox.cpp ) set (wsjt_qtmm_CXXSRCS diff --git a/Configuration.cpp b/Configuration.cpp index 91f9b1926..65b510b8e 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -188,6 +188,7 @@ #include "Network/LotWUsers.hpp" #include "models/DecodeHighlightingModel.hpp" #include "logbook/logbook.h" +#include "widgets/LazyFillComboBox.hpp" #include "ui_Configuration.h" #include "moc_Configuration.cpp" @@ -432,7 +433,6 @@ private: void read_settings (); void write_settings (); - Q_SLOT void lazy_models_load (int); void find_audio_devices (); QAudioDeviceInfo find_audio_device (QAudio::Mode, QComboBox *, QString const& device_name); void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); @@ -653,9 +653,13 @@ private: bool pwrBandTuneMemory_; QAudioDeviceInfo audio_input_device_; + QAudioDeviceInfo next_audio_input_device_; AudioDevice::Channel audio_input_channel_; + AudioDevice::Channel next_audio_input_channel_; QAudioDeviceInfo audio_output_device_; + QAudioDeviceInfo next_audio_output_device_; AudioDevice::Channel audio_output_channel_; + AudioDevice::Channel next_audio_output_channel_; friend class Configuration; }; @@ -856,6 +860,16 @@ void Configuration::sync_transceiver (bool force_signal, bool enforce_mode_and_s } } +void Configuration::invalidate_audio_input_device (QString /* error */) +{ + m_->audio_input_device_ = QAudioDeviceInfo {}; +} + +void Configuration::invalidate_audio_output_device (QString /* error */) +{ + m_->audio_output_device_ = QAudioDeviceInfo {}; +} + bool Configuration::valid_n1mm_info () const { // do very rudimentary checking on the n1mm server name and port number. @@ -1029,6 +1043,17 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // this must be done after the default paths above are set read_settings (); + connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_); + update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); + ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_); + }); + connect (ui_->sound_output_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &next_audio_output_device_); + update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); + ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); + }); + // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { ui_->LotW_CSV_fetch_push_button->setEnabled (true); @@ -1102,7 +1127,6 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // // setup hooks to keep audio channels aligned with devices // - connect (ui_->configuration_tabs, &QTabWidget::currentChanged, this, &Configuration::impl::lazy_models_load); { using namespace std; using namespace std::placeholders; @@ -1199,6 +1223,11 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network enumerate_rigs (); initialize_models (); + audio_input_device_ = next_audio_input_device_; + audio_input_channel_ = next_audio_input_channel_; + audio_output_device_ = next_audio_output_device_; + audio_output_channel_ = next_audio_output_channel_; + transceiver_thread_ = new QThread {this}; transceiver_thread_->start (); } @@ -1210,31 +1239,14 @@ Configuration::impl::~impl () write_settings (); } -void Configuration::impl::lazy_models_load (int current_tab_index) -{ - switch (current_tab_index) - { - case 2: // Audio - // - // load combo boxes with audio setup choices - // - load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &audio_input_device_); - load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &audio_output_device_); - - update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); - update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); - - ui_->sound_input_channel_combo_box->setCurrentIndex (audio_input_channel_); - ui_->sound_output_channel_combo_box->setCurrentIndex (audio_output_channel_); - break; - - default: - break; - } -} - void Configuration::impl::initialize_models () { + next_audio_input_device_ = audio_input_device_; + next_audio_input_channel_ = audio_input_channel_; + next_audio_output_device_ = audio_output_device_; + next_audio_output_channel_ = audio_output_channel_; + restart_sound_input_device_ = false; + restart_sound_output_device_ = false; { SettingsGroup g {settings_, "Configuration"}; find_audio_devices (); @@ -1413,8 +1425,6 @@ void Configuration::impl::read_settings () save_directory_.setPath (settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ()); azel_directory_.setPath (settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ()); - find_audio_devices (); - type_2_msg_gen_ = settings_->value ("Type2MsgGen", QVariant::fromValue (Configuration::type_2_msg_3_full)).value (); monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool (); @@ -1522,24 +1532,24 @@ void Configuration::impl::find_audio_devices () // retrieve audio input device // auto saved_name = settings_->value ("SoundInName").toString (); - if (audio_input_device_.deviceName () != saved_name) + if (next_audio_input_device_.deviceName () != saved_name || next_audio_input_device_.isNull ()) { - audio_input_device_ = find_audio_device (QAudio::AudioInput, ui_->sound_input_combo_box, saved_name); - audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ()); + next_audio_input_device_ = find_audio_device (QAudio::AudioInput, ui_->sound_input_combo_box, saved_name); + next_audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ()); update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); - ui_->sound_input_channel_combo_box->setCurrentIndex (audio_input_channel_); + ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_); } // // retrieve audio output device // saved_name = settings_->value("SoundOutName").toString(); - if (audio_output_device_.deviceName () != saved_name) + if (next_audio_output_device_.deviceName () != saved_name || next_audio_output_device_.isNull ()) { - audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ()); - audio_output_device_ = find_audio_device (QAudio::AudioOutput, ui_->sound_output_combo_box, saved_name); + next_audio_output_device_ = find_audio_device (QAudio::AudioOutput, ui_->sound_output_combo_box, saved_name); + next_audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ()); update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); - ui_->sound_output_channel_combo_box->setCurrentIndex (audio_output_channel_); + ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); } } @@ -1562,10 +1572,16 @@ void Configuration::impl::write_settings () settings_->setValue ("PTTport", rig_params_.ptt_port); settings_->setValue ("SaveDir", save_directory_.absolutePath ()); settings_->setValue ("AzElDir", azel_directory_.absolutePath ()); - settings_->setValue ("SoundInName", audio_input_device_.deviceName ()); - settings_->setValue ("SoundOutName", audio_output_device_.deviceName ()); - settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_)); - settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_)); + if (!audio_input_device_.isNull ()) + { + settings_->setValue ("SoundInName", audio_input_device_.deviceName ()); + settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_)); + } + if (!audio_output_device_.isNull ()) + { + settings_->setValue ("SoundOutName", audio_output_device_.deviceName ()); + settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_)); + } settings_->setValue ("Type2MsgGen", QVariant::fromValue (type_2_msg_gen_)); settings_->setValue ("MonitorOFF", monitor_off_at_startup_); settings_->setValue ("MonitorLastUsed", monitor_last_used_); @@ -1770,7 +1786,7 @@ void Configuration::impl::set_rig_invariants () bool Configuration::impl::validate () { if (ui_->sound_input_combo_box->currentIndex () < 0 - && audio_input_device_.isNull ()) + && next_audio_input_device_.isNull ()) { find_tab (ui_->sound_input_combo_box); MessageBox::critical_message (this, tr ("Invalid audio input device")); @@ -1778,7 +1794,7 @@ bool Configuration::impl::validate () } if (ui_->sound_input_channel_combo_box->currentIndex () < 0 - && audio_input_device_.isNull ()) + && next_audio_input_device_.isNull ()) { find_tab (ui_->sound_input_combo_box); MessageBox::critical_message (this, tr ("Invalid audio input device")); @@ -1786,7 +1802,7 @@ bool Configuration::impl::validate () } if (ui_->sound_output_combo_box->currentIndex () < 0 - && audio_output_device_.isNull ()) + && next_audio_output_device_.isNull ()) { find_tab (ui_->sound_output_combo_box); MessageBox::information_message (this, tr ("Invalid audio output device")); @@ -1842,7 +1858,6 @@ int Configuration::impl::exec () rig_changed_ = false; initialize_models (); - lazy_models_load (ui_->configuration_tabs->currentIndex ()); return QDialog::exec(); } @@ -1941,39 +1956,60 @@ void Configuration::impl::accept () // related configuration parameters rig_is_dummy_ = TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name; - // Check to see whether SoundInThread must be restarted, - // and save user parameters. { auto const& selected_device = ui_->sound_input_combo_box->currentData ().value ().first; - if (selected_device != audio_input_device_) + if (selected_device != next_audio_input_device_) { - audio_input_device_ = selected_device; - restart_sound_input_device_ = true; + next_audio_input_device_ = selected_device; } } { auto const& selected_device = ui_->sound_output_combo_box->currentData ().value ().first; - if (selected_device != audio_output_device_) + if (selected_device != next_audio_output_device_) { - audio_output_device_ = selected_device; - restart_sound_output_device_ = true; + next_audio_output_device_ = selected_device; } } - if (audio_input_channel_ != static_cast (ui_->sound_input_channel_combo_box->currentIndex ())) + if (next_audio_input_channel_ != static_cast (ui_->sound_input_channel_combo_box->currentIndex ())) { - audio_input_channel_ = static_cast (ui_->sound_input_channel_combo_box->currentIndex ()); + next_audio_input_channel_ = static_cast (ui_->sound_input_channel_combo_box->currentIndex ()); + } + Q_ASSERT (next_audio_input_channel_ <= AudioDevice::Right); + + if (next_audio_output_channel_ != static_cast (ui_->sound_output_channel_combo_box->currentIndex ())) + { + next_audio_output_channel_ = static_cast (ui_->sound_output_channel_combo_box->currentIndex ()); + } + Q_ASSERT (next_audio_output_channel_ <= AudioDevice::Both); + + if (audio_input_device_ != next_audio_input_device_ || next_audio_input_device_.isNull ()) + { + audio_input_device_ = next_audio_input_device_; restart_sound_input_device_ = true; } - Q_ASSERT (audio_input_channel_ <= AudioDevice::Right); - - if (audio_output_channel_ != static_cast (ui_->sound_output_channel_combo_box->currentIndex ())) + if (audio_input_channel_ != next_audio_input_channel_) { - audio_output_channel_ = static_cast (ui_->sound_output_channel_combo_box->currentIndex ()); + audio_input_channel_ = next_audio_input_channel_; + restart_sound_input_device_ = true; + } + if (audio_output_device_ != next_audio_output_device_ || next_audio_output_device_.isNull ()) + { + audio_output_device_ = next_audio_output_device_; restart_sound_output_device_ = true; } - Q_ASSERT (audio_output_channel_ <= AudioDevice::Both); + if (audio_output_channel_ != next_audio_output_channel_) + { + audio_output_channel_ = next_audio_output_channel_; + restart_sound_output_device_ = true; + } + // qDebug () << "Configure::accept: audio i/p:" << audio_input_device_.deviceName () + // << "chan:" << audio_input_channel_ + // << "o/p:" << audio_output_device_.deviceName () + // << "chan:" << audio_output_channel_ + // << "reset i/p:" << restart_sound_input_device_ + // << "reset o/p:" << restart_sound_output_device_; my_callsign_ = ui_->callsign_line_edit->text (); my_grid_ = ui_->grid_line_edit->text (); @@ -2112,6 +2148,13 @@ void Configuration::impl::reject () } } + // qDebug () << "Configure::reject: audio i/p:" << audio_input_device_.deviceName () + // << "chan:" << audio_input_channel_ + // << "o/p:" << audio_output_device_.deviceName () + // << "chan:" << audio_output_channel_ + // << "reset i/p:" << restart_sound_input_device_ + // << "reset o/p:" << restart_sound_output_device_; + QDialog::reject (); } @@ -2772,27 +2815,25 @@ QAudioDeviceInfo Configuration::impl::find_audio_device (QAudio::Mode mode, QCom if (device_name.size ()) { - combo_box->clear (); - - int current_index = -1; + Q_EMIT self_->enumerating_audio_devices (); auto const& devices = QAudioDeviceInfo::availableDevices (mode); Q_FOREACH (auto const& p, devices) { - - // convert supported channel counts into something we can store in the item model - QList channel_counts; - auto scc = p.supportedChannelCounts (); - copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); - - combo_box->addItem (p.deviceName (), QVariant::fromValue (audio_info_type {p, channel_counts})); + qDebug () << "Configuration::impl::find_audio_device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); if (p.deviceName () == device_name) { - current_index = combo_box->count () - 1; - combo_box->setCurrentIndex (current_index); + // convert supported channel counts into something we can store in the item model + QList channel_counts; + auto scc = p.supportedChannelCounts (); + copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); + combo_box->insertItem (0, device_name, QVariant::fromValue (audio_info_type {p, channel_counts})); + combo_box->setCurrentIndex (0); return p; } } - combo_box->setCurrentIndex (current_index); + // insert a place holder for the not found device + combo_box->insertItem (0, device_name + " (" + tr ("Not found", "audio device missing") + ")", QVariant::fromValue (audio_info_type {})); + combo_box->setCurrentIndex (0); } return {}; } @@ -2811,7 +2852,7 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com auto const& devices = QAudioDeviceInfo::availableDevices (mode); Q_FOREACH (auto const& p, devices) { - // qDebug () << "Audio device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); + // qDebug () << "Configuration::impl::load_audio_devices: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); // convert supported channel counts into something we can store in the item model QList channel_counts; diff --git a/Configuration.hpp b/Configuration.hpp index 45fafe59b..8594a4a6f 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -260,6 +260,8 @@ public: // i.e. the transceiver is ready for use. Q_SLOT void sync_transceiver (bool force_signal = false, bool enforce_mode_and_split = false); + Q_SLOT void invalidate_audio_input_device (QString error); + Q_SLOT void invalidate_audio_output_device (QString error); // // These signals indicate a font has been selected and accepted for diff --git a/Configuration.ui b/Configuration.ui index 4c8035754..d22032d53 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -7,7 +7,7 @@ 0 0 554 - 557 + 556 @@ -1364,71 +1364,8 @@ radio interface behave as expected. Soundcard - - - - Ou&tput: - - - sound_output_combo_box - - - - - - - &Input: - - - sound_input_combo_box - - - - - - - Select the audio channel used for transmission. -Unless you have multiple radios connected on different -channels; then you will usually want to select mono or -both here. - - - - Mono - - - - - Left - - - - - Right - - - - - Both - - - - - - - - - 1 - 0 - - - - Select the audio CODEC to use for receiving. - - - - + 1 @@ -1471,6 +1408,69 @@ transmitting periods. + + + + + 1 + 0 + + + + Select the audio CODEC to use for receiving. + + + + + + + Ou&tput: + + + sound_output_combo_box + + + + + + + Select the audio channel used for transmission. +Unless you have multiple radios connected on different +channels; then you will usually want to select mono or +both here. + + + + Mono + + + + + Left + + + + + Right + + + + + Both + + + + + + + + &Input: + + + sound_input_combo_box + + + @@ -2997,6 +2997,11 @@ Right click for insert and delete options. QListView
widgets/DecodeHighlightingListView.hpp
+ + LazyFillComboBox + QComboBox +
widgets/LazyFillComboBox.hpp
+
configuration_tabs @@ -3187,13 +3192,13 @@ Right click for insert and delete options. + + + - - - - + diff --git a/Detector/Detector.cpp b/Detector/Detector.cpp index d70dd42ff..164db2ab2 100644 --- a/Detector/Detector.cpp +++ b/Detector/Detector.cpp @@ -36,7 +36,7 @@ void Detector::setBlockSize (unsigned n) bool Detector::reset () { clear (); - // don't call base call reset because it calls seek(0) which causes + // don't call base class reset because it calls seek(0) which causes // a warning return isOpen (); } @@ -56,7 +56,6 @@ void Detector::clear () qint64 Detector::writeData (char const * data, qint64 maxSize) { - //qDebug () << "Detector::writeData: size:" << maxSize; static unsigned mstr0=999999; qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000; unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time diff --git a/Modulator/Modulator.cpp b/Modulator/Modulator.cpp index df365c507..96963029f 100644 --- a/Modulator/Modulator.cpp +++ b/Modulator/Modulator.cpp @@ -149,8 +149,6 @@ void Modulator::close () qint64 Modulator::readData (char * data, qint64 maxSize) { - // qDebug () << "readData: maxSize:" << maxSize; - double toneFrequency=1500.0; if(m_nsps==6) { toneFrequency=1000.0; diff --git a/widgets/LazyFillComboBox.cpp b/widgets/LazyFillComboBox.cpp new file mode 100644 index 000000000..08968f357 --- /dev/null +++ b/widgets/LazyFillComboBox.cpp @@ -0,0 +1,3 @@ +#include "LazyFillComboBox.hpp" + +#include "moc_LazyFillComboBox.cpp" diff --git a/widgets/LazyFillComboBox.hpp b/widgets/LazyFillComboBox.hpp new file mode 100644 index 000000000..7d9052673 --- /dev/null +++ b/widgets/LazyFillComboBox.hpp @@ -0,0 +1,40 @@ +#ifndef LAZY_FILL_COMBO_BOX_HPP__ +#define LAZY_FILL_COMBO_BOX_HPP__ + +#include + +class QWidget; + +// +// Class LazyFillComboBox +// +// QComboBox derivative that signals show and hide of the pop up list. +// +class LazyFillComboBox final + : public QComboBox +{ + Q_OBJECT + +public: + Q_SIGNAL void about_to_show_popup (); + Q_SIGNAL void popup_hidden (); + + explicit LazyFillComboBox (QWidget * parent = nullptr) + : QComboBox {parent} + { + } + + void showPopup () override + { + Q_EMIT about_to_show_popup (); + QComboBox::showPopup (); + } + + void hidePopup () override + { + QComboBox::hidePopup (); + Q_EMIT popup_hidden (); + } +}; + +#endif diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 8b27440c5..d8461bd67 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -454,6 +454,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // hook up sound output stream slots & signals and disposal connect (this, &MainWindow::initializeAudioOutputStream, m_soundOutput, &SoundOutput::setFormat); connect (m_soundOutput, &SoundOutput::error, this, &MainWindow::showSoundOutError); + connect (m_soundOutput, &SoundOutput::error, &m_config, &Configuration::invalidate_audio_output_device); // connect (m_soundOutput, &SoundOutput::status, this, &MainWindow::showStatusMessage); connect (this, &MainWindow::outAttenuationChanged, m_soundOutput, &SoundOutput::setAttenuation); connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater); @@ -472,13 +473,14 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (this, &MainWindow::reset_audio_input_stream, m_soundInput, &SoundInput::reset); connect (this, &MainWindow::finished, m_soundInput, &SoundInput::stop); connect(m_soundInput, &SoundInput::error, this, &MainWindow::showSoundInError); + connect(m_soundInput, &SoundInput::error, &m_config, &Configuration::invalidate_audio_input_device); // connect(m_soundInput, &SoundInput::status, this, &MainWindow::showStatusMessage); connect (m_soundInput, &SoundInput::dropped_frames, this, [this] (qint32 dropped_frames, qint64 usec) { if (dropped_frames > 48000 / 5) // 1/5 second { showStatusMessage (tr ("%1 (%2 sec) audio frames dropped").arg (dropped_frames).arg (usec / 1.e6, 5, 'f', 3)); } - if (dropped_frames > 48000) // 1 second + if (dropped_frames > 5 * 48000) // seconds { auto period = qt_truncate_date_time_to (QDateTime::currentDateTimeUtc ().addMSecs (-m_TRperiod / 2.), m_TRperiod * 1e3); MessageBox::warning_message (this @@ -1824,14 +1826,14 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog m_psk_Reporter.sendReport (true); } - if(m_config.restart_audio_input ()) { + if(m_config.restart_audio_input () && !m_config.audio_input_device ().isNull ()) { Q_EMIT startAudioInputStream (m_config.audio_input_device () , rx_chunk_size * m_downSampleFactor , m_detector, m_downSampleFactor , m_config.audio_input_channel ()); } - if(m_config.restart_audio_output ()) { + if(m_config.restart_audio_output () && !m_config.audio_output_device ().isNull ()) { Q_EMIT initializeAudioOutputStream (m_config.audio_output_device () , AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2 , tx_audio_buffer_size);