mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-09-08 22:36:40 -04:00
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.
203 lines
5.3 KiB
C++
203 lines
5.3 KiB
C++
#include "soundin.h"
|
|
|
|
#include <cstdlib>
|
|
#include <cmath>
|
|
#include <QAudioDeviceInfo>
|
|
#include <QAudioFormat>
|
|
#include <QAudioInput>
|
|
#include <QSysInfo>
|
|
#include <QDebug>
|
|
|
|
#include "moc_soundin.cpp"
|
|
|
|
bool SoundInput::checkStream ()
|
|
{
|
|
bool result (false);
|
|
if (m_stream)
|
|
{
|
|
switch (m_stream->error ())
|
|
{
|
|
case QAudio::OpenError:
|
|
Q_EMIT error (tr ("An error opening the audio input device has occurred."));
|
|
break;
|
|
|
|
case QAudio::IOError:
|
|
Q_EMIT error (tr ("An error occurred during read from the audio input device."));
|
|
break;
|
|
|
|
case QAudio::UnderrunError:
|
|
Q_EMIT error (tr ("Audio data not being fed to the audio input device fast enough."));
|
|
break;
|
|
|
|
case QAudio::FatalError:
|
|
Q_EMIT error (tr ("Non-recoverable error, audio input device not usable at this time."));
|
|
break;
|
|
|
|
case QAudio::NoError:
|
|
result = true;
|
|
break;
|
|
}
|
|
if (!result)
|
|
{
|
|
stop ();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, AudioDevice * sink
|
|
, unsigned downSampleFactor, AudioDevice::Channel channel)
|
|
{
|
|
Q_ASSERT (sink);
|
|
|
|
stop ();
|
|
|
|
m_sink = sink;
|
|
|
|
QAudioFormat format (device.preferredFormat());
|
|
// qDebug () << "Preferred audio input format:" << format;
|
|
format.setChannelCount (AudioDevice::Mono == channel ? 1 : 2);
|
|
format.setCodec ("audio/pcm");
|
|
format.setSampleRate (12000 * downSampleFactor);
|
|
format.setSampleType (QAudioFormat::SignedInt);
|
|
format.setSampleSize (16);
|
|
format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
|
|
if (!format.isValid ())
|
|
{
|
|
Q_EMIT error (tr ("Requested input audio format is not valid."));
|
|
return;
|
|
}
|
|
else if (!device.isFormatSupported (format))
|
|
{
|
|
// qDebug () << "Nearest supported audio format:" << device.nearestFormat (format);
|
|
Q_EMIT error (tr ("Requested input audio format is not supported on device."));
|
|
return;
|
|
}
|
|
// qDebug () << "Selected audio input format:" << format;
|
|
|
|
m_stream.reset (new QAudioInput {device, format});
|
|
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
|
|
// size for period size other platforms seem to optimize themselves
|
|
#if defined (Q_OS_WIN)
|
|
m_stream->setBufferSize (m_stream->format ().bytesForFrames (framesPerBuffer * 5));
|
|
#else
|
|
Q_UNUSED (framesPerBuffer);
|
|
#endif
|
|
if (m_sink->initialize (QIODevice::WriteOnly, channel))
|
|
{
|
|
m_stream->start (sink);
|
|
checkStream ();
|
|
cummulative_lost_usec_ = -1;
|
|
//qDebug () << "SoundIn selected buffer size (bytes):" << m_stream->bufferSize () << "peirod size:" << m_stream->periodSize ();
|
|
}
|
|
else
|
|
{
|
|
Q_EMIT error (tr ("Failed to initialize audio sink device"));
|
|
}
|
|
}
|
|
|
|
void SoundInput::suspend ()
|
|
{
|
|
if (m_stream)
|
|
{
|
|
m_stream->suspend ();
|
|
checkStream ();
|
|
}
|
|
}
|
|
|
|
void SoundInput::resume ()
|
|
{
|
|
// qDebug() << "Resume" << fmod(0.001*QDateTime::currentMSecsSinceEpoch(),6.0);
|
|
if (m_sink)
|
|
{
|
|
m_sink->reset ();
|
|
}
|
|
|
|
if (m_stream)
|
|
{
|
|
m_stream->resume ();
|
|
checkStream ();
|
|
}
|
|
}
|
|
|
|
void SoundInput::handleStateChanged (QAudio::State newState)
|
|
{
|
|
switch (newState)
|
|
{
|
|
case QAudio::IdleState:
|
|
Q_EMIT status (tr ("Idle"));
|
|
break;
|
|
|
|
case QAudio::ActiveState:
|
|
reset (false);
|
|
Q_EMIT status (tr ("Receiving"));
|
|
break;
|
|
|
|
case QAudio::SuspendedState:
|
|
Q_EMIT status (tr ("Suspended"));
|
|
break;
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK (5, 10, 0)
|
|
case QAudio::InterruptedState:
|
|
Q_EMIT status (tr ("Interrupted"));
|
|
break;
|
|
#endif
|
|
|
|
case QAudio::StoppedState:
|
|
if (!checkStream ())
|
|
{
|
|
Q_EMIT status (tr ("Error"));
|
|
}
|
|
else
|
|
{
|
|
Q_EMIT status (tr ("Stopped"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SoundInput::reset (bool report_dropped_frames)
|
|
{
|
|
if (m_stream)
|
|
{
|
|
auto elapsed_usecs = m_stream->elapsedUSecs ();
|
|
while (std::abs (elapsed_usecs - m_stream->processedUSecs ())
|
|
> 24 * 60 * 60 * 500000ll) // half day
|
|
{
|
|
// QAudioInput::elapsedUSecs() wraps after 24 hours
|
|
elapsed_usecs += 24 * 60 * 60 * 1000000ll;
|
|
}
|
|
// don't report first time as we don't yet known latency
|
|
if (cummulative_lost_usec_ != std::numeric_limits<qint64>::min () && report_dropped_frames)
|
|
{
|
|
auto lost_usec = elapsed_usecs - m_stream->processedUSecs () - cummulative_lost_usec_;
|
|
Q_EMIT dropped_frames (m_stream->format ().framesForDuration (lost_usec), lost_usec);
|
|
//qDebug () << "SoundInput::reset: frames dropped:" << m_stream->format ().framesForDuration (lost_usec) << "sec:" << lost_usec / 1.e6;
|
|
}
|
|
cummulative_lost_usec_ = elapsed_usecs - m_stream->processedUSecs ();
|
|
}
|
|
}
|
|
|
|
void SoundInput::stop()
|
|
{
|
|
if (m_stream)
|
|
{
|
|
m_stream->stop ();
|
|
}
|
|
m_stream.reset ();
|
|
}
|
|
|
|
SoundInput::~SoundInput ()
|
|
{
|
|
stop ();
|
|
}
|