WSJT-X/Detector.cpp
Bill Somerville 3fbf5a5686 Fix defects in audio down-sampling on some platforms.
The filter used for 4 times down-sampling cannot handle sample streams
where the hardware or drivers deliver chunks of data that are not
multiples of 4 frames long. This seems to be prevalent on some Linux
platforms. Also de-interleaving of single channel audio from stereo
streams was no longer supported.

I have changed the input strategy to de-interleave the incoming
sample stream into an intermediate buffer large enough to hold all the
samples required for a single unit of processing (one basic waterfall
interval) and apply the down-sampling filter to the whole intermediate
buffer just prior dispatch to the FFT generator.

This now means that we are now using the ubiquitous 48kHz hardware
sample rate for both input and output of audio across all platforms
and decoding a single channel of a stereo stream is again
supported. The down-sampling to 12kHz is done with a high quality FIR
49-tap low pass filter specifically designed by Joe (K1JT) for
operation in a 4kHz bandwidth.



git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@3585 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2013-09-28 18:34:27 +00:00

120 lines
3.5 KiB
C++

#include "Detector.hpp"
#include <QDateTime>
#include <QtAlgorithms>
#include <QDebug>
#include "commons.h"
extern "C" {
void fil4_(qint16*, qint32*, qint16*, qint32*);
}
namespace
{
unsigned const downsampleFactor = 4;
}
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned framesPerSignal, QObject * parent)
: AudioDevice (parent)
, m_frameRate (frameRate)
, m_period (periodLengthInSeconds)
, m_framesPerSignal (framesPerSignal)
, m_monitoring (false)
, m_starting (false)
, m_buffer (new short [framesPerSignal * downsampleFactor])
, m_bufferPos (0)
{
clear ();
}
bool Detector::reset ()
{
clear ();
return QIODevice::reset ();
}
void Detector::clear ()
{
// set index to roughly where we are in time (1ms resolution)
// qint64 now (QDateTime::currentMSecsSinceEpoch ());
// unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000));
// jt9com_.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast<unsigned> (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0])));
jt9com_.kin = 0;
// fill buffer with zeros (G4WJS commented out because it might cause decoder hangs)
// qFill (jt9com_.d2, jt9com_.d2 + sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]), 0);
}
qint64 Detector::writeData (char const * data, qint64 maxSize)
{
if (m_monitoring)
{
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ()))); // no torn frames
// these are in terms of input frames (not down sampled)
size_t framesAcceptable ((sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]) - jt9com_.kin) * downsampleFactor);
size_t framesAccepted (qMin (static_cast<size_t> (maxSize / bytesPerFrame ()), framesAcceptable));
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ()))
{
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted << " frames of data on the floor!";
}
for (unsigned remaining = framesAccepted; remaining; )
{
size_t numFramesProcessed (qMin (m_framesPerSignal * downsampleFactor - m_bufferPos, remaining));
store (&data[(framesAccepted - remaining) * bytesPerFrame ()], numFramesProcessed, &m_buffer[m_bufferPos]);
m_bufferPos += numFramesProcessed;
if (m_bufferPos == m_framesPerSignal * downsampleFactor && m_monitoring)
{
qint32 framesToProcess (m_framesPerSignal * downsampleFactor);
qint32 framesAfterDownSample;
fil4_(&m_buffer[0], &framesToProcess, &jt9com_.d2[jt9com_.kin], &framesAfterDownSample);
m_bufferPos = 0;
jt9com_.kin += framesAfterDownSample;
Q_EMIT framesWritten (jt9com_.kin);
}
if (!secondInPeriod ())
{
if (!m_starting)
{
// next samples will be in new period so wrap around to
// start of buffer
//
// we don't bother calling reset () since we expect to fill
// the whole buffer and don't need to waste cycles zeroing
jt9com_.kin = 0;
m_bufferPos = 0;
m_starting = true;
}
}
else if (m_starting)
{
m_starting = false;
}
remaining -= numFramesProcessed;
}
}
else
{
jt9com_.kin = 0;
m_bufferPos = 0;
}
return maxSize; // we drop any data past the end of the buffer on
// the floor until the next period starts
}
unsigned Detector::secondInPeriod () const
{
// we take the time of the data as the following assuming no latency
// delivering it to us (not true but close enough for us)
qint64 now (QDateTime::currentMSecsSinceEpoch ());
unsigned secondInToday ((now % 86400000LL) / 1000);
return secondInToday % m_period;
}