mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-09-19 11:46:34 -04:00
0cf14dfcc9
Fixed buffer sizes are used. Rx use s 3456 x 1st downsample rate x 5 audio frames of buffer space. On Windows this means that each chunk (periodSize()) delivered from the audio stream is our initial DSP processing chunk size, thus matching audio buffer latency exactly with WSJT-X's own front end latency. This should result in optimal resilience to high system loads that might starve the soundcard ADC of buffers to fill and case dropped audio frames. For Tx a buffer sufficient for 1 s of audio is used at present, on Windows the period size will be set to 1/40 of that which gives reasonably low latency and plenty of resilience to high system loads that might starve the soundcard DAC of audio frames to render. Note that a 1 s buffer will make the "Pwr" slider slow to respond, we may have to reduce the Tx audio buffer size if this is seen as a problem.
192 lines
4.5 KiB
C++
192 lines
4.5 KiB
C++
#include "soundout.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QAudioDeviceInfo>
|
|
#include <QAudioOutput>
|
|
#include <QSysInfo>
|
|
#include <qmath.h>
|
|
#include <QDebug>
|
|
|
|
#include "moc_soundout.cpp"
|
|
|
|
bool SoundOutput::audioError () const
|
|
{
|
|
bool result (true);
|
|
|
|
Q_ASSERT_X (m_stream, "SoundOutput", "programming error");
|
|
if (m_stream) {
|
|
switch (m_stream->error ())
|
|
{
|
|
case QAudio::OpenError:
|
|
Q_EMIT error (tr ("An error opening the audio output device has occurred."));
|
|
break;
|
|
|
|
case QAudio::IOError:
|
|
Q_EMIT error (tr ("An error occurred during write to the audio output device."));
|
|
break;
|
|
|
|
case QAudio::UnderrunError:
|
|
Q_EMIT error (tr ("Audio data not being fed to the audio output device fast enough."));
|
|
break;
|
|
|
|
case QAudio::FatalError:
|
|
Q_EMIT error (tr ("Non-recoverable error, audio output device not usable at this time."));
|
|
break;
|
|
|
|
case QAudio::NoError:
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered)
|
|
{
|
|
Q_ASSERT (0 < channels && channels < 3);
|
|
|
|
m_framesBuffered = frames_buffered;
|
|
|
|
QAudioFormat format (device.preferredFormat ());
|
|
// qDebug () << "Preferred audio output format:" << format;
|
|
format.setChannelCount (channels);
|
|
format.setCodec ("audio/pcm");
|
|
format.setSampleRate (48000);
|
|
format.setSampleType (QAudioFormat::SignedInt);
|
|
format.setSampleSize (16);
|
|
format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
|
|
if (!format.isValid ())
|
|
{
|
|
Q_EMIT error (tr ("Requested output audio format is not valid."));
|
|
}
|
|
else if (!device.isFormatSupported (format))
|
|
{
|
|
Q_EMIT error (tr ("Requested output audio format is not supported on device."));
|
|
}
|
|
qDebug () << "Selected audio output format:" << format;
|
|
|
|
m_stream.reset (new QAudioOutput (device, format));
|
|
audioError ();
|
|
m_stream->setVolume (m_volume);
|
|
m_stream->setNotifyInterval(100);
|
|
|
|
connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged);
|
|
|
|
// qDebug() << "A" << m_volume << m_stream->notifyInterval();
|
|
}
|
|
|
|
void SoundOutput::restart (QIODevice * source)
|
|
{
|
|
Q_ASSERT (m_stream);
|
|
|
|
// we have to set this before every start on the stream because the
|
|
// Windows implementation seems to forget the buffer size after a
|
|
// stop.
|
|
//qDebug () << "SoundOut default buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
|
|
if (m_framesBuffered)
|
|
{
|
|
m_stream->setBufferSize (m_stream->format().bytesForFrames (m_framesBuffered));
|
|
}
|
|
m_stream->setCategory ("production");
|
|
m_stream->start (source);
|
|
//qDebug () << "SoundOut selected buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
|
|
}
|
|
|
|
void SoundOutput::suspend ()
|
|
{
|
|
if (m_stream && QAudio::ActiveState == m_stream->state ())
|
|
{
|
|
m_stream->suspend ();
|
|
audioError ();
|
|
}
|
|
}
|
|
|
|
void SoundOutput::resume ()
|
|
{
|
|
if (m_stream && QAudio::SuspendedState == m_stream->state ())
|
|
{
|
|
m_stream->resume ();
|
|
audioError ();
|
|
}
|
|
}
|
|
|
|
void SoundOutput::reset ()
|
|
{
|
|
if (m_stream)
|
|
{
|
|
m_stream->reset ();
|
|
audioError ();
|
|
}
|
|
}
|
|
|
|
void SoundOutput::stop ()
|
|
{
|
|
if (m_stream)
|
|
{
|
|
m_stream->stop ();
|
|
audioError ();
|
|
}
|
|
}
|
|
|
|
qreal SoundOutput::attenuation () const
|
|
{
|
|
return -(20. * qLn (m_volume) / qLn (10.));
|
|
}
|
|
|
|
void SoundOutput::setAttenuation (qreal a)
|
|
{
|
|
Q_ASSERT (0. <= a && a <= 999.);
|
|
m_volume = qPow(10.0, -a/20.0);
|
|
// qDebug () << "SoundOut: attn = " << a << ", vol = " << m_volume;
|
|
if (m_stream)
|
|
{
|
|
m_stream->setVolume (m_volume);
|
|
}
|
|
}
|
|
|
|
void SoundOutput::resetAttenuation ()
|
|
{
|
|
m_volume = 1.;
|
|
if (m_stream)
|
|
{
|
|
m_stream->setVolume (m_volume);
|
|
}
|
|
}
|
|
|
|
void SoundOutput::handleStateChanged (QAudio::State newState)
|
|
{
|
|
// qDebug () << "SoundOutput::handleStateChanged: newState:" << newState;
|
|
|
|
switch (newState)
|
|
{
|
|
case QAudio::IdleState:
|
|
Q_EMIT status (tr ("Idle"));
|
|
break;
|
|
|
|
case QAudio::ActiveState:
|
|
Q_EMIT status (tr ("Sending"));
|
|
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 (audioError ())
|
|
{
|
|
Q_EMIT status (tr ("Error"));
|
|
}
|
|
else
|
|
{
|
|
Q_EMIT status (tr ("Stopped"));
|
|
}
|
|
break;
|
|
}
|
|
}
|