first commit
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file audioReceiver.cpp
|
||||
* @brief audio receiver class
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-10-02
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define LOG_AUDIO_RECEIVER
|
||||
|
||||
#include "cusdr_audioReceiver.h"
|
||||
|
||||
|
||||
AudioReceiver::AudioReceiver(THPSDRParameter *ioData)
|
||||
: QObject()
|
||||
, set(Settings::instance())
|
||||
, io(ioData)
|
||||
, m_client(0)
|
||||
{
|
||||
}
|
||||
|
||||
AudioReceiver::~AudioReceiver() {
|
||||
|
||||
}
|
||||
|
||||
void AudioReceiver::displayAudioRcvrSocketError(QAbstractSocket::SocketError error) {
|
||||
|
||||
AUDIO_RECEIVER << "audio client socket error:" << error;
|
||||
}
|
||||
|
||||
void AudioReceiver::initClient() {
|
||||
|
||||
quint16 port = (quint16) (set->getAudioPort() + (io->audio_rx * 2));
|
||||
|
||||
QUdpSocket *socket = new QUdpSocket();
|
||||
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
||||
|
||||
if (socket->bind(port, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress)) {
|
||||
|
||||
CHECKED_CONNECT(
|
||||
socket,
|
||||
SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this,
|
||||
SLOT(displayAudioRcvrSocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
socket,
|
||||
SIGNAL(readyRead()),
|
||||
this,
|
||||
SLOT(readPendingAudioRcvrData()));
|
||||
|
||||
clientConnections.append(socket);
|
||||
|
||||
AUDIO_RECEIVER << "client socket binding successful.";
|
||||
m_message = tr("[server]: listening for rx %1 audio on port %2.");
|
||||
emit messageEvent(m_message.arg(io->audio_rx).arg(port));
|
||||
|
||||
//m_dataEngine->clientConnected = true;
|
||||
// need to implement connection in dataEngine !!!!
|
||||
emit clientConnectedEvent(true);
|
||||
//rcveIQ_toggle = false;
|
||||
}
|
||||
else {
|
||||
|
||||
m_message = tr("[server]: bind socket failed for socket on port %1.");
|
||||
emit messageEvent(m_message.arg(port));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReceiver::readPendingAudioRcvrData() {
|
||||
|
||||
QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());
|
||||
|
||||
while (socket->hasPendingDatagrams()) {
|
||||
|
||||
m_datagram.resize(socket->pendingDatagramSize());
|
||||
|
||||
if (socket->readDatagram(m_datagram.data(), m_datagram.size()) < 0) {
|
||||
|
||||
AUDIO_RECEIVER << "read client" << m_client << "socket failed.";
|
||||
if (io->rcveIQ_toggle) { // toggles the rcveIQ signal
|
||||
|
||||
emit rcveIQEvent(this, 2);
|
||||
io->rcveIQ_toggle = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
io->au_queue.enqueue(m_datagram);
|
||||
|
||||
if (!io->rcveIQ_toggle) { // toggles the rcveIQ signal
|
||||
|
||||
emit rcveIQEvent(this, 1);
|
||||
io->rcveIQ_toggle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file audioReceiver.h
|
||||
* @brief audio receiver header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-10-02
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CUSDR_AUDIORECEIVER_H
|
||||
#define _CUSDR_AUDIORECEIVER_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QMutex>
|
||||
//#include <QByteArray>
|
||||
//#include <QBuffer>
|
||||
//#include <QVector>
|
||||
//#include <QList>
|
||||
//#include <QWaitCondition>
|
||||
//#include <QThread>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
|
||||
#ifdef LOG_AUDIO_RECEIVER
|
||||
# define AUDIO_RECEIVER qDebug().nospace() << "AudioReceiver::\t"
|
||||
#else
|
||||
# define AUDIO_RECEIVER nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
class AudioReceiver : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioReceiver(THPSDRParameter *ioData = 0);
|
||||
~AudioReceiver();
|
||||
|
||||
//int id;
|
||||
|
||||
public slots:
|
||||
void initClient();
|
||||
|
||||
private:
|
||||
Settings* set;
|
||||
QMutex m_mutex;
|
||||
|
||||
QList<QUdpSocket *> clientConnections;
|
||||
QString m_message;
|
||||
QByteArray m_datagram;
|
||||
|
||||
THPSDRParameter *io;
|
||||
|
||||
int m_client;
|
||||
|
||||
private slots:
|
||||
void displayAudioRcvrSocketError(QAbstractSocket::SocketError error);
|
||||
void readPendingAudioRcvrData();
|
||||
|
||||
signals:
|
||||
void messageEvent(QString message);
|
||||
void rcveIQEvent(QObject *sender, int value);
|
||||
void outputBufferEvent(unsigned char* outbuffer);
|
||||
void clientConnectedEvent(bool value);
|
||||
void newData();
|
||||
void newAudioData();
|
||||
};
|
||||
|
||||
#endif // _CUSDR_AUDIORECEIVER_H
|
||||
@@ -0,0 +1,576 @@
|
||||
/**
|
||||
* @file cusdr_chirpProcessor.cpp
|
||||
* @brief chirp data processor class
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-09-22
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define LOG_CHIRP_PROCESSOR
|
||||
|
||||
#include "cusdr_chirpProcessor.h"
|
||||
|
||||
ChirpProcessor::ChirpProcessor(THPSDRParameter *ioData)
|
||||
: QObject()
|
||||
, set(Settings::instance())
|
||||
, io(ioData)
|
||||
, m_stopped(false)
|
||||
, m_specMax(0.0f)
|
||||
, m_specMin(0.0f)
|
||||
, m_sampleRate(set->getSampleRate())
|
||||
, m_downRate(set->getChirpDownSampleRate())
|
||||
, m_specAvgLength(1)
|
||||
, m_filterLowerFrequency((float)set->getChirpFilterLowerFrequency())
|
||||
, m_filterUpperFrequency((float)set->getChirpFilterUpperFrequency())
|
||||
, m_switch(false)
|
||||
, m_showChirpFFT(false)
|
||||
, m_chirpBufferLength(set->getChirpBufferLength())
|
||||
, m_chirpSidebandFactor(1.0f)
|
||||
{
|
||||
/*m_QCPXin = initQCPX(BUFFERSIZE);
|
||||
m_temp0 = initQCPX(BUFFERSIZE);
|
||||
m_temp2 = initQCPX(BUFFERSIZE/m_downRate);
|
||||
|
||||
m_temp1 = initQCPX(FULL_BUFFERSIZE);
|
||||
m_temp3 = initQCPX(FULL_BUFFERSIZE);*/
|
||||
|
||||
/*
|
||||
m_cpxIn = mallocCPX(BUFFERSIZE);
|
||||
m_tmp0 = mallocCPX(BUFFERSIZE);
|
||||
m_tmp2 = mallocCPX(BUFFERSIZE/m_downRate);
|
||||
|
||||
m_cpxInFilt = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_cpxRxFFT = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_tmp1 = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_tmp3 = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_cpxChirpIn = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_cpxChirpOut = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_cpxChirpTmp = mallocCPX(FULL_BUFFERSIZE);
|
||||
m_cpxOut = mallocCPX(FULL_BUFFERSIZE);
|
||||
*/
|
||||
m_cpxIn.resize(BUFFERSIZE);
|
||||
m_tmp0.resize(BUFFERSIZE);
|
||||
m_tmp2.resize(BUFFERSIZE/m_downRate);
|
||||
|
||||
m_cpxInFilt.resize(FULL_BUFFERSIZE);
|
||||
m_cpxRxFFT.resize(FULL_BUFFERSIZE);
|
||||
m_tmp1.resize(FULL_BUFFERSIZE);
|
||||
m_tmp3.resize(FULL_BUFFERSIZE);
|
||||
m_cpxChirpIn.resize(FULL_BUFFERSIZE);
|
||||
m_cpxChirpOut.resize(FULL_BUFFERSIZE);
|
||||
m_cpxChirpTmp.resize(FULL_BUFFERSIZE);
|
||||
m_cpxOut.resize(FULL_BUFFERSIZE);
|
||||
|
||||
|
||||
/*
|
||||
memset(m_cpxIn, 0, BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_tmp0, 0, BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_tmp2, 0, (BUFFERSIZE/4) * sizeof(CPX));
|
||||
|
||||
memset(m_cpxInFilt, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_cpxRxFFT, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_tmp1, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_tmp3, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_cpxOut, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_cpxChirpIn, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_cpxChirpOut, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
memset(m_cpxChirpTmp, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
*/
|
||||
|
||||
// FFTs by fftw
|
||||
m_chirpFFT = new QFFT(FULL_BUFFERSIZE);
|
||||
m_matchedFFT = new QFFT(FULL_BUFFERSIZE);
|
||||
|
||||
// FIR band pass filter
|
||||
//m_filter = new QFilter(this, FULL_BUFFERSIZE, 2);
|
||||
// 2 = FIR Bandpass, 5 = Bartlett window
|
||||
m_filter = new QFilter(this, BUFFERSIZE, 2, 2);
|
||||
|
||||
// set the BPF
|
||||
m_filter->setFilter(500.0f, 2500.0f);
|
||||
m_filter->setStreamMode(true);
|
||||
|
||||
setupConnections();
|
||||
}
|
||||
|
||||
ChirpProcessor::~ChirpProcessor() {
|
||||
|
||||
while (average_queue.length() > 0)
|
||||
average_queue.dequeue();
|
||||
|
||||
/*
|
||||
freeCPX(m_cpxIn);
|
||||
freeCPX(m_cpxOut);
|
||||
freeCPX(m_cpxInFilt);
|
||||
freeCPX(m_cpxRxFFT);
|
||||
freeCPX(m_cpxChirpIn);
|
||||
freeCPX(m_cpxChirpOut);
|
||||
freeCPX(m_tmp0);
|
||||
freeCPX(m_tmp1);
|
||||
*/
|
||||
|
||||
/*
|
||||
delete m_cpxIn;
|
||||
delete m_cpxOut;
|
||||
delete m_cpxInFilt;
|
||||
delete m_cpxRxFFT;
|
||||
delete m_cpxChirpIn;
|
||||
delete m_cpxChirpOut;
|
||||
delete m_tmp0;
|
||||
delete m_tmp1;
|
||||
*/
|
||||
|
||||
delete m_chirpFFT;
|
||||
delete m_matchedFFT;
|
||||
}
|
||||
|
||||
void ChirpProcessor::stop() {
|
||||
|
||||
m_stopped = true;
|
||||
}
|
||||
|
||||
void ChirpProcessor::setupConnections() {
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(sampleRateChanged(QObject *, int)),
|
||||
this,
|
||||
SLOT(samplingRateChanged(QObject *, int)));
|
||||
|
||||
CHECKED_CONNECT_OPT(
|
||||
set,
|
||||
SIGNAL(chirpFilterLowerFrequencyChanged(int)),
|
||||
this,
|
||||
SLOT(setFilterLowerFrequency(int)),
|
||||
Qt::DirectConnection);
|
||||
|
||||
CHECKED_CONNECT_OPT(
|
||||
set,
|
||||
SIGNAL(chirpFilterUpperFrequencyChanged(int)),
|
||||
this,
|
||||
SLOT(setFilterUpperFrequency(int)),
|
||||
Qt::DirectConnection);
|
||||
|
||||
CHECKED_CONNECT_OPT(
|
||||
set,
|
||||
SIGNAL(chirpAvgLengthChanged(int)),
|
||||
this,
|
||||
SLOT(setDistSpectrumAvgLength(int)),
|
||||
Qt::DirectConnection);
|
||||
|
||||
CHECKED_CONNECT_OPT(
|
||||
set,
|
||||
SIGNAL(chirpFFTShowChanged(bool)),
|
||||
this,
|
||||
SLOT(setChirpFFTShow(bool)),
|
||||
Qt::DirectConnection);
|
||||
|
||||
CHECKED_CONNECT_OPT(
|
||||
set,
|
||||
SIGNAL(chirpSidebandChanged(bool)),
|
||||
this,
|
||||
SLOT(setChirpSideband(bool)),
|
||||
Qt::DirectConnection);
|
||||
}
|
||||
|
||||
int ChirpProcessor::setSpectrumBufferSize(int size) {
|
||||
|
||||
int i = 1;
|
||||
while (i < size) i *= 2;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
void ChirpProcessor::processChirpData() {
|
||||
|
||||
forever {
|
||||
|
||||
//matchedFilter(io->chirp_queue.dequeue());
|
||||
matchedFilterFIRFilter(io->chirp_queue.dequeue());
|
||||
|
||||
m_mutex.lock();
|
||||
if (m_stopped) {
|
||||
m_stopped = false;
|
||||
m_mutex.unlock();
|
||||
break;
|
||||
}
|
||||
m_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void ChirpProcessor::matchedFilterFIRFilter(QList<qreal> data) {
|
||||
|
||||
int dataLength = data.length();
|
||||
int dataIdx = 0;
|
||||
int buffer = 0;
|
||||
int newsize = (int)(BUFFERSIZE / m_downRate);
|
||||
|
||||
CHIRP_PROCESSOR_DEBUG << "start matched filter - samples:" << dataLength;
|
||||
|
||||
// filtering to 2kHz by constant-overlap-add (COLA)
|
||||
while (dataIdx < dataLength) {
|
||||
|
||||
dataIdx = 2 * buffer * BUFFERSIZE;
|
||||
for (int i = 0; i < 2 * BUFFERSIZE; i += 2) {
|
||||
|
||||
if (dataIdx + i + 1 < dataLength) {
|
||||
|
||||
m_cpxIn[i/2].re = data.at(dataIdx + i);
|
||||
m_cpxIn[i/2].im = data.at(dataIdx + i + 1);
|
||||
}
|
||||
else {
|
||||
|
||||
dataIdx = dataLength;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// BPF 2.5 kHz
|
||||
m_filter->ProcessFilter(m_cpxIn, m_tmp0, BUFFERSIZE);
|
||||
|
||||
// decimate by 4
|
||||
decimate(m_tmp0, m_tmp2, BUFFERSIZE, m_downRate);
|
||||
|
||||
for (int i = 0; i < newsize; i++) {
|
||||
|
||||
m_cpxInFilt[buffer * newsize + i].re = m_tmp2[i].re;
|
||||
m_cpxInFilt[buffer * newsize + i].im = m_tmp2[i].im;
|
||||
}
|
||||
buffer++;
|
||||
}
|
||||
|
||||
// Due to the COLA method the filter output gets shifet by BUFFERSIZE/2 bytes.
|
||||
// In order to have a buffer in full chirp length, we save the last newsize/2 bytes
|
||||
// (due to decimation by downrate) and add it to the beginning of the newbuffer.
|
||||
for (int i = 0; i < FULL_BUFFERSIZE - newsize/2; i++) {
|
||||
|
||||
if (i < newsize/2) {
|
||||
|
||||
m_cpxInFilt[i].re = m_tmp3[i].re;
|
||||
m_cpxInFilt[i].im = m_tmp3[i].im;
|
||||
}
|
||||
else {
|
||||
|
||||
m_cpxInFilt[i].re = m_cpxInFilt[i + newsize/2].re;
|
||||
m_cpxInFilt[i].im = m_cpxInFilt[i + newsize/2].im;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// save the last buffer
|
||||
for (int i = 0; i < newsize/2; i++) {
|
||||
|
||||
m_tmp3[i].re = m_tmp2[i].re;
|
||||
m_tmp3[i].im = m_tmp2[i].im;
|
||||
}
|
||||
|
||||
// map rx signal to the frequency domain
|
||||
m_matchedFFT->DoFFTWForward(m_cpxInFilt, m_cpxRxFFT, FULL_BUFFERSIZE);
|
||||
|
||||
// multiply the chirp signal with the complex conjugate of the received signal
|
||||
for (int i = 0; i < FULL_BUFFERSIZE; i++) {
|
||||
|
||||
// f * ~g
|
||||
m_tmp1[i].re = (m_cpxChirpOut[i].re * m_cpxRxFFT[i].re) + (m_cpxChirpOut[i].im * m_cpxRxFFT[i].im);
|
||||
m_tmp1[i].im = (m_cpxChirpOut[i].im * m_cpxRxFFT[i].re) - (m_cpxChirpOut[i].re * m_cpxRxFFT[i].im);
|
||||
|
||||
// ~f * g
|
||||
//m_tmp1[i].re = (m_cpxRxFFT[i].re * m_cpxChirpOut[i].re) + (m_cpxRxFFT[i].im * m_cpxChirpOut[i].im);
|
||||
//m_tmp1[i].im = (m_cpxRxFFT[i].im * m_cpxChirpOut[i].re) - (m_cpxRxFFT[i].re * m_cpxChirpOut[i].im);
|
||||
|
||||
// f * g
|
||||
//m_tmp1[i].re = (m_cpxChirpOut[i].re * m_cpxRxFFT[i].re) - (m_cpxChirpOut[i].im * m_cpxRxFFT[i].im);
|
||||
//m_tmp1[i].im = (m_cpxChirpOut[i].re * m_cpxRxFFT[i].im) + (m_cpxChirpOut[i].im * m_cpxRxFFT[i].re);
|
||||
}
|
||||
|
||||
// map back to time domain
|
||||
m_matchedFFT->DoFFTWInverse(m_tmp1, m_cpxOut, FULL_BUFFERSIZE);
|
||||
|
||||
float max = -1000;
|
||||
float min = 1000;
|
||||
float mean = 0.0f;
|
||||
float oneOverNorm = 1.0f / FULL_BUFFERSIZE;
|
||||
|
||||
for (int i = 0; i < FULL_BUFFERSIZE; i++) {
|
||||
|
||||
if (!m_showChirpFFT) {
|
||||
|
||||
m_spectrumBufferFull[i] = (float)(10.0 * log10(MagCPX(ScaleCPX(m_cpxOut[i], oneOverNorm)) + 1.5E-45));
|
||||
|
||||
if (m_spectrumBufferFull[i] > max) max = m_spectrumBufferFull[i];
|
||||
if (m_spectrumBufferFull[i] < min) min = m_spectrumBufferFull[i];
|
||||
mean += m_spectrumBufferFull[i];
|
||||
}
|
||||
else {
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(SqrMagCPX(m_tmp1[i]) + 1.5E-45));
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(SqrMagCPX(ScaleCPX(m_tmp1[i], oneOverNorm)) + 1.5E-45));
|
||||
m_spectrumBufferFull[i] = (float)(10.0 * log10(MagCPX(m_cpxRxFFT[i]) + 1.5E-45));
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(SqrMagCPX(m_cpxChirpOut[i]) + 1.5E-45) + 50.0f);
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(SqrMagCPX(SubCPX(m_cpxChirpOut[i], m_cpxRxFFT[i])) + 1.5E-45) + 50.0f);
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(MagCPX(m_cpxInFilt[i]) + 1.5E-45));
|
||||
//m_spectrumBufferFull[i] = (float)(10.0 * log10(MagCPX(m_cpxIn[i]) + 1.5E-45));
|
||||
}
|
||||
}
|
||||
|
||||
mean *= 1.0f/FULL_BUFFERSIZE;
|
||||
CHIRP_PROCESSOR_DEBUG << "dist min" << min << "max" << max << "mean" << mean;
|
||||
CHIRP_PROCESSOR_DEBUG << "dist delta" << max - mean;
|
||||
|
||||
// we take the first half of the matched filter output to display
|
||||
memcpy(m_spectrumBuffer, m_spectrumBufferFull, (FULL_BUFFERSIZE/2) * sizeof(float));
|
||||
|
||||
// we take the full length for the frequency spectrum,
|
||||
// because we want to see positive as well as negative spectras
|
||||
if (m_showChirpFFT) {
|
||||
|
||||
int topsize = FULL_BUFFERSIZE - 1;
|
||||
|
||||
// reorder the RX FFT buffer
|
||||
for (int i = 0; i < FULL_BUFFERSIZE/2; i++) {
|
||||
|
||||
m_fftSpectrumBuffer[topsize - i] = m_spectrumBufferFull[i + FULL_BUFFERSIZE/2];
|
||||
m_fftSpectrumBuffer[FULL_BUFFERSIZE/2 - i] = m_spectrumBufferFull[i];
|
||||
}
|
||||
}
|
||||
setSpectras(m_spectrumBuffer, m_fftSpectrumBuffer);
|
||||
}
|
||||
|
||||
void ChirpProcessor::setSpectras(const float *distance, const float *chirpfft) {
|
||||
|
||||
if (m_showChirpFFT)
|
||||
set->setChirpSpectrumBuffer(m_sampleRate/m_downRate, FULL_BUFFERSIZE, chirpfft);
|
||||
|
||||
else {
|
||||
if (m_specAvgLength > 1)
|
||||
//spectrumAveraging(FULL_BUFFERSIZE, distance);
|
||||
spectrumAveraging(FULL_BUFFERSIZE/2, distance);
|
||||
else
|
||||
//set->setChirpSpectrumBuffer(m_sampleRate/m_downRate, FULL_BUFFERSIZE, distance);
|
||||
set->setChirpSpectrumBuffer(m_sampleRate/m_downRate, FULL_BUFFERSIZE/2, distance);
|
||||
}
|
||||
}
|
||||
|
||||
void ChirpProcessor::spectrumAveraging(qint64 length, const float *buffer) {
|
||||
|
||||
QVector<float> m_specBuf(length);
|
||||
|
||||
m_mutex.lock();
|
||||
|
||||
memcpy(
|
||||
(float *) m_specBuf.data(),
|
||||
(float *) &buffer[0],
|
||||
length * sizeof(float));
|
||||
|
||||
average_queue.enqueue(m_specBuf);
|
||||
|
||||
float specMax = 0.0f;
|
||||
float specMin = 0.0f;
|
||||
float specMean = 0.0f;
|
||||
if (average_queue.size() <= m_specAvgLength) {
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
m_tmpBuf[i] += average_queue.last().data()[i];
|
||||
m_outBuf[i] = m_tmpBuf[i] * (1.0f/average_queue.size());
|
||||
specMean += m_outBuf[i];
|
||||
|
||||
if (m_outBuf[i] > specMax) specMax = m_outBuf[i];
|
||||
if (m_outBuf[i] < specMin) specMin = m_outBuf[i];
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
|
||||
specMean *= 1.0f/FULL_BUFFERSIZE;
|
||||
CHIRP_PROCESSOR_DEBUG << "distance spectrum averaging size" << average_queue.size();
|
||||
CHIRP_PROCESSOR_DEBUG << "specMin =" << specMin << "specMax =" << specMax;
|
||||
CHIRP_PROCESSOR_DEBUG << "distance spectrum mean value" << specMean;
|
||||
set->setChirpSpectrumBuffer(m_sampleRate, FULL_BUFFERSIZE, m_outBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
m_tmpBuf[i] -= average_queue.first().at(i);
|
||||
m_tmpBuf[i] += average_queue.last().at(i);
|
||||
m_outBuf[i] = m_tmpBuf[i] * m_scale;
|
||||
specMean += m_outBuf[i];
|
||||
}
|
||||
|
||||
//set->setChirpSpectrumBuffer(m_sampleRate, FULL_BUFFERSIZE, m_outBuf);
|
||||
set->setChirpSpectrumBuffer(m_sampleRate, FULL_BUFFERSIZE/2, m_outBuf);
|
||||
average_queue.dequeue();
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void ChirpProcessor::generateLocalChirp() {
|
||||
|
||||
const int sampleRate = m_sampleRate / m_downRate;
|
||||
|
||||
//memset(m_cpxChirpIn, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
//memset(m_cpxChirpOut, 0, FULL_BUFFERSIZE * sizeof(CPX));
|
||||
m_cpxChirpIn.resize(FULL_BUFFERSIZE);
|
||||
m_cpxChirpOut.resize(FULL_BUFFERSIZE);
|
||||
|
||||
|
||||
qreal time = set->getChirpBufferDurationUs() / 1.0E6;
|
||||
qint64 length = (qint64)(sampleRate * time);
|
||||
|
||||
qreal a = ONEPI * (set->getUpperChirpFreq() - set->getLowerChirpFreq()) / time;
|
||||
qreal b = TWOPI * set->getLowerChirpFreq();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
// forward chirp
|
||||
qreal t = (qreal)(1.0f * i/length);
|
||||
// backward chirp
|
||||
//qreal t = (qreal)(1.0f * (length-i)/length);
|
||||
|
||||
// complex chirp signal
|
||||
m_cpxChirpIn[i].re = qSin(a * t * t + b * t);
|
||||
m_cpxChirpIn[i].im = qCos(a * t * t + b * t);
|
||||
|
||||
//m_cpxChirpTmp[i].re = qSin(a * t * t + b * t);
|
||||
//m_cpxChirpTmp[i].im = qCos(a * t * t + b * t);
|
||||
|
||||
//m_cpxChirpIn[i].re = qCos(a * t * t + b * t);
|
||||
//m_cpxChirpIn[i].im = qSin(a * t * t + b * t);
|
||||
|
||||
//m_cpxChirpIn[i].re = qSin(a * t * t + b * t);
|
||||
//m_cpxChirpIn[i].im = -qCos(a * t * t + b * t);
|
||||
}
|
||||
|
||||
//m_specBufferSize = setSpectrumBufferSize(length);
|
||||
|
||||
// BPF 2.5 kHz
|
||||
//m_filter->ProcessFilter(m_cpxChirpTmp, m_cpxChirpIn, FULL_BUFFERSIZE);
|
||||
|
||||
// transform chirp signal to frequency domain
|
||||
m_chirpFFT->DoFFTWForward(m_cpxChirpIn, m_cpxChirpOut, FULL_BUFFERSIZE);
|
||||
|
||||
float dur = set->getChirpBufferDurationUs() / 1000.0f;
|
||||
|
||||
CHIRP_PROCESSOR_DEBUG << "chirp buffer changed:";
|
||||
CHIRP_PROCESSOR_DEBUG << " bufferLength" << length;
|
||||
//CHIRP_PROCESSOR_DEBUG << " spectrum buffer size" << m_specBufferSize;
|
||||
CHIRP_PROCESSOR_DEBUG << " start frequency (Hz)" << set->getLowerChirpFreq();
|
||||
CHIRP_PROCESSOR_DEBUG << " end frequency (Hz)" << set->getUpperChirpFreq();
|
||||
CHIRP_PROCESSOR_DEBUG << " duration (ms)" << dur;
|
||||
}
|
||||
|
||||
//void ChirpProcessor::decimate(CPX *in, CPX *out, int size, int downrate) {
|
||||
void ChirpProcessor::decimate(const CPX &in, CPX &out, int size, int downrate) {
|
||||
|
||||
int newsize = size / downrate;
|
||||
|
||||
//memset(out, 0, newsize * sizeof(CPX));
|
||||
out.resize(newsize);
|
||||
|
||||
for (int j = 0; j < newsize; j++) {
|
||||
for (int k = 0; k < downrate; k++) {
|
||||
|
||||
if (j * downrate + k < size) {
|
||||
|
||||
/*out[j].re += m_tmpDec[j * downrate + k].re;
|
||||
out[j].im += m_tmpDec[j * downrate + k].im;*/
|
||||
out[j].re += in[j * downrate + k].re;
|
||||
out[j].im += in[j * downrate + k].im;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ChirpProcessor::decimate(QList<qreal> data, CPX *out, int downrate) {
|
||||
void ChirpProcessor::decimate(const QList<qreal> &data, CPX &out, int downrate) {
|
||||
|
||||
//memset(out, 0, 16 * BUFFER_SIZE * sizeof(CPX));
|
||||
out.resize(16 * BUFFER_SIZE);
|
||||
|
||||
for (int j = 0; j < data.length()/4; j += 2) {
|
||||
for (int k = 0; k < downrate; k++) {
|
||||
|
||||
if (j * downrate + 2*k < data.length()) {
|
||||
|
||||
out[j/2].re += data.at(j * downrate + 2*k);
|
||||
out[j/2].im += data.at(j * downrate + 2*k + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChirpProcessor::samplingRateChanged(QObject *sender, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
m_sampleRate = value;
|
||||
}
|
||||
|
||||
void ChirpProcessor::setDistSpectrumAvgLength(int value) {
|
||||
|
||||
m_mutex.lock();
|
||||
|
||||
//for (int i = 0; i < SAMPLE_BUFFER_SIZE; i++) m_tmpBuf[i] = 0.0f;
|
||||
memset(m_tmpBuf, 0, FULL_BUFFERSIZE * sizeof(float));
|
||||
|
||||
while (!average_queue.isEmpty())
|
||||
average_queue.dequeue();
|
||||
|
||||
m_specAvgLength = value;
|
||||
|
||||
if (m_specAvgLength > 0)
|
||||
m_scale = 1.0f / m_specAvgLength;
|
||||
else
|
||||
m_scale = 1.0f;
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void ChirpProcessor::setChirpFFTShow(bool value) {
|
||||
|
||||
m_showChirpFFT = value;
|
||||
|
||||
}
|
||||
|
||||
void ChirpProcessor::setChirpSideband(bool value) {
|
||||
|
||||
if (value)
|
||||
m_chirpSidebandFactor = 1.0f;
|
||||
else
|
||||
m_chirpSidebandFactor = -1.0f;
|
||||
|
||||
generateLocalChirp();
|
||||
}
|
||||
|
||||
void ChirpProcessor::setFilterLowerFrequency(int value) {
|
||||
|
||||
if ((int)m_filterLowerFrequency == value) return;
|
||||
m_filterLowerFrequency = 1.0f * value;
|
||||
|
||||
m_filter->setFilter(m_filterLowerFrequency, m_filterUpperFrequency);
|
||||
}
|
||||
|
||||
void ChirpProcessor::setFilterUpperFrequency(int value) {
|
||||
|
||||
if ((int)m_filterUpperFrequency == value) return;
|
||||
m_filterUpperFrequency = 1.0f * value;
|
||||
|
||||
m_filter->setFilter(m_filterLowerFrequency, m_filterUpperFrequency);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @file cusdr_chirpProcessor.h
|
||||
* @brief chirp processor header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-09-22
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CUSDR_CHIRPPROCESSOR_H
|
||||
#define _CUSDR_CHIRPPROCESSOR_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QMutex>
|
||||
//#include <QByteArray>
|
||||
//#include <QBuffer>
|
||||
//#include <QVector>
|
||||
//#include <QList>
|
||||
//#include <QWaitCondition>
|
||||
//#include <QThread>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
#include "QtDSP/qtdsp_qComplex.h"
|
||||
#include "QtDSP/qtdsp_filter.h"
|
||||
#include "QtDSP/qtdsp_fft.h"
|
||||
|
||||
#ifdef LOG_CHIRP_PROCESSOR
|
||||
# define CHIRP_PROCESSOR_DEBUG qDebug().nospace() << "ChirpProcessor::\t"
|
||||
#else
|
||||
# define CHIRP_PROCESSOR_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
#define FULL_BUFFERSIZE 16384//65536
|
||||
#define HALF_BUFFERSIZE 32768
|
||||
#define BUFFERSIZE 2048
|
||||
|
||||
class ChirpProcessor : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChirpProcessor(THPSDRParameter *ioData = 0);
|
||||
~ChirpProcessor();
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
void processChirpData();
|
||||
void generateLocalChirp();
|
||||
|
||||
private slots:
|
||||
void matchedFilterFIRFilter(QList<qreal> data);
|
||||
void samplingRateChanged(QObject *sender, int value);
|
||||
void setSpectras(const float *distance, const float *chirpfft);
|
||||
void setDistSpectrumAvgLength(int value);
|
||||
void setChirpFFTShow(bool value);
|
||||
void setChirpSideband(bool value);
|
||||
void setFilterLowerFrequency(int value);
|
||||
void setFilterUpperFrequency(int value);
|
||||
|
||||
private:
|
||||
Settings *set;
|
||||
|
||||
QMutex m_mutex;
|
||||
QString m_message;
|
||||
|
||||
QFFT *m_chirpFFT;
|
||||
QFFT *m_matchedFFT;
|
||||
QFilter *m_filter;
|
||||
|
||||
CPX m_tmp0;
|
||||
CPX m_tmp1;
|
||||
CPX m_tmp2;
|
||||
CPX m_tmp3;
|
||||
CPX m_tmpDec;
|
||||
|
||||
CPX m_cpxRxFFT;
|
||||
CPX m_cpxChirpIn;
|
||||
CPX m_cpxChirpTmp;
|
||||
CPX m_cpxChirpOut;
|
||||
CPX m_cpxIn;
|
||||
CPX m_cpxInFilt;
|
||||
CPX m_cpxOut;
|
||||
|
||||
THPSDRParameter *io;
|
||||
|
||||
QQueue<QVector<float> > average_queue;
|
||||
|
||||
float m_tmpBuf[FULL_BUFFERSIZE];
|
||||
float m_outBuf[FULL_BUFFERSIZE];
|
||||
float m_spectrumBuffer[FULL_BUFFERSIZE];
|
||||
float m_fftSpectrumBuffer[FULL_BUFFERSIZE];
|
||||
float m_spectrumBufferFull[FULL_BUFFERSIZE];
|
||||
float *m_window;
|
||||
|
||||
volatile bool m_stopped;
|
||||
|
||||
float m_specMax;
|
||||
float m_specMin;
|
||||
float m_scale;
|
||||
|
||||
int m_sampleRate;
|
||||
int m_downSampleRate;
|
||||
int m_downRate;
|
||||
int m_specBufferSize;
|
||||
int m_specAvgLength;
|
||||
float m_filterLowerFrequency;
|
||||
float m_filterUpperFrequency;
|
||||
|
||||
bool m_switch;
|
||||
bool m_showChirpFFT;
|
||||
|
||||
|
||||
qint64 m_chirpBufferLength;
|
||||
qreal m_chirpSidebandFactor;
|
||||
|
||||
void setupConnections();
|
||||
void decimate(const CPX &in, CPX &out, int size, int downrate);
|
||||
void decimate(const QList<qreal> &data, CPX &out, int downrate);
|
||||
void spectrumAveraging(qint64 length, const float *buffer);
|
||||
|
||||
int setSpectrumBufferSize(int size);
|
||||
|
||||
signals:
|
||||
void messageEvent(QString message);
|
||||
};
|
||||
|
||||
#endif // _CUSDR_CHIRPPROCESSOR_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* @file cusdr_dataEngine.h
|
||||
* @brief cuSDR data engine header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-02-02
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright 2010 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* using original C code by John Melton, G0ORX/N6LYT and Dave McMcQuate, WA8YWQ
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CUSDR_DATA_ENGINE_H
|
||||
#define _CUSDR_DATA_ENGINE_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QThread>
|
||||
//#include <QMetaType>
|
||||
//#include <QtNetwork>
|
||||
//#include <QHostAddress>
|
||||
//#include <QMutexLocker>
|
||||
//#include <QMutex>
|
||||
//#include <QWaitCondition>
|
||||
//#include <QVariant>
|
||||
//#include <QElapsedTimer>
|
||||
//#include <QFuture>
|
||||
//#include <qtconcurrentrun.h>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
#include "cusdr_dataIO.h"
|
||||
#include "cusdr_receiver.h"
|
||||
#include "cusdr_chirpProcessor.h"
|
||||
#include "cusdr_audioReceiver.h"
|
||||
#include "cusdr_discoverer.h"
|
||||
#include "Util/qcircularbuffer.h"
|
||||
#include "QtDSP/qtdsp_fft.h"
|
||||
#include "QtDSP/qtdsp_filter.h"
|
||||
#include "QtDSP/qtdsp_dualModeAverager.h"
|
||||
#include "AudioEngine/cusdr_audio_engine.h"
|
||||
|
||||
#ifdef LOG_DATA_ENGINE
|
||||
# define DATA_ENGINE_DEBUG qDebug().nospace() << "DataEngine::\t"
|
||||
#else
|
||||
# define DATA_ENGINE_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
#ifdef LOG_DATA_PROCESSOR
|
||||
# define DATA_PROCESSOR_DEBUG qDebug().nospace() << "DataProcessor::\t"
|
||||
#else
|
||||
# define DATA_PROCESSOR_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
#ifdef LOG_WIDEBAND_PROCESSOR
|
||||
# define WIDEBAND_PROCESSOR_DEBUG qDebug().nospace() << "WidebandProcessor::\t"
|
||||
#else
|
||||
# define WIDEBAND_PROCESSOR_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
class DataProcessor;
|
||||
class AudioOutProcessor;
|
||||
class WideBandDataProcessor;
|
||||
|
||||
|
||||
//Q_DECLARE_METATYPE (QAbstractSocket::SocketError)
|
||||
|
||||
|
||||
// *********************************************************************
|
||||
// data engine class
|
||||
|
||||
class DataEngine : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DataEngine(QObject* parent = 0);
|
||||
~DataEngine();
|
||||
|
||||
Settings* set;
|
||||
THPSDRParameter io;
|
||||
|
||||
QList<Receiver *> RX;
|
||||
QList<qreal> chirpData;
|
||||
|
||||
QUdpSocket* sendSocket;
|
||||
DataIO* m_dataIO;
|
||||
|
||||
public slots:
|
||||
bool initDataEngine();
|
||||
void stop();
|
||||
|
||||
// set Server parameter
|
||||
void setRxPeerAddress(int rx, QHostAddress address);
|
||||
void setRxClient(int rx, int client);
|
||||
void setRx(int rx);
|
||||
void setRxSocketState(int rx, const char* prop, QString);
|
||||
|
||||
//void setSendIQSignal(QObject *sender, int value);
|
||||
void setRcveIQSignal(QObject *sender, int value);
|
||||
void setAudioReceiver(QObject *sender, int rx);
|
||||
//void setAudioInProcessorRunning(bool value);
|
||||
void setIQPort(int rx, int port);
|
||||
void setRxConnectedStatus(QObject* sender, int rx, bool value);
|
||||
void setClientConnected(QObject* sender, int rx);
|
||||
void setClientConnected(bool value);
|
||||
void setClientDisconnected(int client);
|
||||
void setFramesPerSecond(QObject *sender, int rx, int value);
|
||||
void createChirpDataProcessor();
|
||||
|
||||
// DSP processing
|
||||
void processFileBuffer(const QList<qreal> data);
|
||||
|
||||
// change HPSDR hardware settings
|
||||
void setPenelopeVersion(QObject *sender, int version);
|
||||
void setHwIOVersion(QObject *sender, int version);
|
||||
void setNumberOfRx(QObject *sender, int value);
|
||||
void setSampleRate(QObject *sender, int value);
|
||||
void setMercuryAttenuator(QObject *sender, HamBand band, int value);
|
||||
void setDither(QObject *sender, int value);
|
||||
void setRandom(QObject *sender, int value);
|
||||
void setTimeStamp(QObject *sender, bool value);
|
||||
void set10MhzSource(QObject *sender, int source);
|
||||
void set122_88MhzSource(QObject *sender, int source);
|
||||
void setMicSource(QObject *sender, int source);
|
||||
void setMercuryClass(QObject *sender, int value);
|
||||
void setMercuryTiming(QObject* sender, int value);
|
||||
void setHamBand(QObject *sender, int rx, bool byBtn, HamBand band);
|
||||
void setFrequency(QObject* sender, int mode, int rx, long frequency);
|
||||
|
||||
void loadWavFile(const QString &fileName);
|
||||
void suspend();
|
||||
void startPlayback();
|
||||
void showSettingsDialog();
|
||||
|
||||
private:
|
||||
void setSystemState(
|
||||
QSDR::_Error err,
|
||||
QSDR::_HWInterfaceMode hwmode,
|
||||
QSDR::_ServerMode mode,
|
||||
QSDR::_DataEngineState state);
|
||||
|
||||
void initAudioEngine();
|
||||
void setupConnections();
|
||||
void connectDSPSlots();
|
||||
void disconnectDSPSlots();
|
||||
void createDiscoverer();
|
||||
void createDataIO();
|
||||
void createDataProcessor();
|
||||
void createAudioOutProcessor();
|
||||
void createWideBandDataProcessor();
|
||||
//void createChirpDataProcessor();
|
||||
//void createAudioReceiver(int rx);
|
||||
void createAudioReceiver();
|
||||
|
||||
bool initReceivers(int rx);
|
||||
bool start();
|
||||
bool startDataEngineWithoutConnection();
|
||||
bool findHPSDRDevices();
|
||||
bool getFirmwareVersions();
|
||||
bool checkFirmwareVersions();
|
||||
bool startDiscoverer(QThread::Priority prio);
|
||||
bool startDataIO(QThread::Priority prio);
|
||||
bool startDataProcessor(QThread::Priority prio);
|
||||
void startAudioOutProcessor(QThread::Priority prio);
|
||||
bool startWideBandDataProcessor(QThread::Priority prio);
|
||||
bool startChirpDataProcessor(QThread::Priority prio);
|
||||
|
||||
void stopDiscoverer();
|
||||
void stopDataIO();
|
||||
void stopDataProcessor();
|
||||
void stopAudioOutProcessor();
|
||||
void stopWideBandDataProcessor();
|
||||
void stopChirpDataProcessor();
|
||||
void setHPSDRConfig();
|
||||
|
||||
private:
|
||||
DataProcessor* m_dataProcessor;
|
||||
WideBandDataProcessor* m_wbDataProcessor;
|
||||
QDSPEngine* m_chirpDspEngine;
|
||||
AudioReceiver* m_audioReceiver;
|
||||
AudioEngine* m_audioEngine;
|
||||
AudioOutProcessor* m_audioOutProcessor;
|
||||
ChirpProcessor* m_chirpProcessor;
|
||||
Discoverer* m_discoverer;
|
||||
|
||||
QThreadEx* m_discoveryThread;
|
||||
QThreadEx* m_dataIOThread;
|
||||
QThreadEx* m_dataProcThread;
|
||||
QThreadEx* m_wbDataProcThread;
|
||||
QThreadEx* m_chirpDataProcThread;
|
||||
QThreadEx* m_AudioRcvrThread;
|
||||
QThreadEx* m_audioInProcThread;
|
||||
QThreadEx* m_audioOutProcThread;
|
||||
|
||||
QList<QThreadEx* > m_dspThreadList;
|
||||
|
||||
QMutex m_mutex;
|
||||
|
||||
QString m_message;
|
||||
QString m_HPSDRDevice;
|
||||
|
||||
QByteArray m_commandDatagram;
|
||||
QByteArray m_datagram;
|
||||
|
||||
QSDR::_Error m_error;
|
||||
QSDR::_ServerMode m_serverMode;
|
||||
QSDR::_HWInterfaceMode m_hwInterface;
|
||||
QSDR::_DataEngineState m_dataEngineState;
|
||||
|
||||
QCircularBuffer<int> audioringbuffer;
|
||||
|
||||
TMeterType m_meterType;
|
||||
|
||||
CPX cpxIn;
|
||||
CPX cpxOut;
|
||||
|
||||
bool m_restart;
|
||||
bool m_networkDeviceRunning;
|
||||
bool m_soundFileLoaded;
|
||||
bool m_clientConnect;
|
||||
//bool m_audioProcessorRunning;
|
||||
bool m_chirpInititalized;
|
||||
bool m_discoveryThreadRunning;
|
||||
bool m_dataIOThreadRunning;
|
||||
bool m_wbDataRcvrThreadRunning;
|
||||
bool m_chirpDataProcThreadRunning;
|
||||
bool m_dataProcThreadRunning;
|
||||
bool m_audioRcvrThreadRunning;
|
||||
bool m_audioInProcThreadRunning;
|
||||
bool m_audioOutProcThreadRunning;
|
||||
bool m_frequencyChange;
|
||||
bool m_hamBandChanged;
|
||||
bool m_chirpThreadStopped;
|
||||
bool m_clientConnected;
|
||||
|
||||
float m_mainVolume;
|
||||
|
||||
int m_hpsdrDevices;
|
||||
int m_fwCount;
|
||||
int m_configure;
|
||||
int m_timeout;
|
||||
int m_txFrame;
|
||||
int m_bytes;
|
||||
int m_remainingTime;
|
||||
int m_found;
|
||||
int m_RxFrequencyChange;
|
||||
int m_counter;
|
||||
|
||||
int m_forwardPower;
|
||||
int m_maxSamples;
|
||||
int m_offset;
|
||||
|
||||
int m_rxSamples;
|
||||
int m_chirpSamples;
|
||||
|
||||
int m_leftSample;
|
||||
int m_rightSample;
|
||||
int m_micSample;
|
||||
|
||||
int m_spectrumSize;
|
||||
int m_sendState;
|
||||
|
||||
float m_lsample;
|
||||
float m_rsample;
|
||||
float m_scale;
|
||||
float m_sMeterValue;
|
||||
float m_sMeterCalibrationOffset;
|
||||
float m_micSample_float;
|
||||
float m_spectrumBuffer[SAMPLE_BUFFER_SIZE];
|
||||
|
||||
qint64 m_audioFileBufferPosition;
|
||||
qint64 m_audioFileBufferLength;
|
||||
QByteArray m_audioFileBuffer;
|
||||
|
||||
float getFilterSizeCalibrationOffset();
|
||||
|
||||
private slots:
|
||||
void systemStateChanged(
|
||||
QObject *sender,
|
||||
QSDR::_Error err,
|
||||
QSDR::_HWInterfaceMode hwmode,
|
||||
QSDR::_ServerMode mode,
|
||||
QSDR::_DataEngineState state);
|
||||
|
||||
//void setCurrentNetworkDevice(TNetworkDevicecard card);
|
||||
void setHPSDRDeviceNumber(int value);
|
||||
void rxListChanged(QList<Receiver *> rxList);
|
||||
void searchHpsdrNetworkDevices();
|
||||
void setCurrentReceiver(QObject* sender, int rx);
|
||||
|
||||
void setMercuryAttenuators(QObject *sender, QList<int> attn);
|
||||
void setAlexConfiguration(quint16 conf);
|
||||
void setAlexStates(HamBand band, const QList<int> &states);
|
||||
void setPennyOCEnabled(bool value);
|
||||
void setRxJ6Pins(const QList<int> &list);
|
||||
void setTxJ6Pins(const QList<int> &list);
|
||||
|
||||
void setAudioFileFormat(QObject *sender, const QAudioFormat &format);
|
||||
void setAudioFilePosition(QObject *sender, qint64 position);
|
||||
void setAudioFileBuffer(QObject *sender, qint64 position, qint64 length, const QByteArray &buffer);
|
||||
|
||||
void setAudioFileBuffer(const QList<qreal> &buffer);
|
||||
|
||||
signals:
|
||||
void error(QUdpSocket::SocketError error);
|
||||
void masterSwitchEvent(QObject *sender, bool power);
|
||||
//void messageEvent(QString message);
|
||||
void penelopeVersionInfoEvent(QObject *sender, int version);
|
||||
void hwIOVersionInfoEvent(QObject *sender, int version);
|
||||
void sendIQEvent(QObject *sender, int sendIQ);
|
||||
void rcveIQEvent(QObject *sender, int value);
|
||||
//void iqDataReady(int rx);
|
||||
void chirpDataReady(int samples);
|
||||
void audioDataReady();
|
||||
void clientConnectedEvent(int rx);
|
||||
void audioRxEvent(int rx);
|
||||
void outMultiplierEvent(int value);
|
||||
void systemMessageEvent(const QString &str, int time);
|
||||
void clearSystemMessageEvent();
|
||||
void DataProcessorReadyEvent();
|
||||
void audioSenderReadyEvent(bool value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// *********************************************************************
|
||||
// Data processor class
|
||||
|
||||
class DataProcessor : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DataProcessor(
|
||||
DataEngine* de = 0,
|
||||
QSDR::_ServerMode serverMode = QSDR::NoServerMode,
|
||||
QSDR::_HWInterfaceMode hwMode = QSDR::NoInterfaceMode);
|
||||
|
||||
~DataProcessor();
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
void processData();
|
||||
void processDeviceData();
|
||||
void externalDspProcessing(int rx);
|
||||
void externalDspProcessingBig(int rx);
|
||||
|
||||
private slots:
|
||||
void initDataProcessorSocket();
|
||||
void displayDataProcessorSocketError(QAbstractSocket::SocketError error);
|
||||
void processInputBuffer(const QByteArray &buffer);
|
||||
void processOutputBuffer(const CPX &buffer);
|
||||
void decodeCCBytes(const QByteArray &buffer);
|
||||
void encodeCCBytes();
|
||||
void setOutputBuffer(int rx, const CPX &buffer);
|
||||
void writeData();
|
||||
|
||||
private:
|
||||
DataEngine* de;
|
||||
Settings* set;
|
||||
QUdpSocket* m_dataProcessorSocket;
|
||||
|
||||
QSDR::_Error m_error;
|
||||
QSDR::_ServerMode m_serverMode;
|
||||
QSDR::_HWInterfaceMode m_hwInterface;
|
||||
QSDR::_DataEngineState m_dataEngineState;
|
||||
|
||||
QHostAddress m_deviceAddress;
|
||||
QMutex m_mutex;
|
||||
QMutex m_spectrumMutex;
|
||||
QByteArray m_IQDatagram;
|
||||
QByteArray m_outDatagram;
|
||||
QByteArray m_deviceSendDataSignature;
|
||||
QString m_message;
|
||||
|
||||
QTime m_SyncChangedTime;
|
||||
QTime m_ADCChangedTime;
|
||||
|
||||
bool m_socketConnected;
|
||||
bool m_setNetworkDeviceHeader;
|
||||
bool m_chirpGateBit;
|
||||
bool m_chirpBit;
|
||||
bool m_chirpStart;
|
||||
|
||||
int m_leftSample;
|
||||
int m_rightSample;
|
||||
int m_micSample;
|
||||
int m_bytes;
|
||||
int m_maxSamples;
|
||||
int m_rxSamples;
|
||||
int m_chirpSamples;
|
||||
int m_fwCount;
|
||||
int m_idx;
|
||||
int m_sendState;
|
||||
int m_chirpStartSample;
|
||||
|
||||
float m_lsample;
|
||||
float m_rsample;
|
||||
float m_micSample_float;
|
||||
|
||||
unsigned long m_IQSequence;
|
||||
unsigned long m_sequenceHi;
|
||||
unsigned short m_offset;
|
||||
unsigned short m_length;
|
||||
|
||||
long m_sendSequence;
|
||||
long m_oldSendSequence;
|
||||
|
||||
volatile bool m_stopped;
|
||||
|
||||
uchar m_ibuffer[IO_BUFFER_SIZE * IO_BUFFERS];
|
||||
signals:
|
||||
void messageEvent(QString message);
|
||||
void connectingEvent(QString addr, quint16 port);
|
||||
void connectedEvent(QString addr, quint16 port);
|
||||
void disconnectedEvent();
|
||||
void serverVersionEvent(QString version);
|
||||
};
|
||||
|
||||
|
||||
// *********************************************************************
|
||||
// Audio out processor class
|
||||
|
||||
class AudioOutProcessor : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioOutProcessor(DataEngine* de = 0, QSDR::_ServerMode serverMode = QSDR::NoServerMode);
|
||||
~AudioOutProcessor();
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
void processData();
|
||||
void processDeviceData();
|
||||
|
||||
private slots:
|
||||
|
||||
private:
|
||||
DataEngine* m_dataEngine;
|
||||
|
||||
QMutex m_mutex;
|
||||
QByteArray m_IQDatagram;
|
||||
QString m_message;
|
||||
|
||||
QSDR::_ServerMode m_serverMode;
|
||||
|
||||
/*int m_bytes;
|
||||
unsigned long m_IQSequence;
|
||||
unsigned long m_sequenceHi;
|
||||
unsigned short m_offset;
|
||||
unsigned short m_length;*/
|
||||
volatile bool m_stopped;
|
||||
|
||||
signals:
|
||||
//void connectingEvent(QString addr, quint16 port);
|
||||
//void connectedEvent(QString addr, quint16 port);
|
||||
//void disconnectedEvent();
|
||||
//void serverVersionEvent(QString version);
|
||||
////void metisVersionEvent(QObject *sender, int version);
|
||||
//void newData();
|
||||
//void newIQData(int rx);
|
||||
//void newAudioDataEvent(float *lBuf, float *rBuf);
|
||||
};
|
||||
|
||||
// *********************************************************************
|
||||
// Wide band data processor class
|
||||
|
||||
class WideBandDataProcessor : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WideBandDataProcessor(THPSDRParameter *ioData = 0, QSDR::_ServerMode serverMode = QSDR::NoServerMode, int size = 0);
|
||||
~WideBandDataProcessor();
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
void processWideBandData();
|
||||
void setWbSpectrumAveraging(QObject* sender, int rx, bool value);
|
||||
|
||||
private slots:
|
||||
//void initDataProcessorSocket();
|
||||
//void displayDataProcessorSocketError(QAbstractSocket::SocketError error);
|
||||
void processWideBandInputBuffer(const QByteArray &buffer);
|
||||
|
||||
private:
|
||||
THPSDRParameter* io;
|
||||
Settings* set;
|
||||
|
||||
QFFT* wbFFT;
|
||||
DualModeAverager* wbAverager;
|
||||
|
||||
CPX cpxWBIn;
|
||||
CPX cpxWBOut;
|
||||
|
||||
QMutex m_mutex;
|
||||
QByteArray m_WBDatagram;
|
||||
QString m_message;
|
||||
|
||||
QSDR::_ServerMode m_serverMode;
|
||||
|
||||
int m_size;
|
||||
int m_bytes;
|
||||
|
||||
bool m_wbSpectrumAveraging;
|
||||
volatile bool m_stopped;
|
||||
|
||||
unsigned char m_ibuffer[IO_BUFFER_SIZE * IO_BUFFERS];
|
||||
|
||||
signals:
|
||||
void messageEvent(QString message);
|
||||
void wbSpectrumBufferChanged(const qVectorFloat &buffer);
|
||||
};
|
||||
|
||||
|
||||
#endif // _CUSDR_DATA_ENGINE_H
|
||||
@@ -0,0 +1,661 @@
|
||||
/**
|
||||
* @file cusdr_dataIO.cpp
|
||||
* @brief Data IO class
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-10-01
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define LOG_DATAIO
|
||||
|
||||
#include "cusdr_dataIO.h"
|
||||
#include "soundout.h"
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
|
||||
DataIO::DataIO(THPSDRParameter *ioData)
|
||||
: QObject()
|
||||
, set(Settings::instance())
|
||||
, io(ioData)
|
||||
, m_dataIOSocketOn(false)
|
||||
, m_setNetworkDeviceHeader(true)
|
||||
, m_sequence(0)
|
||||
, m_oldSequence(-1)
|
||||
, m_sequenceWideBand(0)
|
||||
, m_oldSequenceWideBand(-1)
|
||||
, m_wbBuffers(set->getWidebandBuffers() - 1)
|
||||
, m_wbCount(0)
|
||||
, m_socketBufferSize(set->getSocketBufferSize())
|
||||
, m_sendEP4(false)
|
||||
, m_manualBufferSize(set->getManualSocketBufferSize())
|
||||
, m_packetsToggle(true)
|
||||
, m_firstFrame(true)
|
||||
, m_stopped(false)
|
||||
{
|
||||
m_dataIOSocket = 0;
|
||||
|
||||
m_metisGetDataSignature.resize(3);
|
||||
m_metisGetDataSignature[0] = (char)0xEF;
|
||||
m_metisGetDataSignature[1] = (char)0xFE;
|
||||
m_metisGetDataSignature[2] = (char)0x01;
|
||||
//m_metisGetDataSignature[3] = (char)0x06;
|
||||
|
||||
m_datagram.resize(1032);
|
||||
m_wbDatagram.resize(0);
|
||||
m_twoFramesDatagram.resize(0);
|
||||
|
||||
m_sendSequence = 0L;
|
||||
m_oldSendSequence = 0L;
|
||||
|
||||
m_deviceSendDataSignature.resize(4);
|
||||
m_deviceSendDataSignature[0] = (char)0xEF;
|
||||
m_deviceSendDataSignature[1] = (char)0xFE;
|
||||
m_deviceSendDataSignature[2] = (char)0x01;
|
||||
m_deviceSendDataSignature[3] = (char)0x02;
|
||||
|
||||
m_packetLossTime.start();
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(sampleRateChanged(QObject *, int)),
|
||||
this,
|
||||
SLOT(setSampleRate(QObject *, int)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(manualSocketBufferChanged(QObject*, bool)),
|
||||
this,
|
||||
SLOT(setManualSocketBufferSize(QObject*, bool)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(socketBufferSizeChanged(QObject*, int)),
|
||||
this,
|
||||
SLOT(setSocketBufferSize(QObject*, int)));
|
||||
|
||||
m_message = "m_sendSequence = %1, bytes sent: %2";
|
||||
|
||||
m_pSoundCardOut = new CSoundOut(this);
|
||||
//RRK pass -1 to get the systems "default" audio device
|
||||
m_pSoundCardOut->Start(-1, true, 48000, false);
|
||||
m_pSoundCardOut->SetVolume(80);
|
||||
}
|
||||
|
||||
DataIO::~DataIO() {
|
||||
|
||||
if (m_dataIOSocketOn) {
|
||||
m_dataIOSocket->close();
|
||||
delete m_dataIOSocket;
|
||||
m_dataIOSocket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DataIO::stop() {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
m_stopped = true;
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
if(m_pSoundCardOut) {
|
||||
m_pSoundCardOut->Stop();
|
||||
delete m_pSoundCardOut;
|
||||
}
|
||||
}
|
||||
|
||||
void DataIO::initDataReceiverSocket() {
|
||||
|
||||
m_dataIOSocket = new QUdpSocket();
|
||||
|
||||
int newBufferSize;
|
||||
|
||||
if (m_manualBufferSize) {
|
||||
|
||||
newBufferSize = m_socketBufferSize * 1024;//m_socketBufferSize * 1032;
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "initDataReceiverSocket socket buffer size set to " << m_socketBufferSize << " kB.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
if (io->samplerate == 384000) {
|
||||
|
||||
newBufferSize = 128*1024;//128 * 1032;
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "socket buffer size set to 128 kB.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else if (io->samplerate == 192000) {
|
||||
|
||||
newBufferSize = 64*1024;//64 * 1032;
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "socket buffer size set to 64 kB.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else if (io->samplerate == 96000) {
|
||||
|
||||
newBufferSize = 32*1024;//32 * 1032;
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "socket buffer size set to 32 kB.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else if (io->samplerate == 48000) {
|
||||
|
||||
newBufferSize = 16*1024;//16 * 1032;
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "socket buffer size set to 16 kB.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_dataIOSocket->bind(QHostAddress(set->getHPSDRDeviceLocalAddr()),
|
||||
set->getMetisPort(),
|
||||
QUdpSocket::DontShareAddress))
|
||||
//QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
|
||||
{
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
if (::setsockopt(m_dataIOSocket->socketDescriptor(), SOL_SOCKET,
|
||||
SO_RCVBUF, (char *)&newBufferSize, sizeof(newBufferSize)) == -1) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "dataIOSocket error!";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//m_dataIOSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
||||
//m_dataIOSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
|
||||
|
||||
CHECKED_CONNECT(
|
||||
m_dataIOSocket,
|
||||
SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this,
|
||||
SLOT(displayDataReceiverSocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
/*CHECKED_CONNECT_OPT(
|
||||
m_dataIOSocket,
|
||||
SIGNAL(readyRead()),
|
||||
this,
|
||||
SLOT(readDeviceData()),
|
||||
Qt::DirectConnection);*/
|
||||
|
||||
CHECKED_CONNECT(
|
||||
m_dataIOSocket,
|
||||
SIGNAL(readyRead()),
|
||||
this,
|
||||
SLOT(readDeviceData()));
|
||||
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "data receiver socket bound successful to local port " << m_dataIOSocket->localPort();
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
m_dataIOSocketOn = true;
|
||||
set->setPacketLoss(1);
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "data receiver socket binding failed.";
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
m_dataIOSocketOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DataIO::readDeviceData() {
|
||||
|
||||
while (m_dataIOSocket->hasPendingDatagrams()) {
|
||||
|
||||
QMutexLocker locker(&io->networkIOMutex);
|
||||
if (m_dataIOSocket->readDatagram(m_datagram.data(), m_datagram.size()) == METIS_DATA_SIZE) {
|
||||
|
||||
if (m_datagram.left(3) == m_metisGetDataSignature) {
|
||||
|
||||
if (m_datagram[3] == (char)0x06) {
|
||||
|
||||
m_sequence = (m_datagram[4] & 0xFF) << 24;
|
||||
m_sequence += (m_datagram[5] & 0xFF) << 16;
|
||||
m_sequence += (m_datagram[6] & 0xFF) << 8;
|
||||
m_sequence += (m_datagram[7] & 0xFF);
|
||||
|
||||
if (m_sequence != m_oldSequence + 1) {
|
||||
|
||||
//DATAIO_DEBUG << "readData missed " << m_sequence - m_oldSequence << " packages.";
|
||||
//RRK cout << "readData missed " << m_sequence - m_oldSequence << " packages." << endl;
|
||||
|
||||
if (m_packetLossTime.elapsed() > 100) {
|
||||
|
||||
set->setPacketLoss(2);
|
||||
m_packetLossTime.restart();
|
||||
}
|
||||
}
|
||||
|
||||
m_oldSequence = m_sequence;
|
||||
|
||||
//// enqueue first half of the HPSDR frame from the HPSDR device
|
||||
//io->iq_queue.enqueue(m_datagram.mid(METIS_HEADER_SIZE, BUFFER_SIZE/2));
|
||||
//// enqueue second half of the HPSDR frame from the HPSDR device
|
||||
//io->iq_queue.enqueue(m_datagram.right(BUFFER_SIZE/2));
|
||||
|
||||
// enqueue one frame from the HPSDR device
|
||||
if (!io->iq_queue.isFull()) {
|
||||
io->iq_queue.enqueue(m_datagram.mid(METIS_HEADER_SIZE, BUFFER_SIZE));
|
||||
}
|
||||
|
||||
// collect two HPSDR frames
|
||||
//if (m_firstFrame) {
|
||||
|
||||
// m_twoFramesDatagram += m_datagram.mid(METIS_HEADER_SIZE, BUFFER_SIZE);
|
||||
// m_firstFrame = false;
|
||||
//}
|
||||
//else {
|
||||
|
||||
// m_twoFramesDatagram += m_datagram.mid(METIS_HEADER_SIZE, BUFFER_SIZE);
|
||||
|
||||
// //enqueue the two frames
|
||||
// io->iq_queue.enqueue(m_twoFramesDatagram);
|
||||
// m_firstFrame = true;
|
||||
|
||||
// m_twoFramesDatagram.resize(0);
|
||||
//}
|
||||
}
|
||||
else if (m_datagram[3] == (char)0x04) { // wide band data
|
||||
|
||||
//qDebug() << "wideband data received!";
|
||||
m_sequenceWideBand = (m_datagram[4] & 0xFF) << 24;
|
||||
m_sequenceWideBand += (m_datagram[5] & 0xFF) << 16;
|
||||
m_sequenceWideBand += (m_datagram[6] & 0xFF) << 8;
|
||||
m_sequenceWideBand += (m_datagram[7] & 0xFF);
|
||||
|
||||
if (m_sequenceWideBand != m_oldSequenceWideBand + 1) {
|
||||
|
||||
DATAIO_DEBUG << "wideband readData missed " << m_sequenceWideBand - m_oldSequenceWideBand << " packages.";
|
||||
|
||||
if (m_packetLossTime.elapsed() > 100) {
|
||||
|
||||
set->setPacketLoss(2);
|
||||
m_packetLossTime.restart();
|
||||
}
|
||||
}
|
||||
|
||||
m_oldSequenceWideBand = m_sequenceWideBand;
|
||||
|
||||
// three 'if's from KISS Konsole
|
||||
if ((m_wbBuffers & m_datagram[7]) == 0)
|
||||
{
|
||||
m_sendEP4 = true;
|
||||
m_wbCount = 0;
|
||||
}
|
||||
|
||||
if (m_sendEP4)
|
||||
{
|
||||
m_wbDatagram.append(m_datagram.mid(METIS_HEADER_SIZE, BUFFER_SIZE));
|
||||
}
|
||||
|
||||
if (m_wbCount++ == m_wbBuffers)
|
||||
{
|
||||
// enqueue
|
||||
m_sendEP4 = false;
|
||||
io->wb_queue.enqueue(m_wbDatagram);
|
||||
m_wbDatagram.resize(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
//DATA_RECEIVER_DEBUG << "got wrong HPSDR device signature!";
|
||||
}
|
||||
//DATA_RECEIVER_DEBUG << "got wrong HPSDR device data size!";
|
||||
}
|
||||
//DATA_RECEIVER_DEBUG << "no more pending datagrams.";
|
||||
}
|
||||
|
||||
void DataIO::readData() {
|
||||
|
||||
qint64 length = io->inputBuffer.length();
|
||||
|
||||
//int buffers = qRound(length/(2*BUFFER_SIZE));
|
||||
int buffers = qRound((float) length/128);
|
||||
|
||||
DATAIO_DEBUG << "input buffer length " << length << " buffers " << buffers;
|
||||
|
||||
while (!m_stopped) {
|
||||
|
||||
for (int i = 0; i < buffers; i++) {
|
||||
|
||||
//io->data_queue.enqueue(io->inputBuffer.mid(i*2*BUFFER_SIZE, 2*BUFFER_SIZE));
|
||||
io->data_queue.enqueue(io->inputBuffer.mid(i*128, 128));
|
||||
if (m_stopped) break;
|
||||
}
|
||||
}
|
||||
m_stopped = false;
|
||||
}
|
||||
|
||||
void DataIO::sendInitFramesToNetworkDevice(int rx) {
|
||||
|
||||
QByteArray initDatagram;
|
||||
initDatagram.resize(1032);
|
||||
|
||||
initDatagram[0] = (char)0xEF;
|
||||
initDatagram[1] = (char)0xFE;
|
||||
initDatagram[2] = (char)0x01;
|
||||
initDatagram[3] = (char)0x02;
|
||||
initDatagram[4] = (char)0x00;
|
||||
initDatagram[5] = (char)0x00;
|
||||
initDatagram[6] = (char)0x00;
|
||||
initDatagram[7] = (char)0x00;
|
||||
|
||||
initDatagram[8] = SYNC;
|
||||
initDatagram[9] = SYNC;
|
||||
initDatagram[10] = SYNC;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
|
||||
initDatagram[i + 11] = io->control_out[i];
|
||||
}
|
||||
|
||||
for (int i = 16; i < 520; i++) {
|
||||
|
||||
initDatagram[i] = 0x00;
|
||||
}
|
||||
|
||||
initDatagram[520] = SYNC;
|
||||
initDatagram[521] = SYNC;
|
||||
initDatagram[522] = SYNC;
|
||||
|
||||
initDatagram[523] = io->control_out[0] | ((rx + 2) << 1);
|
||||
initDatagram[524] = set->getCtrFrequencies().at(rx) >> 24;
|
||||
initDatagram[525] = set->getCtrFrequencies().at(rx) >> 16;
|
||||
initDatagram[526] = set->getCtrFrequencies().at(rx) >> 8;
|
||||
initDatagram[527] = set->getCtrFrequencies().at(rx) ;
|
||||
|
||||
|
||||
for (int i = 528; i < 1032; i++) initDatagram[i] = 0x00;
|
||||
|
||||
// for (int i = 0; i < 5; i++) {
|
||||
//
|
||||
// if (m_dataIOSocket->writeDatagram(initDatagram.data(), initDatagram.size(), io->hpsdrDeviceIPAddress, DEVICE_PORT) < 0) {
|
||||
//
|
||||
// io->networkIOMutex.lock();
|
||||
// DATAIO_DEBUG << "error sending init data to device: " << qPrintable(m_dataIOSocket->errorString());
|
||||
// io->networkIOMutex.unlock();
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// if (i == 0) {
|
||||
//
|
||||
// io->networkIOMutex.lock();
|
||||
// DATAIO_DEBUG << "init frames sent to network device.";
|
||||
// io->networkIOMutex.unlock();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (m_dataIOSocket->writeDatagram(initDatagram.data(), initDatagram.size(), io->hpsdrDeviceIPAddress, DEVICE_PORT) < 0) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "error sending init data to device: " << qPrintable(m_dataIOSocket->errorString());
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "init frames sent to network device.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
SleeperThread::msleep(20);
|
||||
|
||||
if (m_dataIOSocket->writeDatagram(initDatagram.data(), initDatagram.size(), io->hpsdrDeviceIPAddress, DEVICE_PORT) < 0) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "error sending init data to device: " << qPrintable(m_dataIOSocket->errorString());
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "init frames sent to network device.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void DataIO::networkDeviceStartStop(char value) {
|
||||
|
||||
TNetworkDevicecard metis = set->getCurrentMetisCard();
|
||||
//QUdpSocket socket;
|
||||
|
||||
// if (socket.bind(QHostAddress(set->getHPSDRDeviceLocalAddr()),
|
||||
// set->getMetisPort(),
|
||||
// //QUdpSocket::DefaultForPlatform))
|
||||
// QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
|
||||
// {
|
||||
//DATAIO_DEBUG << "device start/stop: socket bound successful to local port " << set->getMetisPort();
|
||||
|
||||
m_commandDatagram.resize(64);
|
||||
m_commandDatagram[0] = (char)0xEF;
|
||||
m_commandDatagram[1] = (char)0xFE;
|
||||
m_commandDatagram[2] = (char)0x04;
|
||||
m_commandDatagram[3] = (char)value;
|
||||
|
||||
for (int i = 4; i < 64; i++) m_commandDatagram[i] = 0x00;
|
||||
|
||||
if (m_dataIOSocket->writeDatagram(m_commandDatagram, metis.ip_address, DEVICE_PORT) == 64) {
|
||||
|
||||
//if (value == 1) {
|
||||
if (value != 0) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "sent start command to device at: "<< qPrintable(metis.ip_address.toString());
|
||||
io->networkIOMutex.unlock();
|
||||
m_networkDeviceRunning = true;
|
||||
}
|
||||
else {
|
||||
|
||||
//DATA_ENGINE_DEBUG << "sent stop command to Metis at"<< m_metisCards[0].ip_address.toString();
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "sent stop command to device at: "<< qPrintable(metis.ip_address.toString());
|
||||
io->networkIOMutex.unlock();
|
||||
m_networkDeviceRunning = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
DATAIO_DEBUG << "device start/stop: sending command to device failed.";
|
||||
|
||||
//socket.close();
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// DATAIO_DEBUG << "device start/stop: socket binding failed.";
|
||||
// }
|
||||
|
||||
// socket.close();
|
||||
// DATAIO_DEBUG << "device start/stop: socket closed.";
|
||||
}
|
||||
|
||||
void DataIO::sendAudio(u_char *buf) {
|
||||
//RRK send audio bytes here
|
||||
static TYPECPX cbuf[252];
|
||||
int i, j;
|
||||
short sample;
|
||||
|
||||
for(i = 8, j = 0; i < 512; i += 8, j++) {
|
||||
//bytes are L,R,I,Q skip the I,Q
|
||||
sample = buf[i] << 8 | buf[i+1]; //left
|
||||
cbuf[j].re = (double)sample;
|
||||
sample = buf[i+2] << 8 | buf[i+3]; //right
|
||||
cbuf[j].im = (double)sample;
|
||||
}
|
||||
|
||||
if(m_pSoundCardOut)
|
||||
m_pSoundCardOut->PutOutQueue(63, cbuf);
|
||||
}
|
||||
|
||||
void DataIO::writeData() {
|
||||
|
||||
if (m_setNetworkDeviceHeader) {
|
||||
|
||||
m_outDatagram.resize(0);
|
||||
m_outDatagram += m_deviceSendDataSignature;
|
||||
|
||||
QByteArray seq(reinterpret_cast<const char*>(&m_sendSequence), sizeof(m_sendSequence));
|
||||
|
||||
m_outDatagram += seq;
|
||||
m_outDatagram += io->audioDatagram;
|
||||
|
||||
m_sendSequence++;
|
||||
m_setNetworkDeviceHeader = false;
|
||||
}
|
||||
else {
|
||||
|
||||
m_outDatagram += io->audioDatagram;
|
||||
|
||||
if (m_dataIOSocket->writeDatagram(m_outDatagram, set->getCurrentMetisCard().ip_address, DEVICE_PORT) < 0) {
|
||||
DATAIO_DEBUG << "error sending data to device: " << m_dataIOSocket->errorString();
|
||||
}
|
||||
|
||||
//if (m_sendSequence%100 == 0)
|
||||
// DATAIO_DEBUG << m_sendSequence;
|
||||
|
||||
if (m_sendSequence != m_oldSendSequence + 1) {
|
||||
DATAIO_DEBUG << "output sequence error: old = " << m_oldSendSequence << "; new =" << m_sendSequence;
|
||||
}
|
||||
|
||||
m_oldSendSequence = m_sendSequence;
|
||||
m_setNetworkDeviceHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DataIO::displayDataReceiverSocketError(QAbstractSocket::SocketError error) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "data IO socket error: " << error;
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
void DataIO::setManualSocketBufferSize(QObject *sender, bool value) {
|
||||
|
||||
Q_UNUSED (sender)
|
||||
|
||||
m_manualBufferSize = value;
|
||||
DATAIO_DEBUG << "m_manualBufferSize to change = " << m_manualBufferSize;
|
||||
int socketBufferSize = 1032 * set->getSocketBufferSize();
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
|
||||
if (m_manualBufferSize) {
|
||||
|
||||
DATAIO_DEBUG << "set data IO socket BufferSize to " << m_socketBufferSize;
|
||||
#if defined(Q_OS_WIN32)
|
||||
if (::setsockopt(m_dataIOSocket->socketDescriptor(), SOL_SOCKET,
|
||||
SO_RCVBUF, (char *)&socketBufferSize, sizeof(socketBufferSize)) == -1) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "dataIOSocket error!";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
|
||||
DATAIO_DEBUG << "set data IO socket BufferSize to 32 kB.";
|
||||
socketBufferSize = 1032 * 32;
|
||||
#if defined(Q_OS_WIN32)
|
||||
if (::setsockopt(m_dataIOSocket->socketDescriptor(), SOL_SOCKET,
|
||||
SO_RCVBUF, (char *)&socketBufferSize, sizeof(socketBufferSize)) == -1) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "dataIOSocket error!";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
void DataIO::setSocketBufferSize(QObject *sender, int value) {
|
||||
|
||||
Q_UNUSED (sender)
|
||||
|
||||
int socketBufferSize = value * 1024;
|
||||
DATAIO_DEBUG << "m_socketBufferSize = " << value;
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
#if defined(Q_OS_WIN32)
|
||||
if (::setsockopt(m_dataIOSocket->socketDescriptor(), SOL_SOCKET,
|
||||
SO_RCVBUF, (char *)&socketBufferSize, sizeof(socketBufferSize)) == -1) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DATAIO_DEBUG << "dataIOSocket error!";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
#endif
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
void DataIO::setSampleRate(QObject *sender, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
int bufferSize;
|
||||
io->networkIOMutex.lock();
|
||||
switch (value) {
|
||||
|
||||
case 48000:
|
||||
bufferSize = 16*1024;//128 * 1032
|
||||
DATAIO_DEBUG << "socket buffer size set to 16 kB.";
|
||||
break;
|
||||
|
||||
case 96000:
|
||||
bufferSize = 32*1024;//128 * 1032
|
||||
DATAIO_DEBUG << "socket buffer size set to 32 kB.";
|
||||
break;
|
||||
|
||||
case 192000:
|
||||
bufferSize = 64*1024;//128 * 1032
|
||||
DATAIO_DEBUG << "socket buffer size set to 64 kB.";
|
||||
break;
|
||||
|
||||
case 384000:
|
||||
bufferSize = 128*1024;//128 * 1032
|
||||
DATAIO_DEBUG << "socket buffer size set to 128 kB.";
|
||||
break;
|
||||
|
||||
default:
|
||||
DATAIO_DEBUG << "invalid sample rate !\n";
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
if (::setsockopt(m_dataIOSocket->socketDescriptor(), SOL_SOCKET,
|
||||
SO_RCVBUF, (char *)&bufferSize, sizeof(bufferSize)) == -1) {
|
||||
|
||||
DATAIO_DEBUG << "dataIOSocket error!";
|
||||
}
|
||||
#endif
|
||||
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* @file cusdr_dataIO.h
|
||||
* @brief Data IO header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2011-10-01
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2011 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CUSDR_DATAIO_H
|
||||
#define _CUSDR_DATAIO_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QMutex>
|
||||
//#include <QByteArray>
|
||||
//#include <QBuffer>
|
||||
//#include <QVector>
|
||||
//#include <QList>
|
||||
//#include <QWaitCondition>
|
||||
//#include <QThread>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
#include "soundout.h"
|
||||
|
||||
#ifdef LOG_DATAIO
|
||||
# define DATAIO_DEBUG qDebug().nospace() << "DataIO::\t"
|
||||
#else
|
||||
# define DATAIO_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
class DataIO : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DataIO(THPSDRParameter *ioData = 0);
|
||||
~DataIO();
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
void initDataReceiverSocket();
|
||||
void readData();
|
||||
void writeData();
|
||||
void sendAudio(u_char *buf);
|
||||
void sendInitFramesToNetworkDevice(int rx);
|
||||
void networkDeviceStartStop(char value);
|
||||
//void setWidebandBuffers(int value);
|
||||
|
||||
private slots:
|
||||
void setSampleRate(QObject *sender, int value);
|
||||
void displayDataReceiverSocketError(QAbstractSocket::SocketError error);
|
||||
void setManualSocketBufferSize(QObject *sender, bool value);
|
||||
void setSocketBufferSize(QObject *sender, int value);
|
||||
void readDeviceData();
|
||||
|
||||
private:
|
||||
Settings* set;
|
||||
QUdpSocket* m_dataIOSocket;
|
||||
//QMutex m_mutex;
|
||||
QByteArray m_commandDatagram;
|
||||
QByteArray m_datagram;
|
||||
QByteArray m_wbDatagram;
|
||||
QByteArray m_twoFramesDatagram;
|
||||
QByteArray m_metisGetDataSignature;
|
||||
QByteArray m_outDatagram;
|
||||
QByteArray m_deviceSendDataSignature;
|
||||
QString m_message;
|
||||
|
||||
QTime m_packetLossTime;
|
||||
|
||||
THPSDRParameter* io;
|
||||
//TNetworkDevicecard netDevice;
|
||||
|
||||
bool m_dataIOSocketOn;
|
||||
bool m_networkDeviceRunning;
|
||||
bool m_setNetworkDeviceHeader;
|
||||
|
||||
long m_sequence;
|
||||
long m_oldSequence;
|
||||
long m_sequenceWideBand;
|
||||
long m_oldSequenceWideBand;
|
||||
long m_sendSequence;
|
||||
long m_oldSendSequence;
|
||||
|
||||
|
||||
int m_wbBuffers;
|
||||
int m_wbCount;
|
||||
int m_socketBufferSize;
|
||||
|
||||
bool m_sendEP4;
|
||||
bool m_manualBufferSize;
|
||||
bool m_packetsToggle;
|
||||
bool m_firstFrame;
|
||||
|
||||
volatile bool m_stopped;
|
||||
CSoundOut* m_pSoundCardOut;
|
||||
|
||||
signals:
|
||||
void messageEvent(QString message);
|
||||
};
|
||||
|
||||
#endif // _CUSDR_DATAIO_H
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @file cusdr_discoverer.cpp
|
||||
* @brief HPSDR device discoverer class
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2012-05-19
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright 2012 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define LOG_DISCOVERER
|
||||
|
||||
#include "cusdr_discoverer.h"
|
||||
#include "Util/cusdr_buttons.h"
|
||||
|
||||
|
||||
//#include <QComboBox>
|
||||
//#include <QDialogButtonBox>
|
||||
//#include <QLabel>
|
||||
//#include <QPushButton>
|
||||
//#include <QVBoxLayout>
|
||||
//#include <QCheckBox>
|
||||
//#include <QSlider>
|
||||
//#include <QSpinBox>
|
||||
|
||||
|
||||
|
||||
//#define btn_height 18
|
||||
//#define btn_width 74
|
||||
|
||||
Discoverer::Discoverer(THPSDRParameter *ioData)
|
||||
: QObject()
|
||||
, set(Settings::instance())
|
||||
, io(ioData)
|
||||
{
|
||||
m_deviceCards = set->getMetisCardsList();
|
||||
}
|
||||
|
||||
Discoverer::~Discoverer() {
|
||||
}
|
||||
|
||||
void Discoverer::initHPSDRDevice() {
|
||||
|
||||
m_searchTime.start();
|
||||
|
||||
int deviceNo = 0;
|
||||
while (deviceNo == 0) {
|
||||
|
||||
deviceNo = findHPSDRDevices();
|
||||
|
||||
if (deviceNo > 1) {
|
||||
|
||||
set->setHPSDRDeviceNumber(deviceNo);
|
||||
break;
|
||||
}
|
||||
|
||||
if (deviceNo > 0) {
|
||||
|
||||
set->setHPSDRDeviceNumber(deviceNo);
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_searchTime.elapsed() > 1000) {
|
||||
|
||||
set->setHPSDRDeviceNumber(0);
|
||||
break;
|
||||
}
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "no device found - trying again...";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
io->devicefound.wakeAll();
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
int Discoverer::findHPSDRDevices() {
|
||||
|
||||
int devicesFound = 0;
|
||||
|
||||
m_findDatagram.resize(63);
|
||||
m_findDatagram[0] = (char)0xEF;
|
||||
m_findDatagram[1] = (char)0xFE;
|
||||
m_findDatagram[2] = (char)0x02;
|
||||
for (int i = 3; i < 63; i++)
|
||||
m_findDatagram[i] = (char)0x00;
|
||||
|
||||
QUdpSocket socket;
|
||||
|
||||
CHECKED_CONNECT(
|
||||
&socket,
|
||||
SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this,
|
||||
SLOT(displayDiscoverySocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "using " << qPrintable(QHostAddress(set->getHPSDRDeviceLocalAddr()).toString()) << " for discovery.";
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
// clear comboBox entries in the network dialogue
|
||||
set->clearNetworkIOComboBoxEntry();
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
|
||||
if (socket.bind(
|
||||
QHostAddress(set->getHPSDRDeviceLocalAddr()), 0,
|
||||
QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
|
||||
//QUdpSocket::ReuseAddressHint))
|
||||
{
|
||||
set->setMetisPort(this, socket.localPort());
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery_socket bound successfully to port " << socket.localPort();
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery_socket bind failed.";
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
socket.close();
|
||||
return 0;
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
|
||||
if (socket.bind(
|
||||
QHostAddress(set->getHPSDRDeviceLocalAddr()),
|
||||
QUdpSocket::DefaultForPlatform))
|
||||
{
|
||||
CHECKED_CONNECT(
|
||||
&socket,
|
||||
SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this,
|
||||
SLOT(displayDiscoverySocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
set->setMetisPort(this, socket.localPort());
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery_socket bound successfully to port " << socket.localPort();
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery_socket bind failed.";
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
socket.close();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (socket.writeDatagram(m_findDatagram, QHostAddress::Broadcast, DEVICE_PORT) == 63) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery data sent.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
else {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery data not sent.";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
// wait a little
|
||||
//SleeperThread::msleep(30);
|
||||
SleeperThread::msleep(500);
|
||||
|
||||
while (socket.hasPendingDatagrams()) {
|
||||
|
||||
TNetworkDevicecard mc;
|
||||
quint16 port;
|
||||
|
||||
m_deviceDatagram.resize(socket.pendingDatagramSize());
|
||||
socket.readDatagram(m_deviceDatagram.data(), m_deviceDatagram.size(), &mc.ip_address, &port);
|
||||
|
||||
if (m_deviceDatagram[0] == (char)0xEF && m_deviceDatagram[1] == (char)0xFE) {
|
||||
|
||||
if (m_deviceDatagram[2] == (char)0x02) {
|
||||
|
||||
sprintf(mc.mac_address, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
m_deviceDatagram[3] & 0xFF, m_deviceDatagram[4] & 0xFF, m_deviceDatagram[5] & 0xFF,
|
||||
m_deviceDatagram[6] & 0xFF, m_deviceDatagram[7] & 0xFF, m_deviceDatagram[8] & 0xFF);
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "Device found at " << qPrintable(mc.ip_address.toString()) << ":" << port << "; Mac addr: [" << mc.mac_address << "]";
|
||||
DISCOVERER_DEBUG << "Device code version: " << qPrintable(QString::number(m_deviceDatagram.at(9), 16));
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
int no = m_deviceDatagram.at(10);
|
||||
QString str;
|
||||
if (no == 0)
|
||||
str = "Metis";
|
||||
else if (no == 1)
|
||||
str = "Hermes";
|
||||
else if (no == 2)
|
||||
str = "Griffin";
|
||||
else if (no == 4)
|
||||
str = "Angelia";
|
||||
|
||||
mc.boardID = no;
|
||||
mc.boardName = str;
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "Device board ID: " << no;
|
||||
DISCOVERER_DEBUG << "Device is: " << qPrintable(str);
|
||||
io->networkIOMutex.unlock();
|
||||
|
||||
m_deviceCards.append(mc);
|
||||
|
||||
str += " (";
|
||||
str += mc.ip_address.toString();
|
||||
str += ")";
|
||||
|
||||
set->addNetworkIOComboBoxEntry(str);
|
||||
devicesFound++;
|
||||
}
|
||||
else if (m_deviceDatagram[2] == (char)0x03) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "Device already sending data!";
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
set->setMetisCardList(m_deviceCards);
|
||||
|
||||
if (devicesFound == 1) {
|
||||
|
||||
set->setCurrentHPSDRDevice(m_deviceCards.at(0));
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "Device selected: " << qPrintable(m_deviceCards.at(0).ip_address.toString());
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
socket.close();
|
||||
return devicesFound;
|
||||
}
|
||||
|
||||
void Discoverer::displayDiscoverySocketError(QAbstractSocket::SocketError error) {
|
||||
|
||||
io->networkIOMutex.lock();
|
||||
DISCOVERER_DEBUG << "discovery socket error: " << error;
|
||||
io->networkIOMutex.unlock();
|
||||
}
|
||||
|
||||
void Discoverer::clear() {
|
||||
|
||||
//m_metisDeviceComboBox->clear();
|
||||
m_deviceCards.clear();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @file cusdr_discoverer.h
|
||||
* @brief HPSDR device discoverer header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2012-05-19
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright 2012 Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CUSDR_DISCOVERER_H
|
||||
#define _CUSDR_DISCOVERER_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QComboBox>
|
||||
//#include <QDialogButtonBox>
|
||||
//#include <QLabel>
|
||||
//#include <QPushButton>
|
||||
//#include <QVBoxLayout>
|
||||
//#include <QCheckBox>
|
||||
//#include <QSlider>
|
||||
//#include <QSpinBox>
|
||||
//#include <QElapsedTimer>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
|
||||
#ifdef LOG_DISCOVERER
|
||||
# define DISCOVERER_DEBUG qDebug().nospace() << "Discoverer::\t"
|
||||
#else
|
||||
# define DISCOVERER_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
class Discoverer : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Discoverer(THPSDRParameter *ioData = 0);
|
||||
~Discoverer();
|
||||
|
||||
int findHPSDRDevices();
|
||||
void clear();
|
||||
|
||||
public slots:
|
||||
void initHPSDRDevice();
|
||||
|
||||
|
||||
private slots:
|
||||
void displayDiscoverySocketError(QAbstractSocket::SocketError error);
|
||||
|
||||
private:
|
||||
Settings* set;
|
||||
THPSDRParameter* io;
|
||||
QTime m_searchTime;
|
||||
|
||||
QByteArray m_findDatagram;
|
||||
QByteArray m_deviceDatagram;
|
||||
|
||||
//QString m_deviceStr;
|
||||
|
||||
TNetworkDevicecard m_deviceCard;
|
||||
QList<TNetworkDevicecard> m_deviceCards;
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // _CUSDR_DISCOVERER_H
|
||||
@@ -0,0 +1,772 @@
|
||||
/**
|
||||
* @file cusdr_receiver.cpp
|
||||
* @brief cuSDR receiver class
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2010-11-12
|
||||
*/
|
||||
|
||||
/* Copyright (C)
|
||||
*
|
||||
* 2010 - Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
#define LOG_RECEIVER
|
||||
|
||||
// use: RECEIVER_DEBUG
|
||||
|
||||
#include "cusdr_receiver.h"
|
||||
|
||||
Receiver::Receiver(int rx)
|
||||
: QObject()
|
||||
, set(Settings::instance())
|
||||
, m_filterMode(set->getCurrentFilterMode())
|
||||
, m_stopped(false)
|
||||
, m_receiver(rx)
|
||||
, m_samplerate(set->getSampleRate())
|
||||
, m_audioMode(1)
|
||||
//, m_calOffset(63.0)
|
||||
//, m_calOffset(33.0)
|
||||
{
|
||||
setReceiverData(set->getReceiverDataList().at(m_receiver));
|
||||
|
||||
InitCPX(inBuf, BUFFER_SIZE, 0.0f);
|
||||
InitCPX(outBuf, BUFFER_SIZE, 0.0f);
|
||||
|
||||
newSpectrum.resize(BUFFER_SIZE*4);
|
||||
|
||||
qtdsp = 0;
|
||||
|
||||
setupConnections();
|
||||
|
||||
highResTimer = new HResTimer();
|
||||
m_displayTime = (int)(1000000.0/set->getFramesPerSecond(m_receiver));
|
||||
|
||||
m_smeterTime.start();
|
||||
}
|
||||
|
||||
Receiver::~Receiver() {
|
||||
|
||||
inBuf.clear();
|
||||
outBuf.clear();
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
delete qtdsp;
|
||||
qtdsp = 0;
|
||||
}
|
||||
|
||||
if (highResTimer) {
|
||||
delete highResTimer;
|
||||
}
|
||||
|
||||
m_stopped = false;
|
||||
}
|
||||
|
||||
void Receiver::setupConnections() {
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(systemStateChanged(
|
||||
QObject *,
|
||||
QSDR::_Error,
|
||||
QSDR::_HWInterfaceMode,
|
||||
QSDR::_ServerMode,
|
||||
QSDR::_DataEngineState)),
|
||||
this,
|
||||
SLOT(setSystemState(
|
||||
QObject *,
|
||||
QSDR::_Error,
|
||||
QSDR::_HWInterfaceMode,
|
||||
QSDR::_ServerMode,
|
||||
QSDR::_DataEngineState)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(mainVolumeChanged(QObject *, int, float)),
|
||||
this,
|
||||
SLOT(setAudioVolume(QObject *, int, float)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(sampleRateChanged(QObject *, int)),
|
||||
this,
|
||||
SLOT(setSampleRate(QObject *, int)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(dspModeChanged(QObject *, int, DSPMode)),
|
||||
this,
|
||||
SLOT(setDspMode(QObject *, int, DSPMode)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(hamBandChanged(QObject *, int, bool, HamBand)),
|
||||
this,
|
||||
SLOT(setHamBand(QObject *, int, bool, HamBand)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcModeChanged(QObject *, int, AGCMode, bool)),
|
||||
this,
|
||||
SLOT(setAGCMode(QObject *, int, AGCMode, bool)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcGainChanged(QObject *, int, int)),
|
||||
this,
|
||||
SLOT(setAGCGain(QObject *, int, int)));
|
||||
|
||||
// CHECKED_CONNECT(
|
||||
// set,
|
||||
// SIGNAL(agcMaximumGain_dBmChanged(QObject *, int, int)),
|
||||
// this,
|
||||
// SLOT(setAGCMaximumGain_dBm(QObject *, int, int)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcMaximumGainChanged_dB(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCMaximumGain_dB(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcFixedGainChanged_dB(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCFixedGain_dB(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcThresholdChanged_dB(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCThreshold_dB(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcHangThresholdChanged(QObject *, int, int)),
|
||||
this,
|
||||
SLOT(setAGCHangThreshold(QObject *, int, int)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcHangLevelChanged_dB(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCHangLevel_dB(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcVariableGainChanged_dB(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCVariableGain_dB(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcAttackTimeChanged(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCAttackTime(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcDecayTimeChanged(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCDecayTime(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(agcHangTimeChanged(QObject *, int, qreal)),
|
||||
this,
|
||||
SLOT(setAGCHangTime(QObject *, int, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(filterFrequenciesChanged(QObject *, int, qreal, qreal)),
|
||||
this,
|
||||
SLOT(setFilterFrequencies(QObject *, int, qreal, qreal)));
|
||||
|
||||
CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(framesPerSecondChanged(QObject*, int, int)),
|
||||
this,
|
||||
SLOT(setFramesPerSecond(QObject*, int, int)));
|
||||
|
||||
/*CHECKED_CONNECT(
|
||||
set,
|
||||
SIGNAL(receiverDataReady()),
|
||||
this,
|
||||
SLOT(dspProcessing()));*/
|
||||
}
|
||||
|
||||
void Receiver::setReceiverData(TReceiver data) {
|
||||
|
||||
m_receiverData = data;
|
||||
|
||||
//m_serverMode = m_receiverData.serverMode;
|
||||
m_dspCore = m_receiverData.dspCore;
|
||||
m_sampleRate = m_receiverData.sampleRate;
|
||||
m_hamBand = m_receiverData.hamBand;
|
||||
m_dspMode = m_receiverData.dspMode;
|
||||
m_dspModeList = m_receiverData.dspModeList;
|
||||
m_agcMode = m_receiverData.agcMode;
|
||||
m_agcGain = m_receiverData.acgGain;
|
||||
m_agcFixedGain_dB = m_receiverData.agcFixedGain_dB;
|
||||
m_agcMaximumGain_dB = m_receiverData.agcMaximumGain_dB;
|
||||
m_agcHangThreshold = m_receiverData.agcHangThreshold;
|
||||
m_agcVariableGain = m_receiverData.agcVariableGain;
|
||||
|
||||
m_audioVolume = m_receiverData.audioVolume;
|
||||
|
||||
m_filterLo = m_receiverData.filterLo;
|
||||
m_filterHi = m_receiverData.filterHi;
|
||||
|
||||
m_lastCtrFrequencyList = m_receiverData.lastCenterFrequencyList;
|
||||
m_lastVfoFrequencyList = m_receiverData.lastVfoFrequencyList;
|
||||
m_mercuryAttenuators = m_receiverData.mercuryAttenuators;
|
||||
}
|
||||
|
||||
bool Receiver::initDSPInterface() {
|
||||
if (m_dspCore == QSDR::QtDSP) {
|
||||
|
||||
if (!initQtDSPInterface()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Receiver::initQtDSPInterface() {
|
||||
|
||||
qtdsp = new QDSPEngine(this, m_receiver, BUFFER_SIZE);
|
||||
|
||||
if (qtdsp)
|
||||
qtdsp->setQtDSPStatus(true);
|
||||
else {
|
||||
|
||||
RECEIVER_DEBUG << "could not start QtDSP for receiver: " << m_receiver;
|
||||
qtdsp = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
qtdsp->setVolume(m_audioVolume);
|
||||
|
||||
DSPMode mode = m_dspModeList.at(m_hamBand);
|
||||
RECEIVER_DEBUG << "set DSP mode to: " << set->getDSPModeString(mode);
|
||||
|
||||
qtdsp->setDSPMode(mode);
|
||||
qtdsp->filter->setFilter(
|
||||
getFilterFromDSPMode(set->getDefaultFilterList(), mode).filterLo,
|
||||
getFilterFromDSPMode(set->getDefaultFilterList(), mode).filterHi);
|
||||
qtdsp->wpagc->setMode(m_agcMode);
|
||||
qtdsp->wpagc->setAGCFixedGainDb(m_agcFixedGain_dB);
|
||||
qtdsp->wpagc->setMaximumGainDb(m_agcMaximumGain_dB);
|
||||
|
||||
// if (m_agcMode == (AGCMode) agcOFF)
|
||||
// set->setAGCFixedGain_dB(this, m_receiver, m_agcFixedGain_dB);
|
||||
// else
|
||||
// set->setAGCMaximumGain_dB(this, m_receiver, m_agcMaximumGain_dB);
|
||||
|
||||
RECEIVER_DEBUG << "QtDSP for receiver: " << m_receiver << " started.";
|
||||
return true;
|
||||
}
|
||||
|
||||
void Receiver::deleteDSPInterface() {
|
||||
|
||||
if (m_dspCore == QSDR::QtDSP)
|
||||
deleteQtDSP();
|
||||
}
|
||||
|
||||
void Receiver::deleteQtDSP() {
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
delete qtdsp;
|
||||
qtdsp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::enqueueData() {
|
||||
|
||||
inQueue.enqueue(inBuf);
|
||||
|
||||
if (inQueue.isFull()) {
|
||||
RECEIVER_DEBUG << "inQueue full!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Receiver::stop() {
|
||||
|
||||
m_mutex.lock();
|
||||
m_stopped = true;
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void Receiver::dspProcessing() {
|
||||
|
||||
//RECEIVER_DEBUG << "dspProcessing: " << this->thread();
|
||||
|
||||
//io.mutex.lock();
|
||||
qtdsp->processDSP(inBuf, outBuf, BUFFER_SIZE);
|
||||
//io.mutex.unlock();
|
||||
|
||||
// spectrum
|
||||
qtdsp->getSpectrum(newSpectrum, set->getFFTMultiplicator());
|
||||
if (highResTimer->getElapsedTimeInMicroSec() >= getDisplayDelay()) {
|
||||
|
||||
emit spectrumBufferChanged(m_receiver, newSpectrum);
|
||||
highResTimer->start();
|
||||
}
|
||||
|
||||
if (m_receiver == set->getCurrentReceiver()) {
|
||||
// S-Meter
|
||||
if (m_smeterTime.elapsed() > 20) {
|
||||
|
||||
m_sMeterValue = qtdsp->getSMeterInstValue();
|
||||
emit sMeterValueChanged(m_receiver, m_sMeterValue);
|
||||
m_smeterTime.restart();
|
||||
}
|
||||
|
||||
// process output data
|
||||
emit outputBufferSignal(m_receiver, outBuf);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setSampleRate(QObject *sender, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_samplerate == value) return;
|
||||
|
||||
switch (value) {
|
||||
|
||||
case 48000:
|
||||
m_samplerate = value;
|
||||
break;
|
||||
|
||||
case 96000:
|
||||
m_samplerate = value;
|
||||
break;
|
||||
|
||||
case 192000:
|
||||
m_samplerate = value;
|
||||
break;
|
||||
|
||||
case 384000:
|
||||
m_samplerate = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
RECEIVER_DEBUG << "invalid sample rate (possible values are: 48, 96, 192, or 384 kHz)!\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if (qtdsp)
|
||||
qtdsp->setSampleRate(this, m_samplerate);
|
||||
else
|
||||
RECEIVER_DEBUG << "qtdsp down: cannot set sample rate!\n";
|
||||
}
|
||||
|
||||
void Receiver::setServerMode(QSDR::_ServerMode mode) {
|
||||
|
||||
m_serverMode = mode;
|
||||
}
|
||||
|
||||
QSDR::_ServerMode Receiver::getServerMode() const {
|
||||
|
||||
return m_serverMode;
|
||||
}
|
||||
|
||||
QSDR::_DSPCore Receiver::getDSPCoreMode() const {
|
||||
|
||||
return m_dspCore;
|
||||
}
|
||||
|
||||
//void Receiver::setSocketState(SocketState state) {
|
||||
//
|
||||
// m_socketState = state;
|
||||
//}
|
||||
|
||||
//Receiver::SocketState Receiver::socketState() const {
|
||||
//
|
||||
// return m_socketState;
|
||||
//}
|
||||
|
||||
void Receiver::setSystemState(
|
||||
QObject *sender,
|
||||
QSDR::_Error err,
|
||||
QSDR::_HWInterfaceMode hwmode,
|
||||
QSDR::_ServerMode mode,
|
||||
QSDR::_DataEngineState state)
|
||||
{
|
||||
Q_UNUSED (sender)
|
||||
Q_UNUSED (err)
|
||||
|
||||
if (m_hwInterface != hwmode)
|
||||
m_hwInterface = hwmode;
|
||||
|
||||
if (m_serverMode != mode)
|
||||
m_serverMode = mode;
|
||||
|
||||
if (m_dataEngineState != state)
|
||||
m_dataEngineState = state;
|
||||
}
|
||||
|
||||
void Receiver::setAudioMode(QObject* sender, int mode) {
|
||||
|
||||
if (sender != this && m_audioMode == mode) return;
|
||||
|
||||
m_audioMode = mode;
|
||||
}
|
||||
|
||||
//void Receiver::setID(int value) {
|
||||
//
|
||||
// m_receiverID = value;
|
||||
// RECEIVER_DEBUG << "This is receiver " << m_receiverID;
|
||||
//}
|
||||
|
||||
void Receiver::setReceiver(int value) {
|
||||
|
||||
m_receiver = value;
|
||||
}
|
||||
|
||||
void Receiver::setSampleRate(int value) {
|
||||
|
||||
m_sampleRate = value;
|
||||
}
|
||||
|
||||
void Receiver::setHamBand(QObject *sender, int rx, bool byBtn, HamBand band) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
Q_UNUSED(byBtn)
|
||||
|
||||
if (m_receiver == rx) {
|
||||
|
||||
if (m_hamBand == band) return;
|
||||
m_hamBand = band;
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setDspMode(QObject *sender, int rx, DSPMode mode) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_dspMode == mode) return;
|
||||
|
||||
m_dspMode = mode;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
qtdsp->setDSPMode(mode);
|
||||
qtdsp->filter->setFilter(
|
||||
getFilterFromDSPMode(set->getDefaultFilterList(), mode).filterLo,
|
||||
getFilterFromDSPMode(set->getDefaultFilterList(), mode).filterHi);
|
||||
}
|
||||
|
||||
//QString msg = "[receiver]: set mode for receiver %1 to %2";
|
||||
//emit messageEvent(msg.arg(rx).arg(set->getDSPModeString(m_dspMode)));
|
||||
}
|
||||
|
||||
void Receiver::setAGCMode(QObject *sender, int rx, AGCMode mode, bool hang) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
Q_UNUSED(hang)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcMode == mode) return;
|
||||
|
||||
m_agcMode = mode;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
qtdsp->wpagc->setMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCGain(QObject *sender, int rx, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcGain == value) return;
|
||||
|
||||
m_agcGain = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "AGCThreshDB (plus offset) = " << m_agcGain - AGCOFFSET;
|
||||
//qtdsp->wpagc->setAGCThreshDb(m_filterLo, m_filterHi, BUFFER_SIZE, m_agcGain - AGCOFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCFixedGain_dB(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcFixedGain_dB == value) return;
|
||||
|
||||
m_agcFixedGain_dB = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "m_agcFixedGain = " << m_agcFixedGain;
|
||||
qtdsp->wpagc->setAGCFixedGainDb(m_agcFixedGain_dB);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCMaximumGain_dB(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcMaximumGain_dB == value) return;
|
||||
|
||||
m_agcMaximumGain_dB = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "setAGCMaximumGain_dB = " << m_agcMaximumGain_dB;
|
||||
qtdsp->wpagc->setMaximumGainDb(m_agcMaximumGain_dB);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCThreshold_dB(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcThreshold_dBm == value) return;
|
||||
|
||||
m_agcThreshold_dBm = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "AGCThreshDB (minus offset) for Rx " << m_receiver << ": " << m_agcThreshold_dBm - AGCOFFSET;
|
||||
qtdsp->wpagc->setAGCThreshDb(m_filterLo, m_filterHi, 2*BUFFER_SIZE, m_agcThreshold_dBm - AGCOFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCHangThreshold(QObject *sender, int rx, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcHangThreshold == value) return;
|
||||
|
||||
m_agcHangThreshold = value;
|
||||
if (qtdsp) {
|
||||
|
||||
RECEIVER_DEBUG << "m_agcHangThreshold =" << m_agcHangThreshold/100.0;
|
||||
qtdsp->wpagc->setHangThresh(m_agcHangThreshold/100.0);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCHangLevel_dB(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcHangLevel == value) return;
|
||||
|
||||
m_agcHangLevel = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "m_agcHangLevel = " << m_agcHangLevel - AGCOFFSET;
|
||||
qtdsp->wpagc->setHangLevelDb(m_agcHangLevel - AGCOFFSET);
|
||||
}
|
||||
//set->setAGCHangLeveldB(this, m_receiverID, value);
|
||||
}
|
||||
|
||||
void Receiver::setAGCVariableGain_dB(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcVariableGain == value) return;
|
||||
|
||||
m_agcVariableGain = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
RECEIVER_DEBUG << "m_agcVariableGain = " << m_agcVariableGain;
|
||||
qtdsp->wpagc->setVarGainDb(m_agcVariableGain);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCAttackTime(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcAttackTime == value) return;
|
||||
|
||||
m_agcAttackTime = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
RECEIVER_DEBUG << "m_agcAttackTime = " << m_agcAttackTime;
|
||||
qtdsp->wpagc->setTauAttack(m_agcAttackTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCDecayTime(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcDecayTime == value) return;
|
||||
|
||||
m_agcDecayTime = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
RECEIVER_DEBUG << "m_agcDecayTime = " << m_agcDecayTime;
|
||||
qtdsp->wpagc->setTauDecay(m_agcDecayTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAGCHangTime(QObject *sender, int rx, qreal value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
if (m_agcHangTime == value) return;
|
||||
|
||||
m_agcHangTime = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
RECEIVER_DEBUG << "m_agcHangTime = " << m_agcHangTime;
|
||||
qtdsp->wpagc->setHangTime(m_agcHangTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setAudioVolume(QObject *sender, int rx, float value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver != rx) return;
|
||||
//if (m_audioVolume == value) return;
|
||||
|
||||
m_audioVolume = value;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
//RECEIVER_DEBUG << "setAudioVolume =" << m_audioVolume;
|
||||
qtdsp->setVolume(value);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setFilterFrequencies(QObject *sender, int rx, double low, double high) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver == rx) {
|
||||
|
||||
if (m_filterLo == low && m_filterHi == high) return;
|
||||
m_filterLo = low;
|
||||
m_filterHi = high;
|
||||
|
||||
if (qtdsp) {
|
||||
|
||||
qtdsp->filter->setFilter((float)low, (float)high);
|
||||
qtdsp->wpagc->filterChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::setCtrFrequency(long frequency) {
|
||||
|
||||
if (m_ctrFrequency == frequency) return;
|
||||
m_ctrFrequency = frequency;
|
||||
|
||||
HamBand band = getBandFromFrequency(set->getBandFrequencyList(), frequency);
|
||||
m_lastCtrFrequencyList[(int) band] = m_ctrFrequency;
|
||||
}
|
||||
|
||||
void Receiver::setVfoFrequency(long frequency) {
|
||||
|
||||
if (m_vfoFrequency == frequency) return;
|
||||
m_vfoFrequency = frequency;
|
||||
|
||||
HamBand band = getBandFromFrequency(set->getBandFrequencyList(), frequency);
|
||||
m_lastVfoFrequencyList[(int) band] = m_vfoFrequency;
|
||||
}
|
||||
|
||||
void Receiver::setLastCtrFrequencyList(const QList<long> &fList) {
|
||||
|
||||
m_lastCtrFrequencyList = fList;
|
||||
}
|
||||
|
||||
void Receiver::setLastVfoFrequencyList(const QList<long> &fList) {
|
||||
|
||||
m_lastVfoFrequencyList = fList;
|
||||
}
|
||||
|
||||
void Receiver::setdBmPanScaleMin(qreal value) {
|
||||
|
||||
if (m_dBmPanScaleMin == value) return;
|
||||
m_dBmPanScaleMin = value;
|
||||
}
|
||||
|
||||
void Receiver::setdBmPanScaleMax(qreal value) {
|
||||
|
||||
if (m_dBmPanScaleMax == value) return;
|
||||
m_dBmPanScaleMax = value;
|
||||
}
|
||||
|
||||
void Receiver::setMercuryAttenuators(const QList<int> &attenuators) {
|
||||
|
||||
m_mercuryAttenuators = attenuators;
|
||||
}
|
||||
|
||||
void Receiver::setFramesPerSecond(QObject *sender, int rx, int value) {
|
||||
|
||||
Q_UNUSED(sender)
|
||||
|
||||
if (m_receiver == rx)
|
||||
m_displayTime = (int)(1000000.0/value);
|
||||
}
|
||||
|
||||
void Receiver::setPeerAddress(QHostAddress addr) {
|
||||
|
||||
m_peerAddress = addr;
|
||||
}
|
||||
|
||||
void Receiver::setSocketDescriptor(int value) {
|
||||
|
||||
m_socketDescriptor = value;
|
||||
}
|
||||
|
||||
void Receiver::setClient(int value) {
|
||||
|
||||
m_client = value;
|
||||
}
|
||||
|
||||
void Receiver::setIQPort(int value) {
|
||||
|
||||
m_iqPort = value;
|
||||
}
|
||||
|
||||
void Receiver::setBSPort(int value) {
|
||||
|
||||
m_bsPort = value;
|
||||
}
|
||||
|
||||
void Receiver::setConnectedStatus(bool value) {
|
||||
|
||||
m_connected = value;
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* @file cusdr_receiver.h
|
||||
* @brief cuSDR receiver header file
|
||||
* @author Hermann von Hasseln, DL3HVH
|
||||
* @version 0.1
|
||||
* @date 2010-11-12
|
||||
*/
|
||||
|
||||
/* Copyright (C)
|
||||
*
|
||||
* 2010 - Hermann von Hasseln, DL3HVH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CUSDR_RECEIVER_H
|
||||
#define CUSDR_RECEIVER_H
|
||||
|
||||
//#include <QObject>
|
||||
//#include <QtNetwork>
|
||||
|
||||
#include "cusdr_settings.h"
|
||||
#include "QtDSP/qtdsp_dspEngine.h"
|
||||
#include "Util/cusdr_highResTimer.h"
|
||||
|
||||
#ifdef LOG_RECEIVER
|
||||
# define RECEIVER_DEBUG qDebug().nospace() << "Receiver::\t"
|
||||
#else
|
||||
# define RECEIVER_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
|
||||
class Receiver : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Receiver(int rx = 0);
|
||||
~Receiver();
|
||||
|
||||
void setupConnections();
|
||||
bool initDSPInterface();
|
||||
void deleteDSPInterface();
|
||||
|
||||
void enqueueData();
|
||||
|
||||
|
||||
QSDR::_ServerMode getServerMode() const;
|
||||
QSDR::_DSPCore getDSPCoreMode() const;
|
||||
QHostAddress getPeerAddress() { return m_peerAddress; }
|
||||
HamBand getHamBand() { return m_hamBand; }
|
||||
AGCMode getAGCMode() { return m_agcMode; }
|
||||
QList<int> getMercuryAttenuators() { return m_mercuryAttenuators; }
|
||||
QList<DSPMode> getDSPModeList() { return m_dspModeList; }
|
||||
|
||||
int getAudioMode() { return m_audioMode; }
|
||||
int getSocketDescriptor() { return m_socketDescriptor; }
|
||||
int getReceiverNo() { return m_receiver; }
|
||||
int getClient() { return m_client; }
|
||||
int getIQPort() { return m_iqPort; }
|
||||
int getBSPort() { return m_bsPort; }
|
||||
//int getID() { return m_receiverID; }
|
||||
int getSampleRate() { return m_sampleRate; }
|
||||
int getDisplayDelay() { return m_displayTime; }
|
||||
qreal getAGCGain() { return m_agcGain; }
|
||||
float getAudioVolume() { return m_audioVolume; }
|
||||
long getCtrFrequency() { return m_ctrFrequency; }
|
||||
long getVfoFrequency() { return m_vfoFrequency; }
|
||||
double getFilterLo() { return m_filterLo; }
|
||||
double getFilterHi() { return m_filterHi; }
|
||||
qreal getdBmPanScaleMin() { return m_dBmPanScaleMin; }
|
||||
qreal getdBmPanScaleMax() { return m_dBmPanScaleMax; }
|
||||
bool getConnectedStatus() { return m_connected; }
|
||||
|
||||
float in[BUFFER_SIZE * 2];
|
||||
float out[BUFFER_SIZE * 2];
|
||||
float temp[BUFFER_SIZE * 4];
|
||||
float spectrum[BUFFER_SIZE * 4];
|
||||
float postSpectrum[BUFFER_SIZE * 4];
|
||||
|
||||
QVector<float> newSpectrum;
|
||||
|
||||
QDSPEngine *qtdsp;
|
||||
HResTimer *highResTimer;
|
||||
|
||||
CPX inBuf;
|
||||
CPX outBuf;
|
||||
|
||||
QHQueue<CPX> inQueue;
|
||||
|
||||
public slots:
|
||||
void setReceiverData(TReceiver data);
|
||||
void setAudioMode(QObject* sender, int mode);
|
||||
void setServerMode(QSDR::_ServerMode mode);
|
||||
void setPeerAddress(QHostAddress addr);
|
||||
void setSocketDescriptor(int value);
|
||||
void setReceiver(int value);
|
||||
void setClient(int value);
|
||||
void setIQPort(int value);
|
||||
void setBSPort(int value);
|
||||
void setConnectedStatus(bool value);
|
||||
//void setID(int value);
|
||||
void setSampleRate(int value);
|
||||
void setHamBand(QObject* sender, int rx, bool byBtn, HamBand band);
|
||||
void setDspMode(QObject* sender, int rx, DSPMode mode);
|
||||
void setAGCMode(QObject* sender, int rx, AGCMode mode, bool hang);
|
||||
void setAGCGain(QObject* sender, int rx, int value);
|
||||
void setAudioVolume(QObject* sender, int rx, float value);
|
||||
void setCtrFrequency(long frequency);
|
||||
void setVfoFrequency(long frequency);
|
||||
void setFilterFrequencies(QObject* sender, int rx, qreal low, qreal high);
|
||||
void setLastCtrFrequencyList(const QList<long> &frequencies);
|
||||
void setLastVfoFrequencyList(const QList<long> &frequencies);
|
||||
void setdBmPanScaleMin(qreal value);
|
||||
void setdBmPanScaleMax(qreal value);
|
||||
void setMercuryAttenuators(const QList<int> &attenuators);
|
||||
|
||||
void dspProcessing();
|
||||
void stop();
|
||||
|
||||
private slots:
|
||||
void setSystemState(
|
||||
QObject* sender,
|
||||
QSDR::_Error err,
|
||||
QSDR::_HWInterfaceMode hwmode,
|
||||
QSDR::_ServerMode mode,
|
||||
QSDR::_DataEngineState state);
|
||||
|
||||
void setSampleRate(QObject *sender, int value);
|
||||
void setFramesPerSecond(QObject *sender, int rx, int value);
|
||||
|
||||
bool initQtDSPInterface();
|
||||
void deleteQtDSP();
|
||||
|
||||
//void setAGCMaximumGain_dBm(QObject* sender, int rx, int value);
|
||||
void setAGCMaximumGain_dB(QObject* sender, int rx, qreal value);
|
||||
void setAGCFixedGain_dB(QObject* sender, int rx, qreal value);
|
||||
void setAGCThreshold_dB(QObject* sender, int rx, qreal value);
|
||||
void setAGCHangLevel_dB(QObject* sender, int rx, qreal value);
|
||||
void setAGCHangThreshold(QObject* sender, int rx, int value);
|
||||
void setAGCVariableGain_dB(QObject* sender, int rx, qreal value);
|
||||
void setAGCAttackTime(QObject* sender, int rx, qreal value);
|
||||
void setAGCDecayTime(QObject* sender, int rx, qreal value);
|
||||
void setAGCHangTime(QObject* sender, int rx, qreal value);
|
||||
|
||||
private:
|
||||
Settings* set;
|
||||
|
||||
QSDR::_DSPCore m_dspCore;
|
||||
QSDR::_ServerMode m_serverMode;
|
||||
QSDR::_HWInterfaceMode m_hwInterface;
|
||||
QSDR::_DataEngineState m_dataEngineState;
|
||||
|
||||
TReceiver m_receiverData;
|
||||
QHostAddress m_peerAddress;
|
||||
quint16 m_peerPort;
|
||||
|
||||
HamBand m_hamBand;
|
||||
DSPMode m_dspMode;
|
||||
AGCMode m_agcMode;
|
||||
TDefaultFilterMode m_filterMode;
|
||||
|
||||
QList<long> m_lastCtrFrequencyList;
|
||||
QList<long> m_lastVfoFrequencyList;
|
||||
QList<DSPMode> m_dspModeList;
|
||||
QList<int> m_mercuryAttenuators;
|
||||
|
||||
QTime m_smeterTime;
|
||||
QMutex m_mutex;
|
||||
|
||||
volatile bool m_stopped;
|
||||
|
||||
int m_receiver;
|
||||
int m_samplerate;
|
||||
int m_audioMode; // 1 = audio on, 0 = audio off
|
||||
int m_socketDescriptor;
|
||||
int m_client;
|
||||
int m_iqPort;
|
||||
int m_bsPort;
|
||||
int m_sampleRate;
|
||||
int m_displayTime;
|
||||
|
||||
long m_ctrFrequency;
|
||||
long m_vfoFrequency;
|
||||
|
||||
float m_audioVolume;
|
||||
float m_sMeterValue;
|
||||
|
||||
qreal m_agcGain;
|
||||
qreal m_agcFixedGain_dB;
|
||||
qreal m_agcMaximumGain_dB;
|
||||
qreal m_agcThreshold_dBm;
|
||||
qreal m_agcHangThreshold;
|
||||
qreal m_agcHangLevel;
|
||||
qreal m_agcVariableGain;
|
||||
qreal m_agcAttackTime;
|
||||
qreal m_agcDecayTime;
|
||||
qreal m_agcHangTime;
|
||||
//qreal m_calOffset;
|
||||
qreal m_filterLo;
|
||||
qreal m_filterHi;
|
||||
qreal m_dBmPanScaleMin;
|
||||
qreal m_dBmPanScaleMax;
|
||||
|
||||
bool m_connected;
|
||||
bool m_hangEnabled;
|
||||
|
||||
//void setupConnections();
|
||||
|
||||
signals:
|
||||
void messageEvent(QString msg);
|
||||
void spectrumBufferChanged(int rx, const qVectorFloat& buffer);
|
||||
void sMeterValueChanged(int rx, float value);
|
||||
void outputBufferSignal(int rx, const CPX &buffer);
|
||||
//void audioReady(int rx);
|
||||
};
|
||||
|
||||
#endif // CUSDR_RECEIVER_H
|
||||
@@ -0,0 +1,51 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// datatypes.h: Common data type declarations
|
||||
//
|
||||
// History:
|
||||
// 2010-09-15 Initial creation MSW
|
||||
// 2011-03-27 Initial release
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#ifndef DATATYPES_H
|
||||
#define DATATYPES_H
|
||||
|
||||
#include <math.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
|
||||
//define single or double precision reals and complex types
|
||||
typedef float tSReal;
|
||||
typedef double tDReal;
|
||||
|
||||
typedef struct _sCplx
|
||||
{
|
||||
tSReal re;
|
||||
tSReal im;
|
||||
}tSComplex;
|
||||
|
||||
typedef struct _dCplx
|
||||
{
|
||||
tDReal re;
|
||||
tDReal im;
|
||||
}tDComplex;
|
||||
|
||||
typedef struct _isCplx
|
||||
{
|
||||
qint16 re;
|
||||
qint16 im;
|
||||
}tStereo16;
|
||||
|
||||
|
||||
#define TYPEREAL tDReal
|
||||
#define TYPECPX tDComplex
|
||||
#define TYPESTEREO16 tStereo16
|
||||
#define TYPEMONO16 qint16
|
||||
|
||||
//#define K_2PI (8.0*atan(1)) //maybe some compilers are't too smart to optimize out
|
||||
#define K_2PI (2.0 * 3.14159265358979323846)
|
||||
#define K_PI (3.14159265358979323846)
|
||||
#define K_PI4 (K_PI/4.0)
|
||||
#define K_PI2 (K_PI/2.0)
|
||||
#define K_3PI4 (3.0*K_PI4)
|
||||
|
||||
|
||||
#endif // DATATYPES_H
|
||||
@@ -0,0 +1,352 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// FractResampler.cpp: implementation of the CFractResampler class.
|
||||
//
|
||||
// This class implements a fractional resampler that can be used to
|
||||
//convert between different sample rates. A windowes sinc interpolator
|
||||
// is used to create samples "in between" input samples.
|
||||
//
|
||||
// History:
|
||||
// 2010-09-15 Initial creation MSW
|
||||
// 2011-03-27 Initial release
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
//==========================================================================================
|
||||
// + + + This Software is released under the "Simplified BSD License" + + +
|
||||
//Copyright 2010 Moe Wheatley. All rights reserved.
|
||||
//
|
||||
//Redistribution and use in source and binary forms, with or without modification, are
|
||||
//permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other materials
|
||||
// provided with the distribution.
|
||||
//
|
||||
//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR
|
||||
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//The views and conclusions contained in the software and documentation are those of the
|
||||
//authors and should not be interpreted as representing official policies, either expressed
|
||||
//or implied, of Moe Wheatley.
|
||||
//==========================================================================================
|
||||
|
||||
#include "fractresampler.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Local defines
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#define SINC_PERIOD_PTS 10000 //number of points in sinc table between "zero crossings"
|
||||
//smaller value increases noise floor
|
||||
|
||||
#define SINC_PERIODS 28 //number of input sample periods("zero crossings"-1) in
|
||||
//sinc function(should be even)
|
||||
//decreasing reduces alias free bandwidth
|
||||
|
||||
#define SINC_LENGTH ( (SINC_PERIODS)*SINC_PERIOD_PTS + 1)//number of total points in sinc table
|
||||
|
||||
#define MAX_SOUNDCARDVAL 32767.0
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
CFractResampler::CFractResampler()
|
||||
{
|
||||
m_pSinc = NULL;
|
||||
m_pInputBuf = NULL;
|
||||
|
||||
}
|
||||
|
||||
CFractResampler::~CFractResampler()
|
||||
{
|
||||
if(m_pSinc)
|
||||
delete m_pSinc;
|
||||
if(m_pInputBuf)
|
||||
delete m_pInputBuf;
|
||||
}
|
||||
//
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Initialize resampler memory and create windowed sinc table
|
||||
// MaxInputSize is the largest number of input samples expected to be processed
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
void CFractResampler::Init(int MaxInputSize)
|
||||
{
|
||||
int i;
|
||||
TYPEREAL fi;
|
||||
TYPEREAL window;
|
||||
MaxInputSize += SINC_PERIODS; //expand buffer size to include wrap around
|
||||
if(NULL == m_pSinc)
|
||||
m_pSinc = new TYPEREAL[SINC_LENGTH];
|
||||
if(m_pInputBuf)
|
||||
delete m_pInputBuf;
|
||||
m_pInputBuf = new TYPECPX[MaxInputSize];
|
||||
for(i=0; i<MaxInputSize; i++)
|
||||
{
|
||||
m_pInputBuf[i].re = 0.0;
|
||||
m_pInputBuf[i].im = 0.0;
|
||||
}
|
||||
for(i=0; i<SINC_LENGTH; i++)
|
||||
{ //calc Blackman-Harris window points
|
||||
window = (0.35875
|
||||
- 0.48829*cos( (K_2PI*i)/(SINC_LENGTH-1) )
|
||||
+ 0.14128*cos( (2.0*K_2PI*i)/(SINC_LENGTH-1) )
|
||||
- 0.01168*cos( (3.0*K_2PI*i)/(SINC_LENGTH-1) ) );
|
||||
//calculate sin(x)/x sinc point * window
|
||||
fi = K_PI*(double)(i - SINC_LENGTH/2)/(double)SINC_PERIOD_PTS ;
|
||||
if(i != SINC_LENGTH/2)
|
||||
m_pSinc[i] = window * (TYPEREAL)sin( (double)fi )/(double)fi;
|
||||
else
|
||||
m_pSinc[i] = 1.0;
|
||||
|
||||
}
|
||||
m_FloatTime = 0.0; //init floating point time accumulator
|
||||
|
||||
#if 0 //debug hack to write m_pSinc to a file for analysis
|
||||
QDir::setCurrent("d:/");
|
||||
QFile File;
|
||||
File.setFileName("Sinc.txt");
|
||||
if(File.open(QIODevice::WriteOnly))
|
||||
{
|
||||
qDebug()<<"file Opened OK";
|
||||
char Buf[30000];
|
||||
for( i=0; i<SINC_LENGTH; i++)
|
||||
{
|
||||
sprintf( Buf, "%19.12g\r\n", m_pSinc[i]);
|
||||
File.write(Buf);
|
||||
}
|
||||
}
|
||||
else
|
||||
qDebug()<<"file Failed to Open";
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Resample InLength samples in pInBuf and place into pOutBuf
|
||||
// using Rate = input rate / output rate
|
||||
// !!!! Make sure pOutBuf from caller is large enough to hold all
|
||||
// the generated samples, especially if up converting !!!!!
|
||||
// COMPLEX version
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
int CFractResampler::Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPECPX* pOutBuf)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int IntegerTime = (int)m_FloatTime; //integer input time accumulator
|
||||
double dt = Rate; //output delta time as function of input sample time (input rate/output rate)
|
||||
int outsamples = 0;
|
||||
TYPECPX acc;
|
||||
|
||||
//copy input samples into buffer starting at position SINC_PERIODS
|
||||
j = SINC_PERIODS;
|
||||
for(i=0; i<InLength; i++)
|
||||
m_pInputBuf[j++] = pInBuf[i];
|
||||
//now calculate output samples by looping until end of input buffer
|
||||
// is reached. The output position is incremented in fractional time
|
||||
// of input sample time until all the possible input samples are
|
||||
//processed.
|
||||
while(IntegerTime < InLength )
|
||||
{ //convolve sinc function with input samples where sinc
|
||||
//function is centered at the output fractional time position
|
||||
acc.re = 0.0; acc.im = 0.0;
|
||||
for(i=1; i<=SINC_PERIODS; i++)
|
||||
{
|
||||
j = IntegerTime + i; //temp integer time position for convolution loop
|
||||
int sindx = (int)(( (double)j - m_FloatTime) * (double)SINC_PERIOD_PTS );
|
||||
acc.re += (m_pInputBuf[j].re * m_pSinc[sindx] );
|
||||
acc.im += (m_pInputBuf[j].im * m_pSinc[sindx] );
|
||||
}
|
||||
pOutBuf[outsamples++] = acc;
|
||||
m_FloatTime += dt; //inc floating pt output time step
|
||||
IntegerTime = (int)m_FloatTime; //truncate to integer
|
||||
}
|
||||
m_FloatTime -= (double)InLength; //move floating time position back for next call
|
||||
//keeping leftover fraction
|
||||
//need to copy last SINC_PERIODS input samples in buffer to beginning of buffer
|
||||
// for FIR wrap around management. j points to last input sample processed
|
||||
j = InLength;
|
||||
for(i=0; i<SINC_PERIODS; i++)
|
||||
m_pInputBuf[i] = m_pInputBuf[j++];
|
||||
return outsamples; //return number of output samples processed
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Resample InLength samples in pInBuf and place into pOutBuf
|
||||
// using Rate = input rate / output rate
|
||||
// !!!! Make sure pOutBuf from caller is large enough to hold all
|
||||
// the generated samples, especially if up converting !!!!!
|
||||
// stereo Integer version
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
int CFractResampler::Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPESTEREO16* pOutBuf, TYPEREAL gain)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int IntegerTime = (int)m_FloatTime; //integer input time accumulator
|
||||
double dt = Rate; //output delta time as function of input sample time (input rate/output rate)
|
||||
int outsamples = 0;
|
||||
TYPECPX acc;
|
||||
|
||||
//copy input samples into buffer starting at position SINC_PERIODS
|
||||
j = SINC_PERIODS;
|
||||
for(i=0; i<InLength; i++)
|
||||
{
|
||||
m_pInputBuf[j++] = pInBuf[i];
|
||||
}
|
||||
//now calculate output samples by looping until end of input buffer
|
||||
// is reached. The output position is incremented in fractional time
|
||||
// of input sample time until all the possible input samples are
|
||||
//processed.
|
||||
while(IntegerTime < InLength )
|
||||
{ //convolve sinc function with input samples where sinc
|
||||
//function is centered at the output fractional time position
|
||||
acc.re = 0.0; acc.im = 0.0;
|
||||
for(i=1; i<=SINC_PERIODS; i++)
|
||||
{
|
||||
j = IntegerTime + i; //temp integer time position for convolution loop
|
||||
int sindx = (int)(( (double)j - m_FloatTime) * (double)SINC_PERIOD_PTS );
|
||||
acc.re += (m_pInputBuf[j].re * m_pSinc[sindx] );
|
||||
acc.im += (m_pInputBuf[j].im * m_pSinc[sindx] );
|
||||
}
|
||||
TYPECPX tmp;
|
||||
tmp.re = (acc.re * gain);;
|
||||
tmp.im = (acc.im * gain);;
|
||||
if(tmp.re > MAX_SOUNDCARDVAL)
|
||||
tmp.re = MAX_SOUNDCARDVAL;
|
||||
if(tmp.re < -MAX_SOUNDCARDVAL)
|
||||
tmp.re = -MAX_SOUNDCARDVAL;
|
||||
if(tmp.im>MAX_SOUNDCARDVAL)
|
||||
tmp.im = MAX_SOUNDCARDVAL;
|
||||
if(tmp.im < -MAX_SOUNDCARDVAL)
|
||||
tmp.im = -MAX_SOUNDCARDVAL;
|
||||
pOutBuf[outsamples].re = (qint16)tmp.re;
|
||||
pOutBuf[outsamples++].im = (qint16)tmp.im;
|
||||
|
||||
m_FloatTime += dt; //inc floating pt output time step
|
||||
IntegerTime = (int)m_FloatTime; //truncate to integer
|
||||
}
|
||||
m_FloatTime -= (double)InLength; //move floating time position back for next call
|
||||
//keeping leftover fraction
|
||||
//need to copy last SINC_PERIODS input samples in buffer to beginning of buffer
|
||||
// for FIR wrap around management. j points to last input sample processed
|
||||
j = InLength;
|
||||
for(i=0; i<SINC_PERIODS; i++)
|
||||
m_pInputBuf[i] = m_pInputBuf[j++];
|
||||
return outsamples; //return number of output samples processed
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Resample InLength samples in pInBuf and place into pOutBuf
|
||||
// using Rate = input rate / output rate
|
||||
// !!!! Make sure pOutBuf from caller is large enough to hold all
|
||||
// the generated samples, especially if up converting !!!!!
|
||||
// REAL version
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
int CFractResampler::Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEREAL* pOutBuf)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int IntegerTime = (int)m_FloatTime; //integer input time accumulator
|
||||
double dt = Rate; //output delta time as function of input sample time (input rate/output rate)
|
||||
int outsamples = 0;
|
||||
TYPEREAL acc;
|
||||
|
||||
//copy input samples into buffer starting at position SINC_PERIODS
|
||||
j = SINC_PERIODS;
|
||||
for(i=0; i<InLength; i++)
|
||||
m_pInputBuf[j++].re = pInBuf[i];
|
||||
//now calculate output samples by looping until end of input buffer
|
||||
// is reached. The output position is incremented in fractional time
|
||||
// of input sample time until all the possible input samples are
|
||||
//processed.
|
||||
while(IntegerTime < InLength )
|
||||
{ //convolve sinc function with input samples where sinc
|
||||
//function is centered at the output fractional time position
|
||||
acc = 0.0;
|
||||
for(i=1; i<=SINC_PERIODS; i++)
|
||||
{
|
||||
j = IntegerTime + i; //temp integer time position for convolution loop
|
||||
int sindx = (int)(( (double)j - m_FloatTime) * (double)SINC_PERIOD_PTS );
|
||||
acc += (m_pInputBuf[j].re * m_pSinc[sindx] );
|
||||
}
|
||||
pOutBuf[outsamples++] = acc;
|
||||
m_FloatTime += dt;
|
||||
IntegerTime = (int)m_FloatTime;
|
||||
}
|
||||
m_FloatTime -= (double)InLength; //move floating time position back for next call
|
||||
//keeping leftover fraction
|
||||
//need to copy last SINC_PERIODS input samples in buffer to beginning of buffer
|
||||
// for FIR wrap around management. j points to last input sample processed
|
||||
j = InLength;
|
||||
for(i=0; i<SINC_PERIODS; i++)
|
||||
m_pInputBuf[i].re = m_pInputBuf[j++].re;
|
||||
return outsamples;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Resample InLength samples in pInBuf and place into pOutBuf
|
||||
// using Rate = input rate / output rate
|
||||
// !!!! Make sure pOutBuf from caller is large enough to hold all
|
||||
// the generated samples, especially if up converting !!!!!
|
||||
// short Integer version
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
int CFractResampler::Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEMONO16* pOutBuf, TYPEREAL gain)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int IntegerTime = (int)m_FloatTime; //integer input time accumulator
|
||||
double dt = Rate; //output delta time as function of input sample time (input rate/output rate)
|
||||
int outsamples = 0;
|
||||
TYPEREAL acc;
|
||||
|
||||
//copy input samples into buffer starting at position SINC_PERIODS
|
||||
j = SINC_PERIODS;
|
||||
for(i=0; i<InLength; i++)
|
||||
m_pInputBuf[j++].re = pInBuf[i];
|
||||
//now calculate output samples by looping until end of input buffer
|
||||
// is reached. The output position is incremented in fractional time
|
||||
// of input sample time until all the possible input samples are
|
||||
//processed.
|
||||
while(IntegerTime < InLength )
|
||||
{ //convolve sinc function with input samples where sinc
|
||||
//function is centered at the output fractional time position
|
||||
acc = 0.0;
|
||||
for(i=1; i<=SINC_PERIODS; i++)
|
||||
{
|
||||
j = IntegerTime + i; //temp integer time position for convolution loop
|
||||
int sindx = (int)(( (double)j - m_FloatTime) * (double)SINC_PERIOD_PTS );
|
||||
acc += (m_pInputBuf[j].re * m_pSinc[sindx] );
|
||||
}
|
||||
TYPEREAL tmp;
|
||||
tmp = (acc * gain);;
|
||||
if(tmp > MAX_SOUNDCARDVAL)
|
||||
tmp = MAX_SOUNDCARDVAL;
|
||||
if(tmp < -MAX_SOUNDCARDVAL)
|
||||
tmp = -MAX_SOUNDCARDVAL;
|
||||
pOutBuf[outsamples++] = (TYPEMONO16)tmp;
|
||||
|
||||
m_FloatTime += dt;
|
||||
IntegerTime = (int)m_FloatTime;
|
||||
}
|
||||
m_FloatTime -= (double)InLength; //move floating time position back for next call
|
||||
//keeping leftover fraction
|
||||
//need to copy last SINC_PERIODS input samples in buffer to beginning of buffer
|
||||
// for FIR wrap around management. j points to last input sample processed
|
||||
j = InLength;
|
||||
for(i=0; i<SINC_PERIODS; i++)
|
||||
m_pInputBuf[i].re = m_pInputBuf[j++].re;
|
||||
return outsamples;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// fractresampler.h: interface for the CFractResampler class.
|
||||
//
|
||||
// This class implements a fractional resampler that can be used to
|
||||
//convert between different sample rates
|
||||
//
|
||||
// History:
|
||||
// 2010-09-15 Initial creation MSW
|
||||
// 2011-03-27 Initial release
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#ifndef FRACTRESAMPLER_H
|
||||
#define FRACTRESAMPLER_H
|
||||
|
||||
#include "datatypes.h"
|
||||
|
||||
class CFractResampler
|
||||
{
|
||||
public:
|
||||
CFractResampler();
|
||||
virtual ~CFractResampler();
|
||||
|
||||
void Init(int MaxInputSize);
|
||||
//overloaded functions for processing different data types
|
||||
int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEREAL* pOutBuf);
|
||||
int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPECPX* pOutBuf);
|
||||
int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEMONO16* pOutBuf, TYPEREAL gain);
|
||||
int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPESTEREO16* pOutBuf, TYPEREAL gain);
|
||||
|
||||
private:
|
||||
TYPEREAL m_FloatTime; //floating pt output time accumulator
|
||||
TYPEREAL* m_pSinc; //ptr to sinc table
|
||||
TYPECPX* m_pInputBuf; //internal working input sample buffer
|
||||
};
|
||||
|
||||
#endif // FRACTRESAMPLER_H
|
||||
@@ -0,0 +1,583 @@
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// soundout.cpp: implementation of the CSoundOut class.
|
||||
//
|
||||
// This class implements a class to output data to a soundcard.
|
||||
// A fractional resampler is used to convert the users input rate to
|
||||
// the sound card rate and also perform frequency lock between the
|
||||
// two clock domains.
|
||||
//
|
||||
// History:
|
||||
// 2010-09-15 Initial creation MSW
|
||||
// 2011-03-27 Initial release
|
||||
// 2011-08-07 Changed some debug output
|
||||
// 2015-01-24 RRK, Minor mods, adding to cudaSDR
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
//==========================================================================================
|
||||
// + + + This Software is released under the "Simplified BSD License" + + +
|
||||
//Copyright 2010 Moe Wheatley. All rights reserved.
|
||||
//
|
||||
//Redistribution and use in source and binary forms, with or without modification, are
|
||||
//permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other materials
|
||||
// provided with the distribution.
|
||||
//
|
||||
//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR
|
||||
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//The views and conclusions contained in the software and documentation are those of the
|
||||
//authors and should not be interpreted as representing official policies, either expressed
|
||||
//or implied, of Moe Wheatley.
|
||||
//==========================================================================================
|
||||
#include "soundout.h"
|
||||
#include <QDebug>
|
||||
#include <math.h>
|
||||
|
||||
#define SOUNDCARD_RATE 48000 //output soundcard sample rate
|
||||
//#define SOUNDCARD_RATE 44100
|
||||
|
||||
#define FILTERQLEVEL_ALPHA 0.001
|
||||
#define P_GAIN 2.38e-7 //Proportional gain
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define TEST_ERROR 1.0
|
||||
//#define TEST_ERROR 1.001 //use to force fixed sample rate error for testing
|
||||
//#define TEST_ERROR 0.999
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// constructor/destructor
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
CSoundOut::CSoundOut(QObject *parent) :
|
||||
QThread(parent)
|
||||
{
|
||||
m_pParent = parent;
|
||||
m_pAudioOutput = NULL;
|
||||
m_pOutput = NULL;
|
||||
m_ThreadQuit = true;
|
||||
m_UserDataRate = SOUNDCARD_RATE;
|
||||
m_OutRatio = 1.0;
|
||||
//RRK m_OutAudioFormat.setFrequency(SOUNDCARD_RATE);
|
||||
m_OutAudioFormat.setSampleRate(SOUNDCARD_RATE);
|
||||
m_OutResampler.Init(8192);
|
||||
m_RateCorrection = 0.0;
|
||||
m_Gain = 1.0;
|
||||
m_Startup = true;
|
||||
m_BlockingMode = false;
|
||||
}
|
||||
|
||||
CSoundOut::~CSoundOut()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void GetAlsaMasterVolume(long *volume)
|
||||
{
|
||||
long min, max;
|
||||
snd_mixer_t *handle;
|
||||
snd_mixer_selem_id_t *sid;
|
||||
const char *card = "default";
|
||||
const char *selem_name = "Master";
|
||||
|
||||
snd_mixer_open(&handle, 0);
|
||||
snd_mixer_attach(handle, card);
|
||||
snd_mixer_selem_register(handle, NULL, NULL);
|
||||
snd_mixer_load(handle);
|
||||
|
||||
snd_mixer_selem_id_alloca(&sid);
|
||||
snd_mixer_selem_id_set_index(sid, 0);
|
||||
snd_mixer_selem_id_set_name(sid, selem_name);
|
||||
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
|
||||
|
||||
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume);
|
||||
|
||||
snd_mixer_close(handle);
|
||||
}
|
||||
|
||||
void SetAlsaMasterVolume(long volume)
|
||||
{
|
||||
long min, max;
|
||||
snd_mixer_t *handle;
|
||||
snd_mixer_selem_id_t *sid;
|
||||
const char *card = "default";
|
||||
const char *selem_name = "Master";
|
||||
|
||||
snd_mixer_open(&handle, 0);
|
||||
snd_mixer_attach(handle, card);
|
||||
snd_mixer_selem_register(handle, NULL, NULL);
|
||||
snd_mixer_load(handle);
|
||||
|
||||
snd_mixer_selem_id_alloca(&sid);
|
||||
snd_mixer_selem_id_set_index(sid, 0);
|
||||
snd_mixer_selem_id_set_name(sid, selem_name);
|
||||
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
|
||||
|
||||
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
|
||||
snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);
|
||||
|
||||
snd_mixer_close(handle);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Starts up soundcard output thread using soundcard at list OutDevIndx
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
bool CSoundOut::Start(int OutDevIndx, bool StereoOut, double UsrDataRate, bool BlockingMode)
|
||||
{
|
||||
QAudioDeviceInfo DeviceInfo;
|
||||
long mvolume;
|
||||
m_StereoOut = StereoOut;
|
||||
m_BlockingMode = BlockingMode;
|
||||
//Get required soundcard from list
|
||||
m_OutDevices = DeviceInfo.availableDevices(QAudio::AudioOutput);
|
||||
|
||||
if (-1 == OutDevIndx) GetAlsaMasterVolume(&mvolume);
|
||||
qDebug()<<"Soundcard volume" << mvolume;
|
||||
|
||||
if (-1 == OutDevIndx) m_OutDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
|
||||
else m_OutDeviceInfo = m_OutDevices.at(OutDevIndx);
|
||||
|
||||
#if 0 //RRK get a list of audio devices and the default
|
||||
foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
|
||||
qDebug() << "l:" << deviceInfo.deviceName();
|
||||
}
|
||||
|
||||
QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
|
||||
qDebug() << "res:" << info.deviceName();
|
||||
#endif
|
||||
|
||||
//Setup fixed format for sound ouput
|
||||
m_OutAudioFormat.setCodec("audio/pcm");
|
||||
//m_OutAudioFormat.setFrequency(SOUNDCARD_RATE);
|
||||
m_OutAudioFormat.setSampleRate(SOUNDCARD_RATE);
|
||||
m_OutAudioFormat.setSampleSize(16);
|
||||
m_OutAudioFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
m_OutAudioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
if(m_StereoOut)
|
||||
//RRK m_OutAudioFormat.setChannels(2);
|
||||
m_OutAudioFormat.setChannelCount(2);
|
||||
else
|
||||
m_OutAudioFormat.setChannelCount(1);
|
||||
|
||||
m_pAudioOutput = new QAudioOutput(m_OutDeviceInfo, m_OutAudioFormat, this);
|
||||
if(!m_pAudioOutput)
|
||||
{
|
||||
qDebug()<<"Soundcard output error";
|
||||
return false;
|
||||
}
|
||||
if(QAudio::NoError == m_pAudioOutput->error() )
|
||||
{
|
||||
//initialize the data queue variables
|
||||
m_UserDataRate = 1; //force user data rate to be changed
|
||||
ChangeUserDataRate(UsrDataRate);
|
||||
m_pOutput = m_pAudioOutput->start(); //start QT AudioOutput
|
||||
|
||||
//RRK workaround for default, for some reason choosing default
|
||||
//sets the master volume to max!
|
||||
if (-1 == OutDevIndx) SetAlsaMasterVolume(50);
|
||||
|
||||
//determine how long to sleep between low level reads based on samplerate and period size
|
||||
m_BlockTime = ( 250*m_pAudioOutput->periodSize() )/
|
||||
( SOUNDCARD_RATE*m_OutAudioFormat.channelCount() );
|
||||
//RRK ( SOUNDCARD_RATE*m_OutAudioFormat.channels() );
|
||||
//qDebug()<<"periodSize "<<m_pAudioOutput->periodSize();
|
||||
//qDebug()<<"BlockTime "<<m_BlockTime;
|
||||
m_ThreadQuit = FALSE;
|
||||
start(QThread::HighestPriority); //start worker thread and set its priority
|
||||
// start(QThread::TimeCriticalPriority); //start worker thread and set its priority
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug()<<"Soundcard output error";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Closes down sound card output thread
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::Stop()
|
||||
{
|
||||
if(!m_ThreadQuit)
|
||||
{
|
||||
m_ThreadQuit = TRUE;
|
||||
m_pAudioOutput->stop();
|
||||
wait(500);
|
||||
}
|
||||
if(NULL != m_pAudioOutput)
|
||||
{
|
||||
delete m_pAudioOutput;
|
||||
m_pAudioOutput = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Sets/changes user data input rate
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::ChangeUserDataRate(double UsrDataRate)
|
||||
{
|
||||
if(m_UserDataRate != UsrDataRate)
|
||||
{
|
||||
m_UserDataRate = UsrDataRate;
|
||||
for(int i=0; i<OUTQSIZE ;i++) //zero buffer for data output
|
||||
{
|
||||
m_OutQueueMono[i] = 0;
|
||||
m_OutQueueStereo[i].re = 0;
|
||||
m_OutQueueStereo[i].im = 0;
|
||||
}
|
||||
m_OutRatio = m_UserDataRate/m_OutAudioFormat.sampleRate();
|
||||
m_OutQHead = 0;
|
||||
m_OutQTail = 0;
|
||||
m_OutQLevel = 0;
|
||||
m_AveOutQLevel = OUTQSIZE/2;
|
||||
m_Startup = true;
|
||||
}
|
||||
qDebug()<<"SoundOutRatio Rate"<<(1.0/m_OutRatio) << m_UserDataRate;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Sets/changes volume control gain 0 <= vol <= 99
|
||||
//range scales to attenuation(gain) of -50dB to 0dB
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::SetVolume(qint32 vol)
|
||||
{
|
||||
m_Mutex.lock();
|
||||
if(0==vol) //if zero make infinite attenuation
|
||||
m_Gain = 0.0;
|
||||
else if(vol<=99)
|
||||
m_Gain = pow(10.0, ((double)vol-99.0)/39.2 );
|
||||
m_Mutex.unlock();
|
||||
//qDebug()<<"Volume "<<vol << m_Gain;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//Called by application to put COMPLEX input into
|
||||
// STEREO 2 channel soundcard output queue
|
||||
////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::PutOutQueue(int numsamples, TYPECPX* pData )
|
||||
{
|
||||
TYPESTEREO16 RData[OUTQSIZE]; //buffer to hold resampled data
|
||||
int i;
|
||||
bool overflow = false;
|
||||
if(( 0==numsamples) || m_ThreadQuit)
|
||||
return;
|
||||
//Call Resampler to match sample rates between radio and sound card
|
||||
numsamples = m_OutResampler.Resample(numsamples, TEST_ERROR*m_OutRatio *(1.0+m_RateCorrection),
|
||||
pData, RData, m_Gain);
|
||||
|
||||
if(m_BlockingMode) //if in Blocking Mode then wait for soundcard queue to be available
|
||||
{
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
while( ((m_OutQHead+1) & (OUTQSIZE-1)) == m_OutQTail)
|
||||
msleep(10); //wait if Queue is full
|
||||
m_OutQueueStereo[m_OutQHead++] = RData[i];
|
||||
m_OutQHead &= (OUTQSIZE-1);
|
||||
m_OutQLevel++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //here if non-blocking mode so need to deal with possible over/under flow
|
||||
m_Mutex.lock();
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
m_OutQueueStereo[m_OutQHead++] = RData[i];
|
||||
m_OutQHead &= (OUTQSIZE-1);
|
||||
m_OutQLevel++;
|
||||
if(m_OutQHead==m_OutQTail) //if full
|
||||
{ //remove 1/4 a queue's worth of data
|
||||
m_OutQTail += OUTQSIZE/4;
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
m_OutQLevel -= OUTQSIZE/4;
|
||||
i = numsamples; //force break out of for loop
|
||||
overflow = true;
|
||||
}
|
||||
}
|
||||
if(overflow)
|
||||
{
|
||||
qDebug()<<"Snd Overflow";
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
}
|
||||
//calculate average Queue fill level
|
||||
m_AveOutQLevel = (1.0-FILTERQLEVEL_ALPHA)*m_AveOutQLevel + FILTERQLEVEL_ALPHA*(double)m_OutQLevel;
|
||||
m_Mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//Called by application to put REAL soundcard output samples
|
||||
//into MONO soundcard queue
|
||||
////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::PutOutQueue(int numsamples, TYPEREAL* pData )
|
||||
{
|
||||
TYPEMONO16 RData[OUTQSIZE]; //buffer to hold resampled data
|
||||
int i;
|
||||
bool overflow = false;
|
||||
if(( 0==numsamples) || m_ThreadQuit)
|
||||
return;
|
||||
|
||||
//Call Resampler to match sample rates between radio and sound card
|
||||
numsamples = m_OutResampler.Resample(numsamples, TEST_ERROR*m_OutRatio *(1.0+m_RateCorrection),
|
||||
pData, RData, m_Gain);
|
||||
|
||||
if(m_BlockingMode) //if in Blocking Mode then wait for soundcard queue to be available
|
||||
{
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
while( ((m_OutQHead+1) & (OUTQSIZE-1)) == m_OutQTail)
|
||||
msleep(10); //wait if Queue is full
|
||||
m_OutQueueMono[m_OutQHead++] = RData[i];
|
||||
m_OutQHead &= (OUTQSIZE-1);
|
||||
m_OutQLevel++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //here if non-blocking mode so need to deal with possible over/under flow
|
||||
m_Mutex.lock();
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
m_OutQueueMono[m_OutQHead++] = RData[i];
|
||||
m_OutQHead &= (OUTQSIZE-1);
|
||||
m_OutQLevel++;
|
||||
if(m_OutQHead==m_OutQTail) //if full
|
||||
{ //remove 1/4 a queue's worth of data
|
||||
m_OutQTail += OUTQSIZE/4;
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
m_OutQLevel -= OUTQSIZE/4;
|
||||
i = numsamples; //force break out of for loop
|
||||
overflow = true;
|
||||
}
|
||||
}
|
||||
if(overflow)
|
||||
{
|
||||
qDebug()<<"Snd Overflow";
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
}
|
||||
//calculate average Queue fill level
|
||||
m_AveOutQLevel = (1.0-FILTERQLEVEL_ALPHA)*m_AveOutQLevel + FILTERQLEVEL_ALPHA*(double)m_OutQLevel;
|
||||
m_Mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//Called by CSoundOut worker thread to get new samples from queue
|
||||
// This routine is called from a worker thread so must be careful.
|
||||
// MONO version
|
||||
////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::GetOutQueue(int numsamples, TYPEMONO16* pData )
|
||||
{
|
||||
int i;
|
||||
bool underflow = false;
|
||||
m_Mutex.lock();
|
||||
if(m_Startup)
|
||||
{ //if no data in queue yet just stuff in silence until something is put in queue
|
||||
for( i=0; i<numsamples; i++)
|
||||
pData[i] = 0;
|
||||
if(m_OutQLevel>OUTQSIZE/2)
|
||||
{
|
||||
m_Startup = false;
|
||||
m_RateUpdateCount = -5*SOUNDCARD_RATE; //delay first error update to let settle
|
||||
m_PpmError = 0;
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
m_UpdateToggle = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Mutex.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
if(m_OutQHead!=m_OutQTail)
|
||||
{
|
||||
pData[i] = m_OutQueueMono[m_OutQTail++];
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
m_OutQLevel--;
|
||||
}
|
||||
else //queue went empty
|
||||
{ //backup queue ptr and use previous data in queue
|
||||
m_OutQTail -= (OUTQSIZE/4);
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
pData[i] = m_OutQueueMono[m_OutQTail];
|
||||
m_OutQLevel += (OUTQSIZE/4);
|
||||
underflow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(m_BlockingMode)
|
||||
{ //if in blocking mode just return
|
||||
m_Mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
//calculate average Queue fill level
|
||||
m_AveOutQLevel = (1.0-FILTERQLEVEL_ALPHA)*m_AveOutQLevel + FILTERQLEVEL_ALPHA*m_OutQLevel;
|
||||
if(underflow)
|
||||
{
|
||||
qDebug()<<"Snd Underflow";
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
}
|
||||
|
||||
// See if time to update rate error calculation routine
|
||||
m_RateUpdateCount += numsamples;
|
||||
if(m_RateUpdateCount >= SOUNDCARD_RATE) //every second
|
||||
{
|
||||
CalcError();
|
||||
m_RateUpdateCount = 0;
|
||||
}
|
||||
m_Mutex.unlock();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//Called by CSoundOut worker thread to get new samples from queue
|
||||
// This routine is called from a worker thread so must be careful.
|
||||
// STEREO version
|
||||
////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::GetOutQueue(int numsamples, TYPESTEREO16* pData )
|
||||
{
|
||||
int i;
|
||||
bool underflow = false;
|
||||
m_Mutex.lock();
|
||||
if(m_Startup)
|
||||
{ //if no data in queue yet just stuff in silence until something is put in queue
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
pData[i].re = 0;
|
||||
pData[i].im = 0;
|
||||
}
|
||||
if(m_OutQLevel>OUTQSIZE/2)
|
||||
{
|
||||
m_Startup = false;
|
||||
m_RateUpdateCount = -5*SOUNDCARD_RATE; //delay first error update to let settle
|
||||
m_PpmError = 0;
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
m_UpdateToggle = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Mutex.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for( i=0; i<numsamples; i++)
|
||||
{
|
||||
if(m_OutQHead!=m_OutQTail)
|
||||
{
|
||||
pData[i] = m_OutQueueStereo[m_OutQTail++];
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
m_OutQLevel--;
|
||||
}
|
||||
else //queue went empty
|
||||
{ //backup queue ptr and use previous data in queue
|
||||
m_OutQTail -= (OUTQSIZE/4);
|
||||
m_OutQTail &= (OUTQSIZE-1);
|
||||
pData[i] = m_OutQueueStereo[m_OutQTail];
|
||||
m_OutQLevel += (OUTQSIZE/4);
|
||||
underflow = true;
|
||||
}
|
||||
}
|
||||
if(m_BlockingMode)
|
||||
{ //if in blocking mode just return
|
||||
m_Mutex.unlock();
|
||||
return;
|
||||
}
|
||||
//calculate average Queue fill level
|
||||
m_AveOutQLevel = (1.0-FILTERQLEVEL_ALPHA)*m_AveOutQLevel + FILTERQLEVEL_ALPHA*m_OutQLevel;
|
||||
|
||||
if(underflow)
|
||||
{
|
||||
qDebug()<<"Snd Underflow";
|
||||
m_AveOutQLevel = m_OutQLevel;
|
||||
}
|
||||
// See if time to update rate error calculation routine
|
||||
m_RateUpdateCount += numsamples;
|
||||
if(m_RateUpdateCount >= SOUNDCARD_RATE) //every second
|
||||
{
|
||||
CalcError();
|
||||
m_RateUpdateCount = 0;
|
||||
}
|
||||
m_Mutex.unlock();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Called alternately from the Get routines to update the
|
||||
// error correction process
|
||||
////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::CalcError()
|
||||
{
|
||||
double error;
|
||||
error = (double)(m_AveOutQLevel - OUTQSIZE/2 ); //neg==level is too low pos == level is to high
|
||||
error = error * P_GAIN;
|
||||
m_RateCorrection = error;
|
||||
m_PpmError = (int)( m_RateCorrection*1e6 );
|
||||
if( abs(m_PpmError) > 500)
|
||||
{
|
||||
// qDebug()<<"SoundOut "<<m_PpmError << m_AveOutQLevel;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Worker thread polls QAudioOutput device to see if there is room
|
||||
// to put more data into it and then calls GetOutQueue(..) to get more data.
|
||||
// This thread was needed because the normal "pull" mechanism of Qt does
|
||||
// not work very well since it depends on the main process signal-slot event
|
||||
// queue and you get dropouts if the GUI gets busy.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CSoundOut::run()
|
||||
{
|
||||
while(!m_ThreadQuit ) //execute loop until quit flag set
|
||||
{
|
||||
if( (QAudio::IdleState == m_pAudioOutput->state() ) ||
|
||||
(QAudio::ActiveState == m_pAudioOutput->state() ) )
|
||||
{ //Process sound data while soundcard is active and no errors
|
||||
unsigned int len = m_pAudioOutput->bytesFree(); //in bytes
|
||||
if( len>0 )
|
||||
{
|
||||
//limit size to SOUND_WRITEBUFSIZE
|
||||
if(len > SOUND_WRITEBUFSIZE)
|
||||
len = SOUND_WRITEBUFSIZE;
|
||||
if(m_StereoOut)
|
||||
{
|
||||
len &= ~(0x03); //keep on 4 byte chunks
|
||||
GetOutQueue( len/4, (TYPESTEREO16*)m_pData );
|
||||
}
|
||||
else
|
||||
{
|
||||
len &= ~(0x01); //keep on 2 byte chunks
|
||||
GetOutQueue( len/2, (TYPEMONO16*)m_pData );
|
||||
}
|
||||
m_pOutput->write((char*)m_pData,len);
|
||||
}
|
||||
else //no room in sound card output buffer so wait
|
||||
{ //not good but no other wait or blocking mechanism available
|
||||
msleep(m_BlockTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //bail out if error occurs
|
||||
qDebug()<<"SoundOut Error";
|
||||
#if 0 //RRK
|
||||
if(m_pParent)
|
||||
((CSdrInterface*)m_pParent)->SendIOStatus(CSdrInterface::ERROR);
|
||||
#endif
|
||||
m_ThreadQuit = true;
|
||||
}
|
||||
}
|
||||
qDebug()<<"sound thread exit";
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// soundout.h: interface for the CSoundOut class.
|
||||
//
|
||||
// History:
|
||||
// 2010-09-15 Initial creation MSW
|
||||
// 2011-03-27 Initial release
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#ifndef SOUNDOUT_H
|
||||
#define SOUNDOUT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QAudioOutput>
|
||||
#include "fractresampler.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/mixer.h>
|
||||
|
||||
#define OUTQSIZE 16384 //max samples (keep power of 2 for ptr wrap around)
|
||||
#define SOUND_WRITEBUFSIZE 8192
|
||||
|
||||
class CSoundOut : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CSoundOut(QObject *parent = 0);
|
||||
virtual ~CSoundOut();
|
||||
|
||||
//Exposed functions
|
||||
bool Start(int OutDevIndx, bool StereoOut, double UsrDataRate, bool BlockingMode); //starts soundcard output
|
||||
void Stop(); //stops soundcard output
|
||||
void PutOutQueue(int numsamples, TYPEREAL* pData );
|
||||
void PutOutQueue(int numsamples, TYPECPX* pData );
|
||||
void ChangeUserDataRate(double UsrDataRate);
|
||||
void SetVolume(qint32 vol);
|
||||
int GetRateError(){return (int)m_PpmError;}
|
||||
|
||||
protected:
|
||||
void run(); //implements worker thread loop
|
||||
int m_BlockTime;
|
||||
|
||||
private:
|
||||
void GetOutQueue(int numsamples, TYPEMONO16* pData );
|
||||
void GetOutQueue(int numsamples, TYPESTEREO16* pData );
|
||||
void CalcError();
|
||||
|
||||
QList<QAudioDeviceInfo> m_OutDevices;
|
||||
QAudioDeviceInfo m_OutDeviceInfo;
|
||||
QAudioFormat m_OutAudioFormat;
|
||||
QAudioOutput* m_pAudioOutput;
|
||||
QIODevice* m_pOutput; // ptr to internal soundout IODevice
|
||||
QObject* m_pParent;
|
||||
QMutex m_Mutex;
|
||||
CFractResampler m_OutResampler;
|
||||
|
||||
TYPEMONO16 m_OutQueueMono[OUTQSIZE];
|
||||
TYPESTEREO16 m_OutQueueStereo[OUTQSIZE];
|
||||
bool m_BlockingMode;
|
||||
bool m_ThreadQuit;
|
||||
bool m_Startup;
|
||||
bool m_StereoOut;
|
||||
bool m_UpdateToggle;
|
||||
quint32 m_OutQHead;
|
||||
quint32 m_OutQTail;
|
||||
char m_pData[SOUND_WRITEBUFSIZE];
|
||||
int m_RateUpdateCount;
|
||||
int m_OutQLevel;
|
||||
int m_PpmError;
|
||||
double m_Gain;
|
||||
double m_UserDataRate;
|
||||
double m_OutRatio;
|
||||
double m_RateCorrection;
|
||||
double m_AveOutQLevel;
|
||||
};
|
||||
#endif // SOUNDOUT_H
|
||||
Reference in New Issue
Block a user