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.
This commit is contained in:
Bill Somerville 2020-09-20 18:20:16 +01:00
parent 1ab59a8d6b
commit 542ffe8311
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
15 changed files with 291 additions and 199 deletions

View File

@ -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);
}

View File

@ -33,8 +33,8 @@ public:
Channel channel () const {return m_channel;}
protected:
AudioDevice (QObject * parent = 0)
: QIODevice (parent)
AudioDevice (QObject * parent = nullptr)
: QIODevice {parent}
{
}

View File

@ -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 ()

View File

@ -24,7 +24,6 @@ class SoundInput
public:
SoundInput (QObject * parent = nullptr)
: QObject {parent}
, m_sink {nullptr}
, cummulative_lost_usec_ {std::numeric_limits<qint64>::min ()}
{
}
@ -47,7 +46,7 @@ private:
// used internally
Q_SLOT void handleStateChanged (QAudio::State);
bool audioError () const;
bool checkStream ();
QScopedPointer<QAudioInput> m_stream;
QPointer<AudioDevice> m_sink;

View File

@ -7,11 +7,13 @@
#include <qmath.h>
#include <QDebug>
#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"));
}

View File

@ -6,7 +6,9 @@
#include <QString>
#include <QAudioOutput>
#include <QAudioDeviceInfo>
#include <QPointer>
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<QAudioOutput> m_stream;
QPointer<AudioDevice> m_source;
int m_framesBuffered;
qreal m_volume;
bool error_;

View File

@ -293,6 +293,7 @@ set (wsjt_qt_CXXSRCS
logbook/WorkedBefore.cpp
logbook/Multiplier.cpp
Network/NetworkAccessManager.cpp
widgets/LazyFillComboBox.cpp
)
set (wsjt_qtmm_CXXSRCS

View File

@ -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<Configuration::Type2MsgGen> ();
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<audio_info_type> ().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<audio_info_type> ().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<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ()))
if (next_audio_input_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ()))
{
audio_input_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ());
next_audio_input_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ());
}
Q_ASSERT (next_audio_input_channel_ <= AudioDevice::Right);
if (next_audio_output_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ()))
{
next_audio_output_channel_ = static_cast<AudioDevice::Channel> (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<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ()))
if (audio_input_channel_ != next_audio_input_channel_)
{
audio_output_channel_ = static_cast<AudioDevice::Channel> (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<QVariant> 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<QVariant> 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<QVariant> channel_counts;

View File

@ -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

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>554</width>
<height>557</height>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
@ -1364,71 +1364,8 @@ radio interface behave as expected.</string>
<string>Soundcard</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0">
<widget class="QLabel" name="sound_output_label">
<property name="text">
<string>Ou&amp;tput:</string>
</property>
<property name="buddy">
<cstring>sound_output_combo_box</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="sound_input_label">
<property name="text">
<string>&amp;Input:</string>
</property>
<property name="buddy">
<cstring>sound_input_combo_box</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="sound_output_channel_combo_box">
<property name="toolTip">
<string>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.</string>
</property>
<item>
<property name="text">
<string>Mono</string>
</property>
</item>
<item>
<property name="text">
<string>Left</string>
</property>
</item>
<item>
<property name="text">
<string>Right</string>
</property>
</item>
<item>
<property name="text">
<string>Both</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="sound_input_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select the audio CODEC to use for receiving.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="sound_output_combo_box">
<widget class="LazyFillComboBox" name="sound_output_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
@ -1471,6 +1408,69 @@ transmitting periods.</string>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="LazyFillComboBox" name="sound_input_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select the audio CODEC to use for receiving.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sound_output_label">
<property name="text">
<string>Ou&amp;tput:</string>
</property>
<property name="buddy">
<cstring>sound_output_combo_box</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="sound_output_channel_combo_box">
<property name="toolTip">
<string>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.</string>
</property>
<item>
<property name="text">
<string>Mono</string>
</property>
</item>
<item>
<property name="text">
<string>Left</string>
</property>
</item>
<item>
<property name="text">
<string>Right</string>
</property>
</item>
<item>
<property name="text">
<string>Both</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="sound_input_label">
<property name="text">
<string>&amp;Input:</string>
</property>
<property name="buddy">
<cstring>sound_input_combo_box</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -2997,6 +2997,11 @@ Right click for insert and delete options.</string>
<extends>QListView</extends>
<header>widgets/DecodeHighlightingListView.hpp</header>
</customwidget>
<customwidget>
<class>LazyFillComboBox</class>
<extends>QComboBox</extends>
<header>widgets/LazyFillComboBox.hpp</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>configuration_tabs</tabstop>
@ -3187,13 +3192,13 @@ Right click for insert and delete options.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="split_mode_button_group"/>
</buttongroups>
</ui>

View File

@ -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

View File

@ -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;

View File

@ -0,0 +1,3 @@
#include "LazyFillComboBox.hpp"
#include "moc_LazyFillComboBox.cpp"

View File

@ -0,0 +1,40 @@
#ifndef LAZY_FILL_COMBO_BOX_HPP__
#define LAZY_FILL_COMBO_BOX_HPP__
#include <QComboBox>
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

View File

@ -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);