mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-09 02:26:06 -05:00
60b0c5b984
appropriate delays to approximate real-time data rate.
514 lines
14 KiB
C++
514 lines
14 KiB
C++
#include "soundin.h"
|
|
|
|
#ifdef Q_OS_WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#define NFFT 32768
|
|
#define FRAMES_PER_BUFFER 1024
|
|
|
|
#include <portaudio.h>
|
|
extern "C"
|
|
{
|
|
struct
|
|
{
|
|
double d8[2*60*96000]; //This is "common/datcom/..." in fortran
|
|
float ss[4*322*NFFT];
|
|
float savg[4*NFFT];
|
|
double fcenter;
|
|
int nutc;
|
|
int idphi; //Phase correction for Y pol'n, degrees
|
|
int mousedf; //User-selected DF
|
|
int mousefqso; //User-selected QSO freq (kHz)
|
|
int nagain; //1 ==> decode only at fQSO +/- Tol
|
|
int ndepth; //How much hinted decoding to do?
|
|
int ndiskdat; //1 ==> data read from *.tf2 or *.iq file
|
|
int neme; //Hinted decoding tries only for EME calls
|
|
int newdat; //1 ==> new data, must do long FFT
|
|
int nfa; //Low decode limit (kHz)
|
|
int nfb; //High decode limit (kHz)
|
|
int nfcal; //Frequency correction, for calibration (Hz)
|
|
int nfshift; //Shift of displayed center freq (kHz)
|
|
int mcall3; //1 ==> CALL3.TXT has been modified
|
|
int ntimeout; //Max for timeouts in Messages and BandMap
|
|
int ntol; //+/- decoding range around fQSO (Hz)
|
|
int nxant; //1 ==> add 45 deg to measured pol angle
|
|
int map65RxLog; //Flags to control log files
|
|
int nfsample; //Input sample rate
|
|
int nxpol; //1 if using xpol antennas, 0 otherwise
|
|
int mode65; //JT65 sub-mode: A=1, B=2, C=4
|
|
int nfast; //1No longer used
|
|
int nsave; //Number of s3(64,63) spectra saved
|
|
char mycall[12];
|
|
char mygrid[6];
|
|
char hiscall[12];
|
|
char hisgrid[6];
|
|
char datetime[20];
|
|
} datcom_;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int kin; //Parameters sent to/from the portaudio callback function
|
|
int nrx;
|
|
bool bzero;
|
|
bool iqswap;
|
|
bool b10db;
|
|
} paUserData;
|
|
|
|
//--------------------------------------------------------------- a2dCallback
|
|
extern "C" int a2dCallback( const void *inputBuffer, void *outputBuffer,
|
|
unsigned long framesToProcess,
|
|
const PaStreamCallbackTimeInfo* timeInfo,
|
|
PaStreamCallbackFlags statusFlags,
|
|
void *userData )
|
|
|
|
// This routine called by the PortAudio engine when samples are available.
|
|
// It may be called at interrupt level, so don't do anything
|
|
// that could mess up the system like calling malloc() or free().
|
|
|
|
{
|
|
paUserData *udata=(paUserData*)userData;
|
|
(void) outputBuffer; //Prevent unused variable warnings.
|
|
(void) timeInfo;
|
|
(void) userData;
|
|
int nbytes,i,j;
|
|
float d4[4*FRAMES_PER_BUFFER];
|
|
float d4a[4*FRAMES_PER_BUFFER];
|
|
float tmp;
|
|
float fac;
|
|
|
|
if( (statusFlags&paInputOverflow) != 0) {
|
|
qDebug() << "Input Overflow";
|
|
}
|
|
if(udata->bzero) { //Start of a new minute
|
|
udata->kin=0; //Reset buffer pointer
|
|
udata->bzero=false;
|
|
}
|
|
|
|
nbytes=udata->nrx*8*framesToProcess; //Bytes per frame
|
|
memcpy(d4,inputBuffer,nbytes); //Copy all samples to d4
|
|
|
|
fac=32767.0;
|
|
if(udata->b10db) fac=103618.35;
|
|
|
|
if(udata->nrx==2) {
|
|
for(i=0; i<4*int(framesToProcess); i++) { //Negate odd-numbered frames
|
|
d4[i]=fac*d4[i];
|
|
j=i/4;
|
|
if((j%2)==1) d4[i]=-d4[i];
|
|
}
|
|
if(!udata->iqswap) {
|
|
for(i=0; i<int(framesToProcess); i++) {
|
|
j=4*i;
|
|
tmp=d4[j];
|
|
d4[j]=d4[j+1];
|
|
d4[j+1]=tmp;
|
|
tmp=d4[j+2];
|
|
d4[j+2]=d4[j+3];
|
|
d4[j+3]=tmp;
|
|
}
|
|
}
|
|
memcpy(&datcom_.d8[2*udata->kin],d4,nbytes); //Copy from d4 to dd()
|
|
} else {
|
|
int k=0;
|
|
for(i=0; i<2*int(framesToProcess); i+=2) { //Negate odd-numbered frames
|
|
j=i/2;
|
|
if(j%2==0) {
|
|
d4a[k++]=fac*d4[i];
|
|
d4a[k++]=fac*d4[i+1];
|
|
} else {
|
|
d4a[k++]=-fac*d4[i];
|
|
d4a[k++]=-fac*d4[i+1];
|
|
}
|
|
d4a[k++]=0.0;
|
|
d4a[k++]=0.0;
|
|
}
|
|
if(!udata->iqswap) {
|
|
for(i=0; i<int(framesToProcess); i++) {
|
|
j=4*i;
|
|
tmp=d4a[j];
|
|
d4a[j]=d4a[j+1];
|
|
d4a[j+1]=tmp;
|
|
}
|
|
}
|
|
memcpy(&datcom_.d8[2*udata->kin],d4a,2*nbytes); //Copy from d4a to dd()
|
|
}
|
|
udata->kin += framesToProcess;
|
|
return paContinue;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct COMWrapper
|
|
{
|
|
explicit COMWrapper ()
|
|
{
|
|
#ifdef Q_OS_WIN32
|
|
// required because Qt only does this for GUI thread
|
|
CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
|
#endif
|
|
}
|
|
~COMWrapper ()
|
|
{
|
|
#ifdef Q_OS_WIN32
|
|
CoUninitialize ();
|
|
#endif
|
|
}
|
|
};
|
|
}
|
|
|
|
void SoundInThread::run() //SoundInThread::run()
|
|
{
|
|
quitExecution = false;
|
|
|
|
QFile f("mockRTfiles.txt");
|
|
m_mockRT=f.exists();
|
|
|
|
if(m_mockRT) {
|
|
inputMockRT(); //"Mock realtime" input from a .tf2 file
|
|
return;
|
|
}
|
|
|
|
if (m_net) {
|
|
inputUDP(); //Network input from Linrad (or equivalent)
|
|
return;
|
|
}
|
|
|
|
COMWrapper c;
|
|
|
|
//---------------------------------------------------- Soundcard Setup
|
|
// qDebug() << "Start souncard input";
|
|
|
|
PaError paerr;
|
|
PaStreamParameters inParam;
|
|
PaStream *inStream;
|
|
paUserData udata;
|
|
|
|
udata.kin=0; //Buffer pointer
|
|
udata.bzero=false; //Flag to request reset of kin
|
|
udata.nrx=m_nrx; //Number of polarizations
|
|
udata.iqswap=m_IQswap;
|
|
udata.b10db=m_10db;
|
|
|
|
auto device_info = Pa_GetDeviceInfo (m_nDevIn);
|
|
|
|
inParam.device=m_nDevIn; //### Input Device Number ###
|
|
inParam.channelCount=2*m_nrx; //Number of analog channels
|
|
inParam.sampleFormat=paFloat32; //Get floats from Portaudio
|
|
inParam.suggestedLatency=device_info->defaultHighInputLatency;
|
|
inParam.hostApiSpecificStreamInfo=NULL;
|
|
|
|
paerr=Pa_IsFormatSupported(&inParam,NULL,96000.0);
|
|
if(paerr<0) {
|
|
QString error_message;
|
|
if (paUnanticipatedHostError == paerr)
|
|
{
|
|
auto const * last_host_error = Pa_GetLastHostErrorInfo ();
|
|
error_message = QString {"PortAudio Host API error: %1"}.arg (last_host_error->errorText);
|
|
}
|
|
else
|
|
{
|
|
error_message = "PortAudio says requested soundcard format not supported.";
|
|
}
|
|
emit error(error_message);
|
|
// return;
|
|
}
|
|
paerr=Pa_OpenStream(&inStream, //Input stream
|
|
&inParam, //Input parameters
|
|
NULL, //No output parameters
|
|
96000.0, //Sample rate
|
|
FRAMES_PER_BUFFER, //Frames per buffer
|
|
// paClipOff+paDitherOff, //No clipping or dithering
|
|
paClipOff, //No clipping
|
|
a2dCallback, //Input callbeck routine
|
|
&udata); //userdata
|
|
|
|
paerr=Pa_StartStream(inStream);
|
|
if(paerr<0) {
|
|
emit error("Failed to start audio input stream.");
|
|
return;
|
|
}
|
|
// const PaStreamInfo* p=Pa_GetStreamInfo(inStream);
|
|
|
|
bool qe = quitExecution;
|
|
int ntr0=99;
|
|
int k=0;
|
|
int nsec;
|
|
int ntr;
|
|
int nBusy=0;
|
|
int nhsym0=0;
|
|
|
|
//---------------------------------------------- Soundcard input loop
|
|
while (!qe) {
|
|
qe = quitExecution;
|
|
if (qe) break;
|
|
qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
|
nsec = ms/1000; // Time according to this computer
|
|
ntr = nsec % m_TRperiod;
|
|
|
|
// Reset buffer pointer and symbol number at start of minute
|
|
if(ntr < ntr0 or !m_monitoring or m_TRperiod!=m_TRperiod0) {
|
|
nhsym0=0;
|
|
udata.bzero=true;
|
|
m_TRperiod0=m_TRperiod;
|
|
}
|
|
k=udata.kin;
|
|
udata.iqswap=m_IQswap;
|
|
udata.b10db=m_10db;
|
|
if(m_monitoring) {
|
|
if(m_bForceCenterFreq) {
|
|
datcom_.fcenter=m_dForceCenterFreq;
|
|
} else {
|
|
datcom_.fcenter=144.125;
|
|
}
|
|
m_hsym=(k-2048)*11025.0/(2048.0*m_rate);
|
|
if(m_hsym != nhsym0) {
|
|
if(m_dataSinkBusy) {
|
|
nBusy++;
|
|
} else {
|
|
m_dataSinkBusy=true;
|
|
emit readyForFFT(k); //Signal to compute new FFTs
|
|
}
|
|
nhsym0=m_hsym;
|
|
}
|
|
}
|
|
msleep(100);
|
|
ntr0=ntr;
|
|
}
|
|
Pa_StopStream(inStream);
|
|
Pa_CloseStream(inStream);
|
|
}
|
|
|
|
void SoundInThread::setSwapIQ(bool b)
|
|
{
|
|
m_IQswap=b;
|
|
}
|
|
|
|
void SoundInThread::set10db(bool b)
|
|
{
|
|
m_10db=b;
|
|
}
|
|
void SoundInThread::setPort(int n) //setPort()
|
|
{
|
|
if (isRunning()) return;
|
|
this->m_udpPort=n;
|
|
}
|
|
|
|
void SoundInThread::setInputDevice(int n) //setInputDevice()
|
|
{
|
|
if (isRunning()) return;
|
|
this->m_nDevIn=n;
|
|
}
|
|
|
|
void SoundInThread::setRate(double rate) //setRate()
|
|
{
|
|
if (isRunning()) return;
|
|
this->m_rate = rate;
|
|
}
|
|
|
|
void SoundInThread::setBufSize(unsigned n) //setBufSize()
|
|
{
|
|
if (isRunning()) return;
|
|
this->bufSize = n;
|
|
}
|
|
|
|
void SoundInThread::setFadd(double x)
|
|
{
|
|
m_fAdd=x;
|
|
}
|
|
|
|
|
|
void SoundInThread::quit() //quit()
|
|
{
|
|
quitExecution = true;
|
|
}
|
|
|
|
void SoundInThread::setNetwork(bool b) //setNetwork()
|
|
{
|
|
m_net = b;
|
|
}
|
|
|
|
void SoundInThread::setMonitoring(bool b) //setMonitoring()
|
|
{
|
|
m_monitoring = b;
|
|
}
|
|
|
|
void SoundInThread::setForceCenterFreqBool(bool b)
|
|
{
|
|
m_bForceCenterFreq=b;
|
|
|
|
}
|
|
|
|
void SoundInThread::setForceCenterFreqMHz(double d)
|
|
{
|
|
m_dForceCenterFreq=d;
|
|
}
|
|
|
|
void SoundInThread::setNrx(int n) //setNrx()
|
|
{
|
|
m_nrx = n;
|
|
}
|
|
|
|
int SoundInThread::nrx()
|
|
{
|
|
return m_nrx;
|
|
}
|
|
|
|
int SoundInThread::mhsym()
|
|
{
|
|
return m_hsym;
|
|
}
|
|
|
|
void SoundInThread::setPeriod(int n)
|
|
{
|
|
m_TRperiod=n;
|
|
}
|
|
|
|
//--------------------------------------------------------------- inputUDP()
|
|
void SoundInThread::inputUDP()
|
|
{
|
|
udpSocket = new QUdpSocket();
|
|
if(!udpSocket->bind(m_udpPort,QUdpSocket::ShareAddress) )
|
|
{
|
|
emit error(tr("UDP Socket bind failed."));
|
|
return;
|
|
}
|
|
|
|
// Set this socket's total buffer space for received UDP packets
|
|
udpSocket->setSocketOption (QUdpSocket::ReceiveBufferSizeSocketOption, 141600);
|
|
|
|
bool qe = quitExecution;
|
|
struct linradBuffer {
|
|
double cfreq;
|
|
int msec;
|
|
float userfreq;
|
|
int iptr;
|
|
quint16 iblk;
|
|
qint8 nrx;
|
|
char iusb;
|
|
double d8[174];
|
|
} b;
|
|
|
|
int ntr0=99;
|
|
int k=0;
|
|
int nsec;
|
|
int ntr;
|
|
int nhsym0=0;
|
|
int iz=174;
|
|
int nBusy=0;
|
|
|
|
// Main loop for input of UDP packets over the network:
|
|
while (!qe) {
|
|
qe = quitExecution;
|
|
if (qe) break;
|
|
if (!udpSocket->hasPendingDatagrams()) {
|
|
msleep(2); // Sleep if no packet available
|
|
} else {
|
|
int nBytesRead = udpSocket->readDatagram((char *)&b,1416);
|
|
if (nBytesRead != 1416) qDebug() << "UDP Read Error:" << nBytesRead;
|
|
|
|
qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
|
nsec = ms/1000; // Time according to this computer
|
|
ntr = nsec % m_TRperiod;
|
|
|
|
// Reset buffer pointer and symbol number at start of minute
|
|
if(ntr < ntr0 or !m_monitoring or m_TRperiod!=m_TRperiod0) {
|
|
k=0;
|
|
nhsym0=0;
|
|
m_TRperiod0=m_TRperiod;
|
|
}
|
|
ntr0=ntr;
|
|
|
|
if(m_monitoring) {
|
|
m_nrx=b.nrx;
|
|
if(m_nrx == +1) iz=348; //One RF channel, i*2 data
|
|
if(m_nrx == -1 or m_nrx == +2) iz=174; //One Rf channel, r*4 data
|
|
// or 2 RF channels, i*2 data
|
|
if(m_nrx == -2) iz=87; // Two RF channels, r*4 data
|
|
|
|
// If buffer will not overflow, move data into datcom_
|
|
if ((k+iz) <= 60*96000) {
|
|
int nsam=-1;
|
|
recvpkt_(&nsam, &b.iblk, &b.nrx, &k, b.d8, b.d8, b.d8);
|
|
if(m_bForceCenterFreq) {
|
|
datcom_.fcenter=m_dForceCenterFreq;
|
|
} else {
|
|
datcom_.fcenter=b.cfreq + m_fAdd;
|
|
}
|
|
}
|
|
|
|
m_hsym=(k-2048)*11025.0/(2048.0*m_rate);
|
|
if(m_hsym != nhsym0) {
|
|
if(m_dataSinkBusy) {
|
|
nBusy++;
|
|
} else {
|
|
m_dataSinkBusy=true;
|
|
emit readyForFFT(k); //Signal to compute new FFTs
|
|
}
|
|
nhsym0=m_hsym;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete udpSocket;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------- inputMockRT()
|
|
void SoundInThread::inputMockRT()
|
|
{
|
|
bool qe = quitExecution;
|
|
int ntr0=99;
|
|
int k=0;
|
|
int nsec;
|
|
int ntr;
|
|
int nhsym0=0;
|
|
int iz=174;
|
|
int nBusy=0;
|
|
int mstr=0;
|
|
int mswait=0;
|
|
|
|
// Main loop for mock realtime input of data from .tf2 files
|
|
while (!qe) {
|
|
qe = quitExecution;
|
|
if (qe) break;
|
|
qint64 ms = QDateTime::currentMSecsSinceEpoch(); // Time according to this computer
|
|
mstr = ms % (1000*m_TRperiod);
|
|
nsec = (ms % 86400000l) / 1000;
|
|
ntr = nsec % m_TRperiod;
|
|
|
|
// Reset buffer pointer and symbol number at start of minute
|
|
if(ntr < ntr0 or !m_monitoring or m_TRperiod!=m_TRperiod0) {
|
|
k=0;
|
|
nhsym0=0;
|
|
m_TRperiod0=m_TRperiod;
|
|
}
|
|
ntr0=ntr;
|
|
|
|
if(m_monitoring) {
|
|
// If buffer will not overflow, move data into datcom_
|
|
if ((k+iz) <= 60*96000) {
|
|
if(k>mstr*96) {
|
|
mswait=(k-mstr*96)/96;
|
|
msleep(mswait);
|
|
}
|
|
read_tf2_(&k);
|
|
}
|
|
m_hsym=(k-2048)*11025.0/(2048.0*m_rate);
|
|
if(m_hsym != nhsym0) {
|
|
if(m_dataSinkBusy) {
|
|
nBusy++;
|
|
} else {
|
|
m_dataSinkBusy=true;
|
|
emit readyForFFT(k); //Signal to compute new FFTs
|
|
}
|
|
nhsym0=m_hsym;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|