1
0
mirror of https://github.com/JvanKatwijk/dabradio synced 2025-10-05 15:52:42 +02:00
Files
SDR-DAB_dabradio/radio.cpp
jan van katwijk 1f10b92808 ...
2019-03-20 12:54:53 +01:00

760 lines
23 KiB
C++

#
/*
* Copyright (C) 2013, 2014, 2015, 2016, 2017
* Jan van Katwijk (J.vanKatwijk@gmail.com)
* Lazy Chair Computing
*
* This file is part of the dabradio
* dabradio 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.
*
* dabradio 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 dabradio; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <QSettings>
#include <QMessageBox>
#include <QDebug>
#include <QDateTime>
#include <QFile>
#include <QDir>
#include "dab-constants.h"
#include <numeric>
#include <unistd.h>
#include <vector>
#include <QMouseEvent>
#include "radio.h"
#include "band-handler.h"
#include "audiosink.h"
#include "audio-descriptor.h"
#include <mutex>
#ifdef HAVE_RTLSDR
#include "rtlsdr-handler.h"
#endif
#ifdef HAVE_SDRPLAY
#include "sdrplay-handler.h"
#endif
#ifdef HAVE_AIRSPY
#include "airspy-handler.h"
#endif
#ifdef HAVE_HACKRF
#include "hackrf-handler.h"
#endif
/**
* We use the creation function merely to set up the
* user interface and make the connections between the
* gui elements and the handling agents. All real action
* is initiated by gui buttons
*/
RadioInterface::RadioInterface (QSettings *Si,
QString serviceNames,
bandHandler *theBand,
QWidget *parent):
QMainWindow (parent) {
int16_t latency;
int16_t k;
QString h;
dabSettings = Si;
this -> theBand = theBand;
channels = theBand -> channels ();
running. store (false);
scanning = false;
isSynced = UNSYNCED;
audioBuffer = new RingBuffer<int16_t>(16 * 32768);
threshold =
dabSettings -> value ("threshold", 3). toInt ();
//
// latency is used to allow different settings for different
// situations wrt the output buffering. For windows and the RPI
// this need to be a pretty large value (e.g. 5 to 10)
latency =
dabSettings -> value ("latency", 5). toInt ();
diff_length =
dabSettings -> value ("diff_length", DIFF_LENGTH). toInt ();
dabMode = dabSettings -> value ("dabMode", 1). toInt ();
if ((dabMode != 1) && (dabMode != 2))
dabMode = 1;
dataBuffer = new RingBuffer<uint8_t>(32768);
saveSlides = dabSettings -> value ("saveSlides", 1). toInt ();
showSlides = dabSettings -> value ("showPictures", 1). toInt ();
if (saveSlides != 0)
set_picturePath ();
///////////////////////////////////////////////////////////////////////////
// The settings are done, now creation of the GUI parts
setupUi (this);
inputDevice = setDevice (dabSettings,
gainSelector,
lnaSelector,
agcControl);
if (inputDevice == NULL) {
delete audioBuffer;
throw (33);
}
syncedLabel ->
setStyleSheet ("QLabel {background-color : red}");
//
motLabel ->
setStyleSheet ("QLabel {background-color : red}");
audioQuality -> setValue (0);
ficQuality -> setValue (0);
// display the version
QString v = "j-radio -" + QString (CURRENT_VERSION);
QString versionText = "dabradio version: " + QString(CURRENT_VERSION);
versionText += " Build on: " + QString(__TIMESTAMP__) + QString (" ") + QString (GITHASH);
versionText += "\nCopyright 2018 J van Katwijk, Lazy Chair Computing.";
copyrightLabel -> setToolTip (versionText);
ensembleDisplay = new QListView (NULL);
ensembleDisplay -> show ();
ensembleDisplay -> setToolTip ("Right clicking on a service name will make some technical details on the selected service visible");
soundOut = new audioSink (latency);
((audioSink *)soundOut) -> setupChannels (streamoutSelector);
streamoutSelector -> show ();
bool err;
h = dabSettings -> value ("soundchannel", "default"). toString ();
k = streamoutSelector -> findText (h);
if (k != -1) {
streamoutSelector -> setCurrentIndex (k);
err = !((audioSink *)soundOut) -> selectDevice (k);
}
if ((k == -1) || err)
((audioSink *)soundOut) -> selectDefaultDevice ();
//
connect (streamoutSelector, SIGNAL (activated (int)),
this, SLOT (set_streamSelector (int)));
ficBlocks = 0;
ficSuccess = 0;
pictureLabel = NULL;
//
// and start the timer(s)
// The displaytimer is there to show the number of
// seconds running
displayTimer. setInterval (1000);
displayTimer. start (1000);
numberofSeconds = 0;
//
// timer for channel settings
channelTimer. setSingleShot (true);
channelTimer. setInterval (5000);
// timer for scanning
signalTimer. setSingleShot (true);
selectedChannel = QString ("");
//
my_dabProcessor = new dabProcessor (this,
inputDevice,
dabMode,
threshold,
diff_length,
picturesPath);
connect (my_dabProcessor, SIGNAL (setSynced (char)),
this, SLOT (setSynced (char)));
connect (my_dabProcessor, SIGNAL (show_snr (int)),
this, SLOT (show_snr (int)));
//
serviceCharacteristics = NULL;
secondsTimer. setInterval (1000);
connect (&secondsTimer, SIGNAL (timeout (void)),
this, SLOT (updateTime (void)));
secondsTimer. start (1000);
startScanning ();
qApp -> installEventFilter (this);
serviceDescription = NULL;
}
RadioInterface::~RadioInterface (void) {
fprintf (stderr, "radioInterface is deleted\n");
}
//
// A little tricky, there are two signals that may trigger nextChannel
// The "no dab is here" signal, which should arrive within
// a second or so after starting the decoding,
// or a timeout
void RadioInterface:: startScanning (void) {
disconnect (&signalTimer, SIGNAL (timeout (void)),
this, SLOT (nextChannel (void)));
disconnect (my_dabProcessor, SIGNAL (No_Signal_Found (void)),
this, SLOT (nextChannel (void)));
serviceCount = 0;
serviceCountDisplay -> display (serviceCount);
channelNumber = 0;
while (channelNumber < channels) {
QString channel = theBand -> channel (channelNumber);
if (dabSettings -> value (channel, 1). toInt () > 0) {
dabSettings -> setValue (channel, -1);
break;
}
channelNumber ++;
}
if (channelNumber >= theBand -> channels ()) {
set_ensembleName ("end of scan");
serviceLabel -> setText ("No services found, retry scan");
serviceLabel -> setStyleSheet ("QLabel {background-color : green}");
disconnect (&signalTimer, SIGNAL (timeout (void)),
this, SLOT (nextChannel (void)));
disconnect (my_dabProcessor, SIGNAL (No_Signal_Found (void)),
this, SLOT (nextChannel (void)));
connect (resetButton, SIGNAL (clicked (void)),
this, SLOT (reset (void)));
return;
}
//
// "normal behaviour"
QString text = "scanning ch ";
text. append (theBand -> channel (channelNumber. load ()));
set_ensembleName (text);
int tunedFrequency =
theBand -> Frequency (channelNumber. load ());
connect (&signalTimer, SIGNAL (timeout (void)),
this, SLOT (nextChannel (void)));
connect (my_dabProcessor, SIGNAL (No_Signal_Found (void)),
this, SLOT (nextChannel (void)));
my_dabProcessor -> start (tunedFrequency, true);
running. store (true);
signalTimer. start (5000);
scanning = true;
serviceLabel -> setText ("Wait for services");
serviceLabel -> setStyleSheet ("QLabel {background-color : red}");
}
void RadioInterface::nextChannel (void) {
disconnect (&signalTimer, SIGNAL (timeout (void)),
this, SLOT (nextChannel (void)));
disconnect (my_dabProcessor, SIGNAL (No_Signal_Found (void)),
this, SLOT (nextChannel (void)));
signalTimer. stop ();
my_dabProcessor -> stop ();
channelNumber++;
while (!(channelNumber >= channels)) {
QString channel = theBand -> channel (channelNumber);
if (dabSettings -> value (channel, 1). toInt () > 0) {
dabSettings -> setValue (channel, -1);
break;
}
channelNumber ++;
}
if (channelNumber >= channels) {
scanning = false;
set_ensembleName ("end of scan");
serviceLabel -> setText ("select a services");
serviceLabel -> setStyleSheet ("QLabel {background-color : green}");
connect (ensembleDisplay,
SIGNAL (clicked (QModelIndex)),
this, SLOT (selectService (QModelIndex)));
connect (resetButton, SIGNAL (clicked (void)),
this, SLOT (reset (void)));
return;
}
int tunedFrequency =
theBand -> Frequency (channelNumber);
QString text = "scanning ch ";
text. append (theBand -> channel (channelNumber));
set_ensembleName (text);
connect (&signalTimer, SIGNAL (timeout (void)),
this, SLOT (nextChannel (void)));
connect (my_dabProcessor, SIGNAL (No_Signal_Found (void)),
this, SLOT (nextChannel (void)));
my_dabProcessor -> start (tunedFrequency, true);
signalTimer. start (5000);
}
void RadioInterface::reset (void) {
my_dabProcessor -> stop ();
disconnect (ensembleDisplay,
SIGNAL (clicked (QModelIndex)),
this, SLOT (selectService (QModelIndex)));
disconnect (resetButton, SIGNAL (clicked (void)),
this, SLOT (reset (void)));
for (int i = 0; i < channels; i ++) {
QString channel = theBand -> channel (i);
dabSettings -> setValue (channel, 1);
}
if (serviceDescription != NULL)
delete serviceDescription;
serviceDescription = NULL;
Services = QStringList ();
ensemble. setStringList (Services);
ensembleDisplay -> setModel (&ensemble);
for (std::map<QString, serviceDescriptor *>
::iterator it = serviceMap. begin ();
it != serviceMap. end (); it ++)
delete (it -> second);
serviceMap. clear ();
startScanning ();
}
//
// a slot, called by the fic/fib handlers
// little tricky, since we do not want the data here
// when setting the channel for selecting a service in
// the large list
void RadioInterface::addtoEnsemble (const QString &s) {
if (!scanning)
// if (!scanning && (services. size () > 0))
return;
if (scanning) {
serviceCountDisplay -> display (serviceCount ++);
audiodata d;
QString t = s;
my_dabProcessor -> dataforAudioService (t, &d, 0);
serviceDescriptor *service;
if (d. defined) {
QString channel = theBand -> channel (channelNumber);
service =
new serviceDescriptor (t,
channel,
&d);
Services << s;
Services. removeDuplicates ();
ensemble. setStringList (Services);
ensembleDisplay -> setModel (&ensemble);
dabSettings -> setValue (channel, 1);
serviceMap. insert (mapElement (t, service));
}
}
}
//
/// a slot, called by the fib processor
void RadioInterface::nameofEnsemble (int id, const QString &v) {
QString s;
(void)v;
ensembleName -> setText (v);
my_dabProcessor -> coarseCorrectorOff ();
}
void RadioInterface::set_ensembleName (const QString &v) {
ensembleName -> setText (v);
}
///////////////////////////////////////////////////////////////////////
void RadioInterface::show_ficSuccess (bool b) {
if (b)
ficSuccess ++;
if (++ficBlocks >= 100) {
ficQuality -> setValue (ficSuccess);
ficSuccess = 0;
ficBlocks = 0;
}
}
void RadioInterface::setColor (QLabel *l, uint8_t b) {
if (b)
l -> setStyleSheet ("QLabel {background-color : green}");
else
l -> setStyleSheet ("QLabel {background-color : red}");
}
void RadioInterface::setColor (QPushButton *l, uint8_t b) {
if (b)
l -> setStyleSheet ("QPushButton {background-color : green}");
else
l -> setStyleSheet ("QPushButton {background-color : red}");
}
void RadioInterface::show_motHandling (bool b) {
setColor (motLabel, b);
}
void RadioInterface::showSpectrum (int s) {
(void)s;
}
void RadioInterface::showIQ (int s) {
(void)s;
}
void RadioInterface::showQuality (float f) {
(void)f;
}
void RadioInterface::show_snr (int s) {
snrDisplay -> display (s);
}
void RadioInterface::set_CorrectorDisplay (int c) {
(void)c;
}
void RadioInterface::show_audioQuality (int e) {
audioQuality -> setValue (e);
}
/// just switch a color, obviously GUI dependent, but called
// from the ofdmprocessor
void RadioInterface::setSynced (char b) {
if (isSynced == b)
return;
isSynced = b;
setColor (syncedLabel, b == SYNCED);
}
// showLabel is triggered by the message handler
// the GUI may decide to ignore this
void RadioInterface::showLabel (QString s) {
if (running. load ())
dynamicLabel -> setText (s);
}
void RadioInterface::setStereo (bool s) {
if (s)
stereoLabel ->
setStyleSheet ("QLabel {background-color : green}");
else
stereoLabel ->
setStyleSheet ("QLabel {background-color : red}");
}
//
//////////////////////////////////////////////////////////////////////////
//
void checkDir (QString &s) {
int16_t ind = s. lastIndexOf (QChar ('/'));
int16_t i;
QString dir;
if (ind == -1) // no slash, no directory
return;
for (i = 0; i < ind; i ++)
dir. append (s [i]);
if (QDir (dir). exists ())
return;
QDir (). mkpath (dir);
}
// showMOT is triggered by the MOT handler,
// the GUI may decide to ignore the data sent
// since data is only sent whenever a data channel is selected
void RadioInterface::showMOT (QByteArray data,
int subtype, QString pictureName) {
const char *type;
if (!running. load ())
return;
if (pictureLabel == NULL)
pictureLabel = new QLabel (NULL);
type = subtype == 0 ? "GIF" :
subtype == 1 ? "JPG" :
// subtype == 1 ? "JPEG" :
subtype == 2 ? "BMP" : "PNG";
QPixmap p;
p. loadFromData (data, type);
if (saveSlides && (pictureName != QString (""))) {
pictureName = QDir::toNativeSeparators (pictureName);
FILE *x = fopen (pictureName. toLatin1 (). data (), "w+b");
if (x == NULL)
fprintf (stderr, "cannot write file %s\n",
pictureName. toLatin1 (). data ());
else {
fprintf (stderr, "going to write file %s\n",
pictureName. toLatin1 (). data ());
(void)fwrite (data. data (), 1, data.length (), x);
fclose (x);
}
}
// pictureLabel -> setFrameRect (QRect (0, 0, p. height (), p. width ()));
if (showSlides) {
pictureLabel -> setPixmap (p);
pictureLabel -> show ();
}
}
//
void RadioInterface::showImpulse (int a) {
(void)a;
}
/**
* \brief changeinConfiguration
* No idea yet what to do, so just give up
* with what we were doing. The user will -eventually -
* see the new configuration from which he can select
*/
void RadioInterface::changeinConfiguration (void) {
if (running. load ()) {
soundOut -> stop ();
my_dabProcessor -> reset ();
}
}
//
// In order to not overload with an enormous amount of
// signals, we trigger this function at most 10 times a second
//
void RadioInterface::newAudio (int amount, int rate) {
if (running. load ()) {
int16_t vec [amount];
while (audioBuffer -> GetRingBufferReadAvailable () > amount) {
audioBuffer -> getDataFromBuffer (vec, amount);
soundOut -> audioOut (vec, amount, rate);
}
}
}
////////////////////////////////////////////////////////////////////////////
/**
* \brief TerminateProcess
* Pretty critical, since there are many threads involved
* A clean termination is what is needed, regardless of the GUI
*/
void RadioInterface::TerminateProcess (void) {
running. store (false);
displayTimer. stop ();
signalTimer. stop ();
inputDevice -> stopReader ();
my_dabProcessor -> stop (); // definitely concurrent
soundOut -> stop ();
// everything should be halted by now
fprintf (stderr, "going to delete dabProcessor\n");
delete my_dabProcessor;
fprintf (stderr, "deleted dabProcessor\n");
delete soundOut;
if (inputDevice != NULL)
delete inputDevice;
if (ensembleDisplay != NULL)
delete ensembleDisplay;
if (serviceDescription != NULL)
delete serviceDescription;
if (pictureLabel != NULL)
delete pictureLabel;
pictureLabel = NULL; // signals may be pending, so careful
if (serviceCharacteristics != NULL)
delete serviceCharacteristics;
close ();
fprintf (stderr, ".. end the radio silences\n");
}
//
// Signals from the GUI
/////////////////////////////////////////////////////////////////////////
// Selecting a service is easy, the fib is asked to
// hand over the relevant data in two steps
void RadioInterface::selectService (QModelIndex ind ) {
if (scanning) // be polite, wait until we finished scanning
return;
QString currentProgram = ensemble. data (ind, Qt::DisplayRole). toString ();
running. store (true);
serviceLabel -> setStyleSheet ("QLabel {background-color : white}");
serviceLabel -> setText (currentProgram);
std::map<QString, serviceDescriptor *>::iterator it;
it = serviceMap. find (currentProgram);
if (it == serviceMap. end ())
return;
serviceDescriptor *sd = it -> second;
fprintf (stderr, "channel = %s, currentChannel %s\n",
sd -> channel. toLatin1 (). data (),
selectedChannel. toLatin1 (). data ());
if (selectedChannel != sd -> channel) {
my_dabProcessor -> stop ();
connect (&channelTimer, SIGNAL (timeout (void)),
this, SLOT (channelTimer_timeout (void)));
channelTimer. start (5000);
int tunedFrequency = theBand -> Frequency (sd -> channel);
my_dabProcessor -> start (tunedFrequency, false);
selectedChannel = sd -> channel;
fprintf (stderr, "ready to start %s (%s)\n",
currentProgram. toLatin1 (). data (),
(sd -> channel). toLatin1 (). data ());
currentService = currentProgram;
}
else
startService (currentProgram);
}
void RadioInterface::channelTimer_timeout (void) {
// so, we were looking for a channel. Let us see if
// it has provided us with data
disconnect (&channelTimer, SIGNAL (timeout (void)),
this, SLOT (channelTimer_timeout (void)));
if (my_dabProcessor -> kindofService (currentService) !=
AUDIO_SERVICE)
return;
startService (currentService);
}
void RadioInterface::startService (QString currentService) {
my_dabProcessor -> reset_msc ();
setStereo (false);
audiodata d;
my_dabProcessor -> dataforAudioService (currentService, &d);
if (!d. defined) {
QMessageBox::warning (this, tr ("Warning"),
tr ("insufficient data to run this service\n"));
return;
}
my_dabProcessor -> set_audioChannel (&d, audioBuffer, dataBuffer);
soundOut -> restart ();
showLabel (QString (" "));
if (pictureLabel != NULL)
delete pictureLabel;
pictureLabel = NULL;
}
//
void RadioInterface:: set_streamSelector (int k) {
((audioSink *)(soundOut)) -> selectDevice (k);
}
//
#include <QCloseEvent>
void RadioInterface::closeEvent (QCloseEvent *event) {
QMessageBox::StandardButton resultButton =
QMessageBox::question (this, "dabRadio",
tr("Are you sure?\n"),
QMessageBox::No | QMessageBox::Yes,
QMessageBox::Yes);
if (resultButton != QMessageBox::Yes) {
event -> ignore();
} else {
TerminateProcess ();
event -> accept ();
}
}
void RadioInterface::set_picturePath (void) {
QString defaultPath = QDir::tempPath ();
if (defaultPath. endsWith ("/"))
defaultPath. append ("qt-pictures/");
else
defaultPath. append ("/qt-pictures/");
picturesPath =
dabSettings -> value ("pictures", defaultPath). toString ();
if ((picturesPath != "") && (!picturesPath. endsWith ("/")))
picturesPath. append ("/");
QDir testdir (picturesPath);
if (!testdir. exists ())
testdir. mkdir (picturesPath);
}
void RadioInterface::updateTime (void) {
QDateTime currentTime = QDateTime::currentDateTime ();
timeDisplay -> setText (currentTime.
toString (QString ("dd.MM.yy:hh:mm:ss")));
}
bool RadioInterface::eventFilter (QObject *obj, QEvent *event) {
if ((obj == ensembleDisplay -> viewport ()) &&
(event -> type () == QEvent::MouseButtonPress )) {
QMouseEvent *ev = static_cast<QMouseEvent *>(event);
if (ev -> buttons () & Qt::RightButton) {
audiodata ad;
QString serviceName =
this -> ensembleDisplay -> indexAt (ev -> pos()). data ().toString ();
if (serviceDescription != NULL)
delete serviceDescription;
std::map<QString, serviceDescriptor *>::iterator it;
it = serviceMap. find (serviceName);
if (it == serviceMap. end ())
return true;
serviceDescriptor *sd = it -> second;
if (sd -> defined) {
serviceDescription = new audioDescriptor (sd);
return true;
}
}
}
return QMainWindow::eventFilter (obj, event);
}
deviceHandler *RadioInterface::setDevice (QSettings *dabSettings,
QSpinBox *gainSelector,
QSpinBox *lnaSelector,
QCheckBox *agcControl) {
deviceHandler *inputDevice = NULL;
int gain;
/// OK, everything quiet, now let us see what to do
gainSelector -> hide ();
lnaSelector -> hide ();
agcControl -> hide ();
#ifdef HAVE_AIRSPY
try {
inputDevice = new airspyHandler (dabSettings,
gainSelector,
agcControl);
gainSelector -> show ();
agcControl -> show ();
return inputDevice;
} catch (int e) {
}
#endif
#ifdef HAVE_SDRPLAY
try {
inputDevice = new sdrplayHandler (dabSettings,
gainSelector,
lnaSelector,
agcControl);
gainSelector -> show ();
lnaSelector -> show ();
agcControl -> show ();
return inputDevice;
} catch (int e) {}
#endif
#ifdef HAVE_RTLSDR
try {
inputDevice = new rtlsdrHandler (dabSettings,
gainSelector,
agcControl);
gainSelector -> show ();
agcControl -> show ();
return inputDevice;
} catch (int e) {}
#endif
#ifdef HAVE_HACKRF
try {
inputDevice = new hackrfHandler (dabSettings,
gainSelector,
lnaSelector);
gainSelector -> show ();
lnaSelector -> show ();
return inputDevice;
} catch (int e) {}
#endif
return NULL;
}