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
This commit is contained in:
Bill Somerville 2013-09-28 18:34:27 +00:00
parent b21fc6ce01
commit 8263c52240
4 changed files with 63 additions and 45 deletions

View File

@ -37,7 +37,6 @@ public:
size_t bytesPerFrame () const {return sizeof (qint16) * (Mono == m_channel ? 1 : 2);}
Channel channel () const {return m_channel;}
void channel (Channel newChannel) {m_channel = newChannel;}
protected:
AudioDevice (QObject * parent = 0)
@ -45,7 +44,7 @@ protected:
{
}
void store (char const * source, qint64 numFrames, qint16 * dest)
void store (char const * source, size_t numFrames, qint16 * dest)
{
qint16 const * begin (reinterpret_cast<qint16 const *> (source));
for ( qint16 const * i = begin; i != begin + numFrames * (bytesPerFrame () / sizeof (qint16)); i += bytesPerFrame () / sizeof (qint16))

View File

@ -10,6 +10,11 @@ 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)
@ -17,6 +22,8 @@ Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned
, m_framesPerSignal (framesPerSignal)
, m_monitoring (false)
, m_starting (false)
, m_buffer (new short [framesPerSignal * downsampleFactor])
, m_bufferPos (0)
{
clear ();
}
@ -41,63 +48,64 @@ void Detector::clear ()
qint64 Detector::writeData (char const * data, qint64 maxSize)
{
bool overrun (false);
int excess (0);
if (m_monitoring)
{
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ()))); // no torn frames
qint64 framesAcceptable (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]) - jt9com_.kin);
qint64 framesAccepted (qMin (static_cast<qint64> (maxSize / bytesPerFrame ()), framesAcceptable));
// 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));
overrun = framesAccepted < static_cast<qint64> (maxSize / bytesPerFrame ());
if (overrun)
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ()))
{
qDebug () << "dropped " << maxSize / sizeof (jt9com_.d2[0]) - framesAccepted << " frames of data on the floor!";
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted << " frames of data on the floor!";
}
Q_ASSERT (2 == bytesPerFrame ()); // only mono until fil4 can do stereo
excess = framesAccepted % 4;
qint32 m_n1,m_n2;
m_n1 = framesAccepted - excess;
fil4_((qint16 *)data, &m_n1, m_translate, &m_n2);
store ((char const *) m_translate, m_n2, &jt9com_.d2[jt9com_.kin]);
unsigned lastSignalIndex (jt9com_.kin / m_framesPerSignal);
jt9com_.kin += m_n2;
unsigned currentSignalIndex (jt9com_.kin / m_framesPerSignal);
if (currentSignalIndex != lastSignalIndex && m_monitoring)
for (unsigned remaining = framesAccepted; remaining; )
{
Q_EMIT framesWritten (currentSignalIndex * m_framesPerSignal);
}
if (!secondInPeriod ())
{
if (!m_starting)
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)
{
// 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_starting = true;
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);
}
}
else if (m_starting)
{
m_starting = false;
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 - (overrun ? 0 : excess * bytesPerFrame ());
// we drop any data past the end of the buffer on
// the floor until the next period starts
return maxSize; // we drop any data past the end of the buffer on
// the floor until the next period starts
}
unsigned Detector::secondInPeriod () const

View File

@ -3,6 +3,8 @@
#include "AudioDevice.hpp"
#include <QScopedArrayPointer>
//
// output device that distributes data in predefined chunks via a signal
//
@ -20,6 +22,10 @@ public:
// if the data buffer were not global storage and fixed size then we
// might want maximum size passed as constructor arguments
//
// we down sample by a factor of 4
//
// the framesPerSignal argument is the number after down sampling
//
Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned framesPerSignal, QObject * parent = 0);
bool isMonitoring () const {return m_monitoring;}
@ -35,7 +41,7 @@ protected:
private:
// these are private because we want thread safety, must be called via Qt queued connections
Q_SLOT void open (AudioDevice::Channel channel = Mono) {AudioDevice::open (QIODevice::WriteOnly, channel);}
Q_SLOT void setMonitoring (bool newState) {m_monitoring = newState;}
Q_SLOT void setMonitoring (bool newState) {m_monitoring = newState; m_bufferPos = 0;}
Q_SLOT bool reset ();
Q_SLOT void close () {AudioDevice::close ();}
@ -46,10 +52,15 @@ private:
unsigned m_frameRate;
unsigned m_period;
unsigned m_framesPerSignal;
qint32 m_framesPerSignal; // after down sampling
bool volatile m_monitoring;
bool m_starting;
qint16 m_translate[20000];
QScopedArrayPointer<short> m_buffer; // de-interleaved sample buffer
// big enough for all the
// samples for one increment of
// data (a signals worth) at
// the input sample rate
unsigned m_bufferPos;
};
#endif

View File

@ -1,4 +1,4 @@
//-------------------------------------------------------------- MainWindow
//--------------------------------------------------------------- MainWindow
#include "mainwindow.h"
#include "ui_mainwindow.h"