mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-30 08:08:39 -05:00
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:
parent
b21fc6ce01
commit
8263c52240
@ -37,7 +37,6 @@ public:
|
|||||||
size_t bytesPerFrame () const {return sizeof (qint16) * (Mono == m_channel ? 1 : 2);}
|
size_t bytesPerFrame () const {return sizeof (qint16) * (Mono == m_channel ? 1 : 2);}
|
||||||
|
|
||||||
Channel channel () const {return m_channel;}
|
Channel channel () const {return m_channel;}
|
||||||
void channel (Channel newChannel) {m_channel = newChannel;}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioDevice (QObject * parent = 0)
|
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));
|
qint16 const * begin (reinterpret_cast<qint16 const *> (source));
|
||||||
for ( qint16 const * i = begin; i != begin + numFrames * (bytesPerFrame () / sizeof (qint16)); i += bytesPerFrame () / sizeof (qint16))
|
for ( qint16 const * i = begin; i != begin + numFrames * (bytesPerFrame () / sizeof (qint16)); i += bytesPerFrame () / sizeof (qint16))
|
||||||
|
86
Detector.cpp
86
Detector.cpp
@ -10,6 +10,11 @@ extern "C" {
|
|||||||
void fil4_(qint16*, qint32*, qint16*, qint32*);
|
void fil4_(qint16*, qint32*, qint16*, qint32*);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
unsigned const downsampleFactor = 4;
|
||||||
|
}
|
||||||
|
|
||||||
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned framesPerSignal, QObject * parent)
|
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned framesPerSignal, QObject * parent)
|
||||||
: AudioDevice (parent)
|
: AudioDevice (parent)
|
||||||
, m_frameRate (frameRate)
|
, m_frameRate (frameRate)
|
||||||
@ -17,6 +22,8 @@ Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned
|
|||||||
, m_framesPerSignal (framesPerSignal)
|
, m_framesPerSignal (framesPerSignal)
|
||||||
, m_monitoring (false)
|
, m_monitoring (false)
|
||||||
, m_starting (false)
|
, m_starting (false)
|
||||||
|
, m_buffer (new short [framesPerSignal * downsampleFactor])
|
||||||
|
, m_bufferPos (0)
|
||||||
{
|
{
|
||||||
clear ();
|
clear ();
|
||||||
}
|
}
|
||||||
@ -41,63 +48,64 @@ void Detector::clear ()
|
|||||||
|
|
||||||
qint64 Detector::writeData (char const * data, qint64 maxSize)
|
qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||||
{
|
{
|
||||||
bool overrun (false);
|
|
||||||
int excess (0);
|
|
||||||
if (m_monitoring)
|
if (m_monitoring)
|
||||||
{
|
{
|
||||||
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ()))); // no torn frames
|
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ()))); // no torn frames
|
||||||
|
|
||||||
qint64 framesAcceptable (sizeof (jt9com_.d2) / sizeof (jt9com_.d2[0]) - jt9com_.kin);
|
// these are in terms of input frames (not down sampled)
|
||||||
qint64 framesAccepted (qMin (static_cast<qint64> (maxSize / bytesPerFrame ()), framesAcceptable));
|
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 (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ()))
|
||||||
if (overrun)
|
|
||||||
{
|
{
|
||||||
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
|
for (unsigned remaining = framesAccepted; remaining; )
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Q_EMIT framesWritten (currentSignalIndex * m_framesPerSignal);
|
size_t numFramesProcessed (qMin (m_framesPerSignal * downsampleFactor - m_bufferPos, remaining));
|
||||||
}
|
store (&data[(framesAccepted - remaining) * bytesPerFrame ()], numFramesProcessed, &m_buffer[m_bufferPos]);
|
||||||
|
m_bufferPos += numFramesProcessed;
|
||||||
if (!secondInPeriod ())
|
if (m_bufferPos == m_framesPerSignal * downsampleFactor && m_monitoring)
|
||||||
{
|
|
||||||
if (!m_starting)
|
|
||||||
{
|
{
|
||||||
// next samples will be in new period so wrap around to
|
qint32 framesToProcess (m_framesPerSignal * downsampleFactor);
|
||||||
// start of buffer
|
qint32 framesAfterDownSample;
|
||||||
//
|
fil4_(&m_buffer[0], &framesToProcess, &jt9com_.d2[jt9com_.kin], &framesAfterDownSample);
|
||||||
// we don't bother calling reset () since we expect to fill
|
m_bufferPos = 0;
|
||||||
// the whole buffer and don't need to waste cycles zeroing
|
|
||||||
jt9com_.kin = 0;
|
jt9com_.kin += framesAfterDownSample;
|
||||||
m_starting = true;
|
Q_EMIT framesWritten (jt9com_.kin);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (m_starting)
|
if (!secondInPeriod ())
|
||||||
{
|
{
|
||||||
m_starting = false;
|
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
|
else
|
||||||
{
|
{
|
||||||
jt9com_.kin = 0;
|
jt9com_.kin = 0;
|
||||||
|
m_bufferPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxSize - (overrun ? 0 : excess * bytesPerFrame ());
|
return maxSize; // we drop any data past the end of the buffer on
|
||||||
// we drop any data past the end of the buffer on
|
// the floor until the next period starts
|
||||||
// the floor until the next period starts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned Detector::secondInPeriod () const
|
unsigned Detector::secondInPeriod () const
|
||||||
|
17
Detector.hpp
17
Detector.hpp
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include "AudioDevice.hpp"
|
#include "AudioDevice.hpp"
|
||||||
|
|
||||||
|
#include <QScopedArrayPointer>
|
||||||
|
|
||||||
//
|
//
|
||||||
// output device that distributes data in predefined chunks via a signal
|
// 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
|
// if the data buffer were not global storage and fixed size then we
|
||||||
// might want maximum size passed as constructor arguments
|
// 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);
|
Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned framesPerSignal, QObject * parent = 0);
|
||||||
|
|
||||||
bool isMonitoring () const {return m_monitoring;}
|
bool isMonitoring () const {return m_monitoring;}
|
||||||
@ -35,7 +41,7 @@ protected:
|
|||||||
private:
|
private:
|
||||||
// these are private because we want thread safety, must be called via Qt queued connections
|
// 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 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 bool reset ();
|
||||||
Q_SLOT void close () {AudioDevice::close ();}
|
Q_SLOT void close () {AudioDevice::close ();}
|
||||||
|
|
||||||
@ -46,10 +52,15 @@ private:
|
|||||||
|
|
||||||
unsigned m_frameRate;
|
unsigned m_frameRate;
|
||||||
unsigned m_period;
|
unsigned m_period;
|
||||||
unsigned m_framesPerSignal;
|
qint32 m_framesPerSignal; // after down sampling
|
||||||
bool volatile m_monitoring;
|
bool volatile m_monitoring;
|
||||||
bool m_starting;
|
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
|
#endif
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//-------------------------------------------------------------- MainWindow
|
//--------------------------------------------------------------- MainWindow
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user